diff --git a/frontend/lib/registry/components/universal-form-modal/UniversalFormModalComponent.tsx b/frontend/lib/registry/components/universal-form-modal/UniversalFormModalComponent.tsx index 3e043331..57c50c58 100644 --- a/frontend/lib/registry/components/universal-form-modal/UniversalFormModalComponent.tsx +++ b/frontend/lib/registry/components/universal-form-modal/UniversalFormModalComponent.tsx @@ -19,7 +19,9 @@ import { AlertDialogTitle, } from "@/components/ui/alert-dialog"; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"; -import { ChevronDown, ChevronUp, ChevronRight, Plus, Trash2, Loader2 } from "lucide-react"; +import { ChevronDown, ChevronUp, ChevronRight, Plus, Trash2, Loader2, Check, ChevronsUpDown } from "lucide-react"; +import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; +import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command"; import { toast } from "sonner"; import { cn } from "@/lib/utils"; import { apiClient } from "@/lib/api/client"; @@ -42,6 +44,7 @@ import { TableSectionRenderer } from "./TableSectionRenderer"; /** * ๐Ÿ”— ์—ฐ์‡„ ๋“œ๋กญ๋‹ค์šด Select ํ•„๋“œ ์ปดํฌ๋„ŒํŠธ + * allowCustomInput์ด true์ด๋ฉด Combobox ํ˜•ํƒœ๋กœ ์ง์ ‘ ์ž…๋ ฅ ๊ฐ€๋Šฅ */ interface CascadingSelectFieldProps { fieldId: string; @@ -51,6 +54,7 @@ interface CascadingSelectFieldProps { onChange: (value: string) => void; placeholder?: string; disabled?: boolean; + allowCustomInput?: boolean; } const CascadingSelectField: React.FC = ({ @@ -61,12 +65,20 @@ const CascadingSelectField: React.FC = ({ onChange, placeholder, disabled, + allowCustomInput = false, }) => { + const [open, setOpen] = useState(false); + const [inputValue, setInputValue] = useState(value || ""); const { options, loading } = useCascadingDropdown({ config, parentValue, }); + // value๊ฐ€ ์™ธ๋ถ€์—์„œ ๋ณ€๊ฒฝ๋˜๋ฉด inputValue๋„ ๋™๊ธฐํ™” + useEffect(() => { + setInputValue(value || ""); + }, [value]); + const getPlaceholder = () => { if (!parentValue) { return config.emptyParentMessage || "์ƒ์œ„ ํ•ญ๋ชฉ์„ ๋จผ์ € ์„ ํƒํ•˜์„ธ์š”"; @@ -82,9 +94,82 @@ const CascadingSelectField: React.FC = ({ const isDisabled = disabled || !parentValue || loading; + // Combobox ํ˜•ํƒœ (์ง์ ‘ ์ž…๋ ฅ ํ—ˆ์šฉ) + if (allowCustomInput) { + return ( + + +
+ { + setInputValue(e.target.value); + onChange(e.target.value); + }} + placeholder={getPlaceholder()} + disabled={isDisabled} + className="w-full pr-8" + /> + +
+
+ + + + + + {!parentValue + ? config.emptyParentMessage || "์ƒ์œ„ ํ•ญ๋ชฉ์„ ๋จผ์ € ์„ ํƒํ•˜์„ธ์š”" + : config.noOptionsMessage || "์„ ํƒ ๊ฐ€๋Šฅํ•œ ํ•ญ๋ชฉ์ด ์—†์Šต๋‹ˆ๋‹ค"} + + + {options + .filter((option) => option.value && option.value !== "") + .map((option) => ( + { + setInputValue(option.label); + onChange(option.value); + setOpen(false); + }} + > + + {option.label} + + ))} + + + + +
+ ); + } + + // ๊ธฐ๋ณธ Select ํ˜•ํƒœ (๋ชฉ๋ก์—์„œ๋งŒ ์„ ํƒ) return ( { + setInputValue(e.target.value); + onChange(e.target.value); + }} + placeholder={loading ? "๋กœ๋”ฉ ์ค‘..." : placeholder} + disabled={disabled || loading} + className="w-full pr-8" + /> + + + + + + + + ์„ ํƒ ๊ฐ€๋Šฅํ•œ ํ•ญ๋ชฉ์ด ์—†์Šต๋‹ˆ๋‹ค + + {options + .filter((option) => option.value && option.value !== "") + .map((option) => ( + { + setInputValue(option.label); + onChange(option.value); + setOpen(false); + }} + > + + {option.label} + + ))} + + + + + + ); + } + + // ๊ธฐ๋ณธ Select ํ˜•ํƒœ (๋ชฉ๋ก์—์„œ๋งŒ ์„ ํƒ) return ( - updateField({ - selectOptions: { - ...localField.selectOptions, - type: value as "static" | "code", - }, - }) - } + onValueChange={(value) => { + // ํƒ€์ž… ๋ณ€๊ฒฝ ์‹œ ๊ด€๋ จ ์„ค์ • ์ดˆ๊ธฐํ™” + if (value === "cascading") { + updateField({ + selectOptions: { + type: "cascading", + cascading: { + parentField: "", + clearOnParentChange: true, + }, + }, + }); + } else { + updateField({ + selectOptions: { + ...localField.selectOptions, + type: value as "static" | "table" | "code", + cascading: undefined, + }, + }); + } + }} > @@ -372,8 +473,38 @@ export function FieldDetailSettingsModal({ ))} + + {localField.selectOptions?.type === "cascading" + ? "์—ฐ์‡„ ๋“œ๋กญ๋‹ค์šด: ๋ถ€๋ชจ ํ•„๋“œ ์„ ํƒ์— ๋”ฐ๋ผ ์˜ต์…˜์ด ๋™์ ์œผ๋กœ ๋ณ€๊ฒฝ๋ฉ๋‹ˆ๋‹ค" + : "ํ…Œ์ด๋ธ” ์ฐธ์กฐ: DB ํ…Œ์ด๋ธ”์—์„œ ์˜ต์…˜ ๋ชฉ๋ก์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค."} + + {/* ์ง์ ‘ ์ž…๋ ฅ ํ—ˆ์šฉ - ๋ชจ๋“  Select ํƒ€์ž…์— ๊ณตํ†ต ์ ์šฉ */} +
+
+ ์ง์ ‘ ์ž…๋ ฅ ํ—ˆ์šฉ + + ๋ชฉ๋ก ์„ ํƒ + ์ง์ ‘ ํƒ€์ดํ•‘ ๊ฐ€๋Šฅ + +
+ + updateField({ + selectOptions: { + ...localField.selectOptions, + allowCustomInput: checked, + }, + }) + } + /> +
+ + ํ™œ์„ฑํ™” ์‹œ ๋“œ๋กญ๋‹ค์šด ๋ชฉ๋ก์—์„œ ์„ ํƒํ•˜๊ฑฐ๋‚˜, ์ง์ ‘ ๊ฐ’์„ ์ž…๋ ฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + ๋ชฉ๋ก์— ์—†๋Š” ์ƒˆ๋กœ์šด ๊ฐ’๋„ ์ž…๋ ฅ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. + + {localField.selectOptions?.type === "table" && (
ํ…Œ์ด๋ธ” ์ฐธ์กฐ: DB ํ…Œ์ด๋ธ”์—์„œ ์˜ต์…˜ ๋ชฉ๋ก์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. @@ -584,6 +715,472 @@ export function FieldDetailSettingsModal({
)} + + {localField.selectOptions?.type === "cascading" && ( +
+ + ์—ฐ์‡„ ๋“œ๋กญ๋‹ค์šด: ๋ถ€๋ชจ ํ•„๋“œ์˜ ๊ฐ’์— ๋”ฐ๋ผ ์˜ต์…˜์ด ๋™์ ์œผ๋กœ ํ•„ํ„ฐ๋ง๋ฉ๋‹ˆ๋‹ค. +
+ ์˜ˆ: ๊ฑฐ๋ž˜์ฒ˜ ์„ ํƒ โ†’ ํ•ด๋‹น ๊ฑฐ๋ž˜์ฒ˜์˜ ๋‚ฉํ’ˆ์ฒ˜๋งŒ ํ‘œ์‹œ +
+ + {/* ๋ถ€๋ชจ ํ•„๋“œ ์„ ํƒ - ์ฝค๋ณด๋ฐ•์Šค (์„น์…˜๋ณ„ ๊ทธ๋ฃนํ•‘) */} +
+ + {allFieldsWithSections.length > 0 ? ( + + + + + + + + + + ์„ ํƒ ๊ฐ€๋Šฅํ•œ ํ•„๋“œ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. + + {allFieldsWithSections.map((section) => { + // ์ž๊ธฐ ์ž์‹  ์ œ์™ธํ•œ ํ•„๋“œ ๋ชฉ๋ก + const availableFields = section.fields.filter( + (f) => f.columnName !== field.columnName + ); + if (availableFields.length === 0) return null; + + return ( + + {availableFields.map((f) => ( + { + updateField({ + selectOptions: { + ...localField.selectOptions, + cascading: { + ...localField.selectOptions?.cascading, + parentField: f.columnName, + }, + }, + }); + setParentFieldOpen(false); + }} + className="text-xs" + > + +
+ {f.label} + + {f.columnName} ({f.fieldType}) + +
+
+ ))} +
+ ); + })} +
+
+
+
+ ) : ( + + updateField({ + selectOptions: { + ...localField.selectOptions, + cascading: { + ...localField.selectOptions?.cascading, + parentField: e.target.value, + }, + }, + }) + } + placeholder="customer_code" + className="h-7 text-xs mt-1" + /> + )} + + ์ด ๋“œ๋กญ๋‹ค์šด์˜ ์˜ต์…˜์„ ๊ฒฐ์ •ํ•  ๋ถ€๋ชจ ํ•„๋“œ๋ฅผ ์„ ํƒํ•˜์„ธ์š” +
+ ์˜ˆ: ๊ฑฐ๋ž˜์ฒ˜ ์„ ํƒ โ†’ ๋‚ฉํ’ˆ์ฒ˜ ํ•„ํ„ฐ๋ง +
+
+ + {/* ๊ด€๊ณ„ ์ฝ”๋“œ ์„ ํƒ */} +
+ + + + + + + + + + + ๋“ฑ๋ก๋œ ์—ฐ์‡„ ๊ด€๊ณ„๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. + + + {/* ์ง์ ‘ ์„ค์ • ์˜ต์…˜ */} + { + updateField({ + selectOptions: { + ...localField.selectOptions, + cascading: { + ...localField.selectOptions?.cascading, + relationCode: undefined, + }, + }, + }); + setCascadingRelationOpen(false); + }} + className="text-xs" + > + + ์ง์ ‘ ์„ค์ • + + + {cascadingRelations.map((relation) => ( + { + handleRelationCodeSelect(relation.relation_code); + setCascadingRelationOpen(false); + }} + className="text-xs" + > + +
+ {relation.relation_name} + + {relation.parent_table} โ†’ {relation.child_table} + +
+
+ ))} +
+
+
+
+
+ + ๋ฏธ๋ฆฌ ๋“ฑ๋ก๋œ ๊ด€๊ณ„๋ฅผ ์„ ํƒํ•˜๋ฉด ์„ค์ •์ด ์ž๋™์œผ๋กœ ์ฑ„์›Œ์ง‘๋‹ˆ๋‹ค. +
+ ์ง์ ‘ ์„ค์ •์„ ์„ ํƒํ•˜๋ฉด ์•„๋ž˜์—์„œ ์ˆ˜๋™์œผ๋กœ ์ž…๋ ฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +
+
+ + + + {/* ์ƒ์„ธ ์„ค์ • (์ˆ˜์ • ๊ฐ€๋Šฅ) */} +
+
+ + ์ƒ์„ธ ์„ค์ • (์ˆ˜์ • ๊ฐ€๋Šฅ) +
+ +
+ + + ์˜ต์…˜์„ ๊ฐ€์ ธ์˜ฌ ํ…Œ์ด๋ธ” (์˜ˆ: delivery_destination) +
+ +
+ + {selectTableColumns.length > 0 ? ( + + ) : ( + + updateField({ + selectOptions: { + ...localField.selectOptions, + cascading: { + ...localField.selectOptions?.cascading, + parentKeyColumn: e.target.value, + }, + }, + }) + } + placeholder="customer_code" + className="h-7 text-xs mt-1" + /> + )} + ๋ถ€๋ชจ ๊ฐ’๊ณผ ๋งค์นญํ•  ์ปฌ๋Ÿผ (์˜ˆ: customer_code) +
+ +
+ + {selectTableColumns.length > 0 ? ( + + ) : ( + + updateField({ + selectOptions: { + ...localField.selectOptions, + valueColumn: e.target.value, + }, + }) + } + placeholder="destination_code" + className="h-7 text-xs mt-1" + /> + )} + ๋“œ๋กญ๋‹ค์šด value๋กœ ์‚ฌ์šฉํ•  ์ปฌ๋Ÿผ +
+ +
+ + {selectTableColumns.length > 0 ? ( + + ) : ( + + updateField({ + selectOptions: { + ...localField.selectOptions, + labelColumn: e.target.value, + }, + }) + } + placeholder="destination_name" + className="h-7 text-xs mt-1" + /> + )} + ๋“œ๋กญ๋‹ค์šด์— ํ‘œ์‹œํ•  ์ปฌ๋Ÿผ +
+ + + +
+ + + updateField({ + selectOptions: { + ...localField.selectOptions, + cascading: { + ...localField.selectOptions?.cascading, + emptyParentMessage: e.target.value, + }, + }, + }) + } + placeholder="์ƒ์œ„ ํ•ญ๋ชฉ์„ ๋จผ์ € ์„ ํƒํ•˜์„ธ์š”" + className="h-7 text-xs mt-1" + /> +
+ +
+ + + updateField({ + selectOptions: { + ...localField.selectOptions, + cascading: { + ...localField.selectOptions?.cascading, + noOptionsMessage: e.target.value, + }, + }, + }) + } + placeholder="์„ ํƒ ๊ฐ€๋Šฅํ•œ ํ•ญ๋ชฉ์ด ์—†์Šต๋‹ˆ๋‹ค" + className="h-7 text-xs mt-1" + /> +
+ +
+ ๋ถ€๋ชจ ๋ณ€๊ฒฝ ์‹œ ๊ฐ’ ์ดˆ๊ธฐํ™” + + updateField({ + selectOptions: { + ...localField.selectOptions, + cascading: { + ...localField.selectOptions?.cascading, + clearOnParentChange: checked, + }, + }, + }) + } + /> +
+ ๋ถ€๋ชจ ํ•„๋“œ ๊ฐ’์ด ๋ณ€๊ฒฝ๋˜๋ฉด ์ด ํ•„๋“œ์˜ ๊ฐ’์„ ์ž๋™์œผ๋กœ ์ดˆ๊ธฐํ™”ํ•ฉ๋‹ˆ๋‹ค +
+
+ )} )} @@ -919,7 +1516,7 @@ export function FieldDetailSettingsModal({ preview = `${subLabel} - ${mainLabel}`; } else if (format === "name_code" && subCol) { preview = `${mainLabel} (${subLabel})`; - } else if (format !== "name_only" && !subCol) { + } else if (!subCol) { preview = `${mainLabel} (์„œ๋ธŒ ์ปฌ๋Ÿผ์„ ์„ ํƒํ•˜์„ธ์š”)`; } else { preview = mainLabel; diff --git a/frontend/lib/registry/components/universal-form-modal/types.ts b/frontend/lib/registry/components/universal-form-modal/types.ts index c5ecb1cc..a937f5b2 100644 --- a/frontend/lib/registry/components/universal-form-modal/types.ts +++ b/frontend/lib/registry/components/universal-form-modal/types.ts @@ -7,7 +7,7 @@ // Select ์˜ต์…˜ ์„ค์ • export interface SelectOptionConfig { - type?: "static" | "table" | "code"; // ์˜ต์…˜ ํƒ€์ž… (๊ธฐ๋ณธ: static) + type?: "static" | "table" | "code" | "cascading"; // ์˜ต์…˜ ํƒ€์ž… (๊ธฐ๋ณธ: static) // ์ •์  ์˜ต์…˜ staticOptions?: { value: string; label: string }[]; // ํ…Œ์ด๋ธ” ๊ธฐ๋ฐ˜ ์˜ต์…˜ @@ -19,6 +19,24 @@ export interface SelectOptionConfig { // ์นดํ…Œ๊ณ ๋ฆฌ ์ปฌ๋Ÿผ ๊ธฐ๋ฐ˜ ์˜ต์…˜ (table_column_category_values ํ…Œ์ด๋ธ”) // ํ˜•์‹: "tableName.columnName" (์˜ˆ: "sales_order_mng.incoterms") categoryKey?: string; + + // ์—ฐ์‡„ ๋“œ๋กญ๋‹ค์šด ์„ค์ • (type์ด "cascading"์ผ ๋•Œ ์‚ฌ์šฉ) + cascading?: { + parentField?: string; // ๋ถ€๋ชจ ํ•„๋“œ๋ช… (๊ฐ™์€ ํผ ๋‚ด) + relationCode?: string; // ๊ด€๊ณ„ ์ฝ”๋“œ (cascading_relation ํ…Œ์ด๋ธ”) + // ์ง์ ‘ ์„ค์ • ๋˜๋Š” ๊ด€๊ณ„ ์ฝ”๋“œ์—์„œ ๊ฐ€์ ธ์˜จ ๊ฐ’ ์ˆ˜์ • ์‹œ ์‚ฌ์šฉ + sourceTable?: string; // ์˜ต์…˜์„ ์กฐํšŒํ•  ํ…Œ์ด๋ธ” + parentKeyColumn?: string; // ๋ถ€๋ชจ ๊ฐ’๊ณผ ๋งค์นญํ•  ์ปฌ๋Ÿผ + // valueColumn, labelColumn์€ ์ƒ์œ„ ์†์„ฑ ์‚ฌ์šฉ + emptyParentMessage?: string; // ๋ถ€๋ชจ ๋ฏธ์„ ํƒ ์‹œ ๋ฉ”์‹œ์ง€ + noOptionsMessage?: string; // ์˜ต์…˜ ์—†์Œ ๋ฉ”์‹œ์ง€ + clearOnParentChange?: boolean; // ๋ถ€๋ชจ ๋ณ€๊ฒฝ ์‹œ ๊ฐ’ ์ดˆ๊ธฐํ™” (๊ธฐ๋ณธ: true) + }; + + // ์ง์ ‘ ์ž…๋ ฅ ํ—ˆ์šฉ (๋ชจ๋“  Select ํƒ€์ž…์— ๊ณตํ†ต ์ ์šฉ) + // true: Combobox ํ˜•ํƒœ๋กœ ๋ชฉ๋ก ์„ ํƒ + ์ง์ ‘ ์ž…๋ ฅ ๊ฐ€๋Šฅ + // false/undefined: ๊ธฐ๋ณธ Select ํ˜•ํƒœ๋กœ ๋ชฉ๋ก์—์„œ๋งŒ ์„ ํƒ ๊ฐ€๋Šฅ + allowCustomInput?: boolean; } // ์ฑ„๋ฒˆ๊ทœ์น™ ์„ค์ • @@ -873,6 +891,7 @@ export const SELECT_OPTION_TYPE_OPTIONS = [ { value: "static", label: "์ง์ ‘ ์ž…๋ ฅ" }, { value: "table", label: "ํ…Œ์ด๋ธ” ์ฐธ์กฐ" }, { value: "code", label: "๊ณตํ†ต์ฝ”๋“œ" }, + { value: "cascading", label: "์—ฐ์‡„ ๋“œ๋กญ๋‹ค์šด" }, ] as const; // ์—ฐ๋™ ํ•„๋“œ ํ‘œ์‹œ ํ˜•์‹ ์˜ต์…˜ diff --git a/frontend/lib/utils/buttonActions.ts b/frontend/lib/utils/buttonActions.ts index cc82e386..a54850ee 100644 --- a/frontend/lib/utils/buttonActions.ts +++ b/frontend/lib/utils/buttonActions.ts @@ -1971,19 +1971,43 @@ export class ButtonActionExecutor { } }); - console.log("๐Ÿ“ฆ [handleUniversalFormModalTableSectionSave] ๋ฉ”์ธ ํ…Œ์ด๋ธ” ์ €์žฅ ๋ฐ์ดํ„ฐ:", mainRowToSave); + // ๐Ÿ†• ๋ฉ”์ธ ํ…Œ์ด๋ธ” UPDATE/INSERT ํŒ๋‹จ + // - formData.id๊ฐ€ ์žˆ์œผ๋ฉด ํŽธ์ง‘ ๋ชจ๋“œ โ†’ UPDATE + // - formData.id๊ฐ€ ์—†์œผ๋ฉด ์‹ ๊ทœ ๋“ฑ๋ก โ†’ INSERT + const existingMainId = formData.id; + const isMainUpdate = existingMainId !== undefined && existingMainId !== null && existingMainId !== ""; - const mainSaveResult = await DynamicFormApi.saveFormData({ - screenId: screenId!, - tableName: tableName!, - data: mainRowToSave, + console.log("๐Ÿ“ฆ [handleUniversalFormModalTableSectionSave] ๋ฉ”์ธ ํ…Œ์ด๋ธ” ์ €์žฅ ๋ฐ์ดํ„ฐ:", mainRowToSave); + console.log("๐Ÿ“ฆ [handleUniversalFormModalTableSectionSave] UPDATE/INSERT ํŒ๋‹จ:", { + existingMainId, + isMainUpdate, }); + let mainSaveResult: { success: boolean; data?: any; message?: string }; + + if (isMainUpdate) { + // ๐Ÿ”„ ํŽธ์ง‘ ๋ชจ๋“œ: UPDATE ์‹คํ–‰ + console.log("๐Ÿ”„ [handleUniversalFormModalTableSectionSave] ๋ฉ”์ธ ํ…Œ์ด๋ธ” UPDATE ์‹คํ–‰, ID:", existingMainId); + mainSaveResult = await DynamicFormApi.updateFormData(existingMainId, { + tableName: tableName!, + data: mainRowToSave, + }); + mainRecordId = existingMainId; + } else { + // โž• ์‹ ๊ทœ ๋“ฑ๋ก: INSERT ์‹คํ–‰ + console.log("โž• [handleUniversalFormModalTableSectionSave] ๋ฉ”์ธ ํ…Œ์ด๋ธ” INSERT ์‹คํ–‰"); + mainSaveResult = await DynamicFormApi.saveFormData({ + screenId: screenId!, + tableName: tableName!, + data: mainRowToSave, + }); + mainRecordId = mainSaveResult.data?.id || null; + } + if (!mainSaveResult.success) { throw new Error(mainSaveResult.message || "๋ฉ”์ธ ๋ฐ์ดํ„ฐ ์ €์žฅ ์‹คํŒจ"); } - mainRecordId = mainSaveResult.data?.id || null; console.log("โœ… [handleUniversalFormModalTableSectionSave] ๋ฉ”์ธ ํ…Œ์ด๋ธ” ์ €์žฅ ์™„๋ฃŒ, ID:", mainRecordId); } diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 5eaf3702..35411799 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -77,6 +77,7 @@ "react-dom": "19.1.0", "react-hook-form": "^7.62.0", "react-hot-toast": "^2.6.0", + "react-is": "^18.3.1", "react-leaflet": "^5.0.0", "react-resizable-panels": "^3.0.6", "react-webcam": "^7.2.0", @@ -13213,11 +13214,10 @@ } }, "node_modules/react-is": { - "version": "19.2.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.1.tgz", - "integrity": "sha512-L7BnWgRbMwzMAubQcS7sXdPdNLmKlucPlopgAzx7FtYbksWZgEWiuYM5x9T6UqS2Ne0rsgQTq5kY2SGqpzUkYA==", - "license": "MIT", - "peer": true + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" }, "node_modules/react-leaflet": { "version": "5.0.0", @@ -13454,9 +13454,9 @@ } }, "node_modules/recharts": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.3.0.tgz", - "integrity": "sha512-Vi0qmTB0iz1+/Cz9o5B7irVyUjX2ynvEgImbgMt/3sKRREcUM07QiYjS1QpAVrkmVlXqy5gykq4nGWMz9AS4Rg==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.2.1.tgz", + "integrity": "sha512-0JKwHRiFZdmLq/6nmilxEZl3pqb4T+aKkOkOi/ZISRZwfBhVMgInxzlYU9D4KnCH3KINScLy68m/OvMXoYGZUw==", "license": "MIT", "dependencies": { "@reduxjs/toolkit": "1.x.x || 2.x.x", diff --git a/frontend/package.json b/frontend/package.json index ed6c8286..b68d13db 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -85,6 +85,7 @@ "react-dom": "19.1.0", "react-hook-form": "^7.62.0", "react-hot-toast": "^2.6.0", + "react-is": "^18.3.1", "react-leaflet": "^5.0.0", "react-resizable-panels": "^3.0.6", "react-webcam": "^7.2.0",