mirror of
https://github.com/christianvidalwolf-prog/Craze-Data-check.git
synced 2026-08-03 14:55:24 +02:00
feat: add undo button in DimensionsView for pending changes
This commit is contained in:
@@ -15,7 +15,7 @@ npm run clean # Remove dist folder
|
|||||||
npm run lint # TypeScript type check only (tsc --noEmit)
|
npm run lint # TypeScript type check only (tsc --noEmit)
|
||||||
```
|
```
|
||||||
|
|
||||||
Note: This project does not have a separate test framework configured. To add tests, consider installing Vitest or Jest.
|
Note: No test framework configured. To add tests, install Vitest or Jest.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -27,10 +27,8 @@ Note: This project does not have a separate test framework configured. To add te
|
|||||||
- Keep components focused and modular
|
- Keep components focused and modular
|
||||||
- Use meaningful variable and function names
|
- Use meaningful variable and function names
|
||||||
|
|
||||||
### Imports
|
### Imports (order top to bottom)
|
||||||
|
1. React (`react`)
|
||||||
**Order (top to bottom):**
|
|
||||||
1. React imports (`react`)
|
|
||||||
2. External libraries (`lucide-react`, `xlsx`, etc.)
|
2. External libraries (`lucide-react`, `xlsx`, etc.)
|
||||||
3. Internal components (`./components/...`)
|
3. Internal components (`./components/...`)
|
||||||
4. Internal lib/utils (`./lib/...`)
|
4. Internal lib/utils (`./lib/...`)
|
||||||
@@ -44,19 +42,16 @@ import { cn } from '../lib/utils';
|
|||||||
```
|
```
|
||||||
|
|
||||||
### TypeScript Conventions
|
### TypeScript Conventions
|
||||||
|
|
||||||
- Use explicit types for props and function parameters
|
- Use explicit types for props and function parameters
|
||||||
- Use `any` sparingly; prefer union types or interfaces
|
- Use `any` sparingly; prefer union types or interfaces
|
||||||
- Define column indices in a centralized `COLUMNS` object (see `src/types.ts`)
|
- Define column indices in a centralized `COLUMNS` object (see `src/types.ts`)
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// Good
|
|
||||||
interface ProductDescriptionsProps {
|
interface ProductDescriptionsProps {
|
||||||
data: ExcelRow[];
|
data: ExcelRow[];
|
||||||
onEdit: (index: number) => void;
|
onEdit: (index: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Good - centralized constants
|
|
||||||
export const COLUMNS = {
|
export const COLUMNS = {
|
||||||
ARTICLE_NO: 0,
|
ARTICLE_NO: 0,
|
||||||
ARTICLE_NAME: 2,
|
ARTICLE_NAME: 2,
|
||||||
@@ -76,119 +71,58 @@ export const COLUMNS = {
|
|||||||
| Types | PascalCase | `TabType`, `SortDirection` |
|
| Types | PascalCase | `TabType`, `SortDirection` |
|
||||||
|
|
||||||
### React Patterns
|
### React Patterns
|
||||||
|
|
||||||
- Destructure props in function signature
|
- Destructure props in function signature
|
||||||
- Use `useMemo` for expensive computations
|
- Use `useMemo` for expensive computations
|
||||||
- Use `useCallback` for event handlers passed to child components
|
- Use `useCallback` for event handlers passed to child components
|
||||||
- Keep `useState` calls at the top of component
|
- Keep `useState` calls at the top of component
|
||||||
|
|
||||||
```typescript
|
|
||||||
export function ProductDescriptions({ data, onEdit }: ProductDescriptionsProps) {
|
|
||||||
const [activeTab, setActiveTab] = useState<TabType>('all');
|
|
||||||
const [search, setSearch] = useState('');
|
|
||||||
|
|
||||||
const filteredData = useMemo(() => {
|
|
||||||
// expensive computation
|
|
||||||
}, [data, activeTab, search]);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Error Handling
|
### Error Handling
|
||||||
|
|
||||||
- Use TypeScript's type system for runtime safety
|
- Use TypeScript's type system for runtime safety
|
||||||
- Use optional chaining (`?.`) and nullish coalescing (`??`)
|
- Use optional chaining (`?.`) and nullish coalescing (`??`)
|
||||||
- Validate file uploads with proper type checks
|
- Validate file uploads with proper type checks
|
||||||
|
|
||||||
```typescript
|
|
||||||
const file = e.target.files?.[0];
|
|
||||||
if (!file) return;
|
|
||||||
|
|
||||||
// Validate Excel data
|
|
||||||
if (data.length > 0) {
|
|
||||||
const rawHeaders = data[0];
|
|
||||||
const rawRows = data.slice(1);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### UI/Styling
|
### UI/Styling
|
||||||
|
|
||||||
- Use Tailwind CSS for all styling
|
- Use Tailwind CSS for all styling
|
||||||
- Use `cn()` utility from `lib/utils` for conditional classes
|
- Use `cn()` utility from `lib/utils` for conditional classes
|
||||||
- Follow existing color scheme (slate, blue, green, red for status)
|
- Follow existing color scheme (slate, blue, green, red for status)
|
||||||
- Use `lucide-react` for icons
|
- Use `lucide-react` for icons
|
||||||
- Keep responsive design in mind
|
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { cn } from '../lib/utils';
|
<button className={cn("px-4 py-2 rounded-md", isActive ? "bg-blue-600" : "bg-slate-800")}>
|
||||||
|
|
||||||
<button
|
|
||||||
className={cn(
|
|
||||||
"px-4 py-2 rounded-md text-sm font-medium",
|
|
||||||
isActive
|
|
||||||
? "bg-blue-600 text-white"
|
|
||||||
: "bg-slate-800 text-slate-400"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### File Organization
|
### File Organization
|
||||||
|
|
||||||
```
|
```
|
||||||
src/
|
src/
|
||||||
├── components/ # React components
|
├── components/ # React components
|
||||||
│ ├── Sidebar.tsx
|
├── lib/ # Utilities (utils.ts, auth.ts, supabase.ts)
|
||||||
│ ├── MatrixView.tsx
|
├── services/ # External API integrations (gemini.ts, anthropic.ts)
|
||||||
│ └── ...
|
├── types.ts # TypeScript types and constants
|
||||||
├── lib/ # Utilities and helpers
|
├── App.tsx # Main application
|
||||||
│ ├── utils.ts # cn(), helpers
|
└── main.tsx # Entry point
|
||||||
│ ├── auth.ts # Authentication
|
|
||||||
│ └── supabase.ts # Database operations
|
|
||||||
├── services/ # External API integrations
|
|
||||||
│ ├── gemini.ts
|
|
||||||
│ └── anthropic.ts
|
|
||||||
├── types.ts # TypeScript types and constants
|
|
||||||
├── App.tsx # Main application
|
|
||||||
└── main.tsx # Entry point
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Data Processing
|
### Data Processing
|
||||||
|
- Handle both string and number types when processing Excel data
|
||||||
- When processing Excel data, handle both string and number types
|
|
||||||
- Use centralized column index constants
|
- Use centralized column index constants
|
||||||
- Format numbers consistently (2 decimal places for prices/weights)
|
- Format numbers consistently (2 decimal places for prices/weights)
|
||||||
- Handle Excel date serial numbers properly (convert to readable dates)
|
- Handle Excel date serial numbers properly
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// Handle date columns from Excel
|
// Handle date columns from Excel
|
||||||
if (header.includes('date') || header.includes('launch')) {
|
if (typeof val === 'number' && val >= 25569 && val <= 60000) {
|
||||||
if (typeof val === 'number' && val >= 25569 && val <= 60000) {
|
const excelEpoch = new Date(1899, 11, 30);
|
||||||
const excelEpoch = new Date(1899, 11, 30);
|
return new Date(excelEpoch.getTime() + val * 86400000).toLocaleDateString('en-GB');
|
||||||
const date = new Date(excelEpoch.getTime() + val * 86400000);
|
|
||||||
return date.toLocaleDateString('en-GB');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Git Workflow
|
### Git Workflow
|
||||||
|
|
||||||
- Make small, focused commits
|
- Make small, focused commits
|
||||||
- Write clear commit messages describing what changed
|
- Write clear commit messages describing what changed
|
||||||
- Push to main to trigger Vercel deployment automatically
|
- Push to main to trigger Vercel deployment automatically
|
||||||
|
|
||||||
### Running Single Components
|
|
||||||
|
|
||||||
When testing or developing specific features:
|
|
||||||
```bash
|
|
||||||
npm run dev # Start dev server - access at http://localhost:3000
|
|
||||||
```
|
|
||||||
|
|
||||||
### Environment Variables
|
### Environment Variables
|
||||||
|
|
||||||
- Use `.env` file for local development
|
- Use `.env` file for local development
|
||||||
- Never commit secrets - use Vercel dashboard for production env vars
|
- Never commit secrets - use Vercel dashboard for production env vars
|
||||||
|
|
||||||
Required environment variables (production):
|
Required: `VITE_SUPABASE_URL`, `VITE_SUPABASE_ANON_KEY`, `VITE_GEMINI_API_KEY`, `VITE_ANTHROPIC_API_KEY`
|
||||||
- `VITE_SUPABASE_URL`
|
|
||||||
- `VITE_SUPABASE_ANON_KEY`
|
|
||||||
- `VITE_GEMINI_API_KEY`
|
|
||||||
- `VITE_ANTHROPIC_API_KEY`
|
|
||||||
|
|||||||
@@ -414,6 +414,7 @@ export default function App() {
|
|||||||
onSaveRow={handleSaveRow}
|
onSaveRow={handleSaveRow}
|
||||||
onCaptureState={captureState}
|
onCaptureState={captureState}
|
||||||
rowStatuses={rowStatuses}
|
rowStatuses={rowStatuses}
|
||||||
|
onRevertRow={handleRevertRow}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useState, useMemo } from 'react';
|
import React, { useState, useMemo } from 'react';
|
||||||
import { ExcelRow, COLUMNS } from '../types';
|
import { ExcelRow, COLUMNS } from '../types';
|
||||||
import { AlertTriangle, CheckCircle2, ChevronDown, ChevronRight, Edit2, Package, Boxes, Scale, Loader2, RefreshCw, Layers, Link2, Search, Filter, X as XIcon } from 'lucide-react';
|
import { AlertTriangle, CheckCircle2, ChevronDown, ChevronRight, Edit2, Package, Boxes, Scale, Loader2, RefreshCw, Layers, Link2, Search, Filter, X as XIcon, Undo2 } from 'lucide-react';
|
||||||
import { cn } from '../lib/utils';
|
import { cn } from '../lib/utils';
|
||||||
import { ConfirmModal } from './ConfirmModal';
|
import { ConfirmModal } from './ConfirmModal';
|
||||||
import { ColumnFilterPopover } from './ColumnFilterPopover';
|
import { ColumnFilterPopover } from './ColumnFilterPopover';
|
||||||
@@ -12,6 +12,7 @@ interface DimensionsViewProps {
|
|||||||
onSaveRow: (index: number, updatedRow: ExcelRow) => void;
|
onSaveRow: (index: number, updatedRow: ExcelRow) => void;
|
||||||
onCaptureState: (message: string) => void;
|
onCaptureState: (message: string) => void;
|
||||||
rowStatuses: Record<string, string>;
|
rowStatuses: Record<string, string>;
|
||||||
|
onRevertRow: (articleNo: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DimensionGroup {
|
interface DimensionGroup {
|
||||||
@@ -33,7 +34,7 @@ interface NearDuplicateCluster {
|
|||||||
maxDiffPct: number;
|
maxDiffPct: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DimensionsView({ data, headers, onEdit, onSaveRow, onCaptureState, rowStatuses }: DimensionsViewProps) {
|
export function DimensionsView({ data, headers, onEdit, onSaveRow, onCaptureState, rowStatuses, onRevertRow }: DimensionsViewProps) {
|
||||||
const [expandedGroups, setExpandedGroups] = useState<Set<string>>(new Set());
|
const [expandedGroups, setExpandedGroups] = useState<Set<string>>(new Set());
|
||||||
const [expandedNearDuplicates, setExpandedNearDuplicates] = useState<Set<number>>(new Set());
|
const [expandedNearDuplicates, setExpandedNearDuplicates] = useState<Set<number>>(new Set());
|
||||||
const [showOnlyInconsistent, setShowOnlyInconsistent] = useState(true);
|
const [showOnlyInconsistent, setShowOnlyInconsistent] = useState(true);
|
||||||
@@ -672,6 +673,15 @@ export function DimensionsView({ data, headers, onEdit, onSaveRow, onCaptureStat
|
|||||||
>
|
>
|
||||||
{syncing?.key === group.key && syncing?.field === 'all' ? <Loader2 className="w-4 h-4 animate-spin" /> : <RefreshCw className="w-4 h-4" />}
|
{syncing?.key === group.key && syncing?.field === 'all' ? <Loader2 className="w-4 h-4 animate-spin" /> : <RefreshCw className="w-4 h-4" />}
|
||||||
</button>
|
</button>
|
||||||
|
{isPending && (
|
||||||
|
<button
|
||||||
|
onClick={() => onRevertRow(String(row[COLUMNS.ARTICLE_NO]))}
|
||||||
|
title="Undo pending changes"
|
||||||
|
className="p-1.5 hover:bg-red-600/20 text-slate-500 hover:text-red-400 rounded transition-all"
|
||||||
|
>
|
||||||
|
<Undo2 className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
<button
|
<button
|
||||||
onClick={() => onEdit(index)}
|
onClick={() => onEdit(index)}
|
||||||
title="Edit product"
|
title="Edit product"
|
||||||
|
|||||||
Reference in New Issue
Block a user