@flowselections/offertes-installatie-techniek 1.21.6 → 1.22.2

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.
Files changed (44) hide show
  1. package/dist-lib/components/offertes/ExtraWerkOffertePaneel.js +3 -3
  2. package/dist-lib/components/offertes/HandtekeningVlak.d.ts +4 -0
  3. package/dist-lib/components/offertes/HandtekeningVlak.d.ts.map +1 -0
  4. package/dist-lib/components/offertes/HandtekeningVlak.js +66 -0
  5. package/dist-lib/components/offertes/InterneNotitie.d.ts +8 -0
  6. package/dist-lib/components/offertes/InterneNotitie.d.ts.map +1 -0
  7. package/dist-lib/components/offertes/InterneNotitie.js +16 -0
  8. package/dist-lib/components/offertes/KlikbareTekst.d.ts +5 -0
  9. package/dist-lib/components/offertes/KlikbareTekst.d.ts.map +1 -0
  10. package/dist-lib/components/offertes/KlikbareTekst.js +10 -0
  11. package/dist-lib/components/offertes/NieuweOfferteFormulier.d.ts +1 -0
  12. package/dist-lib/components/offertes/NieuweOfferteFormulier.d.ts.map +1 -1
  13. package/dist-lib/components/offertes/NieuweOfferteFormulier.js +30 -10
  14. package/dist-lib/components/offertes/OfferteA4.d.ts.map +1 -1
  15. package/dist-lib/components/offertes/OfferteA4.js +2 -1
  16. package/dist-lib/components/offertes/OfferteAkkoordPage.d.ts.map +1 -1
  17. package/dist-lib/components/offertes/OfferteAkkoordPage.js +7 -2
  18. package/dist-lib/components/offertes/OfferteDetailPage.d.ts.map +1 -1
  19. package/dist-lib/components/offertes/OfferteDetailPage.js +13 -4
  20. package/dist-lib/components/offertes/VoorstelOffertePaneel.d.ts.map +1 -1
  21. package/dist-lib/components/offertes/VoorstelOffertePaneel.js +3 -0
  22. package/dist-lib/components/settings/OfferteVoorwaardenSettingsCard.d.ts.map +1 -1
  23. package/dist-lib/components/settings/OfferteVoorwaardenSettingsCard.js +2 -1
  24. package/dist-lib/index.d.ts +1 -0
  25. package/dist-lib/index.d.ts.map +1 -1
  26. package/dist-lib/index.js +1 -0
  27. package/dist-lib/integrations/supabase/types.d.ts +3625 -122
  28. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  29. package/dist-lib/integrations/supabase/types.js +1 -0
  30. package/dist-lib/lib/offerteAcceptatie.functions.d.ts +1 -0
  31. package/dist-lib/lib/offerteAcceptatie.functions.d.ts.map +1 -1
  32. package/dist-lib/lib/offerteAcceptatie.functions.js +6 -0
  33. package/dist-lib/lib/offerteMail.d.ts.map +1 -1
  34. package/dist-lib/lib/offerteMail.js +14 -0
  35. package/dist-lib/lib/offerteRegels.d.ts +18 -0
  36. package/dist-lib/lib/offerteRegels.d.ts.map +1 -1
  37. package/dist-lib/lib/offerteRegels.js +15 -0
  38. package/dist-lib/lib/offerteRegels.test.js +13 -1
  39. package/dist-lib/lib/offerteReistijd.functions.d.ts +1 -1
  40. package/dist-lib/lib/offerteReistijd.functions.d.ts.map +1 -1
  41. package/dist-lib/lib/offerteReistijd.functions.js +4 -2
  42. package/dist-lib/lib/offertes.d.ts +3 -0
  43. package/dist-lib/lib/offertes.d.ts.map +1 -1
  44. 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("werkbon_materials").select("description, quantity, unit, unit_price_cents").eq("werkbon_id", wb.id)
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("werkbon_package_lines").select("package_id, quantity").eq("werkbon_id", wb.id)
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,4 @@
1
+ export declare function HandtekeningVlak({ onChange }: {
2
+ onChange: (dataUrl: string | null) => void;
3
+ }): import("react").JSX.Element;
4
+ //# sourceMappingURL=HandtekeningVlak.d.ts.map
@@ -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,5 @@
1
+ export declare function KlikbareTekst({ tekst, className }: {
2
+ tekst: string;
3
+ className?: string;
4
+ }): import("react").JSX.Element | null;
5
+ //# sourceMappingURL=KlikbareTekst.d.ts.map
@@ -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
+ }
@@ -8,6 +8,7 @@ export interface OfferteFormulierInitieel {
8
8
  packages?: {
9
9
  package_id: string;
10
10
  quantity: number;
11
+ extra_minutes?: number;
11
12
  }[];
12
13
  materials?: {
13
14
  description: string;
@@ -1 +1 @@
1
- {"version":3,"file":"NieuweOfferteFormulier.d.ts","sourceRoot":"","sources":["../../../src/components/offertes/NieuweOfferteFormulier.tsx"],"names":[],"mappings":"AAmCA,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,CAAA;KAAE,EAAE,CAAC;IACtD,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,+BA8tBrK"}
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
- const resultaat = await berekenReistijd({ data: { accessToken: token, destination: bestemming } });
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 + Math.round(r.quantity * r.prijs), 0) +
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: gekozen, materials: materialenPayload(),
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: gekozen, materials: materialenPayload(),
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.filter((p) => !(Number(p.travel_minutes) > 0 && Number(p.labor_minutes ?? 0) === 0)).map((p) => (_jsxs("button", { type: "button", className: "relative flex w-full cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent hover:text-accent-foreground", onClick: () => {
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) => (_jsxs("div", { className: "flex items-center gap-2 p-2 text-sm", children: [_jsxs("span", { className: "flex-1", children: [r.naam, r.package_id === automatischReisPakket && _jsx("span", { className: "ml-2 text-xs text-muted-foreground", children: "automatisch" })] }), _jsx(Button, { size: "icon", variant: "ghost", disabled: r.package_id === automatischReisPakket, onClick: () => setGekozen((g) => g.map((x) => (x.package_id === r.package_id ? { ...x, quantity: Math.max(1, x.quantity - 1) } : x))), children: _jsx(Minus, { className: "h-4 w-4" }) }), _jsx("span", { className: "w-6 text-center", children: r.quantity }), _jsx(Button, { size: "icon", variant: "ghost", disabled: r.package_id === automatischReisPakket, onClick: () => setGekozen((g) => g.map((x) => (x.package_id === r.package_id ? { ...x, quantity: x.quantity + 1 } : x))), 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: r.package_id === automatischReisPakket, onClick: () => setGekozen((g) => g.filter((x) => x.package_id !== r.package_id)), children: _jsx(Trash2, { className: "h-4 w-4" }) })] }, r.package_id))), _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: "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) })] })] }))] })] }));
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":"AAOA,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,kGAqIpB,CAAC"}
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":"AAkCA,wBAAgB,kBAAkB,CAAC,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,+BA4J9D"}
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: "whitespace-pre-line text-xs text-muted-foreground", children: 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."] })] })] })) : 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 })] }), 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." }))] }) }));
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":"AAyCA,UAAU,KAAK;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,iBAAiB,CAAC,EAAE,SAAS,EAAE,EAAE,KAAK,+BA0gBrD"}
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;AAW9F,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,+BAqXjI"}
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":"AAOA,wBAAgB,8BAA8B,gCAmD7C"}
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
  }
@@ -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";
@@ -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"}