diff --git a/.qwen/settings.json b/.qwen/settings.json new file mode 100644 index 0000000..f0f69d8 --- /dev/null +++ b/.qwen/settings.json @@ -0,0 +1,14 @@ +{ + "permissions": { + "allow": [ + "Bash(npm run *)", + "Bash(git checkout *)", + "Bash(git merge *)", + "Bash(npx *)", + "Bash(git commit *)", + "Bash(git show *)", + "Bash(diff *)" + ] + }, + "$version": 3 +} \ No newline at end of file diff --git a/.qwen/settings.json.orig b/.qwen/settings.json.orig new file mode 100644 index 0000000..1204668 --- /dev/null +++ b/.qwen/settings.json.orig @@ -0,0 +1,7 @@ +{ + "permissions": { + "allow": [ + "Bash(npm run *)" + ] + } +} \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index ecad282..1928d34 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -6,7 +6,7 @@ import { TopBar } from './components/TopBar'; import { ProductDescriptions } from './components/ProductDescriptions'; import { MatrixView } from './components/MatrixView'; import { EditPanel } from './components/EditPanel'; -import { getAllSyncedRows, saveRowToSupabase } from './lib/supabase'; +import { getAllSyncedRows, saveRowToSupabase, resetAllPendingRows } from './lib/supabase'; import { getStoredSession, signOut, type AuthSession } from './lib/auth'; import { LoginPage } from './components/LoginPage'; import { DimensionsView } from './components/DimensionsView'; @@ -38,6 +38,7 @@ export default function App() { const [editingRowIndex, setEditingRowIndex] = useState(null); const [isLoadingDefault, setIsLoadingDefault] = useState(true); const [defaultLoadError, setDefaultLoadError] = useState(null); + const [rowStatuses, setRowStatuses] = useState>({}); useEffect(() => { const loadDefaultData = async () => { @@ -78,7 +79,13 @@ export default function App() { const processedRows = rawRows.map(row => { const articleNo = String(row[articleNoIdx]); - const finalRow = syncedData[articleNo] || row; + const synced = syncedData[articleNo]; + const finalRow = synced ? synced.data : row; + + // Sync status_check + if (synced && synced.status === 'pending') { + setRowStatuses(prev => ({ ...prev, [articleNo]: 'pending' })); + } // Format numeric/price fields to 2 decimal places return finalRow.map((val, idx) => { @@ -219,6 +226,15 @@ export default function App() { const dateStr = new Date().toISOString().split('T')[0]; XLSX.writeFile(wb, `CRAZE_Products_Updated_${dateStr}.xlsx`); + // 3. Post-export: Reset pending statuses in Supabase + console.log('Resetting pending statuses in Supabase...'); + resetAllPendingRows().then(success => { + if (success) { + console.log('Successfully reset all pending statuses'); + setRowStatuses({}); // Clear local statuses + } + }); + setAppState(prev => ({ ...prev, hasUnsavedChanges: false })); }; @@ -238,6 +254,10 @@ export default function App() { // 2. Persist to Supabase const articleNo = String(updatedRow[COLUMNS.ARTICLE_NO]); console.log(`Saving article ${articleNo} to Supabase...`); + + // Update local status to pending + setRowStatuses(prev => ({ ...prev, [articleNo]: 'pending' })); + const success = await saveRowToSupabase(articleNo, updatedRow); if (success) { @@ -349,10 +369,11 @@ export default function App() { setEditingRowIndex(index)} + rowStatuses={rowStatuses} /> )} {activeModule === 'matrix' && ( - + )} {activeModule === 'dimensions' && ( @@ -362,6 +383,7 @@ export default function App() { onEdit={(index) => setEditingRowIndex(index)} onSaveRow={handleSaveRow} onCaptureState={captureState} + rowStatuses={rowStatuses} /> )} @@ -372,12 +394,14 @@ export default function App() { onSaveRow={handleSaveRow} onCaptureState={captureState} onEdit={(index) => setEditingRowIndex(index)} + rowStatuses={rowStatuses} /> )} {activeModule === 'article_details' && ( setEditingRowIndex(index)} + rowStatuses={rowStatuses} /> )} diff --git a/src/components/ArticleDetails.tsx b/src/components/ArticleDetails.tsx index 9af3a83..41572b0 100644 --- a/src/components/ArticleDetails.tsx +++ b/src/components/ArticleDetails.tsx @@ -7,11 +7,12 @@ import { ColumnFilterPopover } from './ColumnFilterPopover'; interface ArticleDetailsProps { data: ExcelRow[]; onEdit: (index: number) => void; + rowStatuses: Record; } type TabType = 'all' | 'missingDetailsDE' | 'missingDetailsEN' | 'missingAnyDetails' | 'lowStock'; -export function ArticleDetails({ data, onEdit }: ArticleDetailsProps) { +export function ArticleDetails({ data, onEdit, rowStatuses }: ArticleDetailsProps) { const [activeTab, setActiveTab] = useState('all'); const [search, setSearch] = useState(''); const [lineFilter, setLineFilter] = useState(''); @@ -243,8 +244,16 @@ export function ArticleDetails({ data, onEdit }: ArticleDetailsProps) { - {paginatedData.map(({ row, index }) => ( - + {paginatedData.map(({ row, index }) => { + const isPending = rowStatuses[String(row[COLUMNS.ARTICLE_NO])] === 'pending'; + return ( + {row[COLUMNS.ARTICLE_NO]} {row[COLUMNS.ARTICLE_NAME]} @@ -284,7 +293,8 @@ export function ArticleDetails({ data, onEdit }: ArticleDetailsProps) { - ))} + ); + })} {paginatedData.length === 0 && ( diff --git a/src/components/DimensionsView.tsx b/src/components/DimensionsView.tsx index 837b828..dec2816 100644 --- a/src/components/DimensionsView.tsx +++ b/src/components/DimensionsView.tsx @@ -11,6 +11,7 @@ interface DimensionsViewProps { onEdit: (index: number) => void; onSaveRow: (index: number, updatedRow: ExcelRow) => void; onCaptureState: (message: string) => void; + rowStatuses: Record; } interface DimensionGroup { @@ -32,7 +33,7 @@ interface NearDuplicateCluster { maxDiffPct: number; } -export function DimensionsView({ data, headers, onEdit, onSaveRow, onCaptureState }: DimensionsViewProps) { +export function DimensionsView({ data, headers, onEdit, onSaveRow, onCaptureState, rowStatuses }: DimensionsViewProps) { const [expandedGroups, setExpandedGroups] = useState>(new Set()); const [expandedNearDuplicates, setExpandedNearDuplicates] = useState>(new Set()); const [showOnlyInconsistent, setShowOnlyInconsistent] = useState(true); @@ -584,8 +585,16 @@ export function DimensionsView({ data, headers, onEdit, onSaveRow, onCaptureStat - {group.rows.map(({ row, index }) => ( - + {group.rows.map(({ row, index }) => { + const isPending = rowStatuses[String(row[COLUMNS.ARTICLE_NO])] === 'pending'; + return ( +
{row[COLUMNS.ARTICLE_NO]}
{row[COLUMNS.ARTICLE_NAME]}
@@ -661,7 +670,8 @@ export function DimensionsView({ data, headers, onEdit, onSaveRow, onCaptureStat - ))} + ); + })} diff --git a/src/components/MatrixView.tsx b/src/components/MatrixView.tsx index 6e00438..3dcc014 100644 --- a/src/components/MatrixView.tsx +++ b/src/components/MatrixView.tsx @@ -7,9 +7,10 @@ import { ColumnFilterPopover } from './ColumnFilterPopover'; interface MatrixViewProps { data: ExcelRow[]; headers: string[]; + rowStatuses: Record; } -export function MatrixView({ data, headers }: MatrixViewProps) { +export function MatrixView({ data, headers, rowStatuses }: MatrixViewProps) { const [page, setPage] = useState(1); const [pageSize, setPageSize] = useState(25); const [search, setSearch] = useState(''); diff --git a/src/components/PricingView.tsx b/src/components/PricingView.tsx index d9585ef..1c36998 100644 --- a/src/components/PricingView.tsx +++ b/src/components/PricingView.tsx @@ -23,6 +23,7 @@ interface PricingViewProps { onSaveRow: (index: number, updatedRow: ExcelRow) => void; onCaptureState: (message: string) => void; onEdit: (index: number) => void; + rowStatuses: Record; } interface DetectedCol { @@ -45,7 +46,7 @@ function findCol(headers: string[], ...keywords: string[]): number { ); } -export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit }: PricingViewProps) { +export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit, rowStatuses }: PricingViewProps) { const [filterMode, setFilterMode] = useState('all_errors'); const [search, setSearch] = useState(''); const [editingCell, setEditingCell] = useState(null); @@ -454,16 +455,19 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit } {filteredRows.map(({ row, dataIndex, pricingErrors, unitErrors, isCritical }) => { const hasAnyError = pricingErrors.length > 0 || unitErrors.length > 0; + const isPending = rowStatuses[String(row[COLUMNS.ARTICLE_NO])] === 'pending'; return ( 0 - ? 'bg-amber-950/10' - : '' + isPending + ? 'bg-yellow-400/20 border-l-4 border-l-yellow-400' + : isCritical + ? 'bg-red-950/20' + : pricingErrors.length > 0 + ? 'bg-amber-950/10' + : '' )} > {/* Article No */} diff --git a/src/components/ProductDescriptions.tsx b/src/components/ProductDescriptions.tsx index b5f8685..80ea9cd 100644 --- a/src/components/ProductDescriptions.tsx +++ b/src/components/ProductDescriptions.tsx @@ -1,4 +1,4 @@ -import React, { useState, useMemo, useCallback } from 'react'; +import React, { useState, useMemo } from 'react'; import { ExcelRow, COLUMNS } from '../types'; import { Search, Filter, Edit2, ChevronDown, ChevronUp, X } from 'lucide-react'; import { cn } from '../lib/utils'; @@ -7,7 +7,6 @@ import { ColumnFilterPopover } from './ColumnFilterPopover'; interface ProductDescriptionsProps { data: ExcelRow[]; onEdit: (index: number) => void; - rowStatuses: Record; } type TabType = 'all' | 'missingLongDE' | 'missingLongEN' | 'missingLongAny' | 'missingShortDE' | 'missingShortEN' | 'missingShortAny' | 'complete' | 'incomplete'; @@ -15,7 +14,7 @@ type TabType = 'all' | 'missingLongDE' | 'missingLongEN' | 'missingLongAny' | 'm // Description columns that should only have Present/Missing filters const DESCRIPTION_COLUMNS = [COLUMNS.LONG_DE, COLUMNS.LONG_EN, COLUMNS.SHORT_DE, COLUMNS.SHORT_EN]; -export function ProductDescriptions({ data, onEdit, rowStatuses }: ProductDescriptionsProps) { +export function ProductDescriptions({ data, onEdit }: ProductDescriptionsProps) { const [activeTab, setActiveTab] = useState('all'); const [search, setSearch] = useState(''); const [lineFilter, setLineFilter] = useState(''); @@ -308,42 +307,42 @@ export function ProductDescriptions({ data, onEdit, rowStatuses }: ProductDescri {paginatedData.map(({ row, index }) => { const isPending = rowStatuses[String(row[COLUMNS.ARTICLE_NO])] === 'pending'; return ( - - {row[COLUMNS.ARTICLE_NO]} - {row[COLUMNS.ARTICLE_NAME]} - {row[COLUMNS.LINE]} - {row[COLUMNS.LICENSE] || '—'} - - - {row[COLUMNS.CLASSIFICATION] || '—'} - - - - - - - - - - + {row[COLUMNS.ARTICLE_NO]} + {row[COLUMNS.ARTICLE_NAME]} + {row[COLUMNS.LINE]} + {row[COLUMNS.LICENSE] || '—'} + + + {row[COLUMNS.CLASSIFICATION] || '—'} + + + + + + + + + + ); })} {paginatedData.length === 0 && ( diff --git a/src/lib/supabase.ts b/src/lib/supabase.ts index 812b4be..223d2ad 100644 --- a/src/lib/supabase.ts +++ b/src/lib/supabase.ts @@ -10,17 +10,17 @@ export async function saveRowToSupabase(articleNo: string, rowData: ExcelRow) { headers: { 'apikey': SUPABASE_KEY, 'Authorization': `Bearer ${SUPABASE_KEY}`, - 'Content-Type': 'application/json', - 'Prefer': 'resolution=merge-duplicates' + 'Content-Type': 'application/json' }, body: JSON.stringify({ id: articleNo, data: rowData, + status_check: 'pending', updated_at: new Date().toISOString() }) }); - if (response.status === 204 || response.ok) { + if (!response.ok) { // If PATCH didn't find the record, try UPSERT const upsertResponse = await fetch(`${SUPABASE_URL}/rest/v1/products_sync`, { method: 'POST', @@ -33,6 +33,7 @@ export async function saveRowToSupabase(articleNo: string, rowData: ExcelRow) { body: JSON.stringify({ id: articleNo, data: rowData, + status_check: 'pending', updated_at: new Date().toISOString() }) }); @@ -45,7 +46,7 @@ export async function saveRowToSupabase(articleNo: string, rowData: ExcelRow) { } } -export async function getAllSyncedRows(): Promise> { +export async function getAllSyncedRows(): Promise> { try { const response = await fetch(`${SUPABASE_URL}/rest/v1/products_sync`, { headers: { @@ -57,9 +58,12 @@ export async function getAllSyncedRows(): Promise> { if (!response.ok) return {}; const data = await response.json(); - const result: Record = {}; + const result: Record = {}; data.forEach((item: any) => { - result[item.id] = item.data; + result[item.id] = { + data: item.data, + status: item.status_check || 'original' + }; }); return result; } catch (error) { @@ -67,3 +71,24 @@ export async function getAllSyncedRows(): Promise> { return {}; } } + +export async function resetAllPendingRows() { + try { + const response = await fetch(`${SUPABASE_URL}/rest/v1/products_sync?status_check=eq.pending`, { + method: 'PATCH', + headers: { + 'apikey': SUPABASE_KEY, + 'Authorization': `Bearer ${SUPABASE_KEY}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + status_check: 'original', + updated_at: new Date().toISOString() + }) + }); + return response.ok; + } catch (error) { + console.error('Error resetting statuses in Supabase:', error); + return false; + } +}