feat: Euro-Beträge mit Kommaeingabe im Gutscheinformular
This commit is contained in:
1 parent
105f18f0c9
commit
e5e92ca0e1
3 files changed
+53
-30
No files matched your search
+30
-29
@@ -4,7 +4,7 @@ import { useRouter } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
import Shell from "../../components/shell";
|
||||
import LogoImg from "../../components/logo-img";
|
||||
import { validateVoucher } from "@/lib/vouchers";
|
||||
import { validateVoucher, euroToCents } from "@/lib/vouchers";
|
||||
import { ALLOWED_REMINDER_DAYS, DEFAULT_REMINDER_DAYS } from "@/lib/reminders";
|
||||
import type { LogoCandidate } from "@/lib/logo";
|
||||
|
||||
@@ -88,20 +88,17 @@ export default function NewVoucherPage() {
|
||||
|
||||
async function onSubmit(e: React.FormEvent) {
|
||||
e.preventDefault();
|
||||
const mindestRaw = values.mindestbestellwertCents;
|
||||
const wertCents = euroToCents(values.wertEuro);
|
||||
const restParsed = euroToCents(values.restEuro);
|
||||
const mindestParsed = euroToCents(values.mindestEuro);
|
||||
const input = {
|
||||
name: values.name ?? "",
|
||||
anbieter: values.anbieter ?? "",
|
||||
wertTyp,
|
||||
wertCents: wertTyp === "euro" ? Number(values.wertCents) : 0,
|
||||
restCents:
|
||||
wertTyp === "euro"
|
||||
? values.restCents === undefined || values.restCents === ""
|
||||
? Number(values.wertCents)
|
||||
: Number(values.restCents)
|
||||
: 0,
|
||||
wertCents: wertTyp === "euro" ? (wertCents ?? NaN) : 0,
|
||||
restCents: wertTyp === "euro" ? (restParsed === null ? (wertCents ?? NaN) : restParsed) : 0,
|
||||
prozentWert: wertTyp === "prozent" ? Number(values.prozentWert) : null,
|
||||
mindestbestellwertCents: mindestRaw === undefined || mindestRaw === "" ? null : Number(mindestRaw),
|
||||
mindestbestellwertCents: mindestParsed,
|
||||
ablaufdatum: values.ablaufdatum || null,
|
||||
codeText: values.codeText || null,
|
||||
kategorie: values.kategorie || null,
|
||||
@@ -203,27 +200,30 @@ export default function NewVoucherPage() {
|
||||
{wertTyp === "euro" ? (
|
||||
<>
|
||||
<div>
|
||||
<label htmlFor="wertCents" className="block text-[13px] font-semibold">Wert (Cent)</label>
|
||||
<label htmlFor="wertEuro" className="block text-[13px] font-semibold">Wert (€)</label>
|
||||
<input
|
||||
id="wertCents"
|
||||
type="number"
|
||||
value={values.wertCents ?? ""}
|
||||
placeholder="5000"
|
||||
onChange={(e) => set("wertCents", e.target.value)}
|
||||
id="wertEuro"
|
||||
type="text"
|
||||
inputMode="decimal"
|
||||
value={values.wertEuro ?? ""}
|
||||
placeholder="25,50"
|
||||
onChange={(e) => set("wertEuro", e.target.value)}
|
||||
aria-invalid={errors.includes("wertCents")}
|
||||
className={`${inputClass} ${errors.includes("wertCents") ? "border-danger" : ""}`}
|
||||
/>
|
||||
{errors.includes("wertCents") && (
|
||||
<p className="mt-1 text-[13px] font-medium text-danger">Bitte prüfe dieses Feld.</p>
|
||||
<p className="mt-1 text-[13px] font-medium text-danger">Bitte einen gültigen Betrag eingeben.</p>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="restCents" className="block text-[13px] font-semibold">Rest (Cent, leer = Wert)</label>
|
||||
<label htmlFor="restEuro" className="block text-[13px] font-semibold">Rest (€, leer = Wert)</label>
|
||||
<input
|
||||
id="restCents"
|
||||
type="number"
|
||||
value={values.restCents ?? ""}
|
||||
onChange={(e) => set("restCents", e.target.value)}
|
||||
id="restEuro"
|
||||
type="text"
|
||||
inputMode="decimal"
|
||||
value={values.restEuro ?? ""}
|
||||
placeholder="25,50"
|
||||
onChange={(e) => set("restEuro", e.target.value)}
|
||||
aria-invalid={errors.includes("restCents")}
|
||||
className={`${inputClass} ${errors.includes("restCents") ? "border-danger" : ""}`}
|
||||
/>
|
||||
@@ -252,15 +252,16 @@ export default function NewVoucherPage() {
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<label htmlFor="mindestbestellwertCents" className="block text-[13px] font-semibold">
|
||||
Mindestbestellwert (Cent, optional)
|
||||
<label htmlFor="mindestEuro" className="block text-[13px] font-semibold">
|
||||
Mindestbestellwert (€, optional)
|
||||
</label>
|
||||
<input
|
||||
id="mindestbestellwertCents"
|
||||
type="number"
|
||||
value={values.mindestbestellwertCents ?? ""}
|
||||
placeholder="z. B. 5000"
|
||||
onChange={(e) => set("mindestbestellwertCents", e.target.value)}
|
||||
id="mindestEuro"
|
||||
type="text"
|
||||
inputMode="decimal"
|
||||
value={values.mindestEuro ?? ""}
|
||||
placeholder="z. B. 50,00"
|
||||
onChange={(e) => set("mindestEuro", e.target.value)}
|
||||
aria-invalid={errors.includes("mindestbestellwertCents")}
|
||||
className={`${inputClass} ${errors.includes("mindestbestellwertCents") ? "border-danger" : ""}`}
|
||||
/>
|
||||
|
||||
@@ -36,6 +36,19 @@ export function validateVoucher(v: VoucherInput): string[] {
|
||||
export function formatEuro(cents: number): string {
|
||||
return (cents / 100).toLocaleString("de-DE", { style: "currency", currency: "EUR" });
|
||||
}
|
||||
/**
|
||||
* Wandelt eine Euro-Eingabe in Cent um. Akzeptiert deutsches ("25,50",
|
||||
* "1.000,50") und englisches ("25.50") Dezimalformat.
|
||||
* Leere Eingabe → null, ungültige Eingabe → NaN.
|
||||
*/
|
||||
export function euroToCents(s: string | undefined | null): number | null {
|
||||
if (s === undefined || s === null || s.trim() === "") return null;
|
||||
let t = s.trim();
|
||||
if (t.includes(",")) t = t.replace(/\./g, "").replace(",", ".");
|
||||
const n = Number(t);
|
||||
if (!Number.isFinite(n)) return NaN;
|
||||
return Math.round(n * 100);
|
||||
}
|
||||
export function formatWert(v: { wertTyp: string; wertCents: number; prozentWert?: number | null }): string {
|
||||
if (v.wertTyp === "prozent") return `${v.prozentWert ?? 0} % Rabatt`;
|
||||
return formatEuro(v.wertCents);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { validateVoucher, formatEuro, formatWert, formatMindestbestellwert, VOUCHER_SOON_DAYS, isExpiringSoon } from "../lib/vouchers";
|
||||
import { validateVoucher, formatEuro, formatWert, formatMindestbestellwert, euroToCents, VOUCHER_SOON_DAYS, isExpiringSoon } from "../lib/vouchers";
|
||||
describe("validateVoucher", () => {
|
||||
it("lehnt negativen Rest und Rest über Wert ab", () => {
|
||||
const base = { name: "X", anbieter: "Y", wertCents: 5000, restCents: 6000, ablaufdatum: null };
|
||||
@@ -16,6 +16,15 @@ describe("validateVoucher", () => {
|
||||
expect(validateVoucher({ ...base, mindestbestellwertCents: -1 })).toContain("mindestbestellwertCents");
|
||||
expect(validateVoucher({ ...base, wertTyp: "falsch" })).toContain("wertTyp");
|
||||
});
|
||||
it("euroToCents versteht Komma- und Punkteingaben", () => {
|
||||
expect(euroToCents("25,50")).toBe(2550);
|
||||
expect(euroToCents("25.50")).toBe(2550);
|
||||
expect(euroToCents("1.000,50")).toBe(100050);
|
||||
expect(euroToCents("20")).toBe(2000);
|
||||
expect(euroToCents("")).toBeNull();
|
||||
expect(euroToCents(undefined)).toBeNull();
|
||||
expect(Number.isNaN(euroToCents("abc") as number)).toBe(true);
|
||||
});
|
||||
it("formatWert zeigt Euro oder Prozent", () => {
|
||||
expect(formatWert({ wertTyp: "euro", wertCents: 2550 })).toContain("25,50");
|
||||
expect(formatWert({ wertTyp: "prozent", wertCents: 0, prozentWert: 20 })).toBe("20 % Rabatt");
|
||||
|
||||
Reference in new issue
Block a user