diff --git a/src/components/ConfirmModal.tsx b/src/components/ConfirmModal.tsx
new file mode 100644
index 0000000..71f4eda
--- /dev/null
+++ b/src/components/ConfirmModal.tsx
@@ -0,0 +1,100 @@
+import React, { useEffect, useState } from 'react';
+import { X, AlertTriangle, CheckCircle2, Info, Loader2 } from 'lucide-react';
+import { cn } from '../lib/utils';
+
+interface ConfirmModalProps {
+ isOpen: boolean;
+ onConfirm: () => void;
+ onCancel: () => void;
+ title: string;
+ message: string;
+ confirmText?: string;
+ cancelText?: string;
+ type?: 'info' | 'warning' | 'danger';
+ isLoading?: boolean;
+}
+
+export function ConfirmModal({
+ isOpen,
+ onConfirm,
+ onCancel,
+ title,
+ message,
+ confirmText = 'Confirm',
+ cancelText = 'Cancel',
+ type = 'info',
+ isLoading = false
+}: ConfirmModalProps) {
+ const [shouldRender, setShouldRender] = useState(isOpen);
+
+ useEffect(() => {
+ if (isOpen) setShouldRender(true);
+ }, [isOpen]);
+
+ const onAnimationEnd = () => {
+ if (!isOpen) setShouldRender(false);
+ };
+
+ if (!shouldRender) return null;
+
+ const Icon = type === 'danger' || type === 'warning' ? AlertTriangle : Info;
+ const iconColor = type === 'danger' ? 'text-red-500 bg-red-500/10' : type === 'warning' ? 'text-amber-500 bg-amber-500/10' : 'text-blue-500 bg-blue-500/10';
+ const confirmBtnClass = type === 'danger' ? 'bg-red-600 hover:bg-red-700' : type === 'warning' ? 'bg-amber-600 hover:bg-amber-700' : 'bg-blue-600 hover:bg-blue-700';
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/DimensionsView.tsx b/src/components/DimensionsView.tsx
index 1f3446b..4e30724 100644
--- a/src/components/DimensionsView.tsx
+++ b/src/components/DimensionsView.tsx
@@ -2,6 +2,7 @@ import React, { useState, useMemo } from 'react';
import { ExcelRow, COLUMNS } from '../types';
import { AlertTriangle, CheckCircle2, ChevronDown, ChevronRight, Edit2, Package, Boxes, Scale, Loader2, RefreshCw, Layers } from 'lucide-react';
import { cn } from '../lib/utils';
+import { ConfirmModal } from './ConfirmModal';
interface DimensionsViewProps {
data: ExcelRow[];
@@ -27,6 +28,11 @@ export function DimensionsView({ data, headers, onEdit, onSaveRow, onCaptureStat
const [expandedGroups, setExpandedGroups] = useState>(new Set());
const [showOnlyInconsistent, setShowOnlyInconsistent] = useState(true);
const [syncing, setSyncing] = useState<{ key: string, field: string } | null>(null);
+ const [pendingAction, setPendingAction] = useState<{
+ group: DimensionGroup,
+ sourceRow: ExcelRow,
+ fieldType: 'outer' | 'units' | 'moq' | 'all'
+ } | null>(null);
const groups = useMemo(() => {
const groupMap = new Map();
@@ -98,13 +104,21 @@ export function DimensionsView({ data, headers, onEdit, onSaveRow, onCaptureStat
};
const handleSyncField = async (group: DimensionGroup, sourceRow: ExcelRow, fieldType: 'outer' | 'units' | 'moq') => {
- const fieldLabel = fieldType === 'outer' ? 'Outer Box Dimensions' : fieldType === 'units' ? 'Units per Outer' : 'MOQ';
- if (!window.confirm(`Sync ALL products in this group to match "${fieldLabel}" from product ${sourceRow[COLUMNS.ARTICLE_NO]}?`)) {
- return;
- }
+ setPendingAction({ group, sourceRow, fieldType });
+ };
+
+ const handleFullSync = async (group: DimensionGroup, sourceRow: ExcelRow) => {
+ setPendingAction({ group, sourceRow, fieldType: 'all' });
+ };
+
+ const executeSync = async () => {
+ if (!pendingAction) return;
+ const { group, sourceRow, fieldType } = pendingAction;
+ const fieldLabel = fieldType === 'outer' ? 'Outer Box Dimensions' : fieldType === 'units' ? 'Units per Outer' : fieldType === 'moq' ? 'MOQ' : 'Full Packaging Data';
onCaptureState(`Bulk synced ${fieldLabel} in group ${group.innerDims}`);
setSyncing({ key: group.key, field: fieldType });
+ setPendingAction(null);
try {
const outerL = sourceRow[COLUMNS.OUTER_L];
@@ -125,6 +139,12 @@ export function DimensionsView({ data, headers, onEdit, onSaveRow, onCaptureStat
updatedRow[COLUMNS.UNITS_OUTER] = unitsOuter;
} else if (fieldType === 'moq') {
updatedRow[COLUMNS.MOQ] = moq;
+ } else if (fieldType === 'all') {
+ updatedRow[COLUMNS.OUTER_L] = outerL;
+ updatedRow[COLUMNS.OUTER_W] = outerW;
+ updatedRow[COLUMNS.OUTER_H] = outerH;
+ updatedRow[COLUMNS.UNITS_OUTER] = unitsOuter;
+ updatedRow[COLUMNS.MOQ] = moq;
}
await onSaveRow(index, updatedRow);
@@ -134,36 +154,6 @@ export function DimensionsView({ data, headers, onEdit, onSaveRow, onCaptureStat
}
};
- const handleFullSync = async (group: DimensionGroup, sourceRow: ExcelRow) => {
- if (!window.confirm(`Sync ALL Packaging & MOQ data in this group to match product ${sourceRow[COLUMNS.ARTICLE_NO]}?`)) {
- return;
- }
-
- onCaptureState(`Full sync of packaging data in group ${group.innerDims}`);
- setSyncing({ key: group.key, field: 'all' });
-
- try {
- const outerL = sourceRow[COLUMNS.OUTER_L];
- const outerW = sourceRow[COLUMNS.OUTER_W];
- const outerH = sourceRow[COLUMNS.OUTER_H];
- const unitsOuter = sourceRow[COLUMNS.UNITS_OUTER];
- const moq = sourceRow[COLUMNS.MOQ];
-
- for (const { row, index } of group.rows) {
- if (row === sourceRow) continue;
- const updatedRow = [...row];
- updatedRow[COLUMNS.OUTER_L] = outerL;
- updatedRow[COLUMNS.OUTER_W] = outerW;
- updatedRow[COLUMNS.OUTER_H] = outerH;
- updatedRow[COLUMNS.UNITS_OUTER] = unitsOuter;
- updatedRow[COLUMNS.MOQ] = moq;
- await onSaveRow(index, updatedRow);
- }
- } finally {
- setSyncing(null);
- }
- };
-
return (
@@ -363,6 +353,16 @@ export function DimensionsView({ data, headers, onEdit, onSaveRow, onCaptureStat
)}
+
+ setPendingAction(null)}
+ title="Sync Group Data"
+ message={`Are you sure you want to sync ${pendingAction?.fieldType === 'all' ? 'ALL packaging data' : pendingAction?.fieldType} for the whole group using product ${pendingAction?.sourceRow[COLUMNS.ARTICLE_NO]} as the template?`}
+ type="warning"
+ confirmText="Sync Group"
+ />
);
}
diff --git a/src/components/EditPanel.tsx b/src/components/EditPanel.tsx
index 290a42e..08bfe55 100644
--- a/src/components/EditPanel.tsx
+++ b/src/components/EditPanel.tsx
@@ -3,6 +3,7 @@ import { ExcelRow, COLUMNS } from '../types';
import { X, Sparkles, Save, Loader2, Languages, Package } from 'lucide-react';
import { generateGemini } from '../services/gemini';
import { cn } from '../lib/utils';
+import { ConfirmModal } from './ConfirmModal';
interface EditPanelProps {
row: ExcelRow;
@@ -30,6 +31,7 @@ export function EditPanel({ row, rowIndex, onSave, onClose, onCaptureState }: Ed
const [loadingField, setLoadingField] = useState(null);
const [error, setError] = useState(null);
+ const [isConfirmOpen, setIsConfirmOpen] = useState(false);
const isModified = (field: keyof typeof formData) => {
const colMap: Record = {
@@ -101,6 +103,7 @@ export function EditPanel({ row, rowIndex, onSave, onClose, onCaptureState }: Ed
};
const handleSave = () => {
+ setIsConfirmOpen(false);
const hasModifications = Object.keys(formData).some(k => isModified(k as keyof typeof formData));
if (hasModifications) {
onCaptureState(`Updated product ${row[COLUMNS.ARTICLE_NO]}`);
@@ -251,13 +254,23 @@ export function EditPanel({ row, rowIndex, onSave, onClose, onCaptureState }: Ed
Cancel
+
+ setIsConfirmOpen(false)}
+ title="Confirm Changes"
+ message={`Are you sure you want to save the modifications for product ${row[COLUMNS.ARTICLE_NO]}?`}
+ type="info"
+ confirmText="Save changes"
+ />
>
);