@voyantjs/pricing-ui 0.13.0

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 (71) hide show
  1. package/README.md +13 -0
  2. package/dist/components/cancellation-policy-combobox.d.ts +9 -0
  3. package/dist/components/cancellation-policy-combobox.d.ts.map +1 -0
  4. package/dist/components/cancellation-policy-combobox.js +49 -0
  5. package/dist/components/cancellation-policy-rule-dialog.d.ts +11 -0
  6. package/dist/components/cancellation-policy-rule-dialog.d.ts.map +1 -0
  7. package/dist/components/cancellation-policy-rule-dialog.js +87 -0
  8. package/dist/components/dropoff-price-rule-dialog.d.ts +10 -0
  9. package/dist/components/dropoff-price-rule-dialog.d.ts.map +1 -0
  10. package/dist/components/dropoff-price-rule-dialog.js +96 -0
  11. package/dist/components/extra-price-rule-dialog.d.ts +10 -0
  12. package/dist/components/extra-price-rule-dialog.d.ts.map +1 -0
  13. package/dist/components/extra-price-rule-dialog.js +92 -0
  14. package/dist/components/option-price-rule-combobox.d.ts +9 -0
  15. package/dist/components/option-price-rule-combobox.d.ts.map +1 -0
  16. package/dist/components/option-price-rule-combobox.js +45 -0
  17. package/dist/components/option-price-rule-dialog.d.ts +9 -0
  18. package/dist/components/option-price-rule-dialog.d.ts.map +1 -0
  19. package/dist/components/option-price-rule-dialog.js +132 -0
  20. package/dist/components/option-start-time-rule-dialog.d.ts +10 -0
  21. package/dist/components/option-start-time-rule-dialog.d.ts.map +1 -0
  22. package/dist/components/option-start-time-rule-dialog.js +90 -0
  23. package/dist/components/option-unit-price-rule-dialog.d.ts +10 -0
  24. package/dist/components/option-unit-price-rule-dialog.d.ts.map +1 -0
  25. package/dist/components/option-unit-price-rule-dialog.js +103 -0
  26. package/dist/components/option-unit-tier-dialog.d.ts +10 -0
  27. package/dist/components/option-unit-tier-dialog.d.ts.map +1 -0
  28. package/dist/components/option-unit-tier-dialog.js +78 -0
  29. package/dist/components/pickup-price-rule-dialog.d.ts +10 -0
  30. package/dist/components/pickup-price-rule-dialog.d.ts.map +1 -0
  31. package/dist/components/pickup-price-rule-dialog.js +88 -0
  32. package/dist/components/price-catalog-combobox.d.ts +9 -0
  33. package/dist/components/price-catalog-combobox.d.ts.map +1 -0
  34. package/dist/components/price-catalog-combobox.js +45 -0
  35. package/dist/components/price-schedule-combobox.d.ts +10 -0
  36. package/dist/components/price-schedule-combobox.d.ts.map +1 -0
  37. package/dist/components/price-schedule-combobox.js +48 -0
  38. package/dist/components/price-schedule-dialog.d.ts +9 -0
  39. package/dist/components/price-schedule-dialog.d.ts.map +1 -0
  40. package/dist/components/price-schedule-dialog.js +87 -0
  41. package/dist/components/pricing-category-combobox.d.ts +9 -0
  42. package/dist/components/pricing-category-combobox.d.ts.map +1 -0
  43. package/dist/components/pricing-category-combobox.js +54 -0
  44. package/dist/components/pricing-category-dependency-dialog.d.ts +9 -0
  45. package/dist/components/pricing-category-dependency-dialog.d.ts.map +1 -0
  46. package/dist/components/pricing-category-dependency-dialog.js +11 -0
  47. package/dist/components/pricing-category-dependency-form.d.ts +15 -0
  48. package/dist/components/pricing-category-dependency-form.d.ts.map +1 -0
  49. package/dist/components/pricing-category-dependency-form.js +90 -0
  50. package/dist/components/pricing-category-dialog.d.ts +9 -0
  51. package/dist/components/pricing-category-dialog.d.ts.map +1 -0
  52. package/dist/components/pricing-category-dialog.js +13 -0
  53. package/dist/components/pricing-category-form.d.ts +15 -0
  54. package/dist/components/pricing-category-form.d.ts.map +1 -0
  55. package/dist/components/pricing-category-form.js +99 -0
  56. package/dist/components/pricing-category-list.d.ts +5 -0
  57. package/dist/components/pricing-category-list.d.ts.map +1 -0
  58. package/dist/components/pricing-category-list.js +44 -0
  59. package/dist/components/pricing-shared-labels.d.ts +22 -0
  60. package/dist/components/pricing-shared-labels.d.ts.map +1 -0
  61. package/dist/components/pricing-shared-labels.js +32 -0
  62. package/dist/components/product-combobox.d.ts +9 -0
  63. package/dist/components/product-combobox.d.ts.map +1 -0
  64. package/dist/components/product-combobox.js +41 -0
  65. package/dist/components/product-option-combobox.d.ts +10 -0
  66. package/dist/components/product-option-combobox.d.ts.map +1 -0
  67. package/dist/components/product-option-combobox.js +51 -0
  68. package/dist/index.d.ts +23 -0
  69. package/dist/index.d.ts.map +1 -0
  70. package/dist/index.js +22 -0
  71. package/package.json +70 -0
@@ -0,0 +1,132 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useOptionPriceRuleMutation } from "@voyantjs/pricing-react";
4
+ import { Button, Dialog, DialogBody, DialogContent, DialogFooter, DialogHeader, DialogTitle, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Switch, Textarea, } from "@voyantjs/voyant-ui/components";
5
+ import { zodResolver } from "@voyantjs/voyant-ui/lib/zod-resolver";
6
+ import { Loader2 } from "lucide-react";
7
+ import { useEffect } from "react";
8
+ import { useForm } from "react-hook-form";
9
+ import { z } from "zod/v4";
10
+ import { CancellationPolicyCombobox } from "./cancellation-policy-combobox";
11
+ import { PriceCatalogCombobox } from "./price-catalog-combobox";
12
+ import { PriceScheduleCombobox } from "./price-schedule-combobox";
13
+ import { ProductCombobox } from "./product-combobox";
14
+ import { ProductOptionCombobox } from "./product-option-combobox";
15
+ const PRICING_MODES = ["per_person", "per_booking", "starting_from", "free", "on_request"];
16
+ const formSchema = z.object({
17
+ productId: z.string().min(1, "Product is required"),
18
+ optionId: z.string().min(1, "Option is required"),
19
+ priceCatalogId: z.string().min(1, "Catalog is required"),
20
+ priceScheduleId: z.string().optional().nullable(),
21
+ cancellationPolicyId: z.string().optional().nullable(),
22
+ name: z.string().min(1, "Name is required").max(255),
23
+ code: z.string().max(100).optional().nullable(),
24
+ description: z.string().optional().nullable(),
25
+ pricingMode: z.enum(PRICING_MODES),
26
+ baseSell: z.coerce.number().min(0),
27
+ baseCost: z.coerce.number().min(0),
28
+ minPerBooking: z.coerce.number().int().min(0).optional().or(z.literal("")).nullable(),
29
+ maxPerBooking: z.coerce.number().int().min(0).optional().or(z.literal("")).nullable(),
30
+ allPricingCategories: z.boolean(),
31
+ isDefault: z.boolean(),
32
+ active: z.boolean(),
33
+ notes: z.string().optional().nullable(),
34
+ });
35
+ const toInt = (value) => typeof value === "number" ? value : null;
36
+ export function OptionPriceRuleDialog({ open, onOpenChange, rule, onSuccess, }) {
37
+ const isEditing = !!rule;
38
+ const { create, update } = useOptionPriceRuleMutation();
39
+ const form = useForm({
40
+ resolver: zodResolver(formSchema),
41
+ defaultValues: {
42
+ productId: "",
43
+ optionId: "",
44
+ priceCatalogId: "",
45
+ priceScheduleId: "",
46
+ cancellationPolicyId: "",
47
+ name: "",
48
+ code: "",
49
+ description: "",
50
+ pricingMode: "per_person",
51
+ baseSell: 0,
52
+ baseCost: 0,
53
+ minPerBooking: "",
54
+ maxPerBooking: "",
55
+ allPricingCategories: true,
56
+ isDefault: false,
57
+ active: true,
58
+ notes: "",
59
+ },
60
+ });
61
+ const watchedProductId = form.watch("productId");
62
+ const watchedCatalogId = form.watch("priceCatalogId");
63
+ useEffect(() => {
64
+ if (open && rule) {
65
+ form.reset({
66
+ productId: rule.productId,
67
+ optionId: rule.optionId,
68
+ priceCatalogId: rule.priceCatalogId,
69
+ priceScheduleId: rule.priceScheduleId ?? "",
70
+ cancellationPolicyId: rule.cancellationPolicyId ?? "",
71
+ name: rule.name,
72
+ code: rule.code ?? "",
73
+ description: rule.description ?? "",
74
+ pricingMode: rule.pricingMode,
75
+ baseSell: rule.baseSellAmountCents != null ? rule.baseSellAmountCents / 100 : 0,
76
+ baseCost: rule.baseCostAmountCents != null ? rule.baseCostAmountCents / 100 : 0,
77
+ minPerBooking: rule.minPerBooking ?? "",
78
+ maxPerBooking: rule.maxPerBooking ?? "",
79
+ allPricingCategories: rule.allPricingCategories,
80
+ isDefault: rule.isDefault,
81
+ active: rule.active,
82
+ notes: rule.notes ?? "",
83
+ });
84
+ }
85
+ else if (open) {
86
+ form.reset();
87
+ }
88
+ }, [open, rule, form]);
89
+ const onSubmit = async (values) => {
90
+ const payload = {
91
+ productId: values.productId,
92
+ optionId: values.optionId,
93
+ priceCatalogId: values.priceCatalogId,
94
+ priceScheduleId: values.priceScheduleId || null,
95
+ cancellationPolicyId: values.cancellationPolicyId || null,
96
+ name: values.name,
97
+ code: values.code || null,
98
+ description: values.description || null,
99
+ pricingMode: values.pricingMode,
100
+ baseSellAmountCents: Math.round(values.baseSell * 100),
101
+ baseCostAmountCents: Math.round(values.baseCost * 100),
102
+ minPerBooking: toInt(values.minPerBooking),
103
+ maxPerBooking: toInt(values.maxPerBooking),
104
+ allPricingCategories: values.allPricingCategories,
105
+ isDefault: values.isDefault,
106
+ active: values.active,
107
+ notes: values.notes || null,
108
+ };
109
+ const saved = isEditing
110
+ ? await update.mutateAsync({ id: rule.id, input: payload })
111
+ : await create.mutateAsync(payload);
112
+ onSuccess?.(saved);
113
+ onOpenChange(false);
114
+ };
115
+ const isSubmitting = create.isPending || update.isPending;
116
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { size: "lg", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: isEditing ? "Edit Option Price Rule" : "Add Option Price Rule" }) }), _jsxs("form", { onSubmit: form.handleSubmit(onSubmit), children: [_jsxs(DialogBody, { className: "grid gap-4", children: [_jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Product" }), _jsx(ProductCombobox, { value: form.watch("productId"), onChange: (value) => {
117
+ form.setValue("productId", value ?? "", {
118
+ shouldDirty: true,
119
+ shouldValidate: true,
120
+ });
121
+ form.setValue("optionId", "", { shouldDirty: true, shouldValidate: true });
122
+ }, disabled: isEditing }), form.formState.errors.productId ? (_jsx("p", { className: "text-xs text-destructive", children: form.formState.errors.productId.message })) : null] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Option" }), _jsx(ProductOptionCombobox, { productId: watchedProductId, value: form.watch("optionId"), onChange: (value) => form.setValue("optionId", value ?? "", {
123
+ shouldDirty: true,
124
+ shouldValidate: true,
125
+ }), disabled: isEditing }), form.formState.errors.optionId ? (_jsx("p", { className: "text-xs text-destructive", children: form.formState.errors.optionId.message })) : null] })] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Name" }), _jsx(Input, { ...form.register("name") }), form.formState.errors.name ? (_jsx("p", { className: "text-xs text-destructive", children: form.formState.errors.name.message })) : null] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Code" }), _jsx(Input, { ...form.register("code") })] })] }), _jsxs("div", { className: "grid grid-cols-3 gap-3", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Catalog" }), _jsx(PriceCatalogCombobox, { value: form.watch("priceCatalogId"), onChange: (value) => {
126
+ form.setValue("priceCatalogId", value ?? "", {
127
+ shouldDirty: true,
128
+ shouldValidate: true,
129
+ });
130
+ form.setValue("priceScheduleId", "", { shouldDirty: true });
131
+ } }), form.formState.errors.priceCatalogId ? (_jsx("p", { className: "text-xs text-destructive", children: form.formState.errors.priceCatalogId.message })) : null] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Schedule (optional)" }), _jsx(PriceScheduleCombobox, { priceCatalogId: watchedCatalogId, value: form.watch("priceScheduleId"), onChange: (value) => form.setValue("priceScheduleId", value ?? "", { shouldDirty: true }) })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Cancellation policy (optional)" }), _jsx(CancellationPolicyCombobox, { value: form.watch("cancellationPolicyId"), onChange: (value) => form.setValue("cancellationPolicyId", value ?? "", { shouldDirty: true }) })] })] }), _jsxs("div", { className: "grid grid-cols-3 gap-3", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Pricing mode" }), _jsxs(Select, { items: PRICING_MODES.map((x) => ({ label: x.replace(/_/g, " "), value: x })), value: form.watch("pricingMode"), onValueChange: (value) => form.setValue("pricingMode", value), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: PRICING_MODES.map((mode) => (_jsx(SelectItem, { value: mode, className: "capitalize", children: mode.replace(/_/g, " ") }, mode))) })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Base sell" }), _jsx(Input, { ...form.register("baseSell"), type: "number", min: "0", step: "0.01" })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Base cost" }), _jsx(Input, { ...form.register("baseCost"), type: "number", min: "0", step: "0.01" })] })] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Min per booking" }), _jsx(Input, { ...form.register("minPerBooking"), type: "number", min: "0" })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Max per booking" }), _jsx(Input, { ...form.register("maxPerBooking"), type: "number", min: "0" })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Description" }), _jsx(Textarea, { ...form.register("description"), rows: 2 })] }), _jsxs("div", { className: "grid grid-cols-3 gap-4", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Switch, { checked: form.watch("allPricingCategories"), onCheckedChange: (checked) => form.setValue("allPricingCategories", checked) }), _jsx(Label, { children: "All pricing categories" })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Switch, { checked: form.watch("isDefault"), onCheckedChange: (checked) => form.setValue("isDefault", checked) }), _jsx(Label, { children: "Default rule" })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Switch, { checked: form.watch("active"), onCheckedChange: (checked) => form.setValue("active", checked) }), _jsx(Label, { children: "Active" })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Notes" }), _jsx(Textarea, { ...form.register("notes"), rows: 2 })] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", variant: "ghost", onClick: () => onOpenChange(false), children: "Cancel" }), _jsxs(Button, { type: "submit", disabled: isSubmitting, children: [isSubmitting ? _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }) : null, isEditing ? "Save Changes" : "Create Rule"] })] })] })] }) }));
132
+ }
@@ -0,0 +1,10 @@
1
+ import { type OptionStartTimeRuleRecord } from "@voyantjs/pricing-react";
2
+ type Props = {
3
+ open: boolean;
4
+ onOpenChange: (open: boolean) => void;
5
+ rule?: OptionStartTimeRuleRecord;
6
+ onSuccess?: (rule: OptionStartTimeRuleRecord) => void;
7
+ };
8
+ export declare function OptionStartTimeRuleDialog({ open, onOpenChange, rule, onSuccess }: Props): import("react/jsx-runtime").JSX.Element;
9
+ export {};
10
+ //# sourceMappingURL=option-start-time-rule-dialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"option-start-time-rule-dialog.d.ts","sourceRoot":"","sources":["../../src/components/option-start-time-rule-dialog.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,yBAAyB,EAE/B,MAAM,yBAAyB,CAAA;AAiDhC,KAAK,KAAK,GAAG;IACX,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,IAAI,CAAC,EAAE,yBAAyB,CAAA;IAChC,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,yBAAyB,KAAK,IAAI,CAAA;CACtD,CAAA;AAOD,wBAAgB,yBAAyB,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,KAAK,2CAuMvF"}
@@ -0,0 +1,90 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useOptionStartTimeRuleMutation, } from "@voyantjs/pricing-react";
4
+ import { Button, Dialog, DialogBody, DialogContent, DialogFooter, DialogHeader, DialogTitle, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Switch, Textarea, } from "@voyantjs/voyant-ui/components";
5
+ import { zodResolver } from "@voyantjs/voyant-ui/lib/zod-resolver";
6
+ import { Loader2 } from "lucide-react";
7
+ import { useEffect } from "react";
8
+ import { useForm } from "react-hook-form";
9
+ import { z } from "zod/v4";
10
+ import { OptionPriceRuleCombobox } from "./option-price-rule-combobox";
11
+ const RULE_MODES = ["included", "excluded", "override", "adjustment"];
12
+ const ADJUSTMENT_TYPES = ["fixed", "percentage"];
13
+ const formSchema = z.object({
14
+ optionPriceRuleId: z.string().min(1, "Option price rule is required"),
15
+ optionId: z.string().min(1, "Option ID is required"),
16
+ startTimeId: z.string().min(1, "Start time ID is required"),
17
+ ruleMode: z.enum(RULE_MODES),
18
+ adjustmentType: z.enum(ADJUSTMENT_TYPES).optional().nullable(),
19
+ sellAdjustment: z.coerce.number().min(0).optional().or(z.literal("")).nullable(),
20
+ costAdjustment: z.coerce.number().min(0).optional().or(z.literal("")).nullable(),
21
+ adjustmentPercent: z.coerce.number().min(0).max(100).optional().or(z.literal("")).nullable(),
22
+ active: z.boolean(),
23
+ notes: z.string().optional().nullable(),
24
+ });
25
+ const toCents = (value) => typeof value === "number" ? Math.round(value * 100) : null;
26
+ const toBasisPoints = (value) => typeof value === "number" ? Math.round(value * 100) : null;
27
+ export function OptionStartTimeRuleDialog({ open, onOpenChange, rule, onSuccess }) {
28
+ const isEditing = !!rule;
29
+ const { create, update } = useOptionStartTimeRuleMutation();
30
+ const form = useForm({
31
+ resolver: zodResolver(formSchema),
32
+ defaultValues: {
33
+ optionPriceRuleId: "",
34
+ optionId: "",
35
+ startTimeId: "",
36
+ ruleMode: "included",
37
+ adjustmentType: null,
38
+ sellAdjustment: "",
39
+ costAdjustment: "",
40
+ adjustmentPercent: "",
41
+ active: true,
42
+ notes: "",
43
+ },
44
+ });
45
+ useEffect(() => {
46
+ if (open && rule) {
47
+ form.reset({
48
+ optionPriceRuleId: rule.optionPriceRuleId,
49
+ optionId: rule.optionId,
50
+ startTimeId: rule.startTimeId,
51
+ ruleMode: rule.ruleMode,
52
+ adjustmentType: rule.adjustmentType,
53
+ sellAdjustment: rule.sellAdjustmentCents != null ? rule.sellAdjustmentCents / 100 : "",
54
+ costAdjustment: rule.costAdjustmentCents != null ? rule.costAdjustmentCents / 100 : "",
55
+ adjustmentPercent: rule.adjustmentBasisPoints != null ? rule.adjustmentBasisPoints / 100 : "",
56
+ active: rule.active,
57
+ notes: rule.notes ?? "",
58
+ });
59
+ }
60
+ else if (open) {
61
+ form.reset();
62
+ }
63
+ }, [form, open, rule]);
64
+ const watchedMode = form.watch("ruleMode");
65
+ const showAdjustment = watchedMode === "adjustment" || watchedMode === "override";
66
+ const onSubmit = async (values) => {
67
+ const payload = {
68
+ optionPriceRuleId: values.optionPriceRuleId,
69
+ optionId: values.optionId,
70
+ startTimeId: values.startTimeId,
71
+ ruleMode: values.ruleMode,
72
+ adjustmentType: showAdjustment ? values.adjustmentType || null : null,
73
+ sellAdjustmentCents: showAdjustment ? toCents(values.sellAdjustment) : null,
74
+ costAdjustmentCents: showAdjustment ? toCents(values.costAdjustment) : null,
75
+ adjustmentBasisPoints: showAdjustment ? toBasisPoints(values.adjustmentPercent) : null,
76
+ active: values.active,
77
+ notes: values.notes || null,
78
+ };
79
+ const saved = isEditing
80
+ ? await update.mutateAsync({ id: rule.id, input: payload })
81
+ : await create.mutateAsync(payload);
82
+ onSuccess?.(saved);
83
+ onOpenChange(false);
84
+ };
85
+ const isSubmitting = create.isPending || update.isPending;
86
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { size: "lg", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: isEditing ? "Edit Start Time Rule" : "Add Start Time Rule" }) }), _jsxs("form", { onSubmit: form.handleSubmit(onSubmit), children: [_jsxs(DialogBody, { className: "grid gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Option price rule" }), _jsx(OptionPriceRuleCombobox, { value: form.watch("optionPriceRuleId"), onChange: (value) => form.setValue("optionPriceRuleId", value ?? "", {
87
+ shouldDirty: true,
88
+ shouldValidate: true,
89
+ }), disabled: isEditing }), form.formState.errors.optionPriceRuleId ? (_jsx("p", { className: "text-xs text-destructive", children: form.formState.errors.optionPriceRuleId.message })) : null] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Option ID" }), _jsx(Input, { ...form.register("optionId"), placeholder: "popt_\u2026" })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Start time ID" }), _jsx(Input, { ...form.register("startTimeId"), placeholder: "pst_\u2026" })] })] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Rule mode" }), _jsxs(Select, { items: RULE_MODES.map((x) => ({ label: x.replace(/_/g, " "), value: x })), value: form.watch("ruleMode"), onValueChange: (value) => form.setValue("ruleMode", value), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: RULE_MODES.map((mode) => (_jsx(SelectItem, { value: mode, className: "capitalize", children: mode.replace(/_/g, " ") }, mode))) })] })] }), showAdjustment ? (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Adjustment type" }), _jsxs(Select, { items: ADJUSTMENT_TYPES.map((x) => ({ label: x.replace(/_/g, " "), value: x })), value: form.watch("adjustmentType") ?? "", onValueChange: (value) => form.setValue("adjustmentType", (value || null)), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, { placeholder: "Select\u2026" }) }), _jsx(SelectContent, { children: ADJUSTMENT_TYPES.map((type) => (_jsx(SelectItem, { value: type, className: "capitalize", children: type }, type))) })] })] })) : null] }), showAdjustment ? (_jsxs("div", { className: "grid grid-cols-3 gap-3", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Sell adjustment" }), _jsx(Input, { ...form.register("sellAdjustment"), type: "number", step: "0.01", min: "0" })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Cost adjustment" }), _jsx(Input, { ...form.register("costAdjustment"), type: "number", step: "0.01", min: "0" })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Adjustment (%)" }), _jsx(Input, { ...form.register("adjustmentPercent"), type: "number", step: "0.01", min: "0", max: "100" })] })] })) : null, _jsxs("div", { className: "flex items-center gap-3", children: [_jsx(Switch, { checked: form.watch("active"), onCheckedChange: (checked) => form.setValue("active", checked) }), _jsx(Label, { children: "Active" })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Notes" }), _jsx(Textarea, { ...form.register("notes") })] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", variant: "ghost", onClick: () => onOpenChange(false), children: "Cancel" }), _jsxs(Button, { type: "submit", disabled: isSubmitting, children: [isSubmitting ? _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }) : null, isEditing ? "Save Changes" : "Add Rule"] })] })] })] }) }));
90
+ }
@@ -0,0 +1,10 @@
1
+ import { type OptionUnitPriceRuleRecord } from "@voyantjs/pricing-react";
2
+ type Props = {
3
+ open: boolean;
4
+ onOpenChange: (open: boolean) => void;
5
+ rule?: OptionUnitPriceRuleRecord;
6
+ onSuccess?: (rule: OptionUnitPriceRuleRecord) => void;
7
+ };
8
+ export declare function OptionUnitPriceRuleDialog({ open, onOpenChange, rule, onSuccess }: Props): import("react/jsx-runtime").JSX.Element;
9
+ export {};
10
+ //# sourceMappingURL=option-unit-price-rule-dialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"option-unit-price-rule-dialog.d.ts","sourceRoot":"","sources":["../../src/components/option-unit-price-rule-dialog.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,yBAAyB,EAE/B,MAAM,yBAAyB,CAAA;AAwDhC,KAAK,KAAK,GAAG;IACX,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,IAAI,CAAC,EAAE,yBAAyB,CAAA;IAChC,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,yBAAyB,KAAK,IAAI,CAAA;CACtD,CAAA;AAOD,wBAAgB,yBAAyB,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,KAAK,2CAgMvF"}
@@ -0,0 +1,103 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useOptionUnitPriceRuleMutation, } from "@voyantjs/pricing-react";
4
+ import { Button, Dialog, DialogBody, DialogContent, DialogFooter, DialogHeader, DialogTitle, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Switch, Textarea, } from "@voyantjs/voyant-ui/components";
5
+ import { zodResolver } from "@voyantjs/voyant-ui/lib/zod-resolver";
6
+ import { Loader2 } from "lucide-react";
7
+ import { useEffect } from "react";
8
+ import { useForm } from "react-hook-form";
9
+ import { z } from "zod/v4";
10
+ import { OptionPriceRuleCombobox } from "./option-price-rule-combobox";
11
+ import { PricingCategoryCombobox } from "./pricing-category-combobox";
12
+ const PRICING_MODES = [
13
+ "per_unit",
14
+ "per_person",
15
+ "per_booking",
16
+ "included",
17
+ "free",
18
+ "on_request",
19
+ ];
20
+ const formSchema = z.object({
21
+ optionPriceRuleId: z.string().min(1, "Option price rule is required"),
22
+ optionId: z.string().min(1, "Option ID is required"),
23
+ unitId: z.string().min(1, "Unit ID is required"),
24
+ pricingCategoryId: z.string().optional().nullable(),
25
+ pricingMode: z.enum(PRICING_MODES),
26
+ sellAmount: z.coerce.number().min(0).optional().or(z.literal("")).nullable(),
27
+ costAmount: z.coerce.number().min(0).optional().or(z.literal("")).nullable(),
28
+ minQuantity: z.coerce.number().int().min(0).optional().or(z.literal("")).nullable(),
29
+ maxQuantity: z.coerce.number().int().min(0).optional().or(z.literal("")).nullable(),
30
+ sortOrder: z.coerce.number().int(),
31
+ active: z.boolean(),
32
+ notes: z.string().optional().nullable(),
33
+ });
34
+ const toInt = (value) => typeof value === "number" ? value : null;
35
+ const toCents = (value) => typeof value === "number" ? Math.round(value * 100) : null;
36
+ export function OptionUnitPriceRuleDialog({ open, onOpenChange, rule, onSuccess }) {
37
+ const isEditing = !!rule;
38
+ const { create, update } = useOptionUnitPriceRuleMutation();
39
+ const form = useForm({
40
+ resolver: zodResolver(formSchema),
41
+ defaultValues: {
42
+ optionPriceRuleId: "",
43
+ optionId: "",
44
+ unitId: "",
45
+ pricingCategoryId: "",
46
+ pricingMode: "per_unit",
47
+ sellAmount: "",
48
+ costAmount: "",
49
+ minQuantity: "",
50
+ maxQuantity: "",
51
+ sortOrder: 0,
52
+ active: true,
53
+ notes: "",
54
+ },
55
+ });
56
+ useEffect(() => {
57
+ if (open && rule) {
58
+ form.reset({
59
+ optionPriceRuleId: rule.optionPriceRuleId,
60
+ optionId: rule.optionId,
61
+ unitId: rule.unitId,
62
+ pricingCategoryId: rule.pricingCategoryId ?? "",
63
+ pricingMode: rule.pricingMode,
64
+ sellAmount: rule.sellAmountCents != null ? rule.sellAmountCents / 100 : "",
65
+ costAmount: rule.costAmountCents != null ? rule.costAmountCents / 100 : "",
66
+ minQuantity: rule.minQuantity ?? "",
67
+ maxQuantity: rule.maxQuantity ?? "",
68
+ sortOrder: rule.sortOrder,
69
+ active: rule.active,
70
+ notes: rule.notes ?? "",
71
+ });
72
+ }
73
+ else if (open) {
74
+ form.reset();
75
+ }
76
+ }, [open, rule, form]);
77
+ const onSubmit = async (values) => {
78
+ const payload = {
79
+ optionPriceRuleId: values.optionPriceRuleId,
80
+ optionId: values.optionId,
81
+ unitId: values.unitId,
82
+ pricingCategoryId: values.pricingCategoryId || null,
83
+ pricingMode: values.pricingMode,
84
+ sellAmountCents: toCents(values.sellAmount),
85
+ costAmountCents: toCents(values.costAmount),
86
+ minQuantity: toInt(values.minQuantity),
87
+ maxQuantity: toInt(values.maxQuantity),
88
+ sortOrder: values.sortOrder,
89
+ active: values.active,
90
+ notes: values.notes || null,
91
+ };
92
+ const saved = isEditing
93
+ ? await update.mutateAsync({ id: rule.id, input: payload })
94
+ : await create.mutateAsync(payload);
95
+ onSuccess?.(saved);
96
+ onOpenChange(false);
97
+ };
98
+ const isSubmitting = create.isPending || update.isPending;
99
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { size: "lg", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: isEditing ? "Edit Option Unit Price Rule" : "Add Option Unit Price Rule" }) }), _jsxs("form", { onSubmit: form.handleSubmit(onSubmit), children: [_jsxs(DialogBody, { className: "grid gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Option price rule" }), _jsx(OptionPriceRuleCombobox, { value: form.watch("optionPriceRuleId"), onChange: (value) => form.setValue("optionPriceRuleId", value ?? "", {
100
+ shouldDirty: true,
101
+ shouldValidate: true,
102
+ }), disabled: isEditing }), form.formState.errors.optionPriceRuleId ? (_jsx("p", { className: "text-xs text-destructive", children: form.formState.errors.optionPriceRuleId.message })) : null] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Option ID" }), _jsx(Input, { ...form.register("optionId"), placeholder: "popt_\u2026" })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Unit ID" }), _jsx(Input, { ...form.register("unitId"), placeholder: "punit_\u2026" })] })] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Pricing category (optional)" }), _jsx(PricingCategoryCombobox, { value: form.watch("pricingCategoryId"), onChange: (value) => form.setValue("pricingCategoryId", value ?? "", { shouldDirty: true }) })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Pricing mode" }), _jsxs(Select, { items: PRICING_MODES.map((x) => ({ label: x.replace(/_/g, " "), value: x })), value: form.watch("pricingMode"), onValueChange: (value) => form.setValue("pricingMode", value), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: PRICING_MODES.map((mode) => (_jsx(SelectItem, { value: mode, className: "capitalize", children: mode.replace(/_/g, " ") }, mode))) })] })] })] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Sell amount" }), _jsx(Input, { ...form.register("sellAmount"), type: "number", step: "0.01", min: "0" })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Cost amount" }), _jsx(Input, { ...form.register("costAmount"), type: "number", step: "0.01", min: "0" })] })] }), _jsxs("div", { className: "grid grid-cols-3 gap-3", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Min quantity" }), _jsx(Input, { ...form.register("minQuantity"), type: "number", min: "0" })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Max quantity" }), _jsx(Input, { ...form.register("maxQuantity"), type: "number", min: "0" })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Sort order" }), _jsx(Input, { ...form.register("sortOrder"), type: "number" })] })] }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsx(Switch, { checked: form.watch("active"), onCheckedChange: (checked) => form.setValue("active", checked) }), _jsx(Label, { children: "Active" })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Notes" }), _jsx(Textarea, { ...form.register("notes") })] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", variant: "ghost", onClick: () => onOpenChange(false), children: "Cancel" }), _jsxs(Button, { type: "submit", disabled: isSubmitting, children: [isSubmitting ? _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }) : null, isEditing ? "Save Changes" : "Add Rule"] })] })] })] }) }));
103
+ }
@@ -0,0 +1,10 @@
1
+ import { type OptionUnitTierRecord } from "@voyantjs/pricing-react";
2
+ type Props = {
3
+ open: boolean;
4
+ onOpenChange: (open: boolean) => void;
5
+ tier?: OptionUnitTierRecord;
6
+ onSuccess?: (tier: OptionUnitTierRecord) => void;
7
+ };
8
+ export declare function OptionUnitTierDialog({ open, onOpenChange, tier, onSuccess }: Props): import("react/jsx-runtime").JSX.Element;
9
+ export {};
10
+ //# sourceMappingURL=option-unit-tier-dialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"option-unit-tier-dialog.d.ts","sourceRoot":"","sources":["../../src/components/option-unit-tier-dialog.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,oBAAoB,EAA6B,MAAM,yBAAyB,CAAA;AAgC9F,KAAK,KAAK,GAAG;IACX,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,IAAI,CAAC,EAAE,oBAAoB,CAAA;IAC3B,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,oBAAoB,KAAK,IAAI,CAAA;CACjD,CAAA;AAOD,wBAAgB,oBAAoB,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,KAAK,2CA0IlF"}
@@ -0,0 +1,78 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useOptionUnitTierMutation } from "@voyantjs/pricing-react";
4
+ import { Button, Dialog, DialogBody, DialogContent, DialogFooter, DialogHeader, DialogTitle, Input, Label, Switch, } from "@voyantjs/voyant-ui/components";
5
+ import { zodResolver } from "@voyantjs/voyant-ui/lib/zod-resolver";
6
+ import { Loader2 } from "lucide-react";
7
+ import { useEffect } from "react";
8
+ import { useForm } from "react-hook-form";
9
+ import { z } from "zod/v4";
10
+ const formSchema = z.object({
11
+ optionUnitPriceRuleId: z.string().min(1, "Option unit price rule is required"),
12
+ minQuantity: z.coerce.number().int().min(1, "Min quantity must be at least 1"),
13
+ maxQuantity: z.coerce.number().int().min(1).optional().or(z.literal("")).nullable(),
14
+ sellAmount: z.coerce.number().min(0).optional().or(z.literal("")).nullable(),
15
+ costAmount: z.coerce.number().min(0).optional().or(z.literal("")).nullable(),
16
+ active: z.boolean(),
17
+ sortOrder: z.coerce.number().int(),
18
+ });
19
+ const toCents = (value) => typeof value === "number" ? Math.round(value * 100) : null;
20
+ const toInt = (value) => typeof value === "number" ? value : null;
21
+ export function OptionUnitTierDialog({ open, onOpenChange, tier, onSuccess }) {
22
+ const isEditing = !!tier;
23
+ const { create, update } = useOptionUnitTierMutation();
24
+ const form = useForm({
25
+ resolver: zodResolver(formSchema),
26
+ defaultValues: {
27
+ optionUnitPriceRuleId: "",
28
+ minQuantity: 1,
29
+ maxQuantity: "",
30
+ sellAmount: "",
31
+ costAmount: "",
32
+ active: true,
33
+ sortOrder: 0,
34
+ },
35
+ });
36
+ useEffect(() => {
37
+ if (open && tier) {
38
+ form.reset({
39
+ optionUnitPriceRuleId: tier.optionUnitPriceRuleId,
40
+ minQuantity: tier.minQuantity,
41
+ maxQuantity: tier.maxQuantity ?? "",
42
+ sellAmount: tier.sellAmountCents != null ? tier.sellAmountCents / 100 : "",
43
+ costAmount: tier.costAmountCents != null ? tier.costAmountCents / 100 : "",
44
+ active: tier.active,
45
+ sortOrder: tier.sortOrder,
46
+ });
47
+ }
48
+ else if (open) {
49
+ form.reset({
50
+ optionUnitPriceRuleId: "",
51
+ minQuantity: 1,
52
+ maxQuantity: "",
53
+ sellAmount: "",
54
+ costAmount: "",
55
+ active: true,
56
+ sortOrder: 0,
57
+ });
58
+ }
59
+ }, [open, tier, form]);
60
+ const onSubmit = async (values) => {
61
+ const payload = {
62
+ optionUnitPriceRuleId: values.optionUnitPriceRuleId,
63
+ minQuantity: values.minQuantity,
64
+ maxQuantity: toInt(values.maxQuantity),
65
+ sellAmountCents: toCents(values.sellAmount),
66
+ costAmountCents: toCents(values.costAmount),
67
+ active: values.active,
68
+ sortOrder: values.sortOrder,
69
+ };
70
+ const saved = isEditing
71
+ ? await update.mutateAsync({ id: tier.id, input: payload })
72
+ : await create.mutateAsync(payload);
73
+ onSuccess?.(saved);
74
+ onOpenChange(false);
75
+ };
76
+ const isSubmitting = create.isPending || update.isPending;
77
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: isEditing ? "Edit Unit Tier" : "Add Unit Tier" }) }), _jsxs("form", { onSubmit: form.handleSubmit(onSubmit), children: [_jsxs(DialogBody, { className: "grid gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Option unit price rule ID" }), _jsx(Input, { ...form.register("optionUnitPriceRuleId"), placeholder: "oupr_\u2026", disabled: isEditing }), form.formState.errors.optionUnitPriceRuleId ? (_jsx("p", { className: "text-xs text-destructive", children: form.formState.errors.optionUnitPriceRuleId.message })) : null] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Min quantity" }), _jsx(Input, { ...form.register("minQuantity"), type: "number", min: "1" }), form.formState.errors.minQuantity ? (_jsx("p", { className: "text-xs text-destructive", children: form.formState.errors.minQuantity.message })) : null] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Max quantity" }), _jsx(Input, { ...form.register("maxQuantity"), type: "number", min: "1" })] })] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Sell amount" }), _jsx(Input, { ...form.register("sellAmount"), type: "number", step: "0.01", min: "0" })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Cost amount" }), _jsx(Input, { ...form.register("costAmount"), type: "number", step: "0.01", min: "0" })] })] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Sort order" }), _jsx(Input, { ...form.register("sortOrder"), type: "number" })] }), _jsxs("div", { className: "flex items-center gap-3 pt-6", children: [_jsx(Switch, { checked: form.watch("active"), onCheckedChange: (checked) => form.setValue("active", checked) }), _jsx(Label, { children: "Active" })] })] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", variant: "ghost", onClick: () => onOpenChange(false), children: "Cancel" }), _jsxs(Button, { type: "submit", disabled: isSubmitting, children: [isSubmitting ? _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }) : null, isEditing ? "Save Changes" : "Add Tier"] })] })] })] }) }));
78
+ }
@@ -0,0 +1,10 @@
1
+ import { type PickupPriceRuleRecord } from "@voyantjs/pricing-react";
2
+ type Props = {
3
+ open: boolean;
4
+ onOpenChange: (open: boolean) => void;
5
+ rule?: PickupPriceRuleRecord;
6
+ onSuccess?: (rule: PickupPriceRuleRecord) => void;
7
+ };
8
+ export declare function PickupPriceRuleDialog({ open, onOpenChange, rule, onSuccess }: Props): import("react/jsx-runtime").JSX.Element;
9
+ export {};
10
+ //# sourceMappingURL=pickup-price-rule-dialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pickup-price-rule-dialog.d.ts","sourceRoot":"","sources":["../../src/components/pickup-price-rule-dialog.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,qBAAqB,EAA8B,MAAM,yBAAyB,CAAA;AAmDhG,KAAK,KAAK,GAAG;IACX,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,IAAI,CAAC,EAAE,qBAAqB,CAAA;IAC5B,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,qBAAqB,KAAK,IAAI,CAAA;CAClD,CAAA;AAKD,wBAAgB,qBAAqB,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,KAAK,2CAmKnF"}
@@ -0,0 +1,88 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { usePickupPriceRuleMutation } from "@voyantjs/pricing-react";
4
+ import { Button, Dialog, DialogBody, DialogContent, DialogFooter, DialogHeader, DialogTitle, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Switch, Textarea, } from "@voyantjs/voyant-ui/components";
5
+ import { zodResolver } from "@voyantjs/voyant-ui/lib/zod-resolver";
6
+ import { Loader2 } from "lucide-react";
7
+ import { useEffect } from "react";
8
+ import { useForm } from "react-hook-form";
9
+ import { z } from "zod/v4";
10
+ import { OptionPriceRuleCombobox } from "./option-price-rule-combobox";
11
+ const ADDON_PRICING_MODES = [
12
+ "included",
13
+ "per_person",
14
+ "per_booking",
15
+ "on_request",
16
+ "unavailable",
17
+ ];
18
+ const formSchema = z.object({
19
+ optionPriceRuleId: z.string().min(1, "Option price rule is required"),
20
+ optionId: z.string().min(1, "Option ID is required"),
21
+ pickupPointId: z.string().min(1, "Pickup point is required"),
22
+ pricingMode: z.enum(ADDON_PRICING_MODES),
23
+ sellAmount: z.coerce.number().min(0).optional().or(z.literal("")).nullable(),
24
+ costAmount: z.coerce.number().min(0).optional().or(z.literal("")).nullable(),
25
+ active: z.boolean(),
26
+ sortOrder: z.coerce.number().int(),
27
+ notes: z.string().optional().nullable(),
28
+ });
29
+ const toCents = (value) => typeof value === "number" ? Math.round(value * 100) : null;
30
+ export function PickupPriceRuleDialog({ open, onOpenChange, rule, onSuccess }) {
31
+ const isEditing = !!rule;
32
+ const { create, update } = usePickupPriceRuleMutation();
33
+ const form = useForm({
34
+ resolver: zodResolver(formSchema),
35
+ defaultValues: {
36
+ optionPriceRuleId: "",
37
+ optionId: "",
38
+ pickupPointId: "",
39
+ pricingMode: "included",
40
+ sellAmount: "",
41
+ costAmount: "",
42
+ active: true,
43
+ sortOrder: 0,
44
+ notes: "",
45
+ },
46
+ });
47
+ useEffect(() => {
48
+ if (open && rule) {
49
+ form.reset({
50
+ optionPriceRuleId: rule.optionPriceRuleId,
51
+ optionId: rule.optionId,
52
+ pickupPointId: rule.pickupPointId,
53
+ pricingMode: rule.pricingMode,
54
+ sellAmount: rule.sellAmountCents != null ? rule.sellAmountCents / 100 : "",
55
+ costAmount: rule.costAmountCents != null ? rule.costAmountCents / 100 : "",
56
+ active: rule.active,
57
+ sortOrder: rule.sortOrder,
58
+ notes: rule.notes ?? "",
59
+ });
60
+ }
61
+ else if (open) {
62
+ form.reset();
63
+ }
64
+ }, [form, open, rule]);
65
+ const onSubmit = async (values) => {
66
+ const payload = {
67
+ optionPriceRuleId: values.optionPriceRuleId,
68
+ optionId: values.optionId,
69
+ pickupPointId: values.pickupPointId,
70
+ pricingMode: values.pricingMode,
71
+ sellAmountCents: toCents(values.sellAmount),
72
+ costAmountCents: toCents(values.costAmount),
73
+ active: values.active,
74
+ sortOrder: values.sortOrder,
75
+ notes: values.notes || null,
76
+ };
77
+ const saved = isEditing
78
+ ? await update.mutateAsync({ id: rule.id, input: payload })
79
+ : await create.mutateAsync(payload);
80
+ onSuccess?.(saved);
81
+ onOpenChange(false);
82
+ };
83
+ const isSubmitting = create.isPending || update.isPending;
84
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { size: "lg", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: isEditing ? "Edit Pickup Price Rule" : "Add Pickup Price Rule" }) }), _jsxs("form", { onSubmit: form.handleSubmit(onSubmit), children: [_jsxs(DialogBody, { className: "grid gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Option price rule" }), _jsx(OptionPriceRuleCombobox, { value: form.watch("optionPriceRuleId"), onChange: (value) => form.setValue("optionPriceRuleId", value ?? "", {
85
+ shouldDirty: true,
86
+ shouldValidate: true,
87
+ }), disabled: isEditing }), form.formState.errors.optionPriceRuleId ? (_jsx("p", { className: "text-xs text-destructive", children: form.formState.errors.optionPriceRuleId.message })) : null] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Option ID" }), _jsx(Input, { ...form.register("optionId"), placeholder: "popt_\u2026" })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Pickup point ID" }), _jsx(Input, { ...form.register("pickupPointId"), placeholder: "ppnt_\u2026" })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Pricing mode" }), _jsxs(Select, { items: ADDON_PRICING_MODES.map((x) => ({ label: x.replace(/_/g, " "), value: x })), value: form.watch("pricingMode"), onValueChange: (value) => form.setValue("pricingMode", value), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: ADDON_PRICING_MODES.map((mode) => (_jsx(SelectItem, { value: mode, className: "capitalize", children: mode.replace(/_/g, " ") }, mode))) })] })] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Sell amount" }), _jsx(Input, { ...form.register("sellAmount"), type: "number", step: "0.01", min: "0" })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Cost amount" }), _jsx(Input, { ...form.register("costAmount"), type: "number", step: "0.01", min: "0" })] })] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Sort order" }), _jsx(Input, { ...form.register("sortOrder"), type: "number" })] }), _jsxs("div", { className: "flex items-center gap-3 pt-6", children: [_jsx(Switch, { checked: form.watch("active"), onCheckedChange: (checked) => form.setValue("active", checked) }), _jsx(Label, { children: "Active" })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Notes" }), _jsx(Textarea, { ...form.register("notes") })] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", variant: "ghost", onClick: () => onOpenChange(false), children: "Cancel" }), _jsxs(Button, { type: "submit", disabled: isSubmitting, children: [isSubmitting ? _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }) : null, isEditing ? "Save Changes" : "Add Rule"] })] })] })] }) }));
88
+ }
@@ -0,0 +1,9 @@
1
+ type Props = {
2
+ value: string | null | undefined;
3
+ onChange: (value: string | null) => void;
4
+ placeholder?: string;
5
+ disabled?: boolean;
6
+ };
7
+ export declare function PriceCatalogCombobox({ value, onChange, placeholder, disabled, }: Props): import("react/jsx-runtime").JSX.Element;
8
+ export {};
9
+ //# sourceMappingURL=price-catalog-combobox.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"price-catalog-combobox.d.ts","sourceRoot":"","sources":["../../src/components/price-catalog-combobox.tsx"],"names":[],"mappings":"AAYA,KAAK,KAAK,GAAG;IACX,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;IAChC,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;IACxC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,CAAA;AAID,wBAAgB,oBAAoB,CAAC,EACnC,KAAK,EACL,QAAQ,EACR,WAAsC,EACtC,QAAQ,GACT,EAAE,KAAK,2CAsEP"}