@voyantjs/products-ui 0.101.0 → 0.101.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.
- package/dist/components/option-unit-form.d.ts.map +1 -1
- package/dist/components/option-unit-form.js +6 -4
- package/dist/components/product-detail/date-picker.d.ts +44 -0
- package/dist/components/product-detail/date-picker.d.ts.map +1 -0
- package/dist/components/product-detail/date-picker.js +125 -0
- package/dist/components/product-detail/host.d.ts +53 -0
- package/dist/components/product-detail/host.d.ts.map +1 -0
- package/dist/components/product-detail/host.js +24 -0
- package/dist/components/product-detail/index.d.ts +6 -0
- package/dist/components/product-detail/index.d.ts.map +1 -0
- package/dist/components/product-detail/index.js +5 -0
- package/dist/components/product-detail/product-activity-section.d.ts +4 -0
- package/dist/components/product-detail/product-activity-section.d.ts.map +1 -0
- package/dist/components/product-detail/product-activity-section.js +37 -0
- package/dist/components/product-detail/product-day-sheet.d.ts +14 -0
- package/dist/components/product-detail/product-day-sheet.d.ts.map +1 -0
- package/dist/components/product-detail/product-day-sheet.js +75 -0
- package/dist/components/product-detail/product-day-translation.d.ts +41 -0
- package/dist/components/product-detail/product-day-translation.d.ts.map +1 -0
- package/dist/components/product-detail/product-day-translation.js +111 -0
- package/dist/components/product-detail/product-departure-dialog.d.ts +11 -0
- package/dist/components/product-detail/product-departure-dialog.d.ts.map +1 -0
- package/dist/components/product-detail/product-departure-dialog.js +10 -0
- package/dist/components/product-detail/product-departure-form.d.ts +25 -0
- package/dist/components/product-detail/product-departure-form.d.ts.map +1 -0
- package/dist/components/product-detail/product-departure-form.js +217 -0
- package/dist/components/product-detail/product-departure-pricing-override-dialog.d.ts +8 -0
- package/dist/components/product-detail/product-departure-pricing-override-dialog.d.ts.map +1 -0
- package/dist/components/product-detail/product-departure-pricing-override-dialog.js +125 -0
- package/dist/components/product-detail/product-detail-day-row.d.ts +14 -0
- package/dist/components/product-detail/product-detail-day-row.d.ts.map +1 -0
- package/dist/components/product-detail/product-detail-day-row.js +43 -0
- package/dist/components/product-detail/product-detail-dialog.d.ts +10 -0
- package/dist/components/product-detail/product-detail-dialog.d.ts.map +1 -0
- package/dist/components/product-detail/product-detail-dialog.js +10 -0
- package/dist/components/product-detail/product-detail-form.d.ts +19 -0
- package/dist/components/product-detail/product-detail-form.d.ts.map +1 -0
- package/dist/components/product-detail/product-detail-form.js +177 -0
- package/dist/components/product-detail/product-detail-header.d.ts +12 -0
- package/dist/components/product-detail/product-detail-header.d.ts.map +1 -0
- package/dist/components/product-detail/product-detail-header.js +19 -0
- package/dist/components/product-detail/product-detail-itinerary-section.d.ts +4 -0
- package/dist/components/product-detail/product-detail-itinerary-section.d.ts.map +1 -0
- package/dist/components/product-detail/product-detail-itinerary-section.js +201 -0
- package/dist/components/product-detail/product-detail-page.d.ts +4 -0
- package/dist/components/product-detail/product-detail-page.d.ts.map +1 -0
- package/dist/components/product-detail/product-detail-page.js +97 -0
- package/dist/components/product-detail/product-detail-sections.d.ts +63 -0
- package/dist/components/product-detail/product-detail-sections.d.ts.map +1 -0
- package/dist/components/product-detail/product-detail-sections.js +143 -0
- package/dist/components/product-detail/product-detail-shared.d.ts +264 -0
- package/dist/components/product-detail/product-detail-shared.d.ts.map +1 -0
- package/dist/components/product-detail/product-detail-shared.js +157 -0
- package/dist/components/product-detail/product-detail-skeleton.d.ts +9 -0
- package/dist/components/product-detail/product-detail-skeleton.d.ts.map +1 -0
- package/dist/components/product-detail/product-detail-skeleton.js +53 -0
- package/dist/components/product-detail/product-extras-section.d.ts +4 -0
- package/dist/components/product-detail/product-extras-section.d.ts.map +1 -0
- package/dist/components/product-detail/product-extras-section.js +141 -0
- package/dist/components/product-detail/product-itinerary-form.d.ts +16 -0
- package/dist/components/product-detail/product-itinerary-form.d.ts.map +1 -0
- package/dist/components/product-detail/product-itinerary-form.js +38 -0
- package/dist/components/product-detail/product-market-rules-section.d.ts +6 -0
- package/dist/components/product-detail/product-market-rules-section.d.ts.map +1 -0
- package/dist/components/product-detail/product-market-rules-section.js +81 -0
- package/dist/components/product-detail/product-media-gallery.d.ts +19 -0
- package/dist/components/product-detail/product-media-gallery.d.ts.map +1 -0
- package/dist/components/product-detail/product-media-gallery.js +114 -0
- package/dist/components/product-detail/product-option-price-rule-dialog.d.ts +12 -0
- package/dist/components/product-detail/product-option-price-rule-dialog.d.ts.map +1 -0
- package/dist/components/product-detail/product-option-price-rule-dialog.js +10 -0
- package/dist/components/product-detail/product-option-price-rule-form.d.ts +29 -0
- package/dist/components/product-detail/product-option-price-rule-form.d.ts.map +1 -0
- package/dist/components/product-detail/product-option-price-rule-form.js +125 -0
- package/dist/components/product-detail/product-options-pricing.d.ts +6 -0
- package/dist/components/product-detail/product-options-pricing.d.ts.map +1 -0
- package/dist/components/product-detail/product-options-pricing.js +363 -0
- package/dist/components/product-detail/product-options-shared.d.ts +609 -0
- package/dist/components/product-detail/product-options-shared.d.ts.map +1 -0
- package/dist/components/product-detail/product-options-shared.js +34 -0
- package/dist/components/product-detail/product-payment-policy-section.d.ts +17 -0
- package/dist/components/product-detail/product-payment-policy-section.d.ts.map +1 -0
- package/dist/components/product-detail/product-payment-policy-section.js +58 -0
- package/dist/components/product-detail/product-schedule-dialog.d.ts +11 -0
- package/dist/components/product-detail/product-schedule-dialog.d.ts.map +1 -0
- package/dist/components/product-detail/product-schedule-dialog.js +10 -0
- package/dist/components/product-detail/product-schedule-form.d.ts +17 -0
- package/dist/components/product-detail/product-schedule-form.d.ts.map +1 -0
- package/dist/components/product-detail/product-schedule-form.js +222 -0
- package/dist/components/product-detail/product-service-dialog.d.ts +12 -0
- package/dist/components/product-detail/product-service-dialog.d.ts.map +1 -0
- package/dist/components/product-detail/product-service-dialog.js +10 -0
- package/dist/components/product-detail/product-service-form.d.ts +22 -0
- package/dist/components/product-detail/product-service-form.d.ts.map +1 -0
- package/dist/components/product-detail/product-service-form.js +154 -0
- package/dist/components/product-detail/product-translation-popover.d.ts +91 -0
- package/dist/components/product-detail/product-translation-popover.d.ts.map +1 -0
- package/dist/components/product-detail/product-translation-popover.js +217 -0
- package/dist/components/product-detail/product-unit-dialog.d.ts +12 -0
- package/dist/components/product-detail/product-unit-dialog.d.ts.map +1 -0
- package/dist/components/product-detail/product-unit-dialog.js +10 -0
- package/dist/components/product-detail/product-unit-form.d.ts +26 -0
- package/dist/components/product-detail/product-unit-form.d.ts.map +1 -0
- package/dist/components/product-detail/product-unit-form.js +109 -0
- package/dist/components/product-detail/product-unit-price-rule-dialog.d.ts +16 -0
- package/dist/components/product-detail/product-unit-price-rule-dialog.d.ts.map +1 -0
- package/dist/components/product-detail/product-unit-price-rule-dialog.js +10 -0
- package/dist/components/product-detail/product-unit-price-rule-form.d.ts +28 -0
- package/dist/components/product-detail/product-unit-price-rule-form.d.ts.map +1 -0
- package/dist/components/product-detail/product-unit-price-rule-form.js +126 -0
- package/dist/components/product-detail/timezone-options.d.ts +9 -0
- package/dist/components/product-detail/timezone-options.d.ts.map +1 -0
- package/dist/components/product-detail/timezone-options.js +28 -0
- package/dist/components/product-detail/use-product-detail-data.d.ts +41 -0
- package/dist/components/product-detail/use-product-detail-data.d.ts.map +1 -0
- package/dist/components/product-detail/use-product-detail-data.js +143 -0
- package/dist/components/product-detail/use-product-detail-dialogs.d.ts +24 -0
- package/dist/components/product-detail/use-product-detail-dialogs.d.ts.map +1 -0
- package/dist/components/product-detail/use-product-detail-dialogs.js +40 -0
- package/dist/components/product-detail/zod-resolver.d.ts +4 -0
- package/dist/components/product-detail/zod-resolver.d.ts.map +1 -0
- package/dist/components/product-detail/zod-resolver.js +39 -0
- package/dist/components/product-option-form.js +1 -1
- package/dist/components/product-options-section.d.ts +3 -1
- package/dist/components/product-options-section.d.ts.map +1 -1
- package/dist/components/product-options-section.js +102 -5
- package/dist/i18n/en.d.ts +21 -0
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +54 -33
- package/dist/i18n/messages.d.ts +21 -0
- package/dist/i18n/messages.d.ts.map +1 -1
- package/dist/i18n/provider.d.ts +42 -0
- package/dist/i18n/provider.d.ts.map +1 -1
- package/dist/i18n/ro.d.ts +21 -0
- package/dist/i18n/ro.d.ts.map +1 -1
- package/dist/i18n/ro.js +53 -32
- package/package.json +38 -19
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"product-translation-popover.d.ts","sourceRoot":"","sources":["../../../src/components/product-detail/product-translation-popover.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useProductTranslationMutation, useProductTranslations, } from "@voyantjs/products-react";
|
|
3
|
+
import { Button, Input, Label } from "@voyantjs/ui/components";
|
|
4
|
+
import { Combobox, ComboboxContent, ComboboxEmpty, ComboboxInput, ComboboxItem, ComboboxList, } from "@voyantjs/ui/components/combobox";
|
|
5
|
+
import { Popover, PopoverContent, PopoverTrigger } from "@voyantjs/ui/components/popover";
|
|
6
|
+
import { RichTextEditor } from "@voyantjs/ui/components/rich-text-editor";
|
|
7
|
+
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "@voyantjs/ui/components/tooltip";
|
|
8
|
+
import { cn } from "@voyantjs/ui/lib/utils";
|
|
9
|
+
import { languages } from "@voyantjs/utils/languages";
|
|
10
|
+
import { Globe, Plus, X } from "lucide-react";
|
|
11
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
12
|
+
function recordToDraft(record) {
|
|
13
|
+
return {
|
|
14
|
+
id: record.id,
|
|
15
|
+
languageTag: record.languageTag,
|
|
16
|
+
name: record.name,
|
|
17
|
+
description: record.description ?? "",
|
|
18
|
+
slug: record.slug ?? "",
|
|
19
|
+
shortDescription: record.shortDescription,
|
|
20
|
+
inclusionsHtml: record.inclusionsHtml,
|
|
21
|
+
exclusionsHtml: record.exclusionsHtml,
|
|
22
|
+
termsHtml: record.termsHtml,
|
|
23
|
+
seoTitle: record.seoTitle,
|
|
24
|
+
seoDescription: record.seoDescription,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function emptyDraft(languageTag) {
|
|
28
|
+
return {
|
|
29
|
+
id: null,
|
|
30
|
+
languageTag,
|
|
31
|
+
name: "",
|
|
32
|
+
description: "",
|
|
33
|
+
slug: "",
|
|
34
|
+
shortDescription: null,
|
|
35
|
+
inclusionsHtml: null,
|
|
36
|
+
exclusionsHtml: null,
|
|
37
|
+
termsHtml: null,
|
|
38
|
+
seoTitle: null,
|
|
39
|
+
seoDescription: null,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
// Rich text is "set" only when it has visible text, not just empty markup like <p></p>.
|
|
43
|
+
export function richTextHasContent(html) {
|
|
44
|
+
return (html
|
|
45
|
+
.replace(/<[^>]*>/g, "")
|
|
46
|
+
.replace(/ /g, " ")
|
|
47
|
+
.trim().length > 0);
|
|
48
|
+
}
|
|
49
|
+
function fieldHasContent(draft, field) {
|
|
50
|
+
if (field === "description")
|
|
51
|
+
return richTextHasContent(draft.description);
|
|
52
|
+
return draft[field].trim().length > 0;
|
|
53
|
+
}
|
|
54
|
+
export function languageLabel(tag) {
|
|
55
|
+
const base = tag.split("-")[0]?.toLowerCase() ?? tag;
|
|
56
|
+
return languages[base] ?? tag;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Manages an in-memory draft of a product's translations so Name/Description/
|
|
60
|
+
* Slug can be edited in context from the edit sheet. Seeds from the saved
|
|
61
|
+
* translation records and persists create/update/delete on save.
|
|
62
|
+
*
|
|
63
|
+
* The base product columns hold the default language's Name/Description, so the
|
|
64
|
+
* default-language translation row (if any) just mirrors them and carries the
|
|
65
|
+
* slug (base has no slug column). Fields we don't edit here (short description,
|
|
66
|
+
* inclusions, SEO, …) are preserved untouched.
|
|
67
|
+
*/
|
|
68
|
+
export function useProductTranslationDrafts(productId) {
|
|
69
|
+
const query = useProductTranslations(productId ?? undefined, {
|
|
70
|
+
limit: 100,
|
|
71
|
+
enabled: !!productId,
|
|
72
|
+
});
|
|
73
|
+
const mutations = useProductTranslationMutation();
|
|
74
|
+
const [drafts, setDrafts] = useState([]);
|
|
75
|
+
const seededKey = useRef(null);
|
|
76
|
+
const existingRef = useRef([]);
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
const key = productId ?? "__new__";
|
|
79
|
+
if (productId && query.isPending)
|
|
80
|
+
return;
|
|
81
|
+
if (seededKey.current === key)
|
|
82
|
+
return;
|
|
83
|
+
const records = query.data?.data ?? [];
|
|
84
|
+
existingRef.current = records;
|
|
85
|
+
setDrafts(records.map(recordToDraft));
|
|
86
|
+
seededKey.current = key;
|
|
87
|
+
}, [productId, query.isPending, query.data]);
|
|
88
|
+
const setFieldValue = useCallback((languageTag, field, value) => {
|
|
89
|
+
setDrafts((prev) => {
|
|
90
|
+
if (prev.some((draft) => draft.languageTag === languageTag)) {
|
|
91
|
+
return prev.map((draft) => draft.languageTag === languageTag ? { ...draft, [field]: value } : draft);
|
|
92
|
+
}
|
|
93
|
+
return [...prev, { ...emptyDraft(languageTag), [field]: value }];
|
|
94
|
+
});
|
|
95
|
+
}, []);
|
|
96
|
+
const addLanguage = useCallback((languageTag) => {
|
|
97
|
+
setDrafts((prev) => prev.some((draft) => draft.languageTag === languageTag)
|
|
98
|
+
? prev
|
|
99
|
+
: [...prev, emptyDraft(languageTag)]);
|
|
100
|
+
}, []);
|
|
101
|
+
const removeLanguage = useCallback((languageTag) => {
|
|
102
|
+
setDrafts((prev) => prev.filter((draft) => draft.languageTag !== languageTag));
|
|
103
|
+
}, []);
|
|
104
|
+
const persist = useCallback(async (resolvedProductId, options) => {
|
|
105
|
+
const { defaultLanguageTag, baseName, baseDescription } = options;
|
|
106
|
+
const original = existingRef.current;
|
|
107
|
+
const currentLanguages = new Set(drafts.map((draft) => draft.languageTag));
|
|
108
|
+
const deletes = original
|
|
109
|
+
.filter((record) => !currentLanguages.has(record.languageTag))
|
|
110
|
+
.map((record) => mutations.remove.mutateAsync({
|
|
111
|
+
productId: resolvedProductId,
|
|
112
|
+
translationId: record.id,
|
|
113
|
+
}));
|
|
114
|
+
const upserts = drafts.map((draft) => {
|
|
115
|
+
const isDefault = draft.languageTag === defaultLanguageTag;
|
|
116
|
+
// The default-language row mirrors the base columns so public serving
|
|
117
|
+
// (which prefers translations) stays consistent with what's edited.
|
|
118
|
+
// When base is empty we keep the row's own value rather than wiping it —
|
|
119
|
+
// legacy products often have empty base columns with content only here.
|
|
120
|
+
const name = isDefault ? baseName : draft.name.trim() || baseName;
|
|
121
|
+
const description = isDefault
|
|
122
|
+
? richTextHasContent(baseDescription)
|
|
123
|
+
? baseDescription
|
|
124
|
+
: richTextHasContent(draft.description)
|
|
125
|
+
? draft.description
|
|
126
|
+
: null
|
|
127
|
+
: richTextHasContent(draft.description)
|
|
128
|
+
? draft.description
|
|
129
|
+
: null;
|
|
130
|
+
const slug = draft.slug.trim() ? draft.slug.trim() : null;
|
|
131
|
+
if (draft.id) {
|
|
132
|
+
return mutations.update.mutateAsync({
|
|
133
|
+
productId: resolvedProductId,
|
|
134
|
+
translationId: draft.id,
|
|
135
|
+
input: { name, description, slug },
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
// A brand-new row is only worth creating once it carries content.
|
|
139
|
+
const isEmpty = isDefault
|
|
140
|
+
? !slug
|
|
141
|
+
: !draft.name.trim() && !richTextHasContent(draft.description) && !slug;
|
|
142
|
+
if (isEmpty)
|
|
143
|
+
return Promise.resolve(null);
|
|
144
|
+
return mutations.create.mutateAsync({
|
|
145
|
+
productId: resolvedProductId,
|
|
146
|
+
input: { languageTag: draft.languageTag, name, description, slug },
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
await Promise.all([...deletes, ...upserts]);
|
|
150
|
+
// Force a reseed from the refreshed server state so a second save patches
|
|
151
|
+
// (with real ids) instead of re-creating.
|
|
152
|
+
seededKey.current = null;
|
|
153
|
+
}, [drafts, mutations]);
|
|
154
|
+
return {
|
|
155
|
+
drafts,
|
|
156
|
+
isLoading: !!productId && query.isPending,
|
|
157
|
+
setFieldValue,
|
|
158
|
+
addLanguage,
|
|
159
|
+
removeLanguage,
|
|
160
|
+
persist,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
/** Top-of-sheet switcher: picks which language every translatable field edits. */
|
|
164
|
+
export function ContentLanguageSwitcher({ activeLanguage, defaultLanguageTag, languageTags, messages, onSelect, onAddLanguage, onRemoveLanguage, }) {
|
|
165
|
+
const [addOpen, setAddOpen] = useState(false);
|
|
166
|
+
const otherLanguages = languageTags.filter((tag) => tag !== defaultLanguageTag);
|
|
167
|
+
return (_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx("span", { className: "text-xs font-medium text-muted-foreground", children: messages.editingLanguageLabel }), _jsxs("div", { className: "flex flex-wrap items-center gap-1.5", children: [_jsx(LanguageChip, { active: activeLanguage === defaultLanguageTag, languageTag: defaultLanguageTag, badge: messages.defaultBadge, onSelect: () => onSelect(defaultLanguageTag) }), otherLanguages.map((tag) => (_jsx(LanguageChip, { active: activeLanguage === tag, languageTag: tag, onSelect: () => onSelect(tag), onRemove: () => onRemoveLanguage(tag), removeLabel: messages.translationRemoveLanguage }, tag))), _jsxs(Popover, { open: addOpen, onOpenChange: setAddOpen, children: [_jsx(PopoverTrigger, { render: _jsxs(Button, { type: "button", variant: "outline", size: "sm", className: "h-7 border-dashed", children: [_jsx(Plus, { className: "size-3.5" }), messages.addLanguage] }) }), _jsx(PopoverContent, { align: "start", className: "w-64", children: _jsx(LanguageCombobox, { value: "", exclude: [defaultLanguageTag, ...otherLanguages], placeholder: messages.translationLanguageSearch, emptyLabel: messages.translationLanguageEmpty, onValueChange: (code) => {
|
|
168
|
+
if (code) {
|
|
169
|
+
onAddLanguage(code);
|
|
170
|
+
setAddOpen(false);
|
|
171
|
+
}
|
|
172
|
+
} }) })] })] })] }));
|
|
173
|
+
}
|
|
174
|
+
function LanguageChip({ active, languageTag, badge, onSelect, onRemove, removeLabel, }) {
|
|
175
|
+
return (_jsxs("div", { className: cn("inline-flex items-center gap-1.5 rounded-md border px-2 py-1 text-xs transition-colors", active
|
|
176
|
+
? "border-primary bg-primary/10 text-foreground"
|
|
177
|
+
: "border-input text-muted-foreground hover:bg-accent"), children: [_jsxs("button", { type: "button", onClick: onSelect, className: "inline-flex items-center gap-1.5", children: [_jsx("span", { className: "font-medium", children: languageLabel(languageTag) }), _jsx("span", { className: "font-mono uppercase opacity-70", children: languageTag }), badge ? (_jsx("span", { className: "rounded bg-muted px-1 text-[10px] font-medium uppercase tracking-wide", children: badge })) : null] }), onRemove ? (_jsx("button", { type: "button", onClick: onRemove, "aria-label": removeLabel, className: "text-muted-foreground hover:text-destructive", children: _jsx(X, { className: "size-3" }) })) : null] }));
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* A field bound to the sheet's active language. When that's the default
|
|
181
|
+
* language (and the field has a base column), it edits the base value;
|
|
182
|
+
* otherwise it edits the active language's translation draft. The globe is an
|
|
183
|
+
* informational indicator (green when the field has any non-default translation).
|
|
184
|
+
*/
|
|
185
|
+
export function TranslatableField({ label, type, field, activeLanguage, defaultLanguageTag, base, translations, messages, placeholder, autoFocus, error, }) {
|
|
186
|
+
const usesBase = !!base && activeLanguage === defaultLanguageTag;
|
|
187
|
+
const activeDraft = translations.drafts.find((draft) => draft.languageTag === activeLanguage);
|
|
188
|
+
const defaultDraft = translations.drafts.find((draft) => draft.languageTag === defaultLanguageTag);
|
|
189
|
+
// When editing the default language, show the base value — but fall back to
|
|
190
|
+
// the default-language translation (legacy products keep content only there).
|
|
191
|
+
// Editing writes to the base columns, promoting that content forward.
|
|
192
|
+
const value = usesBase
|
|
193
|
+
? (base?.value ?? "") || (defaultDraft?.[field] ?? "")
|
|
194
|
+
: (activeDraft?.[field] ?? "");
|
|
195
|
+
const handleChange = usesBase
|
|
196
|
+
? (base?.onChange ?? (() => { }))
|
|
197
|
+
: (next) => translations.setFieldValue(activeLanguage, field, next);
|
|
198
|
+
const translatedLanguages = translations.drafts
|
|
199
|
+
.filter((draft) => draft.languageTag !== defaultLanguageTag && fieldHasContent(draft, field))
|
|
200
|
+
.map((draft) => draft.languageTag);
|
|
201
|
+
return (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx(Label, { children: label }), _jsx(TranslationIndicator, { languages: translatedLanguages, messages: messages })] }), type === "richtext" ? (_jsx(RichTextEditor, { value: value, onChange: handleChange, placeholder: placeholder, editorClassName: "max-h-[280px] overflow-y-auto" })) : (_jsx(Input, { value: value, onChange: (event) => handleChange(event.target.value), placeholder: placeholder, autoFocus: autoFocus })), error ? _jsx("p", { className: "text-xs text-destructive", children: error }) : null] }));
|
|
202
|
+
}
|
|
203
|
+
export function TranslationIndicator({ languages: translatedLanguages, messages, }) {
|
|
204
|
+
const isTranslated = translatedLanguages.length > 0;
|
|
205
|
+
return (_jsx(TooltipProvider, { delay: 150, children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { render: _jsx("button", { type: "button", className: "inline-flex cursor-help items-center", children: _jsx(Globe, { className: cn("size-3.5", isTranslated ? "text-emerald-500" : "text-muted-foreground/50") }) }) }), _jsx(TooltipContent, { children: isTranslated
|
|
206
|
+
? `${messages.fieldTranslated}: ${translatedLanguages
|
|
207
|
+
.map((tag) => tag.toUpperCase())
|
|
208
|
+
.join(", ")}`
|
|
209
|
+
: messages.fieldNotTranslated })] }) }));
|
|
210
|
+
}
|
|
211
|
+
export function LanguageCombobox({ value, onValueChange, exclude = [], placeholder, emptyLabel, }) {
|
|
212
|
+
const excludeKey = exclude.join("|");
|
|
213
|
+
const options = useMemo(() => Object.entries(languages)
|
|
214
|
+
.filter(([code]) => !excludeKey.split("|").includes(code))
|
|
215
|
+
.map(([code, name]) => ({ value: code, label: name })), [excludeKey]);
|
|
216
|
+
return (_jsxs(Combobox, { value: value, onValueChange: (next) => onValueChange(next ?? ""), children: [_jsx(ComboboxInput, { placeholder: placeholder, className: "w-full" }), _jsx(ComboboxContent, { children: _jsxs(ComboboxList, { children: [options.map((option) => (_jsxs(ComboboxItem, { value: option.value, children: [_jsx("span", { className: "truncate", children: option.label }), _jsx("span", { className: "font-mono text-xs text-muted-foreground", children: option.value })] }, option.value))), _jsx(ComboboxEmpty, { children: emptyLabel })] }) })] }));
|
|
217
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type OptionUnitData } from "./product-unit-form.js";
|
|
2
|
+
export type { OptionUnitData };
|
|
3
|
+
type UnitDialogProps = {
|
|
4
|
+
open: boolean;
|
|
5
|
+
onOpenChange: (open: boolean) => void;
|
|
6
|
+
optionId: string;
|
|
7
|
+
unit?: OptionUnitData;
|
|
8
|
+
nextSortOrder?: number;
|
|
9
|
+
onSuccess: () => void;
|
|
10
|
+
};
|
|
11
|
+
export declare function UnitDialog({ open, onOpenChange, optionId, unit, nextSortOrder, onSuccess, }: UnitDialogProps): import("react/jsx-runtime").JSX.Element;
|
|
12
|
+
//# sourceMappingURL=product-unit-dialog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"product-unit-dialog.d.ts","sourceRoot":"","sources":["../../../src/components/product-detail/product-unit-dialog.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,cAAc,EAAY,MAAM,wBAAwB,CAAA;AAEtE,YAAY,EAAE,cAAc,EAAE,CAAA;AAE9B,KAAK,eAAe,GAAG;IACrB,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,CAAC,EAAE,cAAc,CAAA;IACrB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,SAAS,EAAE,MAAM,IAAI,CAAA;CACtB,CAAA;AAED,wBAAgB,UAAU,CAAC,EACzB,IAAI,EACJ,YAAY,EACZ,QAAQ,EACR,IAAI,EACJ,aAAa,EACb,SAAS,GACV,EAAE,eAAe,2CAuBjB"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Sheet, SheetBody, SheetContent, SheetHeader, SheetTitle } from "@voyantjs/ui/components";
|
|
3
|
+
import { useProductDetailMessages } from "./host.js";
|
|
4
|
+
import { UnitForm } from "./product-unit-form.js";
|
|
5
|
+
export function UnitDialog({ open, onOpenChange, optionId, unit, nextSortOrder, onSuccess, }) {
|
|
6
|
+
const messages = useProductDetailMessages();
|
|
7
|
+
const unitMessages = messages.products.operations.units;
|
|
8
|
+
const isEditing = !!unit;
|
|
9
|
+
return (_jsx(Sheet, { open: open, onOpenChange: onOpenChange, children: _jsxs(SheetContent, { side: "right", size: "lg", children: [_jsx(SheetHeader, { children: _jsx(SheetTitle, { children: isEditing ? unitMessages.editTitle : unitMessages.newTitle }) }), _jsx(SheetBody, { children: _jsx(UnitForm, { optionId: optionId, unit: unit, nextSortOrder: nextSortOrder, onSuccess: onSuccess, onCancel: () => onOpenChange(false) }) })] }) }));
|
|
10
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export type OptionUnitData = {
|
|
2
|
+
id: string;
|
|
3
|
+
optionId: string;
|
|
4
|
+
name: string;
|
|
5
|
+
code: string | null;
|
|
6
|
+
description: string | null;
|
|
7
|
+
unitType: "person" | "group" | "room" | "vehicle" | "service" | "other";
|
|
8
|
+
minQuantity: number | null;
|
|
9
|
+
maxQuantity: number | null;
|
|
10
|
+
minAge: number | null;
|
|
11
|
+
maxAge: number | null;
|
|
12
|
+
occupancyMin: number | null;
|
|
13
|
+
occupancyMax: number | null;
|
|
14
|
+
isRequired: boolean;
|
|
15
|
+
isHidden: boolean;
|
|
16
|
+
sortOrder: number;
|
|
17
|
+
};
|
|
18
|
+
export interface UnitFormProps {
|
|
19
|
+
optionId: string;
|
|
20
|
+
unit?: OptionUnitData;
|
|
21
|
+
nextSortOrder?: number;
|
|
22
|
+
onSuccess: () => void;
|
|
23
|
+
onCancel?: () => void;
|
|
24
|
+
}
|
|
25
|
+
export declare function UnitForm({ optionId, unit, nextSortOrder, onSuccess, onCancel }: UnitFormProps): import("react/jsx-runtime").JSX.Element;
|
|
26
|
+
//# sourceMappingURL=product-unit-form.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"product-unit-form.d.ts","sourceRoot":"","sources":["../../../src/components/product-detail/product-unit-form.tsx"],"names":[],"mappings":"AA2CA,MAAM,MAAM,cAAc,GAAG;IAC3B,EAAE,EAAE,MAAM,CAAA;IACV,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,QAAQ,EAAE,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAA;IACvE,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,UAAU,EAAE,OAAO,CAAA;IACnB,QAAQ,EAAE,OAAO,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;CAClB,CAAA;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,CAAC,EAAE,cAAc,CAAA;IACrB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,SAAS,EAAE,MAAM,IAAI,CAAA;IACrB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;CACtB;AAqCD,wBAAgB,QAAQ,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,aAAa,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE,aAAa,2CAuL7F"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useOptionUnitMutation } from "@voyantjs/products-react";
|
|
3
|
+
import { Button, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Switch, Textarea, } from "@voyantjs/ui/components";
|
|
4
|
+
import { Loader2 } from "lucide-react";
|
|
5
|
+
import { useEffect } from "react";
|
|
6
|
+
import { useForm } from "react-hook-form";
|
|
7
|
+
import { z } from "zod/v4";
|
|
8
|
+
import { useProductDetailMessages } from "./host.js";
|
|
9
|
+
import { zodResolver } from "./zod-resolver.js";
|
|
10
|
+
const buildUnitFormSchema = (messages) => z.object({
|
|
11
|
+
name: z.string().min(1, messages.validationNameRequired).max(255),
|
|
12
|
+
code: z.string().max(100).optional().nullable(),
|
|
13
|
+
description: z.string().optional().nullable(),
|
|
14
|
+
unitType: z.enum(["person", "group", "room", "vehicle", "service", "other"]),
|
|
15
|
+
minQuantity: z.coerce.number().int().min(0).optional().or(z.literal("")).nullable(),
|
|
16
|
+
maxQuantity: z.coerce.number().int().min(0).optional().or(z.literal("")).nullable(),
|
|
17
|
+
minAge: z.coerce.number().int().min(0).optional().or(z.literal("")).nullable(),
|
|
18
|
+
maxAge: z.coerce.number().int().min(0).optional().or(z.literal("")).nullable(),
|
|
19
|
+
occupancyMin: z.coerce.number().int().min(0).optional().or(z.literal("")).nullable(),
|
|
20
|
+
occupancyMax: z.coerce.number().int().min(0).optional().or(z.literal("")).nullable(),
|
|
21
|
+
isRequired: z.boolean(),
|
|
22
|
+
isHidden: z.boolean(),
|
|
23
|
+
sortOrder: z.coerce.number().int(),
|
|
24
|
+
});
|
|
25
|
+
function initialValues(unit, nextSortOrder) {
|
|
26
|
+
if (unit) {
|
|
27
|
+
return {
|
|
28
|
+
name: unit.name,
|
|
29
|
+
code: unit.code ?? "",
|
|
30
|
+
description: unit.description ?? "",
|
|
31
|
+
unitType: unit.unitType,
|
|
32
|
+
minQuantity: unit.minQuantity ?? "",
|
|
33
|
+
maxQuantity: unit.maxQuantity ?? "",
|
|
34
|
+
minAge: unit.minAge ?? "",
|
|
35
|
+
maxAge: unit.maxAge ?? "",
|
|
36
|
+
occupancyMin: unit.occupancyMin ?? "",
|
|
37
|
+
occupancyMax: unit.occupancyMax ?? "",
|
|
38
|
+
isRequired: unit.isRequired,
|
|
39
|
+
isHidden: unit.isHidden,
|
|
40
|
+
sortOrder: unit.sortOrder,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
name: "",
|
|
45
|
+
code: "",
|
|
46
|
+
description: "",
|
|
47
|
+
unitType: "person",
|
|
48
|
+
minQuantity: "",
|
|
49
|
+
maxQuantity: "",
|
|
50
|
+
minAge: "",
|
|
51
|
+
maxAge: "",
|
|
52
|
+
occupancyMin: "",
|
|
53
|
+
occupancyMax: "",
|
|
54
|
+
isRequired: false,
|
|
55
|
+
isHidden: false,
|
|
56
|
+
sortOrder: nextSortOrder ?? 0,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
export function UnitForm({ optionId, unit, nextSortOrder, onSuccess, onCancel }) {
|
|
60
|
+
const messages = useProductDetailMessages();
|
|
61
|
+
const productMessages = messages.products.core;
|
|
62
|
+
const unitMessages = messages.products.operations.units;
|
|
63
|
+
const isEditing = !!unit;
|
|
64
|
+
const { create, update } = useOptionUnitMutation();
|
|
65
|
+
const unitFormSchema = buildUnitFormSchema(unitMessages);
|
|
66
|
+
const unitTypes = [
|
|
67
|
+
{ value: "person", label: unitMessages.typePerson },
|
|
68
|
+
{ value: "group", label: unitMessages.typeGroup },
|
|
69
|
+
{ value: "room", label: unitMessages.typeRoom },
|
|
70
|
+
{ value: "vehicle", label: unitMessages.typeVehicle },
|
|
71
|
+
{ value: "service", label: unitMessages.typeService },
|
|
72
|
+
{ value: "other", label: unitMessages.typeOther },
|
|
73
|
+
];
|
|
74
|
+
const form = useForm({
|
|
75
|
+
resolver: zodResolver(unitFormSchema),
|
|
76
|
+
defaultValues: initialValues(unit, nextSortOrder),
|
|
77
|
+
});
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
form.reset(initialValues(unit, nextSortOrder));
|
|
80
|
+
}, [unit, nextSortOrder, form]);
|
|
81
|
+
const onSubmit = async (values) => {
|
|
82
|
+
const canHaveAge = values.unitType === "person";
|
|
83
|
+
const canHaveOccupancy = values.unitType === "group" || values.unitType === "room" || values.unitType === "vehicle";
|
|
84
|
+
const payload = {
|
|
85
|
+
name: values.name,
|
|
86
|
+
code: values.code || null,
|
|
87
|
+
description: values.description || null,
|
|
88
|
+
unitType: values.unitType,
|
|
89
|
+
minQuantity: typeof values.minQuantity === "number" ? values.minQuantity : null,
|
|
90
|
+
maxQuantity: typeof values.maxQuantity === "number" ? values.maxQuantity : null,
|
|
91
|
+
minAge: canHaveAge && typeof values.minAge === "number" ? values.minAge : null,
|
|
92
|
+
maxAge: canHaveAge && typeof values.maxAge === "number" ? values.maxAge : null,
|
|
93
|
+
occupancyMin: canHaveOccupancy && typeof values.occupancyMin === "number" ? values.occupancyMin : null,
|
|
94
|
+
occupancyMax: canHaveOccupancy && typeof values.occupancyMax === "number" ? values.occupancyMax : null,
|
|
95
|
+
isRequired: values.isRequired,
|
|
96
|
+
isHidden: values.isHidden,
|
|
97
|
+
sortOrder: values.sortOrder,
|
|
98
|
+
};
|
|
99
|
+
if (isEditing) {
|
|
100
|
+
await update.mutateAsync({ id: unit.id, input: payload });
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
await create.mutateAsync({ optionId, ...payload });
|
|
104
|
+
}
|
|
105
|
+
onSuccess();
|
|
106
|
+
};
|
|
107
|
+
const unitType = form.watch("unitType");
|
|
108
|
+
return (_jsxs("form", { onSubmit: form.handleSubmit(onSubmit), className: "flex flex-1 flex-col gap-4 overflow-hidden", children: [_jsxs("div", { 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: unitMessages.nameLabel }), _jsx(Input, { ...form.register("name"), placeholder: unitMessages.namePlaceholder }), form.formState.errors.name && (_jsx("p", { className: "text-xs text-destructive", children: form.formState.errors.name.message }))] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: unitMessages.codeLabel }), _jsx(Input, { ...form.register("code"), placeholder: unitMessages.codePlaceholder })] })] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: unitMessages.typeLabel }), _jsxs(Select, { value: unitType, onValueChange: (v) => form.setValue("unitType", v), items: unitTypes, children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: unitTypes.map((t) => (_jsx(SelectItem, { value: t.value, children: t.label }, t.value))) })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: unitMessages.sortOrderLabel }), _jsx(Input, { ...form.register("sortOrder"), type: "number" })] })] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: unitMessages.minQuantityLabel }), _jsx(Input, { ...form.register("minQuantity"), type: "number", min: "0" })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: unitMessages.maxQuantityLabel }), _jsx(Input, { ...form.register("maxQuantity"), type: "number", min: "0" })] })] }), unitType === "person" && (_jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: unitMessages.minAgeLabel }), _jsx(Input, { ...form.register("minAge"), type: "number", min: "0" })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: unitMessages.maxAgeLabel }), _jsx(Input, { ...form.register("maxAge"), type: "number", min: "0" })] })] })), (unitType === "room" || unitType === "vehicle" || unitType === "group") && (_jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: unitMessages.occupancyMinLabel }), _jsx(Input, { ...form.register("occupancyMin"), type: "number", min: "0" })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: unitMessages.occupancyMaxLabel }), _jsx(Input, { ...form.register("occupancyMax"), type: "number", min: "0" })] })] })), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: unitMessages.descriptionLabel }), _jsx(Textarea, { ...form.register("description") })] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Switch, { checked: form.watch("isRequired"), onCheckedChange: (v) => form.setValue("isRequired", v) }), _jsx(Label, { children: unitMessages.requiredLabel })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Switch, { checked: form.watch("isHidden"), onCheckedChange: (v) => form.setValue("isHidden", v) }), _jsx(Label, { children: unitMessages.hiddenLabel })] })] })] }), _jsxs("div", { className: "flex items-center justify-end gap-2", children: [onCancel ? (_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: onCancel, children: productMessages.cancel })) : null, _jsxs(Button, { type: "submit", size: "sm", disabled: form.formState.isSubmitting || create.isPending || update.isPending, children: [(form.formState.isSubmitting || create.isPending || update.isPending) && (_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" })), isEditing ? productMessages.saveChanges : unitMessages.create] })] })] }));
|
|
109
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { OptionUnitData } from "./product-unit-form.js";
|
|
2
|
+
import { type OptionUnitPriceRuleData } from "./product-unit-price-rule-form.js";
|
|
3
|
+
export type { OptionUnitPriceRuleData };
|
|
4
|
+
type UnitPriceRuleDialogProps = {
|
|
5
|
+
open: boolean;
|
|
6
|
+
onOpenChange: (open: boolean) => void;
|
|
7
|
+
optionPriceRuleId: string;
|
|
8
|
+
optionId: string;
|
|
9
|
+
units: OptionUnitData[];
|
|
10
|
+
preselectedUnitId?: string;
|
|
11
|
+
preselectedCategoryId?: string | null;
|
|
12
|
+
cell?: OptionUnitPriceRuleData;
|
|
13
|
+
onSuccess: () => void;
|
|
14
|
+
};
|
|
15
|
+
export declare function UnitPriceRuleDialog({ open, onOpenChange, optionPriceRuleId, optionId, units, preselectedUnitId, preselectedCategoryId, cell, onSuccess, }: UnitPriceRuleDialogProps): import("react/jsx-runtime").JSX.Element;
|
|
16
|
+
//# sourceMappingURL=product-unit-price-rule-dialog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"product-unit-price-rule-dialog.d.ts","sourceRoot":"","sources":["../../../src/components/product-detail/product-unit-price-rule-dialog.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAC5D,OAAO,EAAE,KAAK,uBAAuB,EAAqB,MAAM,mCAAmC,CAAA;AAEnG,YAAY,EAAE,uBAAuB,EAAE,CAAA;AAEvC,KAAK,wBAAwB,GAAG;IAC9B,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,iBAAiB,EAAE,MAAM,CAAA;IACzB,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,cAAc,EAAE,CAAA;IACvB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,qBAAqB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACrC,IAAI,CAAC,EAAE,uBAAuB,CAAA;IAC9B,SAAS,EAAE,MAAM,IAAI,CAAA;CACtB,CAAA;AAED,wBAAgB,mBAAmB,CAAC,EAClC,IAAI,EACJ,YAAY,EACZ,iBAAiB,EACjB,QAAQ,EACR,KAAK,EACL,iBAAiB,EACjB,qBAAqB,EACrB,IAAI,EACJ,SAAS,GACV,EAAE,wBAAwB,2CA4B1B"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Sheet, SheetBody, SheetContent, SheetHeader, SheetTitle } from "@voyantjs/ui/components";
|
|
3
|
+
import { useProductDetailMessages } from "./host.js";
|
|
4
|
+
import { UnitPriceRuleForm } from "./product-unit-price-rule-form.js";
|
|
5
|
+
export function UnitPriceRuleDialog({ open, onOpenChange, optionPriceRuleId, optionId, units, preselectedUnitId, preselectedCategoryId, cell, onSuccess, }) {
|
|
6
|
+
const messages = useProductDetailMessages();
|
|
7
|
+
const unitPriceMessages = messages.products.operations.unitPrices;
|
|
8
|
+
const isEditing = !!cell;
|
|
9
|
+
return (_jsx(Sheet, { open: open, onOpenChange: onOpenChange, children: _jsxs(SheetContent, { side: "right", children: [_jsx(SheetHeader, { children: _jsx(SheetTitle, { children: isEditing ? unitPriceMessages.editTitle : unitPriceMessages.newTitle }) }), _jsx(SheetBody, { children: _jsx(UnitPriceRuleForm, { optionPriceRuleId: optionPriceRuleId, optionId: optionId, units: units, preselectedUnitId: preselectedUnitId, preselectedCategoryId: preselectedCategoryId, cell: cell, onSuccess: onSuccess, onCancel: () => onOpenChange(false) }) })] }) }));
|
|
10
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { OptionUnitData } from "./product-unit-form.js";
|
|
2
|
+
export type OptionUnitPriceRuleData = {
|
|
3
|
+
id: string;
|
|
4
|
+
optionPriceRuleId: string;
|
|
5
|
+
optionId: string;
|
|
6
|
+
unitId: string;
|
|
7
|
+
pricingCategoryId: string | null;
|
|
8
|
+
pricingMode: "per_unit" | "per_person" | "per_booking" | "included" | "free" | "on_request";
|
|
9
|
+
sellAmountCents: number | null;
|
|
10
|
+
costAmountCents: number | null;
|
|
11
|
+
minQuantity: number | null;
|
|
12
|
+
maxQuantity: number | null;
|
|
13
|
+
sortOrder: number;
|
|
14
|
+
active: boolean;
|
|
15
|
+
notes: string | null;
|
|
16
|
+
};
|
|
17
|
+
export interface UnitPriceRuleFormProps {
|
|
18
|
+
optionPriceRuleId: string;
|
|
19
|
+
optionId: string;
|
|
20
|
+
units: OptionUnitData[];
|
|
21
|
+
preselectedUnitId?: string;
|
|
22
|
+
preselectedCategoryId?: string | null;
|
|
23
|
+
cell?: OptionUnitPriceRuleData;
|
|
24
|
+
onSuccess: () => void;
|
|
25
|
+
onCancel?: () => void;
|
|
26
|
+
}
|
|
27
|
+
export declare function UnitPriceRuleForm({ optionPriceRuleId, optionId, units, preselectedUnitId, preselectedCategoryId, cell, onSuccess, onCancel, }: UnitPriceRuleFormProps): import("react/jsx-runtime").JSX.Element;
|
|
28
|
+
//# sourceMappingURL=product-unit-price-rule-form.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"product-unit-price-rule-form.d.ts","sourceRoot":"","sources":["../../../src/components/product-detail/product-unit-price-rule-form.tsx"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAsD5D,MAAM,MAAM,uBAAuB,GAAG;IACpC,EAAE,EAAE,MAAM,CAAA;IACV,iBAAiB,EAAE,MAAM,CAAA;IACzB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAA;IAChC,WAAW,EAAE,UAAU,GAAG,YAAY,GAAG,aAAa,GAAG,UAAU,GAAG,MAAM,GAAG,YAAY,CAAA;IAC3F,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,OAAO,CAAA;IACf,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CACrB,CAAA;AAED,MAAM,WAAW,sBAAsB;IACrC,iBAAiB,EAAE,MAAM,CAAA;IACzB,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,cAAc,EAAE,CAAA;IACvB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,qBAAqB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACrC,IAAI,CAAC,EAAE,uBAAuB,CAAA;IAC9B,SAAS,EAAE,MAAM,IAAI,CAAA;IACrB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;CACtB;AAmCD,wBAAgB,iBAAiB,CAAC,EAChC,iBAAiB,EACjB,QAAQ,EACR,KAAK,EACL,iBAAiB,EACjB,qBAAqB,EACrB,IAAI,EACJ,SAAS,EACT,QAAQ,GACT,EAAE,sBAAsB,2CAyKxB"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useOptionUnitPriceRuleMutation } from "@voyantjs/pricing-react";
|
|
3
|
+
import { PricingCategoryCombobox } from "@voyantjs/pricing-ui/components/pricing-category-combobox";
|
|
4
|
+
import { Button, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Switch, Textarea, } from "@voyantjs/ui/components";
|
|
5
|
+
import { Loader2 } from "lucide-react";
|
|
6
|
+
import { useEffect } from "react";
|
|
7
|
+
import { useForm } from "react-hook-form";
|
|
8
|
+
import { z } from "zod/v4";
|
|
9
|
+
import { useProductDetailMessages } from "./host.js";
|
|
10
|
+
import { zodResolver } from "./zod-resolver.js";
|
|
11
|
+
function getUnitTypeLabel(type, messages) {
|
|
12
|
+
switch (type) {
|
|
13
|
+
case "person":
|
|
14
|
+
return messages.typePerson;
|
|
15
|
+
case "group":
|
|
16
|
+
return messages.typeGroup;
|
|
17
|
+
case "room":
|
|
18
|
+
return messages.typeRoom;
|
|
19
|
+
case "vehicle":
|
|
20
|
+
return messages.typeVehicle;
|
|
21
|
+
case "service":
|
|
22
|
+
return messages.typeService;
|
|
23
|
+
case "other":
|
|
24
|
+
return messages.typeOther;
|
|
25
|
+
default:
|
|
26
|
+
return type;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const buildCellFormSchema = (messages) => z.object({
|
|
30
|
+
unitId: z.string().min(1, messages.validationUnitRequired),
|
|
31
|
+
pricingCategoryId: z.string().optional().nullable(),
|
|
32
|
+
pricingMode: z.enum([
|
|
33
|
+
"per_unit",
|
|
34
|
+
"per_person",
|
|
35
|
+
"per_booking",
|
|
36
|
+
"included",
|
|
37
|
+
"free",
|
|
38
|
+
"on_request",
|
|
39
|
+
]),
|
|
40
|
+
sell: z.coerce.number().min(0),
|
|
41
|
+
cost: z.coerce.number().min(0),
|
|
42
|
+
minQuantity: z.coerce.number().int().min(0).optional().or(z.literal("")).nullable(),
|
|
43
|
+
maxQuantity: z.coerce.number().int().min(0).optional().or(z.literal("")).nullable(),
|
|
44
|
+
sortOrder: z.coerce.number().int(),
|
|
45
|
+
active: z.boolean(),
|
|
46
|
+
notes: z.string().optional().nullable(),
|
|
47
|
+
});
|
|
48
|
+
function initialValues(cell, preselectedUnitId, preselectedCategoryId) {
|
|
49
|
+
if (cell) {
|
|
50
|
+
return {
|
|
51
|
+
unitId: cell.unitId,
|
|
52
|
+
pricingCategoryId: cell.pricingCategoryId ?? "",
|
|
53
|
+
pricingMode: cell.pricingMode,
|
|
54
|
+
sell: (cell.sellAmountCents ?? 0) / 100,
|
|
55
|
+
cost: (cell.costAmountCents ?? 0) / 100,
|
|
56
|
+
minQuantity: cell.minQuantity ?? "",
|
|
57
|
+
maxQuantity: cell.maxQuantity ?? "",
|
|
58
|
+
sortOrder: cell.sortOrder,
|
|
59
|
+
active: cell.active,
|
|
60
|
+
notes: cell.notes ?? "",
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
unitId: preselectedUnitId ?? "",
|
|
65
|
+
pricingCategoryId: preselectedCategoryId ?? "",
|
|
66
|
+
pricingMode: "per_person",
|
|
67
|
+
sell: 0,
|
|
68
|
+
cost: 0,
|
|
69
|
+
minQuantity: "",
|
|
70
|
+
maxQuantity: "",
|
|
71
|
+
sortOrder: 0,
|
|
72
|
+
active: true,
|
|
73
|
+
notes: "",
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
export function UnitPriceRuleForm({ optionPriceRuleId, optionId, units, preselectedUnitId, preselectedCategoryId, cell, onSuccess, onCancel, }) {
|
|
77
|
+
const messages = useProductDetailMessages();
|
|
78
|
+
const productMessages = messages.products.core;
|
|
79
|
+
const unitPriceMessages = messages.products.operations.unitPrices;
|
|
80
|
+
const unitMessages = messages.products.operations.units;
|
|
81
|
+
const isEditing = !!cell;
|
|
82
|
+
const { create, update } = useOptionUnitPriceRuleMutation();
|
|
83
|
+
const cellFormSchema = buildCellFormSchema(unitPriceMessages);
|
|
84
|
+
const pricingModes = [
|
|
85
|
+
{ value: "per_unit", label: unitPriceMessages.pricingModePerUnit },
|
|
86
|
+
{ value: "per_person", label: unitPriceMessages.pricingModePerPerson },
|
|
87
|
+
{ value: "per_booking", label: unitPriceMessages.pricingModePerBooking },
|
|
88
|
+
{ value: "included", label: unitPriceMessages.pricingModeIncluded },
|
|
89
|
+
{ value: "free", label: unitPriceMessages.pricingModeFree },
|
|
90
|
+
{ value: "on_request", label: unitPriceMessages.pricingModeOnRequest },
|
|
91
|
+
];
|
|
92
|
+
const form = useForm({
|
|
93
|
+
resolver: zodResolver(cellFormSchema),
|
|
94
|
+
defaultValues: initialValues(cell, preselectedUnitId, preselectedCategoryId),
|
|
95
|
+
});
|
|
96
|
+
useEffect(() => {
|
|
97
|
+
form.reset(initialValues(cell, preselectedUnitId, preselectedCategoryId));
|
|
98
|
+
}, [cell, preselectedUnitId, preselectedCategoryId, form]);
|
|
99
|
+
const onSubmit = async (values) => {
|
|
100
|
+
const payload = {
|
|
101
|
+
optionPriceRuleId,
|
|
102
|
+
optionId,
|
|
103
|
+
unitId: values.unitId,
|
|
104
|
+
pricingCategoryId: values.pricingCategoryId || null,
|
|
105
|
+
pricingMode: values.pricingMode,
|
|
106
|
+
sellAmountCents: Math.round(values.sell * 100),
|
|
107
|
+
costAmountCents: Math.round(values.cost * 100),
|
|
108
|
+
minQuantity: typeof values.minQuantity === "number" ? values.minQuantity : null,
|
|
109
|
+
maxQuantity: typeof values.maxQuantity === "number" ? values.maxQuantity : null,
|
|
110
|
+
sortOrder: values.sortOrder,
|
|
111
|
+
active: values.active,
|
|
112
|
+
notes: values.notes || null,
|
|
113
|
+
};
|
|
114
|
+
if (isEditing) {
|
|
115
|
+
await update.mutateAsync({ id: cell.id, input: payload });
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
await create.mutateAsync(payload);
|
|
119
|
+
}
|
|
120
|
+
onSuccess();
|
|
121
|
+
};
|
|
122
|
+
return (_jsxs("form", { onSubmit: form.handleSubmit(onSubmit), className: "flex flex-1 flex-col gap-4 overflow-hidden", children: [_jsxs("div", { 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: unitPriceMessages.unitLabel }), _jsxs(Select, { value: form.watch("unitId") || undefined, onValueChange: (v) => form.setValue("unitId", v ?? "", { shouldValidate: true }), items: units.map((u) => ({
|
|
123
|
+
value: u.id,
|
|
124
|
+
label: `${u.name} (${getUnitTypeLabel(u.unitType, unitMessages)})`,
|
|
125
|
+
})), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, { placeholder: unitPriceMessages.unitPlaceholder }) }), _jsx(SelectContent, { children: units.map((u) => (_jsxs(SelectItem, { value: u.id, children: [u.name, " (", getUnitTypeLabel(u.unitType, unitMessages), ")"] }, u.id))) })] }), form.formState.errors.unitId && (_jsx("p", { className: "text-xs text-destructive", children: form.formState.errors.unitId.message }))] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: unitPriceMessages.categoryLabel }), _jsx(PricingCategoryCombobox, { value: form.watch("pricingCategoryId"), onChange: (value) => form.setValue("pricingCategoryId", value ?? "", { shouldDirty: true }), placeholder: unitPriceMessages.categoryPlaceholder })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: unitPriceMessages.pricingModeLabel }), _jsxs(Select, { value: form.watch("pricingMode"), onValueChange: (v) => form.setValue("pricingMode", v), items: pricingModes, children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: pricingModes.map((m) => (_jsx(SelectItem, { value: m.value, children: m.label }, m.value))) })] })] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: unitPriceMessages.sellLabel }), _jsx(Input, { ...form.register("sell"), type: "number", step: "0.01", min: "0" })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: unitPriceMessages.costLabel }), _jsx(Input, { ...form.register("cost"), 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: unitPriceMessages.minQuantityLabel }), _jsx(Input, { ...form.register("minQuantity"), type: "number", min: "0" })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: unitPriceMessages.maxQuantityLabel }), _jsx(Input, { ...form.register("maxQuantity"), type: "number", min: "0" })] })] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: unitPriceMessages.sortOrderLabel }), _jsx(Input, { ...form.register("sortOrder"), type: "number" })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Switch, { checked: form.watch("active"), onCheckedChange: (v) => form.setValue("active", v) }), _jsx(Label, { children: unitPriceMessages.activeLabel })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: unitPriceMessages.notesLabel }), _jsx(Textarea, { ...form.register("notes") })] })] }), _jsxs("div", { className: "flex items-center justify-end gap-2", children: [onCancel ? (_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: onCancel, children: productMessages.cancel })) : null, _jsxs(Button, { type: "submit", size: "sm", disabled: form.formState.isSubmitting, children: [form.formState.isSubmitting && _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), isEditing ? productMessages.saveChanges : unitPriceMessages.create] })] })] }));
|
|
126
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type TimezoneOption = {
|
|
2
|
+
id: string;
|
|
3
|
+
label: string;
|
|
4
|
+
offset: number;
|
|
5
|
+
};
|
|
6
|
+
export declare const TIMEZONE_OPTIONS: TimezoneOption[];
|
|
7
|
+
export declare const TIMEZONE_IDS: string[];
|
|
8
|
+
export declare function getTimezoneLabel(id: string): string;
|
|
9
|
+
//# sourceMappingURL=timezone-options.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timezone-options.d.ts","sourceRoot":"","sources":["../../../src/components/product-detail/timezone-options.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,cAAc,GAAG;IAC3B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;CACf,CAAA;AAsBD,eAAO,MAAM,gBAAgB,kBAAyB,CAAA;AACtD,eAAO,MAAM,YAAY,UAAoC,CAAA;AAI7D,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAGnD"}
|