@flowselections/offertes-installatie-techniek 1.21.6 → 1.22.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-lib/components/offertes/ExtraWerkOffertePaneel.js +3 -3
- package/dist-lib/components/offertes/HandtekeningVlak.d.ts +4 -0
- package/dist-lib/components/offertes/HandtekeningVlak.d.ts.map +1 -0
- package/dist-lib/components/offertes/HandtekeningVlak.js +66 -0
- package/dist-lib/components/offertes/InterneNotitie.d.ts +8 -0
- package/dist-lib/components/offertes/InterneNotitie.d.ts.map +1 -0
- package/dist-lib/components/offertes/InterneNotitie.js +16 -0
- package/dist-lib/components/offertes/KlikbareTekst.d.ts +5 -0
- package/dist-lib/components/offertes/KlikbareTekst.d.ts.map +1 -0
- package/dist-lib/components/offertes/KlikbareTekst.js +10 -0
- package/dist-lib/components/offertes/NieuweOfferteFormulier.d.ts +1 -0
- package/dist-lib/components/offertes/NieuweOfferteFormulier.d.ts.map +1 -1
- package/dist-lib/components/offertes/NieuweOfferteFormulier.js +30 -10
- package/dist-lib/components/offertes/OfferteA4.d.ts.map +1 -1
- package/dist-lib/components/offertes/OfferteA4.js +2 -1
- package/dist-lib/components/offertes/OfferteAkkoordPage.d.ts.map +1 -1
- package/dist-lib/components/offertes/OfferteAkkoordPage.js +7 -2
- package/dist-lib/components/offertes/OfferteDetailPage.d.ts.map +1 -1
- package/dist-lib/components/offertes/OfferteDetailPage.js +13 -4
- package/dist-lib/components/offertes/VoorstelOffertePaneel.d.ts.map +1 -1
- package/dist-lib/components/offertes/VoorstelOffertePaneel.js +3 -0
- package/dist-lib/components/settings/OfferteVoorwaardenSettingsCard.d.ts.map +1 -1
- package/dist-lib/components/settings/OfferteVoorwaardenSettingsCard.js +2 -1
- package/dist-lib/index.d.ts +1 -0
- package/dist-lib/index.d.ts.map +1 -1
- package/dist-lib/index.js +1 -0
- package/dist-lib/integrations/supabase/types.d.ts +3625 -122
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.js +1 -0
- package/dist-lib/lib/offerteAcceptatie.functions.d.ts +1 -0
- package/dist-lib/lib/offerteAcceptatie.functions.d.ts.map +1 -1
- package/dist-lib/lib/offerteAcceptatie.functions.js +6 -0
- package/dist-lib/lib/offerteMail.d.ts.map +1 -1
- package/dist-lib/lib/offerteMail.js +14 -0
- package/dist-lib/lib/offerteRegels.d.ts +18 -0
- package/dist-lib/lib/offerteRegels.d.ts.map +1 -1
- package/dist-lib/lib/offerteRegels.js +15 -0
- package/dist-lib/lib/offerteRegels.test.js +13 -1
- package/dist-lib/lib/offerteReistijd.functions.d.ts +1 -1
- package/dist-lib/lib/offerteReistijd.functions.d.ts.map +1 -1
- package/dist-lib/lib/offerteReistijd.functions.js +4 -2
- package/dist-lib/lib/offertes.d.ts +3 -0
- package/dist-lib/lib/offertes.d.ts.map +1 -1
- package/package.json +2 -2
|
@@ -46,10 +46,10 @@ export function ExtraWerkOffertePaneel({ melding, index, aantal, onNavigate, onC
|
|
|
46
46
|
const [lijnen, pl, ml] = await Promise.all([
|
|
47
47
|
laadBronwerkbonRegels(wb.id),
|
|
48
48
|
wb.id
|
|
49
|
-
? db.from("
|
|
49
|
+
? db.from("werkbon_package_lines").select("package_id, quantity, extra_minutes").eq("werkbon_id", wb.id)
|
|
50
50
|
: Promise.resolve({ data: [] }),
|
|
51
51
|
wb.id
|
|
52
|
-
? db.from("
|
|
52
|
+
? db.from("werkbon_materials").select("description, quantity, unit, unit_price_cents").eq("werkbon_id", wb.id)
|
|
53
53
|
: Promise.resolve({ data: [] }),
|
|
54
54
|
]);
|
|
55
55
|
if (!actief)
|
|
@@ -58,7 +58,7 @@ export function ExtraWerkOffertePaneel({ melding, index, aantal, onNavigate, onC
|
|
|
58
58
|
const pakRows = Array.isArray(pl?.data) ? pl.data : [];
|
|
59
59
|
const packages = pakRows
|
|
60
60
|
.filter((r) => r.package_id)
|
|
61
|
-
.map((r) => ({ package_id: r.package_id, quantity: Number(r.quantity) || 1 }));
|
|
61
|
+
.map((r) => ({ package_id: r.package_id, quantity: Number(r.quantity) || 1, extra_minutes: Number(r.extra_minutes) || 0 }));
|
|
62
62
|
setInitial({
|
|
63
63
|
customerId: melding.customer_id ?? wb.customer_id ?? null,
|
|
64
64
|
werkadres: wb.work_address ?? "",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HandtekeningVlak.d.ts","sourceRoot":"","sources":["../../../src/components/offertes/HandtekeningVlak.tsx"],"names":[],"mappings":"AAKA,wBAAgB,gBAAgB,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;CAAE,+BAyE5F"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// Tekenvlak voor de handtekening van de klant (muis, vinger of pen).
|
|
3
|
+
import { useEffect, useRef } from "react";
|
|
4
|
+
import { Button } from "@flowselections/core";
|
|
5
|
+
import { Eraser } from "lucide-react";
|
|
6
|
+
export function HandtekeningVlak({ onChange }) {
|
|
7
|
+
const canvasRef = useRef(null);
|
|
8
|
+
const tekenen = useRef(false);
|
|
9
|
+
const heeftLijn = useRef(false);
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
const c = canvasRef.current;
|
|
12
|
+
if (!c)
|
|
13
|
+
return;
|
|
14
|
+
const ratio = window.devicePixelRatio || 1;
|
|
15
|
+
const rect = c.getBoundingClientRect();
|
|
16
|
+
c.width = rect.width * ratio;
|
|
17
|
+
c.height = rect.height * ratio;
|
|
18
|
+
const ctx = c.getContext("2d");
|
|
19
|
+
if (!ctx)
|
|
20
|
+
return;
|
|
21
|
+
ctx.scale(ratio, ratio);
|
|
22
|
+
ctx.lineWidth = 2;
|
|
23
|
+
ctx.lineCap = "round";
|
|
24
|
+
ctx.lineJoin = "round";
|
|
25
|
+
ctx.strokeStyle = getComputedStyle(c).color || "black";
|
|
26
|
+
}, []);
|
|
27
|
+
const punt = (e) => {
|
|
28
|
+
const r = e.currentTarget.getBoundingClientRect();
|
|
29
|
+
return { x: e.clientX - r.left, y: e.clientY - r.top };
|
|
30
|
+
};
|
|
31
|
+
const start = (e) => {
|
|
32
|
+
const ctx = e.currentTarget.getContext("2d");
|
|
33
|
+
if (!ctx)
|
|
34
|
+
return;
|
|
35
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
36
|
+
tekenen.current = true;
|
|
37
|
+
const p = punt(e);
|
|
38
|
+
ctx.beginPath();
|
|
39
|
+
ctx.moveTo(p.x, p.y);
|
|
40
|
+
};
|
|
41
|
+
const beweeg = (e) => {
|
|
42
|
+
if (!tekenen.current)
|
|
43
|
+
return;
|
|
44
|
+
const ctx = e.currentTarget.getContext("2d");
|
|
45
|
+
if (!ctx)
|
|
46
|
+
return;
|
|
47
|
+
const p = punt(e);
|
|
48
|
+
ctx.lineTo(p.x, p.y);
|
|
49
|
+
ctx.stroke();
|
|
50
|
+
heeftLijn.current = true;
|
|
51
|
+
};
|
|
52
|
+
const stop = () => {
|
|
53
|
+
if (!tekenen.current)
|
|
54
|
+
return;
|
|
55
|
+
tekenen.current = false;
|
|
56
|
+
if (heeftLijn.current && canvasRef.current)
|
|
57
|
+
onChange(canvasRef.current.toDataURL("image/png"));
|
|
58
|
+
};
|
|
59
|
+
const wissen = () => {
|
|
60
|
+
const c = canvasRef.current;
|
|
61
|
+
c?.getContext("2d")?.clearRect(0, 0, c.width, c.height);
|
|
62
|
+
heeftLijn.current = false;
|
|
63
|
+
onChange(null);
|
|
64
|
+
};
|
|
65
|
+
return (_jsxs("div", { className: "space-y-2", children: [_jsx("canvas", { ref: canvasRef, "aria-label": "Handtekeningvlak", className: "h-40 w-full touch-none rounded-md border border-border bg-background text-foreground", onPointerDown: start, onPointerMove: beweeg, onPointerUp: stop, onPointerLeave: stop }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: wissen, children: [_jsx(Eraser, { className: "mr-2 h-4 w-4" }), "Wissen"] })] }));
|
|
66
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
waarde: string | null;
|
|
3
|
+
onOpslaan: (tekst: string | null) => Promise<boolean>;
|
|
4
|
+
}
|
|
5
|
+
/** Interne notitie: hoort bij de offerte, komt nooit op A4, PDF of akkoordpagina. */
|
|
6
|
+
export declare function InterneNotitie({ waarde, onOpslaan }: Props): import("react").JSX.Element;
|
|
7
|
+
export {};
|
|
8
|
+
//# sourceMappingURL=InterneNotitie.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InterneNotitie.d.ts","sourceRoot":"","sources":["../../../src/components/offertes/InterneNotitie.tsx"],"names":[],"mappings":"AAGA,UAAU,KAAK;IACb,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACvD;AAED,qFAAqF;AACrF,wBAAgB,cAAc,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE,KAAK,+BA0B1D"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
import { toast } from "@flowselections/core";
|
|
4
|
+
/** Interne notitie: hoort bij de offerte, komt nooit op A4, PDF of akkoordpagina. */
|
|
5
|
+
export function InterneNotitie({ waarde, onOpslaan }) {
|
|
6
|
+
const [tekst, setTekst] = useState(waarde ?? "");
|
|
7
|
+
useEffect(() => setTekst(waarde ?? ""), [waarde]);
|
|
8
|
+
const opslaan = async () => {
|
|
9
|
+
const nieuw = tekst.trim() ? tekst.slice(0, 5000) : null;
|
|
10
|
+
if ((nieuw ?? "") === (waarde ?? ""))
|
|
11
|
+
return;
|
|
12
|
+
if (await onOpslaan(nieuw))
|
|
13
|
+
toast.success("Notitie opgeslagen");
|
|
14
|
+
};
|
|
15
|
+
return (_jsxs("div", { className: "w-full max-w-md flex-1 space-y-1", children: [_jsx("label", { className: "text-xs font-medium text-muted-foreground", htmlFor: "interne-notitie", children: "Interne notitie (niet zichtbaar voor de klant)" }), _jsx("textarea", { id: "interne-notitie", value: tekst, maxLength: 5000, onChange: (e) => setTekst(e.target.value), onBlur: opslaan, placeholder: "Notitie bij deze offerte\u2026", className: "min-h-[88px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring" })] }));
|
|
16
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"KlikbareTekst.d.ts","sourceRoot":"","sources":["../../../src/components/offertes/KlikbareTekst.tsx"],"names":[],"mappings":"AAIA,wBAAgB,aAAa,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,sCAuBxF"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
// Zet URL's in een tekst om naar klikbare links (A4, PDF en publieke akkoordpagina).
|
|
3
|
+
// Wordt ook gebruikt door de PDF-generatie: elke link krijgt data-offerte-link mee.
|
|
4
|
+
const URL_PATROON = /(https?:\/\/[^\s<>")\]]+)/g;
|
|
5
|
+
export function KlikbareTekst({ tekst, className }) {
|
|
6
|
+
if (!tekst)
|
|
7
|
+
return null;
|
|
8
|
+
const delen = tekst.split(URL_PATROON);
|
|
9
|
+
return (_jsx("p", { className: `whitespace-pre-wrap ${className ?? ""}`, children: delen.map((deel, i) => i % 2 === 1 ? (_jsx("a", { href: deel, target: "_blank", rel: "noopener noreferrer", "data-offerte-link": deel, className: "break-all underline underline-offset-2 hover:text-primary", children: deel }, i)) : (_jsx("span", { children: deel }, i))) }));
|
|
10
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NieuweOfferteFormulier.d.ts","sourceRoot":"","sources":["../../../src/components/offertes/NieuweOfferteFormulier.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"NieuweOfferteFormulier.d.ts","sourceRoot":"","sources":["../../../src/components/offertes/NieuweOfferteFormulier.tsx"],"names":[],"mappings":"AAoCA,MAAM,WAAW,wBAAwB;IACvC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC9E,SAAS,CAAC,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACjG;AAED,UAAU,KAAK;IACb,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACvC,SAAS,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,8EAA8E;IAC9E,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,wBAAwB,CAAC;IACnC,4EAA4E;IAC5E,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAsED,wBAAgB,sBAAsB,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,WAAW,EAAE,EAAE,KAAK,+BA4vBrK"}
|
|
@@ -11,6 +11,7 @@ import { OfferteA4 } from "./OfferteA4";
|
|
|
11
11
|
import { a4NaarPdfBase64, haalOfferteVoorwaarden, verstuurOfferteEmail } from "../../lib/offerteMail";
|
|
12
12
|
import { useHuisstijl } from "../../hooks/useHuisstijl";
|
|
13
13
|
import { ArtikelKiezer, ArtikelFoto } from "./ArtikelKiezer";
|
|
14
|
+
import { pakketBerekening, wijzigExtraMinuten } from "../../lib/offerteRegels";
|
|
14
15
|
const db = supabase;
|
|
15
16
|
const SOORTEN = ["Storing / reparatie", "Onderhoud", "Vervanging", "Nieuwe installatie", "Overig"];
|
|
16
17
|
const VAKGEBIEDEN = ["Verwarming", "Sanitair", "Ventilatie", "Elektra", "Koeling", "Overig"];
|
|
@@ -156,7 +157,14 @@ export function NieuweOfferteFormulier({ open, onOpenChange: onOpenChangeProp, o
|
|
|
156
157
|
const token = sessie?.session?.access_token;
|
|
157
158
|
if (!token)
|
|
158
159
|
throw new Error("Niet ingelogd");
|
|
159
|
-
|
|
160
|
+
let resultaat = await berekenReistijd({ data: { accessToken: token, destination: bestemming } });
|
|
161
|
+
if (resultaat.status === "niet_ingelogd") {
|
|
162
|
+
// Token verlopen: één keer verversen en opnieuw proberen.
|
|
163
|
+
const { data: ververst } = await supabase.auth.refreshSession();
|
|
164
|
+
const nieuwToken = ververst?.session?.access_token;
|
|
165
|
+
if (nieuwToken)
|
|
166
|
+
resultaat = await berekenReistijd({ data: { accessToken: nieuwToken, destination: bestemming } });
|
|
167
|
+
}
|
|
160
168
|
if (!actief)
|
|
161
169
|
return;
|
|
162
170
|
if (resultaat.status === "wacht" && pogingen >= 12) {
|
|
@@ -198,16 +206,19 @@ export function NieuweOfferteFormulier({ open, onOpenChange: onOpenChangeProp, o
|
|
|
198
206
|
}, [open, werkadres, pakketten.length, berekenReistijd, reistijdPoging]);
|
|
199
207
|
const regels = useMemo(() => gekozen.map((g) => {
|
|
200
208
|
const p = pakketten.find((x) => x.id === g.package_id);
|
|
201
|
-
return { ...g, naam: p?.name ?? "Pakket", prijs: Number(p?.unit_price_cents ?? 0) };
|
|
209
|
+
return { ...g, ...pakketBerekening(p ?? {}, g), naam: p?.name ?? "Pakket", prijs: Number(p?.unit_price_cents ?? 0), basis: p?.package_type === "basis", kwartierPrijs: Number(p?.extra_quarter_price_cents ?? 0) };
|
|
202
210
|
}), [gekozen, pakketten]);
|
|
203
|
-
const subtotaal = regels.reduce((s, r) => s +
|
|
211
|
+
const subtotaal = regels.reduce((s, r) => s + r.totaalPrijs, 0) +
|
|
204
212
|
materialen.reduce((s, m) => s + Math.round(m.quantity * m.unit_price_cents), 0);
|
|
213
|
+
const gedekteMinuten = regels.reduce((som, regel) => som + regel.gedekteMinuten, 0);
|
|
214
|
+
const pakkettenPayload = () => regels.map(({ package_id, quantity, extra_minutes }) => ({ package_id, quantity, extra_minutes }));
|
|
215
|
+
const kwartierRegels = () => regels.filter((r) => r.basis && r.kwartieren > 0).map((r) => ({ description: `Extra kwartier — ${r.naam}`, unit: "kwartier", quantity: r.kwartieren, unit_price_cents: r.kwartierPrijs, article_number: null }));
|
|
205
216
|
const materialenPayload = () => materialen.map((m) => ({ description: m.description.trim(), unit: m.unit.trim() || "st", quantity: m.quantity, unit_price_cents: m.unit_price_cents, article_number: m.article_number ?? null }));
|
|
206
217
|
const materialenOngeldig = materialen.some((m) => !m.description.trim() || !(m.quantity > 0));
|
|
207
218
|
const voegPakketToe = (id) => {
|
|
208
219
|
if (!id)
|
|
209
220
|
return;
|
|
210
|
-
setGekozen((g) => (g.some((x) => x.package_id === id) ? g.map((x) => (x.package_id === id ? { ...x, quantity: x.quantity + 1 } : x)) : [...g, { package_id: id, quantity: 1 }]));
|
|
221
|
+
setGekozen((g) => (g.some((x) => x.package_id === id) ? g.map((x) => (x.package_id === id && pakketten.find((p) => p.id === id)?.package_type !== "basis" ? { ...x, quantity: x.quantity + 1 } : x)) : [...g, { package_id: id, quantity: 1 }]));
|
|
211
222
|
setPakketKeuze("");
|
|
212
223
|
};
|
|
213
224
|
const formulierData = () => ({
|
|
@@ -215,7 +226,7 @@ export function NieuweOfferteFormulier({ open, onOpenChange: onOpenChangeProp, o
|
|
|
215
226
|
werkadres, kamer, contactpersoon_op_locatie: contactpersoon,
|
|
216
227
|
onderwerp, soort_werk: soort, vakgebied, toelichting_eigenaar: toelichting,
|
|
217
228
|
bijzonderheden: [bijzTekst].filter(Boolean),
|
|
218
|
-
pakketten: regels.map((r) => ({ naam: r.naam, aantal: r.quantity })),
|
|
229
|
+
pakketten: regels.map((r) => ({ naam: r.naam, aantal: r.quantity, extra_minutes: r.extra_minutes })),
|
|
219
230
|
});
|
|
220
231
|
const stelTekstenOp = async () => {
|
|
221
232
|
if (!toelichting.trim()) {
|
|
@@ -364,6 +375,7 @@ export function NieuweOfferteFormulier({ open, onOpenChange: onOpenChangeProp, o
|
|
|
364
375
|
vatRate: btw,
|
|
365
376
|
regels: [
|
|
366
377
|
...regels.map((r) => ({ description: r.naam, unit: "st", quantity: r.quantity, unit_price_cents: r.prijs })),
|
|
378
|
+
...kwartierRegels(),
|
|
367
379
|
...materialenPayload(),
|
|
368
380
|
],
|
|
369
381
|
});
|
|
@@ -434,7 +446,7 @@ export function NieuweOfferteFormulier({ open, onOpenChange: onOpenChangeProp, o
|
|
|
434
446
|
customer_id: klant.id, onderwerp: onderwerp.trim(), klant_tekst: klantTekst.trim(),
|
|
435
447
|
monteur_instructies: monteurTekst.trim(), aandachtspunten: aandachtspuntenTekst(),
|
|
436
448
|
work_address: werkadres.trim(), room_number: kamer.trim(), vat_rate: btw, geldig_dagen: geldig,
|
|
437
|
-
notities: [soort, vakgebied].filter(Boolean).join(" · "), packages:
|
|
449
|
+
notities: [soort, vakgebied].filter(Boolean).join(" · "), packages: pakkettenPayload(), materials: materialenPayload(),
|
|
438
450
|
});
|
|
439
451
|
toast.success("Offerte als concept opgeslagen");
|
|
440
452
|
onCreated(id);
|
|
@@ -470,7 +482,7 @@ export function NieuweOfferteFormulier({ open, onOpenChange: onOpenChangeProp, o
|
|
|
470
482
|
vat_rate: btw,
|
|
471
483
|
geldig_dagen: geldig,
|
|
472
484
|
notities: [soort, vakgebied].filter(Boolean).join(" · "),
|
|
473
|
-
packages:
|
|
485
|
+
packages: pakkettenPayload(), materials: materialenPayload(),
|
|
474
486
|
};
|
|
475
487
|
offerteId = await slaOp(payload);
|
|
476
488
|
const { data: nr, error: nrErr } = await db.rpc("quote_assign_number", { _offerte_id: offerteId });
|
|
@@ -511,13 +523,21 @@ export function NieuweOfferteFormulier({ open, onOpenChange: onOpenChangeProp, o
|
|
|
511
523
|
};
|
|
512
524
|
const velden = (_jsxs("div", { className: "space-y-4", children: [_jsxs(Sectie, { nr: 1, titel: "Klant", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsxs(Select, { value: klantId, onValueChange: setKlantId, children: [_jsx(SelectTrigger, { className: "flex-1", children: _jsx(SelectValue, { placeholder: "Kies een klant" }) }), _jsx(SelectContent, { className: "max-h-72", children: klanten.map((k) => _jsx(SelectItem, { value: k.id, children: k.name }, k.id)) })] }), _jsxs(Button, { type: "button", variant: "outline", onClick: openKlantDialoog, children: [_jsx(UserPlus, { className: "mr-1 h-4 w-4" }), "Nieuwe klant aanmaken"] })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { children: "E-mailadres voor de offerte" }), _jsx(Input, { type: "email", value: email, onChange: (e) => setEmail(e.target.value), placeholder: "klant@voorbeeld.nl" })] })] }), _jsx(Sectie, { nr: 2, titel: "Locatie", children: _jsxs("div", { className: "grid gap-3 sm:grid-cols-2", children: [_jsxs("div", { className: "space-y-1 sm:col-span-2", children: [_jsx(Label, { children: "Werkadres" }), _jsx(Input, { value: werkadres, onChange: (e) => setWerkadres(e.target.value) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { children: "Kamer / ruimte" }), _jsx(Input, { value: kamer, onChange: (e) => setKamer(e.target.value) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { children: "Contactpersoon op locatie" }), _jsx(Input, { value: contactpersoon, onChange: (e) => setContactpersoon(e.target.value) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { children: "Telefoon contactpersoon" }), _jsx(Input, { value: contactTel, onChange: (e) => setContactTel(e.target.value) })] })] }) }), _jsxs(Sectie, { nr: 3, titel: "Het werk", children: [_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { children: "Onderwerp / klus *" }), _jsx(Input, { value: onderwerp, onChange: (e) => setOnderwerp(e.target.value), placeholder: "Bijv. vervangen cv-ketel" })] }), _jsxs("div", { className: "grid gap-3 sm:grid-cols-2", children: [_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { children: "Soort werk" }), _jsxs(Select, { value: soort, onValueChange: setSoort, children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Kies" }) }), _jsx(SelectContent, { children: SOORTEN.map((s) => _jsx(SelectItem, { value: s, children: s }, s)) })] })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { children: "Vakgebied" }), _jsxs(Select, { value: vakgebied, onValueChange: setVakgebied, children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Kies" }) }), _jsx(SelectContent, { children: VAKGEBIEDEN.map((s) => _jsx(SelectItem, { value: s, children: s }, s)) })] })] })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { children: "Korte toelichting" }), _jsx(Textarea, { rows: 3, value: toelichting, onChange: (e) => setToelichting(normalizeBullets(e.target.value)), onKeyDown: toelichtingKeyDown, onFocus: () => setToelichting((v) => (v ? v : "• ")), placeholder: "Geef de punten die aangepakt gaan worden" })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { children: "Bijzonderheden" }), _jsx(Input, { value: bijzTekst, onChange: (e) => setBijzTekst(e.target.value), placeholder: "Overige bijzonderheden" })] })] }), _jsxs(Sectie, { nr: 4, titel: "Prijs (pakketten en materialen)", children: [_jsxs("div", { className: `flex items-start gap-2 rounded-md border p-3 text-sm ${reistijd && reistijd.status !== "ok" ? "border-destructive text-destructive" : "bg-muted/40"}`, children: [reistijdBezig || reistijd?.status === "wacht" ? _jsx(Loader2, { className: "mt-0.5 h-4 w-4 shrink-0 animate-spin" }) : _jsx(Clock3, { className: "mt-0.5 h-4 w-4 shrink-0" }), _jsxs("div", { children: [_jsx("p", { className: "font-medium", children: "Reistijd vanaf de zaak" }), _jsx("p", { className: reistijd && reistijd.status !== "ok" ? "" : "text-muted-foreground", children: reistijdBezig ? "Reistijd wordt exact berekend…" : reistijd?.pakket
|
|
513
525
|
? `${reistijd.enkeleRitMinuten ?? reistijd.minuten} min heen + ${reistijd.enkeleRitMinuten ?? reistijd.minuten} min terug = ${reistijd.minuten} min · ${reistijd.pakket.aantal}× ${reistijd.pakket.naam} automatisch toegevoegd`
|
|
514
|
-
: reistijd?.melding ?? "Vul een werkadres in om de reistijd te berekenen." }), !reistijdBezig && (reistijd?.status === "kaarten_fout" || (reistijd?.status === "onbekend" && !reistijd.melding.startsWith("Vul "))) && (_jsx(Button, { type: "button", variant: "outline", size: "sm", className: "mt-2", onClick: () => setReistijdPoging((n) => n + 1), children: "Opnieuw proberen" }))] })] }), _jsxs("div", { className: "grid gap-2", children: [_jsxs(Popover, { open: pakketOpen, onOpenChange: setPakketOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { type: "button", variant: "outline", className: "w-full justify-between font-normal", children: ["Pakket toevoegen", _jsx(ChevronsUpDown, { className: "ml-2 h-4 w-4 shrink-0 opacity-50" })] }) }), _jsxs(PopoverContent, { className: "w-[var(--radix-popover-trigger-width)] p-0", align: "start", children: [_jsx("div", { className: "border-b p-2", children: _jsx(Input, { autoFocus: true, value: pakketZoek, onChange: (e) => setPakketZoek(e.target.value), placeholder: "Zoek een pakket\u2026" }) }), _jsxs("div", { className: "max-h-72 overflow-y-auto p-1", children: [pakkettenGefilterd.length === 0 && (_jsx("p", { className: "py-6 text-center text-sm text-muted-foreground", children: "Geen pakket gevonden." })), pakkettenGefilterd
|
|
526
|
+
: reistijd?.melding ?? "Vul een werkadres in om de reistijd te berekenen." }), !reistijdBezig && (reistijd?.status === "kaarten_fout" || (reistijd?.status === "onbekend" && !reistijd.melding.startsWith("Vul "))) && (_jsx(Button, { type: "button", variant: "outline", size: "sm", className: "mt-2", onClick: () => setReistijdPoging((n) => n + 1), children: "Opnieuw proberen" }))] })] }), _jsxs("div", { className: "grid gap-2", children: [_jsxs(Popover, { open: pakketOpen, onOpenChange: setPakketOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { type: "button", variant: "outline", className: "w-full justify-between font-normal", children: ["Pakket toevoegen", _jsx(ChevronsUpDown, { className: "ml-2 h-4 w-4 shrink-0 opacity-50" })] }) }), _jsxs(PopoverContent, { className: "w-[var(--radix-popover-trigger-width)] p-0", align: "start", children: [_jsx("div", { className: "border-b p-2", children: _jsx(Input, { autoFocus: true, value: pakketZoek, onChange: (e) => setPakketZoek(e.target.value), placeholder: "Zoek een pakket\u2026" }) }), _jsxs("div", { className: "max-h-72 overflow-y-auto p-1", children: [pakkettenGefilterd.length === 0 && (_jsx("p", { className: "py-6 text-center text-sm text-muted-foreground", children: "Geen pakket gevonden." })), pakkettenGefilterd
|
|
527
|
+
.filter((p) => !(Number(p.travel_minutes) > 0 && Number(p.labor_minutes ?? 0) === 0))
|
|
528
|
+
.map((p, i) => ({ p, i }))
|
|
529
|
+
.sort((a, b) => Number(b.p.package_type === "basis") - Number(a.p.package_type === "basis") || a.i - b.i)
|
|
530
|
+
.map(({ p }) => (_jsxs("button", { type: "button", className: "relative flex w-full cursor-pointer select-none items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm outline-none hover:bg-accent hover:text-accent-foreground", onClick: () => {
|
|
515
531
|
voegPakketToe(p.id);
|
|
516
532
|
setPakketOpen(false);
|
|
517
533
|
setPakketZoek("");
|
|
518
|
-
}, children: [p.code ? `${p.code} · ` : "", p.name, " \u2014 ", formatEuro(Number(p.unit_price_cents ?? 0))] }, p.id)))] })] })] }), _jsx(ArtikelKiezer, { onKies: (a) => setMaterialen((m) => [...m, { key: crypto.randomUUID(), description: a.description, unit: a.unit, quantity: 1, unit_price_cents: a.unit_price_cents, article_number: a.article_number, foto_url: a.foto_url }]) })] }), materialen.length > 0 && (_jsx("div", { className: "divide-y rounded-md border", children: materialen.map((m) => {
|
|
534
|
+
}, children: [_jsxs("span", { className: "flex-1", children: [p.code ? `${p.code} · ` : "", p.name, " \u2014 ", formatEuro(Number(p.unit_price_cents ?? 0))] }), p.package_type === "basis" && (_jsx("span", { className: "rounded bg-primary/10 px-1.5 py-0.5 text-xs font-medium text-primary", children: "Basis" }))] }, p.id)))] })] })] }), _jsx(ArtikelKiezer, { onKies: (a) => setMaterialen((m) => [...m, { key: crypto.randomUUID(), description: a.description, unit: a.unit, quantity: 1, unit_price_cents: a.unit_price_cents, article_number: a.article_number, foto_url: a.foto_url }]) })] }), materialen.length > 0 && (_jsx("div", { className: "divide-y rounded-md border", children: materialen.map((m) => {
|
|
519
535
|
const upd = (patch) => setMaterialen((lijst) => lijst.map((x) => (x.key === m.key ? { ...x, ...patch } : x)));
|
|
520
536
|
return (_jsxs("div", { className: "flex flex-wrap items-center gap-2 p-2 text-sm", children: [_jsx("span", { className: "text-xs text-muted-foreground", children: "materiaal" }), _jsx(Input, { className: "min-w-40 flex-1", placeholder: "Omschrijving", value: m.description, onChange: (e) => upd({ description: e.target.value }) }), _jsx(Input, { className: "w-20 text-right", type: "number", step: "1", min: 0, value: m.quantity, onChange: (e) => upd({ quantity: Number(e.target.value) || 0 }) }), _jsx(Input, { className: "w-16", placeholder: "st", value: m.unit, onChange: (e) => upd({ unit: e.target.value }) }), _jsx(Input, { className: "w-24 text-right", type: "number", step: "0.01", min: 0, placeholder: "Prijs invullen", value: m.unit_price_cents ? m.unit_price_cents / 100 : "", onChange: (e) => upd({ unit_price_cents: Math.round((Number(e.target.value) || 0) * 100) }) }), _jsx("span", { className: "w-24 text-right", children: formatEuro(Math.round(m.quantity * m.unit_price_cents)) }), _jsx(ArtikelFoto, { url: m.foto_url, className: "size-8" }), _jsx(Button, { size: "icon", variant: "ghost", onClick: () => setMaterialen((lijst) => lijst.filter((x) => x.key !== m.key)), children: _jsx(Trash2, { className: "h-4 w-4" }) })] }, m.key));
|
|
521
|
-
}) })), (regels.length > 0 || materialen.length > 0) && (_jsxs("div", { className: "divide-y rounded-md border", children: [regels.map((r) =>
|
|
537
|
+
}) })), (regels.length > 0 || materialen.length > 0) && (_jsxs("div", { className: "divide-y rounded-md border", children: [regels.map((r) => {
|
|
538
|
+
const auto = r.package_id === automatischReisPakket;
|
|
539
|
+
const zet = (patch) => setGekozen((g) => g.map((x) => (x.package_id === r.package_id ? { ...x, quantity: r.quantity, ...patch } : x)));
|
|
540
|
+
return (_jsxs("div", { className: "p-2 text-sm", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsxs("span", { className: "flex-1", children: [r.naam, auto && _jsx("span", { className: "ml-2 text-xs text-muted-foreground", children: "automatisch" }), r.basis && _jsx("span", { className: "ml-2 rounded bg-primary/10 px-1.5 py-0.5 text-xs font-medium text-primary", children: "Basis" })] }), _jsx(Button, { type: "button", size: "icon", variant: "ghost", "aria-label": r.basis ? `Kwartier minder bij ${r.naam}` : `Aantal verlagen bij ${r.naam}`, disabled: auto || (r.basis && r.kwartieren === 0), onClick: () => (r.basis ? zet({ extra_minutes: wijzigExtraMinuten(r.extra_minutes, -1) }) : zet({ quantity: Math.max(1, r.quantity - 1) })), children: _jsx(Minus, { className: "h-4 w-4" }) }), _jsx("span", { className: "min-w-14 text-center tabular-nums", children: r.basis ? `+${r.kwartieren} kw` : `× ${r.quantity}` }), _jsx(Button, { type: "button", size: "icon", variant: "ghost", "aria-label": r.basis ? `Kwartier erbij bij ${r.naam}` : `Aantal verhogen bij ${r.naam}`, disabled: auto, onClick: () => (r.basis ? zet({ extra_minutes: wijzigExtraMinuten(r.extra_minutes, 1) }) : zet({ quantity: r.quantity + 1 })), children: _jsx(Plus, { className: "h-4 w-4" }) }), _jsx("span", { className: "w-24 text-right", children: formatEuro(Math.round(r.quantity * r.prijs)) }), _jsx(Button, { size: "icon", variant: "ghost", disabled: auto, onClick: () => setGekozen((g) => g.filter((x) => x.package_id !== r.package_id)), children: _jsx(Trash2, { className: "h-4 w-4" }) })] }), r.basis && (_jsx("div", { className: "mt-1 flex justify-between pl-2 pr-12 text-xs text-muted-foreground", children: _jsxs("span", { children: ["+", r.kwartieren, " kwartier (", Math.floor(r.extra_minutes / 60), ":", String(r.extra_minutes % 60).padStart(2, "0"), " uur)"] }) }))] }, r.package_id));
|
|
541
|
+
}), _jsxs("div", { className: "flex justify-between p-2 text-sm font-medium", children: [_jsx("span", { children: "Subtotaal excl. btw" }), _jsx("span", { children: formatEuro(subtotaal) })] }), _jsxs("div", { className: "flex justify-between p-2 text-sm", children: [_jsx("span", { children: "Gedekte pakkettijd" }), _jsxs("span", { className: "tabular-nums", children: [Math.floor(gedekteMinuten / 60), ":", String(gedekteMinuten % 60).padStart(2, "0"), " uur"] })] })] })), _jsxs("div", { className: "grid gap-3 sm:grid-cols-2", children: [_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { children: "Btw %" }), _jsx(Input, { type: "number", value: btw, onChange: (e) => setBtw(Number(e.target.value) || 0) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { children: "Geldig (dagen)" }), _jsx(Input, { type: "number", value: geldig, onChange: (e) => setGeldig(Math.max(1, Number(e.target.value) || 30)) })] })] }), _jsx("p", { className: "text-xs text-muted-foreground", children: "De duur van de werkbon komt uit de pakketten; het mandaat uit de pakketten en materialen samen." })] }), _jsxs(Sectie, { nr: 5, titel: "Teksten *", fout: !tekstenCompleet, children: [_jsx("p", { className: "text-xs text-muted-foreground", children: "Verplicht: klik op Teksten opstellen voordat je kunt verzenden." }), _jsxs(Button, { variant: "outline", onClick: stelTekstenOp, disabled: aiBezig, children: [aiBezig ? _jsx(Loader2, { className: "mr-1 h-4 w-4 animate-spin" }) : _jsx(Sparkles, { className: "mr-1 h-4 w-4" }), "Teksten opstellen"] }), (klantTekst || monteurTekst) && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { children: "Tekst voor de klant (in de offerte)" }), _jsx(Textarea, { rows: 5, value: klantTekst, onChange: (e) => setKlantTekst(e.target.value) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { children: "Instructies voor de monteur (in de werkbon)" }), _jsx(Textarea, { rows: 6, value: monteurTekst, onChange: (e) => setMonteurTekst(e.target.value) })] })] }))] })] }));
|
|
522
542
|
return (_jsxs(_Fragment, { children: [inline ? (_jsxs("div", { className: "space-y-4", children: [velden, _jsxs("div", { className: "flex flex-wrap items-center justify-end gap-2 border-t pt-4", children: [!tekstenCompleet && (_jsx("p", { className: "mr-auto text-xs text-destructive", children: "Stel eerst de teksten op met Teksten opstellen." })), _jsx(Button, { variant: "outline", onClick: opslaanAlsConcept, disabled: saving, children: "Opslaan als concept" }), _jsxs(Button, { onClick: openControle, disabled: saving || !tekstenCompleet, children: [_jsx(Send, { className: "mr-1 h-4 w-4" }), "Verzenden"] })] })] })) : (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "max-h-[92vh] overflow-y-auto sm:max-w-3xl", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: "Nieuwe offerte" }) }), velden, _jsxs(DialogFooter, { className: "items-center", children: [!tekstenCompleet && (_jsx("p", { className: "text-xs text-destructive", children: "Stel eerst de teksten op met Teksten opstellen." })), _jsx(Button, { variant: "outline", onClick: () => onOpenChange(false), children: "Annuleren" }), _jsxs(Button, { onClick: openControle, disabled: saving || !tekstenCompleet, children: [_jsx(Send, { className: "mr-1 h-4 w-4" }), "Verzenden"] })] })] }) })), _jsx(Dialog, { open: controleOpen, onOpenChange: (o) => !saving && setControleOpen(o), children: _jsxs(DialogContent, { className: "z-[80] max-h-[92vh] overflow-y-auto sm:max-w-[1200px]", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: "Controleer offerte en e-mail" }) }), _jsxs("div", { className: "grid gap-6 lg:grid-cols-[minmax(0,1fr)_380px]", children: [_jsx("div", { className: "max-h-[75vh] overflow-auto rounded-md border bg-muted/30 p-4", children: voorbeeld && (_jsx("div", { ref: pdfRef, children: _jsx(OfferteA4, { waarden: voorbeeld, offerteNummer: defNummer, werkbonNummer: bronWerkbonNummer ?? null }) })) }), _jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { children: "Aan" }), _jsx(Input, { type: "email", value: mailAan, onChange: (e) => setMailAan(e.target.value) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { children: "Onderwerp" }), _jsx(Input, { value: mailOnderwerp, onChange: (e) => setMailOnderwerp(e.target.value) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { children: "Bericht" }), _jsx(Textarea, { rows: 16, value: mailBericht, onChange: (e) => setMailBericht(e.target.value) })] }), _jsx("p", { className: "text-xs text-muted-foreground", children: "De offerte gaat als PDF-bijlage mee. De werkbon wordt klaargezet en gaat pas naar Werkbonnen zodra de offerte is goedgekeurd." })] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "outline", onClick: () => setControleOpen(false), disabled: saving, children: "Terug" }), _jsxs(Button, { onClick: bevestigVersturen, disabled: saving, children: [saving && _jsx(Loader2, { className: "mr-1 h-4 w-4 animate-spin" }), "Bevestig versturen"] })] })] }) }), _jsx(Dialog, { open: klantOpen, onOpenChange: setKlantOpen, children: _jsxs(DialogContent, { className: "z-[80] sm:max-w-lg", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: "Nieuwe klant aanmaken" }) }), _jsxs("div", { className: "grid gap-3 sm:grid-cols-2", children: [_jsx("div", { className: "sm:col-span-2", children: _jsx(Veld, { label: "Naam", verplicht: true, fout: klantFouten.name, children: _jsx(Input, { ...nk("name") }) }) }), _jsx(Veld, { label: "E-mailadres", verplicht: true, fout: klantFouten.general_email, children: _jsx(Input, { type: "email", ...nk("general_email") }) }), _jsx(Veld, { label: "Telefoon", verplicht: true, fout: klantFouten.general_phone, children: _jsx(Input, { ...nk("general_phone") }) }), _jsx("div", { className: "sm:col-span-2", children: _jsx(Veld, { label: "Straat + huisnummer", verplicht: true, fout: klantFouten.billing_street, children: _jsx(Input, { ...nk("billing_street") }) }) }), _jsx(Veld, { label: "Postcode", verplicht: true, fout: klantFouten.billing_postcode, children: _jsx(Input, { ...nk("billing_postcode") }) }), _jsx(Veld, { label: "Plaats", verplicht: true, fout: klantFouten.billing_city, children: _jsx(Input, { ...nk("billing_city") }) }), _jsx(Veld, { label: "KvK (optioneel)", children: _jsx(Input, { ...nk("kvk_number") }) }), _jsx(Veld, { label: "Btw-nummer (optioneel)", children: _jsx(Input, { ...nk("vat_number") }) })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "outline", onClick: () => setKlantOpen(false), children: "Annuleren" }), _jsxs(Button, { onClick: slaKlantOp, disabled: klantSaving, children: [klantSaving && _jsx(Loader2, { className: "mr-1 h-4 w-4 animate-spin" }), "Klant opslaan"] })] })] }) })] }));
|
|
523
543
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OfferteA4.d.ts","sourceRoot":"","sources":["../../../src/components/offertes/OfferteA4.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"OfferteA4.d.ts","sourceRoot":"","sources":["../../../src/components/offertes/OfferteA4.tsx"],"names":[],"mappings":"AAQA,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,cAAc,EAAE,CAAC;CAC1B;AAED,UAAU,KAAK;IACb,OAAO,EAAE,gBAAgB,CAAC;IAC1B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAE,gBAAgB,KAAK,IAAI,CAAC;CAC1C;AAyED,eAAO,MAAM,SAAS,kGAuIpB,CAAC"}
|
|
@@ -5,6 +5,7 @@ import { forwardRef, useEffect, useRef } from "react";
|
|
|
5
5
|
import { Plus, Trash2 } from "lucide-react";
|
|
6
6
|
import { formatEuro } from "../../lib/offertes";
|
|
7
7
|
import { useHuisstijl } from "../../hooks/useHuisstijl";
|
|
8
|
+
import { KlikbareTekst } from "./KlikbareTekst";
|
|
8
9
|
const invoer = "w-full rounded-sm border border-dashed border-border bg-transparent px-1 py-0.5 outline-none focus:border-primary";
|
|
9
10
|
function datum(v) {
|
|
10
11
|
if (!v)
|
|
@@ -42,6 +43,6 @@ export const OfferteA4 = forwardRef(function OfferteA4({ waarden, offerteNummer,
|
|
|
42
43
|
const totaal = subtotaal + btw;
|
|
43
44
|
const zet = (k, v) => onChange?.({ ...waarden, [k]: v });
|
|
44
45
|
const zetRegel = (i, patch) => zet("regels", regels.map((r, j) => (j === i ? { ...r, ...patch } : r)));
|
|
45
|
-
const T = ({ k, placeholder, className }) => editable ? (_jsx(AutoTekst, { value: waarden[k], placeholder: placeholder, onChange: (v) => zet(k, v), className: className })) : (_jsx("p", { className: `whitespace-pre-wrap ${className ?? ""}`, children: waarden[k] || (placeholder ? "" : "") }));
|
|
46
|
+
const T = ({ k, placeholder, className }) => editable ? (_jsx(AutoTekst, { value: waarden[k], placeholder: placeholder, onChange: (v) => zet(k, v), className: className })) : k === "voorwaarden" ? (_jsx(KlikbareTekst, { tekst: waarden[k], className: className })) : (_jsx("p", { className: `whitespace-pre-wrap ${className ?? ""}`, children: waarden[k] || (placeholder ? "" : "") }));
|
|
46
47
|
return (_jsxs("div", { ref: ref, className: "flex flex-col items-center gap-6", children: [!loading && (fout || !ingesteld) && (_jsx("p", { className: "w-[794px] rounded-md bg-warning/15 px-3 py-2 text-sm text-warning-foreground", children: fout ?? "Vul eerst de huisstijl in bij Instellingen (bedrijfsnaam, logo, KvK, btw, IBAN en kleuren)." })), _jsxs(Pagina, { h: huisstijl, logoUrl: logoUrl, children: [_jsx("h1", { className: "mb-5 text-2xl font-bold", children: "OFFERTE" }), _jsxs("div", { className: "mb-5 flex items-start justify-between gap-8", children: [_jsxs("div", { className: "w-72 space-y-1", children: [_jsx(T, { k: "klantNaam", placeholder: "Klantnaam", className: "font-semibold" }), _jsx(T, { k: "werkadres", placeholder: "Werkadres" })] }), _jsxs("dl", { className: "grid min-w-64 grid-cols-[auto_1fr] items-center gap-x-5 gap-y-1", children: [_jsx("dt", { children: "Offertenummer" }), _jsx("dd", { className: "font-medium", children: offerteNummer ?? "Concept" }), _jsx("dt", { children: "Datum offerte" }), _jsx("dd", { children: datum(new Date().toISOString()) }), _jsx("dt", { children: "Geldig t/m" }), _jsx("dd", { children: editable ? _jsx("input", { type: "date", className: invoer, value: waarden.geldigTot, onChange: (e) => zet("geldigTot", e.target.value) }) : datum(waarden.geldigTot) }), _jsx("dt", { children: "Werkbonnummer" }), _jsx("dd", { children: werkbonNummer ?? "—" })] })] }), _jsxs("div", { className: "mb-6 flex items-start gap-2", children: [_jsx("strong", { className: "shrink-0 pt-0.5", children: "Betreft:" }), _jsx("div", { className: "flex-1", children: _jsx(T, { k: "onderwerp", placeholder: "Onderwerp" }) })] }), _jsxs("section", { className: "mb-6", children: [_jsx("h2", { className: "mb-2 text-base font-bold", children: "Aanleiding en context" }), _jsx(T, { k: "aanleiding", placeholder: "Aanleiding (melding monteur)", className: "mb-2" }), _jsx(T, { k: "omschrijving", placeholder: "Toelichting op de offerte" })] }), _jsxs("section", { className: "mb-6", children: [_jsx("h2", { className: "mb-2 text-base font-bold", children: "Werkzaamheden" }), regels.length ? (_jsx("ol", { className: "list-decimal space-y-1 pl-7", children: regels.map((r, i) => _jsx("li", { className: "pl-1 whitespace-pre-wrap", children: r.description || "—" }, i)) })) : _jsx("p", { children: "Er zijn nog geen werkzaamheden toegevoegd." })] }), _jsxs("section", { children: [_jsx("h2", { className: "mb-2 text-base font-bold", children: "Risico's en aandachtspunten" }), _jsx(T, { k: "aandachtspunten", placeholder: "Aandachtspunten" })] })] }), _jsxs(Pagina, { h: huisstijl, logoUrl: logoUrl, children: [_jsxs("section", { className: "mb-7", children: [_jsx("h2", { className: "mb-4 text-base font-bold", children: "Financieel overzicht" }), _jsxs("table", { className: "w-full table-fixed border-collapse text-left", children: [_jsx("thead", { className: "border-y border-border", children: _jsxs("tr", { className: "font-bold", children: [_jsx("th", { className: "w-[44%] py-2", children: "Omschrijving" }), _jsx("th", { className: "w-[11%] py-2", children: "Eenheid" }), _jsx("th", { className: "w-[10%] py-2 text-right", children: "Aantal" }), _jsx("th", { className: "w-[15%] py-2 text-right", children: "Stukprijs" }), _jsx("th", { className: "w-[15%] py-2 text-right", children: "Totaalprijs" }), editable && _jsx("th", { className: "w-[5%]" })] }) }), _jsxs("tbody", { children: [regels.map((r, i) => (_jsxs("tr", { className: "align-top", children: [_jsx("td", { className: "py-1 pr-2", children: editable ? _jsx(AutoTekst, { value: r.description, placeholder: "Omschrijving", onChange: (v) => zetRegel(i, { description: v }) }) : _jsx("span", { className: "whitespace-pre-wrap break-words", children: r.description || "—" }) }), _jsx("td", { className: "py-1 pr-2", children: editable ? _jsx("input", { className: invoer, value: r.unit, onChange: (e) => zetRegel(i, { unit: e.target.value }) }) : r.unit }), _jsx("td", { className: "py-1 text-right", children: editable ? _jsx("input", { type: "number", className: `${invoer} text-right`, value: r.quantity, onChange: (e) => zetRegel(i, { quantity: Number(e.target.value) }) }) : r.quantity }), _jsx("td", { className: "py-1 text-right", children: editable ? _jsx("input", { type: "number", step: "0.01", className: `${invoer} text-right`, value: (r.unit_price_cents / 100).toString(), onChange: (e) => zetRegel(i, { unit_price_cents: Math.round(Number(e.target.value) * 100) }) }) : formatEuro(r.unit_price_cents) }), _jsx("td", { className: "py-1 text-right", children: formatEuro(Math.round(r.quantity * r.unit_price_cents)) }), editable && (_jsx("td", { className: "py-1 text-right", children: _jsx("button", { type: "button", "aria-label": "Regel verwijderen", className: "text-muted-foreground hover:text-destructive", onClick: () => zet("regels", regels.filter((_, j) => j !== i)), children: _jsx(Trash2, { className: "h-4 w-4" }) }) }))] }, i))), editable && (_jsx("tr", { children: _jsx("td", { colSpan: 6, className: "py-2", children: _jsxs("button", { type: "button", className: "flex items-center gap-1 text-primary", onClick: () => zet("regels", [...regels, { description: "", unit: "st", quantity: 1, unit_price_cents: 0 }]), children: [_jsx(Plus, { className: "h-4 w-4" }), " Regel toevoegen"] }) }) })), _jsxs("tr", { className: "border-t border-border", children: [_jsx("td", { colSpan: 4, className: "pt-2", children: "Subtotaal" }), _jsx("td", { className: "pt-2 text-right", children: formatEuro(subtotaal) })] }), _jsxs("tr", { children: [_jsx("td", { colSpan: 4, children: _jsxs("span", { className: "flex items-center gap-1", children: ["Btw ", editable ? _jsx("input", { type: "number", className: `${invoer} w-14 text-right`, value: waarden.vatRate, onChange: (e) => zet("vatRate", Number(e.target.value)) }) : waarden.vatRate, "%"] }) }), _jsx("td", { className: "text-right", children: formatEuro(btw) })] }), _jsxs("tr", { className: "font-bold", children: [_jsx("td", { colSpan: 4, children: "Totaalprijs incl. btw" }), _jsx("td", { className: "text-right", children: formatEuro(totaal) })] })] })] })] }), _jsxs("section", { className: "mb-8", children: [_jsx("h2", { className: "mb-3 text-base font-bold", children: "Voorwaarden en toelichting" }), _jsx(T, { k: "voorwaarden", placeholder: "Voorwaarden" })] }), _jsxs("section", { children: [_jsx("p", { className: "mb-6", children: "Voor akkoord:" }), _jsxs("div", { className: "space-y-5", children: [_jsx("p", { children: "Plaats: ................................................................................" }), _jsx("p", { children: "Datum: ................................................................................." }), _jsx("p", { children: "Naam ondertekenaar: ............................................................." }), _jsx("p", { className: "pt-6", children: "Handtekening: ......................................................................." })] })] })] })] }));
|
|
47
48
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OfferteAkkoordPage.d.ts","sourceRoot":"","sources":["../../../src/components/offertes/OfferteAkkoordPage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"OfferteAkkoordPage.d.ts","sourceRoot":"","sources":["../../../src/components/offertes/OfferteAkkoordPage.tsx"],"names":[],"mappings":"AAoCA,wBAAgB,kBAAkB,CAAC,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,+BA2K9D"}
|
|
@@ -6,7 +6,9 @@ import { useServerFn } from "@tanstack/react-start";
|
|
|
6
6
|
import { Button, Input, Label, supabase } from "@flowselections/core";
|
|
7
7
|
import { CheckCircle2, Loader2 } from "lucide-react";
|
|
8
8
|
import { formatEuro } from "../../lib/offertes";
|
|
9
|
+
import { KlikbareTekst } from "./KlikbareTekst";
|
|
9
10
|
import { accepteerPubliekeOfferte } from "../../lib/offerteAcceptatie.functions";
|
|
11
|
+
import { HandtekeningVlak } from "./HandtekeningVlak";
|
|
10
12
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
11
13
|
const db = supabase;
|
|
12
14
|
export function OfferteAkkoordPage({ token }) {
|
|
@@ -15,6 +17,7 @@ export function OfferteAkkoordPage({ token }) {
|
|
|
15
17
|
const [naam, setNaam] = useState("");
|
|
16
18
|
const [email, setEmail] = useState("");
|
|
17
19
|
const [akkoord, setAkkoord] = useState(false);
|
|
20
|
+
const [handtekening, setHandtekening] = useState(null);
|
|
18
21
|
const [bezig, setBezig] = useState(false);
|
|
19
22
|
const [fout, setFout] = useState(null);
|
|
20
23
|
const [klaar, setKlaar] = useState(false);
|
|
@@ -41,9 +44,11 @@ export function OfferteAkkoordPage({ token }) {
|
|
|
41
44
|
return setFout("Vul een geldig e-mailadres in.");
|
|
42
45
|
if (!akkoord)
|
|
43
46
|
return setFout("Vink aan dat u akkoord gaat met de offerte en voorwaarden.");
|
|
47
|
+
if (!handtekening)
|
|
48
|
+
return setFout("Zet uw handtekening.");
|
|
44
49
|
setBezig(true);
|
|
45
50
|
try {
|
|
46
|
-
await accepteer({ data: { token, naam: naam.trim(), email: email.trim(), akkoord: true } });
|
|
51
|
+
await accepteer({ data: { token, naam: naam.trim(), email: email.trim(), akkoord: true, handtekening } });
|
|
47
52
|
setKlaar(true);
|
|
48
53
|
}
|
|
49
54
|
catch (error) {
|
|
@@ -62,5 +67,5 @@ export function OfferteAkkoordPage({ token }) {
|
|
|
62
67
|
const geaccepteerd = klaar || Boolean(offerte.acceptatie || offerte.signed_at);
|
|
63
68
|
const kanAccepteren = !geaccepteerd && ["verzonden", "bekeken"].includes(offerte.status);
|
|
64
69
|
const akkoordverklaring = `Ik heb de offerte en voorwaarden gelezen en ga akkoord met offerte #${offerte.offerte_number ?? ""} voor ${formatEuro(offerte.total_cents)} incl. btw.`;
|
|
65
|
-
return (_jsx("main", { className: "min-h-screen bg-muted px-4 py-8", children: _jsxs("div", { className: "mx-auto max-w-3xl space-y-6 rounded-lg border border-border bg-card p-6 text-card-foreground shadow-sm sm:p-10", children: [_jsxs("header", { className: "flex flex-wrap items-start justify-between gap-4 border-b border-border pb-4", children: [_jsxs("div", { children: [_jsx("p", { className: "text-lg font-semibold", children: h.company_name ?? "" }), _jsx("p", { className: "text-xs text-muted-foreground", children: [h.address, [h.postal_code, h.city].filter(Boolean).join(" "), h.email, h.phone].filter(Boolean).join(" · ") })] }), _jsxs("div", { className: "text-right", children: [_jsx("p", { className: "text-sm text-muted-foreground", children: "Offerte" }), _jsx("p", { className: "font-mono text-lg font-semibold", children: offerte.offerte_number ?? "" })] })] }), _jsxs("section", { className: "space-y-2", children: [_jsxs("p", { className: "text-sm text-muted-foreground", children: ["Voor ", offerte.klant_naam ?? "u"] }), offerte.onderwerp && _jsx("h1", { className: "text-2xl font-semibold", children: offerte.onderwerp }), offerte.omschrijving && _jsx("p", { className: "whitespace-pre-line text-sm", children: offerte.omschrijving }), offerte.aandachtspunten && (_jsx("p", { className: "whitespace-pre-line text-sm text-muted-foreground", children: offerte.aandachtspunten }))] }), _jsxs("section", { className: "overflow-x-auto", children: [_jsxs("table", { className: "w-full text-sm", children: [_jsx("thead", { className: "border-b border-border text-left text-muted-foreground", children: _jsxs("tr", { children: [_jsx("th", { className: "py-2", children: "Omschrijving" }), _jsx("th", { className: "py-2 text-right", children: "Aantal" }), _jsx("th", { className: "py-2 text-right", children: "Prijs" }), _jsx("th", { className: "py-2 text-right", children: "Totaal" })] }) }), _jsx("tbody", { children: regels.map((r, i) => (_jsxs("tr", { className: "border-b border-border/60", children: [_jsx("td", { className: "py-2 pr-2", children: r.omschrijving }), _jsxs("td", { className: "py-2 text-right", children: [r.quantity, " ", r.unit ?? ""] }), _jsx("td", { className: "py-2 text-right", children: formatEuro(r.unit_price_cents) }), _jsx("td", { className: "py-2 text-right", children: formatEuro(r.line_total_cents) })] }, i))) })] }), _jsxs("div", { className: "ml-auto mt-3 w-full max-w-xs space-y-1 text-sm", children: [_jsxs("div", { className: "flex justify-between", children: [_jsx("span", { children: "Subtotaal" }), _jsx("span", { children: formatEuro(offerte.subtotal_cents) })] }), _jsxs("div", { className: "flex justify-between", children: [_jsxs("span", { children: ["Btw ", Number(offerte.vat_rate ?? 21), "%"] }), _jsx("span", { children: formatEuro(offerte.vat_cents) })] }), _jsxs("div", { className: "flex justify-between border-t border-border pt-1 font-semibold", children: [_jsx("span", { children: "Totaal" }), _jsx("span", { children: formatEuro(offerte.total_cents) })] })] }), offerte.valid_until && (_jsxs("p", { className: "mt-2 text-xs text-muted-foreground", children: ["Geldig tot ", new Date(offerte.valid_until).toLocaleDateString("nl-NL")] }))] }), offerte.voorwaarden && (_jsx("section", { className: "
|
|
70
|
+
return (_jsx("main", { className: "min-h-screen bg-muted px-4 py-8", children: _jsxs("div", { className: "mx-auto max-w-3xl space-y-6 rounded-lg border border-border bg-card p-6 text-card-foreground shadow-sm sm:p-10", children: [_jsxs("header", { className: "flex flex-wrap items-start justify-between gap-4 border-b border-border pb-4", children: [_jsxs("div", { children: [_jsx("p", { className: "text-lg font-semibold", children: h.company_name ?? "" }), _jsx("p", { className: "text-xs text-muted-foreground", children: [h.address, [h.postal_code, h.city].filter(Boolean).join(" "), h.email, h.phone].filter(Boolean).join(" · ") })] }), _jsxs("div", { className: "text-right", children: [_jsx("p", { className: "text-sm text-muted-foreground", children: "Offerte" }), _jsx("p", { className: "font-mono text-lg font-semibold", children: offerte.offerte_number ?? "" })] })] }), _jsxs("section", { className: "space-y-2", children: [_jsxs("p", { className: "text-sm text-muted-foreground", children: ["Voor ", offerte.klant_naam ?? "u"] }), offerte.onderwerp && _jsx("h1", { className: "text-2xl font-semibold", children: offerte.onderwerp }), offerte.omschrijving && _jsx("p", { className: "whitespace-pre-line text-sm", children: offerte.omschrijving }), offerte.aandachtspunten && (_jsx("p", { className: "whitespace-pre-line text-sm text-muted-foreground", children: offerte.aandachtspunten }))] }), _jsxs("section", { className: "overflow-x-auto", children: [_jsxs("table", { className: "w-full text-sm", children: [_jsx("thead", { className: "border-b border-border text-left text-muted-foreground", children: _jsxs("tr", { children: [_jsx("th", { className: "py-2", children: "Omschrijving" }), _jsx("th", { className: "py-2 text-right", children: "Aantal" }), _jsx("th", { className: "py-2 text-right", children: "Prijs" }), _jsx("th", { className: "py-2 text-right", children: "Totaal" })] }) }), _jsx("tbody", { children: regels.map((r, i) => (_jsxs("tr", { className: "border-b border-border/60", children: [_jsx("td", { className: "py-2 pr-2", children: r.omschrijving }), _jsxs("td", { className: "py-2 text-right", children: [r.quantity, " ", r.unit ?? ""] }), _jsx("td", { className: "py-2 text-right", children: formatEuro(r.unit_price_cents) }), _jsx("td", { className: "py-2 text-right", children: formatEuro(r.line_total_cents) })] }, i))) })] }), _jsxs("div", { className: "ml-auto mt-3 w-full max-w-xs space-y-1 text-sm", children: [_jsxs("div", { className: "flex justify-between", children: [_jsx("span", { children: "Subtotaal" }), _jsx("span", { children: formatEuro(offerte.subtotal_cents) })] }), _jsxs("div", { className: "flex justify-between", children: [_jsxs("span", { children: ["Btw ", Number(offerte.vat_rate ?? 21), "%"] }), _jsx("span", { children: formatEuro(offerte.vat_cents) })] }), _jsxs("div", { className: "flex justify-between border-t border-border pt-1 font-semibold", children: [_jsx("span", { children: "Totaal" }), _jsx("span", { children: formatEuro(offerte.total_cents) })] })] }), offerte.valid_until && (_jsxs("p", { className: "mt-2 text-xs text-muted-foreground", children: ["Geldig tot ", new Date(offerte.valid_until).toLocaleDateString("nl-NL")] }))] }), offerte.voorwaarden && (_jsx("section", { className: "text-xs text-muted-foreground", children: _jsx(KlikbareTekst, { tekst: offerte.voorwaarden }) })), geaccepteerd ? (_jsxs("section", { className: "flex items-start gap-3 rounded-md border border-primary/40 bg-primary/10 p-4", children: [_jsx(CheckCircle2, { className: "mt-0.5 h-5 w-5 text-primary" }), _jsxs("div", { children: [_jsx("p", { className: "font-semibold", children: "Bedankt voor uw akkoord!" }), _jsxs("p", { className: "text-sm text-muted-foreground", children: ["De offerte is online geaccepteerd", offerte.acceptatie?.signer_name || offerte.signer_name || naam ? ` door ${offerte.acceptatie?.signer_name ?? offerte.signer_name ?? naam}` : "", offerte.acceptatie?.accepted_at ? ` op ${new Date(offerte.acceptatie.accepted_at).toLocaleString("nl-NL")}` : "", ". De werkzaamheden worden ingepland."] }), (offerte.acceptatie?.handtekening || handtekening) && (_jsx("img", { src: offerte.acceptatie?.handtekening ?? handtekening ?? "", alt: "Handtekening", className: "mt-3 h-24 rounded border border-border bg-background" }))] })] })) : kanAccepteren ? (_jsxs("section", { className: "space-y-4 border-t border-border pt-6", children: [_jsx("h2", { className: "text-lg font-semibold", children: "Akkoord geven" }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "akkoord-naam", children: "Uw naam" }), _jsx(Input, { id: "akkoord-naam", value: naam, onChange: (e) => setNaam(e.target.value), maxLength: 200 })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "akkoord-email", children: "Uw e-mailadres" }), _jsx(Input, { id: "akkoord-email", type: "email", value: email, onChange: (e) => setEmail(e.target.value), maxLength: 254 })] }), _jsxs("label", { className: "flex items-start gap-3 rounded-md border border-border bg-muted/40 p-4 text-sm", children: [_jsx("input", { type: "checkbox", checked: akkoord, onChange: (e) => setAkkoord(e.target.checked), className: "mt-1" }), _jsx("span", { children: akkoordverklaring })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { children: "Uw handtekening" }), _jsx(HandtekeningVlak, { onChange: setHandtekening })] }), fout && _jsx("p", { className: "text-sm text-destructive", children: fout }), _jsxs(Button, { size: "lg", className: "w-full", onClick: () => void accepteren(), disabled: bezig, children: [bezig ? _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }) : _jsx(CheckCircle2, { className: "mr-2 h-4 w-4" }), "Offerte accepteren"] })] })) : (_jsx("p", { className: "border-t border-border pt-4 text-sm text-muted-foreground", children: "Deze offerte kan niet meer online worden geaccepteerd. Neem contact met ons op." }))] }) }));
|
|
66
71
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OfferteDetailPage.d.ts","sourceRoot":"","sources":["../../../src/components/offertes/OfferteDetailPage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"OfferteDetailPage.d.ts","sourceRoot":"","sources":["../../../src/components/offertes/OfferteDetailPage.tsx"],"names":[],"mappings":"AA0CA,UAAU,KAAK;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,iBAAiB,CAAC,EAAE,SAAS,EAAE,EAAE,KAAK,+BA0hBrD"}
|
|
@@ -12,9 +12,10 @@ import { OfferteStatusBadge } from "./OfferteStatusBadge";
|
|
|
12
12
|
import { OfferteA4 } from "./OfferteA4";
|
|
13
13
|
import { MoederWerkbonPaneel } from "./MoederWerkbonPaneel";
|
|
14
14
|
import { effectieveStatus } from "../../lib/offertes";
|
|
15
|
-
import { a4NaarPdfBase64, verstuurOfferteEmail } from "../../lib/offerteMail";
|
|
15
|
+
import { a4NaarPdfBase64, haalOfferteVoorwaarden, verstuurOfferteEmail } from "../../lib/offerteMail";
|
|
16
16
|
import { useHuisstijl } from "../../hooks/useHuisstijl";
|
|
17
17
|
import { WerkbonAanmakenPaneel } from "./WerkbonAanmakenPaneel";
|
|
18
|
+
import { InterneNotitie } from "./InterneNotitie";
|
|
18
19
|
export function OfferteDetailPage({ offerteId }) {
|
|
19
20
|
const navigate = useNavigate();
|
|
20
21
|
const { offerte, klantNaam, klantEmail, werkbonNummer, bronWerkbon, regels, restantMandaat, werkbonAanvraag, loading, reload, updateOfferte, saveDocument, maakWerkbon, } = useOfferte(offerteId);
|
|
@@ -64,6 +65,10 @@ export function OfferteDetailPage({ offerteId }) {
|
|
|
64
65
|
if (id)
|
|
65
66
|
navigate({ to: "/offertes/$offerteId", params: { offerteId: id } });
|
|
66
67
|
};
|
|
68
|
+
const [standaardVoorwaarden, setStandaardVoorwaarden] = useState("");
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
haalOfferteVoorwaarden().then(setStandaardVoorwaarden).catch(() => { });
|
|
71
|
+
}, []);
|
|
67
72
|
const documentWaarden = useMemo(() => {
|
|
68
73
|
if (!offerte)
|
|
69
74
|
return null;
|
|
@@ -80,7 +85,11 @@ export function OfferteDetailPage({ offerteId }) {
|
|
|
80
85
|
omschrijving: offerte.omschrijving ?? "",
|
|
81
86
|
aandachtspunten: offerte.aandachtspunten ??
|
|
82
87
|
(typeof bron.technician_notes === "string" ? bron.technician_notes : ""),
|
|
83
|
-
voorwaarden: offerte.voorwaarden
|
|
88
|
+
voorwaarden: offerte.voorwaarden && offerte.voorwaarden.trim()
|
|
89
|
+
? offerte.voorwaarden
|
|
90
|
+
: ["concept", "mandaat"].includes(String(offerte.status ?? "concept"))
|
|
91
|
+
? standaardVoorwaarden
|
|
92
|
+
: "",
|
|
84
93
|
geldigTot: offerte.valid_until?.slice(0, 10) ?? "",
|
|
85
94
|
vatRate: Number(offerte.vat_rate ?? 21),
|
|
86
95
|
regels: veilig.map((regel) => ({
|
|
@@ -90,7 +99,7 @@ export function OfferteDetailPage({ offerteId }) {
|
|
|
90
99
|
unit_price_cents: Number(regel.unit_price_cents ?? 0),
|
|
91
100
|
})),
|
|
92
101
|
};
|
|
93
|
-
}, [bronWerkbon, klantNaam, offerte, regels]);
|
|
102
|
+
}, [bronWerkbon, klantNaam, offerte, regels, standaardVoorwaarden]);
|
|
94
103
|
const werkbonConcept = useMemo(() => {
|
|
95
104
|
if (!offerte)
|
|
96
105
|
return null;
|
|
@@ -260,7 +269,7 @@ export function OfferteDetailPage({ offerteId }) {
|
|
|
260
269
|
setVerzenden(false);
|
|
261
270
|
}
|
|
262
271
|
};
|
|
263
|
-
return (_jsxs("main", { className: "space-y-6 p-6", children: [_jsxs("div", { className: "flex flex-wrap items-start justify-between gap-4", children: [_jsxs("div", { className: "space-y-1", children: [_jsxs(Button, { variant: "ghost", size: "sm", className: "-ml-2", onClick: () => navigate({ to: "/offertes" }), children: [_jsx(ArrowLeft, { className: "mr-1 h-4 w-4" }), " Offertes"] }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("h1", { className: "font-mono text-2xl font-semibold text-foreground", children: nummer ?? "Concept" }), _jsx(OfferteStatusBadge, { status: status })] }), _jsx("p", { className: "text-sm text-muted-foreground", children: klantNaam ?? "Onbekende klant" }), _jsx("div", { className: "mt-2 rounded-md border bg-muted/40 px-3 py-2 text-sm", children: werkbonNummer ? (_jsx("p", { children: _jsxs("span", { className: "font-medium", children: ["Vervolg op werkbon ", werkbonNummer] }) })) : (_jsx("p", { className: "font-medium", children: "Nieuwe klus, geen eerdere werkbon" })) })] }), _jsxs("div", { className: "flex flex-col items-end gap-2", children: [_jsxs("div", { className: "flex flex-wrap justify-end gap-2", children: [bewerkbaar && !binnenMandaat && (_jsxs(Button, { variant: "outline", onClick: openVerzenden, children: [_jsx(Send, { className: "mr-1 h-4 w-4" }), " Verzenden"] })), bewerkbaar && binnenMandaat && status !== "mandaat" && (_jsxs(Button, { variant: "outline", onClick: markeerMandaat, children: [_jsx(ShieldCheck, { className: "mr-1 h-4 w-4" }), " Binnen mandaat"] })), ["verzonden", "bekeken"].includes(status) && (_jsxs(_Fragment, { children: [_jsxs(Button, { variant: "outline", onClick: openVerzenden, disabled: verzenden, children: [_jsx(Send, { className: "mr-1 h-4 w-4" }), " Opnieuw verzenden"] }), _jsxs(Button, { variant: "outline", onClick: afwijzen, children: [_jsx(XCircle, { className: "mr-1 h-4 w-4" }), " Afwijzen"] }), _jsxs(Button, { onClick: goedkeuren, children: [_jsx(CheckCircle2, { className: "mr-1 h-4 w-4" }), " Goedkeuren"] })] })), status === "goedgekeurd" && !isOmgezet && !wachtOpWerkbon && (_jsxs(Button, { variant: "outline", onClick: afwijzen, children: [_jsx(XCircle, { className: "mr-1 h-4 w-4" }), " Toch afwijzen"] })), status === "afgewezen" && (_jsxs(Button, { onClick: goedkeuren, children: [_jsx(CheckCircle2, { className: "mr-1 h-4 w-4" }), " Toch akkoord"] })), !isOmgezet &&
|
|
272
|
+
return (_jsxs("main", { className: "space-y-6 p-6", children: [_jsxs("div", { className: "flex flex-wrap items-start justify-between gap-4", children: [_jsxs("div", { className: "space-y-1", children: [_jsxs(Button, { variant: "ghost", size: "sm", className: "-ml-2", onClick: () => navigate({ to: "/offertes" }), children: [_jsx(ArrowLeft, { className: "mr-1 h-4 w-4" }), " Offertes"] }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("h1", { className: "font-mono text-2xl font-semibold text-foreground", children: nummer ?? "Concept" }), _jsx(OfferteStatusBadge, { status: status })] }), _jsx("p", { className: "text-sm text-muted-foreground", children: klantNaam ?? "Onbekende klant" }), _jsx("div", { className: "mt-2 rounded-md border bg-muted/40 px-3 py-2 text-sm", children: werkbonNummer ? (_jsx("p", { children: _jsxs("span", { className: "font-medium", children: ["Vervolg op werkbon ", werkbonNummer] }) })) : (_jsx("p", { className: "font-medium", children: "Nieuwe klus, geen eerdere werkbon" })) })] }), _jsx(InterneNotitie, { waarde: offerte.interne_notitie ?? null, onOpslaan: (t) => updateOfferte({ interne_notitie: t }) }), _jsxs("div", { className: "flex flex-col items-end gap-2", children: [_jsxs("div", { className: "flex flex-wrap justify-end gap-2", children: [bewerkbaar && !binnenMandaat && (_jsxs(Button, { variant: "outline", onClick: openVerzenden, children: [_jsx(Send, { className: "mr-1 h-4 w-4" }), " Verzenden"] })), bewerkbaar && binnenMandaat && status !== "mandaat" && (_jsxs(Button, { variant: "outline", onClick: markeerMandaat, children: [_jsx(ShieldCheck, { className: "mr-1 h-4 w-4" }), " Binnen mandaat"] })), ["verzonden", "bekeken"].includes(status) && (_jsxs(_Fragment, { children: [_jsxs(Button, { variant: "outline", onClick: openVerzenden, disabled: verzenden, children: [_jsx(Send, { className: "mr-1 h-4 w-4" }), " Opnieuw verzenden"] }), _jsxs(Button, { variant: "outline", onClick: afwijzen, children: [_jsx(XCircle, { className: "mr-1 h-4 w-4" }), " Afwijzen"] }), _jsxs(Button, { onClick: goedkeuren, children: [_jsx(CheckCircle2, { className: "mr-1 h-4 w-4" }), " Goedkeuren"] })] })), status === "goedgekeurd" && !isOmgezet && !wachtOpWerkbon && (_jsxs(Button, { variant: "outline", onClick: afwijzen, children: [_jsx(XCircle, { className: "mr-1 h-4 w-4" }), " Toch afwijzen"] })), status === "afgewezen" && (_jsxs(Button, { onClick: goedkeuren, children: [_jsx(CheckCircle2, { className: "mr-1 h-4 w-4" }), " Toch akkoord"] })), !isOmgezet &&
|
|
264
273
|
(status === "goedgekeurd" || status === "wacht_op_werkbon" || aanvraagMislukt || status === "mandaat") && (_jsxs(Button, { onClick: () => {
|
|
265
274
|
setWerkbonOpen(true);
|
|
266
275
|
setMoederOpen(false);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VoorstelOffertePaneel.d.ts","sourceRoot":"","sources":["../../../src/components/offertes/VoorstelOffertePaneel.tsx"],"names":[],"mappings":"AAiBA,OAAO,EAEL,KAAK,eAAe,EAErB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAA0B,KAAK,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"VoorstelOffertePaneel.d.ts","sourceRoot":"","sources":["../../../src/components/offertes/VoorstelOffertePaneel.tsx"],"names":[],"mappings":"AAiBA,OAAO,EAEL,KAAK,eAAe,EAErB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAA0B,KAAK,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAY9F,UAAU,KAAK;IACb,QAAQ,EAAE,eAAe,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,iBAAiB,EAAE,MAAM,CAAC;IAC1B,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,SAAS,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvD,aAAa,EAAE,CAAC,CAAC,EAAE,gBAAgB,KAAK,IAAI,CAAC;CAC9C;AAkBD,wBAAgB,qBAAqB,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,iBAAiB,EAAE,UAAU,EAAE,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,EAAE,KAAK,+BAuXjI"}
|
|
@@ -8,6 +8,7 @@ import { ChevronLeft, ChevronRight, Loader2, Plus, RefreshCw, Trash2, Wand2, X }
|
|
|
8
8
|
import { laadVoorstelRegels, } from "../../hooks/useWerkbonVoorstellen";
|
|
9
9
|
import { genereerOfferteConcept } from "../../lib/offerteAi.functions";
|
|
10
10
|
import { formatDatum, formatEuro } from "../../lib/offertes";
|
|
11
|
+
import { haalOfferteVoorwaarden } from "../../lib/offerteMail";
|
|
11
12
|
const db = supabase;
|
|
12
13
|
function plusDagen(n) {
|
|
13
14
|
const d = new Date();
|
|
@@ -90,9 +91,11 @@ export function VoorstelOffertePaneel({ voorstel, index, aantalVoorstellen, onNa
|
|
|
90
91
|
const maakAan = async () => {
|
|
91
92
|
setOpslaan(true);
|
|
92
93
|
try {
|
|
94
|
+
const standaardVoorwaarden = await haalOfferteVoorwaarden().catch(() => "");
|
|
93
95
|
const { data, error } = await db
|
|
94
96
|
.from("itk_offertes")
|
|
95
97
|
.insert({
|
|
98
|
+
voorwaarden: standaardVoorwaarden.trim() ? standaardVoorwaarden : null,
|
|
96
99
|
source_type: "werkbon_voorstel",
|
|
97
100
|
source_ref: voorstel.id,
|
|
98
101
|
source_number: voorstel.externe_referentie || voorstel.subject || null,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OfferteVoorwaardenSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/components/settings/OfferteVoorwaardenSettingsCard.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"OfferteVoorwaardenSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/components/settings/OfferteVoorwaardenSettingsCard.tsx"],"names":[],"mappings":"AAQA,wBAAgB,8BAA8B,gCAyD7C"}
|
|
@@ -3,6 +3,7 @@ import { useEffect, useState } from "react";
|
|
|
3
3
|
import { Button, Card, CardContent, CardDescription, CardHeader, CardTitle, Label, Textarea, toast, } from "@flowselections/core";
|
|
4
4
|
import { ScrollText } from "lucide-react";
|
|
5
5
|
import { bewaarOfferteVoorwaarden, haalOfferteVoorwaarden } from "../../lib/offerteMail";
|
|
6
|
+
import { KlikbareTekst } from "../offertes/KlikbareTekst";
|
|
6
7
|
export function OfferteVoorwaardenSettingsCard() {
|
|
7
8
|
const [tekst, setTekst] = useState("");
|
|
8
9
|
const [loading, setLoading] = useState(true);
|
|
@@ -26,5 +27,5 @@ export function OfferteVoorwaardenSettingsCard() {
|
|
|
26
27
|
setSaving(false);
|
|
27
28
|
}
|
|
28
29
|
};
|
|
29
|
-
return (_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(ScrollText, { className: "h-5 w-5 text-primary" }), _jsx(CardTitle, { children: "Voorwaarden en toelichting" })] }), _jsx(CardDescription, { children: "Deze tekst staat standaard op elke nieuwe offerte, onder het financi\u00EBle overzicht. Per offerte kun je de tekst nog aanpassen." })] }), _jsx(CardContent, { className: "space-y-3", children: loading ? _jsx("p", { className: "text-sm text-muted-foreground", children: "Laden\u2026" }) : _jsxs(_Fragment, { children: [_jsxs("div", { className: "space-y-1.5", children: [_jsx(Label, { children: "Standaardtekst op de offerte" }), _jsx(Textarea, { rows: 8, value: tekst, onChange: (e) => setTekst(e.target.value), placeholder: "Bijvoorbeeld:\n- Deze offerte is 30 dagen geldig.\n- Wijzigingen in de uitvoering kunnen de prijs beïnvloeden.\n- Betaling binnen 14 dagen na factuurdatum." })] }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Leeg laten = geen standaardtekst op nieuwe offertes. Al verzonden offertes veranderen niet." }), _jsx("div", { className: "flex justify-end", children: _jsx(Button, { onClick: () => void opslaan(), disabled: saving, children: saving ? "Opslaan…" : "Opslaan" }) })] }) })] }));
|
|
30
|
+
return (_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(ScrollText, { className: "h-5 w-5 text-primary" }), _jsx(CardTitle, { children: "Voorwaarden en toelichting" })] }), _jsx(CardDescription, { children: "Deze tekst staat standaard op elke nieuwe offerte, onder het financi\u00EBle overzicht. Per offerte kun je de tekst nog aanpassen." })] }), _jsx(CardContent, { className: "space-y-3", children: loading ? _jsx("p", { className: "text-sm text-muted-foreground", children: "Laden\u2026" }) : _jsxs(_Fragment, { children: [_jsxs("div", { className: "space-y-1.5", children: [_jsx(Label, { children: "Standaardtekst op de offerte" }), _jsx(Textarea, { rows: 8, value: tekst, onChange: (e) => setTekst(e.target.value), placeholder: "Bijvoorbeeld:\n- Deze offerte is 30 dagen geldig.\n- Wijzigingen in de uitvoering kunnen de prijs beïnvloeden.\n- Betaling binnen 14 dagen na factuurdatum." })] }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Leeg laten = geen standaardtekst op nieuwe offertes. Al verzonden offertes veranderen niet." }), tekst.trim() && (_jsxs("div", { className: "rounded-md border bg-muted/40 p-3 text-sm", children: [_jsx("p", { className: "mb-1 text-xs font-medium text-muted-foreground", children: "Zo staat het op de offerte (links zijn hier en op de offerte klikbaar)" }), _jsx(KlikbareTekst, { tekst: tekst })] })), _jsx("div", { className: "flex justify-end", children: _jsx(Button, { onClick: () => void opslaan(), disabled: saving, children: saving ? "Opslaan…" : "Opslaan" }) })] }) })] }));
|
|
30
31
|
}
|
package/dist-lib/index.d.ts
CHANGED
|
@@ -25,6 +25,7 @@ export { useWerkbonVoorstellen } from "./hooks/useWerkbonVoorstellen";
|
|
|
25
25
|
export { ExtraWerkMeldingenLijst } from "./components/offertes/ExtraWerkMeldingenLijst";
|
|
26
26
|
export { ExtraWerkOffertePaneel } from "./components/offertes/ExtraWerkOffertePaneel";
|
|
27
27
|
export { OfferteA4 } from "./components/offertes/OfferteA4";
|
|
28
|
+
export { KlikbareTekst } from "./components/offertes/KlikbareTekst";
|
|
28
29
|
export { WerkbonAanmakenPaneel } from "./components/offertes/WerkbonAanmakenPaneel";
|
|
29
30
|
export { MoederWerkbonPaneel } from "./components/offertes/MoederWerkbonPaneel";
|
|
30
31
|
export type { WerkbonConcept, WerkbonConceptRegel } from "./components/offertes/WerkbonAanmakenPaneel";
|
package/dist-lib/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,4BAA4B,EAAE,MAAM,oDAAoD,CAAC;AAClG,OAAO,EAAE,wBAAwB,EAAE,MAAM,gDAAgD,CAAC;AAC1F,OAAO,EAAE,8BAA8B,EAAE,MAAM,sDAAsD,CAAC;AACtG,OAAO,EAAE,wBAAwB,EAAE,CAAC;AACpC,OAAO,EAAE,4BAA4B,EAAE,CAAC;AACxC,OAAO,EAAE,8BAA8B,EAAE,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEpD,cAAc,kBAAkB,CAAC;AACjC,cAAc,gBAAgB,CAAC;AAE/B,OAAO,EAAE,qBAAqB,EAAE,MAAM,6CAA6C,CAAC;AACpF,OAAO,EAAE,iBAAiB,EAAE,MAAM,yCAAyC,CAAC;AAC5E,OAAO,EAAE,kBAAkB,EAAE,MAAM,0CAA0C,CAAC;AAC9E,OAAO,EAAE,kBAAkB,EAAE,MAAM,0CAA0C,CAAC;AAC9E,OAAO,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AAClE,OAAO,EAAE,kBAAkB,EAAE,MAAM,0CAA0C,CAAC;AAC9E,OAAO,EAAE,mBAAmB,EAAE,MAAM,2CAA2C,CAAC;AAChF,OAAO,EAAE,sBAAsB,EAAE,MAAM,8CAA8C,CAAC;AAEtF,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,uBAAuB,EAAE,MAAM,+CAA+C,CAAC;AACxF,OAAO,EAAE,qBAAqB,EAAE,MAAM,6CAA6C,CAAC;AACpF,OAAO,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AACtE,OAAO,EAAE,uBAAuB,EAAE,MAAM,+CAA+C,CAAC;AACxF,OAAO,EAAE,sBAAsB,EAAE,MAAM,8CAA8C,CAAC;AACtF,OAAO,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AAC5D,OAAO,EAAE,qBAAqB,EAAE,MAAM,6CAA6C,CAAC;AACpF,OAAO,EAAE,mBAAmB,EAAE,MAAM,2CAA2C,CAAC;AAChF,YAAY,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,6CAA6C,CAAC;AACvG,OAAO,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AAEtE,eAAO,MAAM,iCAAiC,EAAE,UAkB/C,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,4BAA4B,EAAE,MAAM,oDAAoD,CAAC;AAClG,OAAO,EAAE,wBAAwB,EAAE,MAAM,gDAAgD,CAAC;AAC1F,OAAO,EAAE,8BAA8B,EAAE,MAAM,sDAAsD,CAAC;AACtG,OAAO,EAAE,wBAAwB,EAAE,CAAC;AACpC,OAAO,EAAE,4BAA4B,EAAE,CAAC;AACxC,OAAO,EAAE,8BAA8B,EAAE,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEpD,cAAc,kBAAkB,CAAC;AACjC,cAAc,gBAAgB,CAAC;AAE/B,OAAO,EAAE,qBAAqB,EAAE,MAAM,6CAA6C,CAAC;AACpF,OAAO,EAAE,iBAAiB,EAAE,MAAM,yCAAyC,CAAC;AAC5E,OAAO,EAAE,kBAAkB,EAAE,MAAM,0CAA0C,CAAC;AAC9E,OAAO,EAAE,kBAAkB,EAAE,MAAM,0CAA0C,CAAC;AAC9E,OAAO,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AAClE,OAAO,EAAE,kBAAkB,EAAE,MAAM,0CAA0C,CAAC;AAC9E,OAAO,EAAE,mBAAmB,EAAE,MAAM,2CAA2C,CAAC;AAChF,OAAO,EAAE,sBAAsB,EAAE,MAAM,8CAA8C,CAAC;AAEtF,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,uBAAuB,EAAE,MAAM,+CAA+C,CAAC;AACxF,OAAO,EAAE,qBAAqB,EAAE,MAAM,6CAA6C,CAAC;AACpF,OAAO,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AACtE,OAAO,EAAE,uBAAuB,EAAE,MAAM,+CAA+C,CAAC;AACxF,OAAO,EAAE,sBAAsB,EAAE,MAAM,8CAA8C,CAAC;AACtF,OAAO,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AAC5D,OAAO,EAAE,aAAa,EAAE,MAAM,qCAAqC,CAAC;AACpE,OAAO,EAAE,qBAAqB,EAAE,MAAM,6CAA6C,CAAC;AACpF,OAAO,EAAE,mBAAmB,EAAE,MAAM,2CAA2C,CAAC;AAChF,YAAY,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,6CAA6C,CAAC;AACvG,OAAO,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AAEtE,eAAO,MAAM,iCAAiC,EAAE,UAkB/C,CAAC"}
|