mirror of
https://github.com/christianvidalwolf-prog/Craze-Data-check.git
synced 2026-08-03 11:05:24 +02:00
UI: Implement Maximize mode for tables. Added a 'MAXIMIZE' button to TopBar and an 'X' close button in fullscreen mode. Hides sidebar and header to provide maximum screen space for data tables.
This commit is contained in:
@@ -0,0 +1,171 @@
|
|||||||
|
# Maximize Tab View — Implementation Plan
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task.
|
||||||
|
|
||||||
|
**Goal:** Add fullscreen maximize mode to each tab view, hiding Sidebar and TopBar for maximum data visibility.
|
||||||
|
|
||||||
|
**Architecture:** Each view component manages its own `isFullscreen` state locally. No global state. Toggle between normal and fullscreen modes with conditional rendering.
|
||||||
|
|
||||||
|
**Tech Stack:** React useState, lucide-react icons, Tailwind CSS
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 1: ProductDescriptions.tsx
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/components/ProductDescriptions.tsx`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add imports and state**
|
||||||
|
|
||||||
|
Add `Maximize2` to lucide-react imports.
|
||||||
|
Add `useState` if not present.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
const [isFullscreen, setIsFullscreen] = useState(false);
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Add maximize button**
|
||||||
|
|
||||||
|
Find the component's header section (where stats/controls are rendered).
|
||||||
|
Add maximize button with `Maximize2` icon next to existing controls.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<button
|
||||||
|
onClick={() => setIsFullscreen(true)}
|
||||||
|
className="p-2 text-slate-400 hover:text-slate-200 hover:bg-slate-700 rounded transition-colors"
|
||||||
|
title="Fullscreen"
|
||||||
|
>
|
||||||
|
<Maximize2 className="w-5 h-5" />
|
||||||
|
</button>
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: Wrap content in conditional render**
|
||||||
|
|
||||||
|
After existing return statement, wrap in:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{isFullscreen ? (
|
||||||
|
<div className="fixed inset-0 z-40 bg-[#041021] p-6 overflow-auto">
|
||||||
|
<button
|
||||||
|
onClick={() => setIsFullscreen(false)}
|
||||||
|
className="fixed top-4 right-4 z-50 w-10 h-10 flex items-center justify-center bg-slate-800/90 backdrop-blur border border-slate-600 text-white rounded hover:bg-slate-700 hover:scale-105 transition-all"
|
||||||
|
title="Exit fullscreen"
|
||||||
|
>
|
||||||
|
<X className="w-5 h-5" />
|
||||||
|
</button>
|
||||||
|
{/* Existing JSX content - remove any overflow constraints */}
|
||||||
|
<div className="[content goes here]" />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="...">
|
||||||
|
{/* Existing JSX content - keep current overflow settings */}
|
||||||
|
<div className="[content goes here]" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 2: MatrixView.tsx
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/components/MatrixView.tsx`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add imports and state**
|
||||||
|
|
||||||
|
Add `Maximize2`, `X` to lucide-react imports.
|
||||||
|
Add `useState` if not present.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Add maximize button**
|
||||||
|
|
||||||
|
Add button in the header area (search bar section).
|
||||||
|
|
||||||
|
- [ ] **Step 3: Add conditional render**
|
||||||
|
|
||||||
|
Same pattern as Task 1.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 3: DimensionsView.tsx
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/components/DimensionsView.tsx`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add imports and state**
|
||||||
|
|
||||||
|
- [ ] **Step 2: Add maximize button**
|
||||||
|
|
||||||
|
- [ ] **Step 3: Add conditional render**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 4: PricingView.tsx
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/components/PricingView.tsx`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add imports and state**
|
||||||
|
|
||||||
|
- [ ] **Step 2: Add maximize button**
|
||||||
|
|
||||||
|
- [ ] **Step 3: Add conditional render**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 5: ArticleDetails.tsx
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/components/ArticleDetails.tsx`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add imports and state**
|
||||||
|
|
||||||
|
- [ ] **Step 2: Add maximize button**
|
||||||
|
|
||||||
|
- [ ] **Step 3: Add conditional render**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 6: PendingValidationView.tsx
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/components/PendingValidationView.tsx`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add imports and state**
|
||||||
|
|
||||||
|
- [ ] **Step 2: Add maximize button**
|
||||||
|
|
||||||
|
- [ ] **Step 3: Add conditional render**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 7: MissingDataView.tsx
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/components/MissingDataView.tsx`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add imports and state**
|
||||||
|
|
||||||
|
- [ ] **Step 2: Add maximize button**
|
||||||
|
|
||||||
|
- [ ] **Step 3: Add conditional render**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 8: HistoryView.tsx
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/components/HistoryView.tsx`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add imports and state**
|
||||||
|
|
||||||
|
- [ ] **Step 2: Add maximize button**
|
||||||
|
|
||||||
|
- [ ] **Step 3: Add conditional render**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Verification:** Run `npm run lint` to ensure no TypeScript errors.
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
# Maximize Tab View — Spec
|
||||||
|
|
||||||
|
## Concept & Vision
|
||||||
|
|
||||||
|
Each tab view (Matrix, Product Descriptions, Dimensions, etc.) can be maximized to fullscreen mode, hiding all navigation chrome (Sidebar, TopBar) to maximize data visibility. A floating close button allows returning to normal view. The effect is immersive and focused — like pressing F11 in a browser.
|
||||||
|
|
||||||
|
## Design
|
||||||
|
|
||||||
|
### Normal Mode
|
||||||
|
- Sidebar (240px) visible on left
|
||||||
|
- TopBar visible on top
|
||||||
|
- Main content fills remaining space
|
||||||
|
|
||||||
|
### Maximized Mode
|
||||||
|
- Sidebar: `display: none`
|
||||||
|
- TopBar: `display: none`
|
||||||
|
- Fullscreen overlay covers entire viewport
|
||||||
|
- Background: `#041021` (matches main content bg)
|
||||||
|
- Content area: max-height `100vh`, overflow scroll
|
||||||
|
- Floating close button: top-right corner, fixed position
|
||||||
|
|
||||||
|
### Close Button
|
||||||
|
- Position: fixed, top-right
|
||||||
|
- Size: 40x40px
|
||||||
|
- Background: `rgba(30, 41, 59, 0.9)` with blur
|
||||||
|
- Border: 1px `slate-600`
|
||||||
|
- Icon: `X` from lucide-react, white, 20px
|
||||||
|
- Hover: bg `slate-700`, scale 1.05
|
||||||
|
- Z-index: 50
|
||||||
|
- Tooltip: "Exit fullscreen"
|
||||||
|
|
||||||
|
## Layout & Structure
|
||||||
|
|
||||||
|
### Implementation Pattern
|
||||||
|
|
||||||
|
Each view component receives `isFullscreen?: boolean` prop and renders:
|
||||||
|
|
||||||
|
```
|
||||||
|
{maximizeButton && (
|
||||||
|
<button onClick={toggleFullscreen} className="..." title="Fullscreen">
|
||||||
|
<Maximize2 />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isFullscreen ? (
|
||||||
|
<div className="fixed inset-0 z-40 bg-[#041021] p-6 overflow-auto">
|
||||||
|
<button onClick={toggleFullscreen} className="fixed top-4 right-4 ...">
|
||||||
|
<X />
|
||||||
|
</button>
|
||||||
|
{/* Tab content rendered at full height */}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="...">
|
||||||
|
{/* Tab content normal */}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
```
|
||||||
|
|
||||||
|
### State Management
|
||||||
|
- Each tab manages its own `isFullscreen` state locally
|
||||||
|
- No global state needed — each tab remembers its own fullscreen mode
|
||||||
|
- State resets to normal when switching tabs (intentional)
|
||||||
|
|
||||||
|
## Features & Interactions
|
||||||
|
|
||||||
|
### Toggle Button
|
||||||
|
- Location: Each tab view has a maximize button (icon) in its header area
|
||||||
|
- Icon: `Maximize2` from lucide-react
|
||||||
|
- Click: Enter fullscreen mode
|
||||||
|
- Hover: Slight scale increase, cursor pointer
|
||||||
|
|
||||||
|
### Exit Fullscreen
|
||||||
|
- Click floating X button
|
||||||
|
- Instantly returns to normal layout
|
||||||
|
- No animation needed
|
||||||
|
|
||||||
|
## Component Inventory
|
||||||
|
|
||||||
|
### MaximizeButton (in each tab header)
|
||||||
|
- States: default, hover
|
||||||
|
- Color: `slate-400` default, `slate-200` hover
|
||||||
|
|
||||||
|
### FullscreenCloseButton (floating)
|
||||||
|
- States: default, hover, active
|
||||||
|
- Default: semi-transparent dark bg, subtle border
|
||||||
|
- Hover: lighter bg, slight scale
|
||||||
|
- Z-index ensures it's above all content
|
||||||
|
|
||||||
|
### FullscreenOverlay
|
||||||
|
- Fixed positioning
|
||||||
|
- Matches app background color
|
||||||
|
- Contains scrollable content
|
||||||
|
|
||||||
|
## Technical Approach
|
||||||
|
|
||||||
|
- Add `isFullscreen` state to each view component
|
||||||
|
- Add toggle function
|
||||||
|
- Conditional rendering based on state
|
||||||
|
- Icons from `lucide-react`: `Maximize2`, `X`
|
||||||
|
- All styling via Tailwind classes
|
||||||
|
- No additional dependencies
|
||||||
|
|
||||||
|
## Affected Components
|
||||||
|
|
||||||
|
1. `ProductDescriptions.tsx`
|
||||||
|
2. `MatrixView.tsx`
|
||||||
|
3. `DimensionsView.tsx`
|
||||||
|
4. `PricingView.tsx`
|
||||||
|
5. `ArticleDetails.tsx`
|
||||||
|
6. `PendingValidationView.tsx`
|
||||||
|
7. `MissingDataView.tsx`
|
||||||
|
8. `HistoryView.tsx`
|
||||||
+44
-21
@@ -523,6 +523,8 @@ export default function App() {
|
|||||||
};
|
};
|
||||||
}, [appState.data]);
|
}, [appState.data]);
|
||||||
|
|
||||||
|
const [isMaximized, setIsMaximized] = useState(false);
|
||||||
|
|
||||||
if (!session) {
|
if (!session) {
|
||||||
return <LoginPage onLogin={() => {
|
return <LoginPage onLogin={() => {
|
||||||
const stored = getStoredSession();
|
const stored = getStoredSession();
|
||||||
@@ -534,28 +536,49 @@ export default function App() {
|
|||||||
return (
|
return (
|
||||||
<ColumnsProvider headers={appState.headers}>
|
<ColumnsProvider headers={appState.headers}>
|
||||||
<div className="h-screen bg-[#040d1a] text-slate-200 flex flex-col font-sans overflow-hidden">
|
<div className="h-screen bg-[#040d1a] text-slate-200 flex flex-col font-sans overflow-hidden">
|
||||||
<TopBar
|
{!isMaximized && (
|
||||||
stats={stats}
|
<TopBar
|
||||||
activeModule={activeModule}
|
stats={stats}
|
||||||
onExport={handleExport}
|
activeModule={activeModule}
|
||||||
onRefresh={() => { setRefreshTrigger(t => t + 1); }}
|
onExport={handleExport}
|
||||||
hasData={appState.data.length > 0}
|
onRefresh={() => { setRefreshTrigger(t => t + 1); }}
|
||||||
hasUnsavedChanges={appState.hasUnsavedChanges}
|
hasData={appState.data.length > 0}
|
||||||
userEmail={session.user.email}
|
hasUnsavedChanges={appState.hasUnsavedChanges}
|
||||||
onSignOut={handleSignOut}
|
userEmail={session.user.email}
|
||||||
canUndo={undoHistory.length > 0}
|
onSignOut={handleSignOut}
|
||||||
onUndo={handleUndo}
|
canUndo={undoHistory.length > 0}
|
||||||
undoMessage={undoHistory[0]?.message}
|
onUndo={handleUndo}
|
||||||
undoSteps={undoHistory.length}
|
undoMessage={undoHistory[0]?.message}
|
||||||
pendingCount={Object.keys(pendingRows).length}
|
undoSteps={undoHistory.length}
|
||||||
pendingChanges={Object.fromEntries(Object.entries(pendingRows).map(([k, v]) => [k, { articleName: (v as any).articleName }]))}
|
pendingCount={Object.keys(pendingRows).length}
|
||||||
onSaveAll={handleSaveAll}
|
pendingChanges={Object.fromEntries(Object.entries(pendingRows).map(([k, v]) => [k, { articleName: (v as any).articleName }]))}
|
||||||
onRevertRow={handleRevertRow}
|
onSaveAll={handleSaveAll}
|
||||||
isSavingAll={isSavingAll}
|
onRevertRow={handleRevertRow}
|
||||||
/>
|
isSavingAll={isSavingAll}
|
||||||
|
isMaximized={isMaximized}
|
||||||
|
onToggleMaximize={() => setIsMaximized(true)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<div className="flex flex-1 overflow-hidden">
|
<div className="flex flex-1 overflow-hidden">
|
||||||
<Sidebar activeModule={activeModule} setActiveModule={setActiveModule} userEmail={session.user.email} />
|
{!isMaximized && (
|
||||||
<main className="flex-1 overflow-auto relative p-6 bg-[#041021]">
|
<Sidebar activeModule={activeModule} setActiveModule={setActiveModule} userEmail={session.user.email} />
|
||||||
|
)}
|
||||||
|
<main className={cn(
|
||||||
|
"flex-1 overflow-auto relative bg-[#041021] transition-all duration-300",
|
||||||
|
isMaximized ? "p-0" : "p-6"
|
||||||
|
)}>
|
||||||
|
{isMaximized && (
|
||||||
|
<button
|
||||||
|
onClick={() => setIsMaximized(false)}
|
||||||
|
className="fixed top-4 right-4 z-[100] p-3 bg-slate-800 hover:bg-slate-700 text-slate-400 hover:text-white rounded-full shadow-2xl border border-slate-700 transition-all active:scale-95 group"
|
||||||
|
title="Exit Fullscreen"
|
||||||
|
>
|
||||||
|
<X className="w-6 h-6" />
|
||||||
|
<span className="absolute right-full mr-2 top-1/2 -translate-y-1/2 px-2 py-1 bg-slate-900 text-xs text-white rounded opacity-0 group-hover:opacity-100 transition-opacity whitespace-nowrap">
|
||||||
|
Exit Fullscreen
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
{isLoadingDefault ? (
|
{isLoadingDefault ? (
|
||||||
<div className="flex flex-col items-center justify-center h-full text-slate-400">
|
<div className="flex flex-col items-center justify-center h-full text-slate-400">
|
||||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500 mb-4"></div>
|
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500 mb-4"></div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React, { useState, useMemo } from 'react';
|
import React, { useState, useMemo } from 'react';
|
||||||
import { ExcelRow } from '../types';
|
import { ExcelRow } from '../types';
|
||||||
import { useColumns } from '../contexts/ColumnsContext';
|
import { useColumns } from '../contexts/ColumnsContext';
|
||||||
import { Search, Filter, Edit2, ChevronDown, ChevronUp, X } from 'lucide-react';
|
import { Search, Filter, Edit2, ChevronDown, ChevronUp, X, Maximize2 } from 'lucide-react';
|
||||||
import { cn } from '../lib/utils';
|
import { cn } from '../lib/utils';
|
||||||
import { ColumnFilterPopover } from './ColumnFilterPopover';
|
import { ColumnFilterPopover } from './ColumnFilterPopover';
|
||||||
|
|
||||||
@@ -15,6 +15,7 @@ interface ProductDescriptionsProps {
|
|||||||
|
|
||||||
export function ProductDescriptions({ data, headers, asinColumnIndex, onEdit, rowStatuses }: ProductDescriptionsProps) {
|
export function ProductDescriptions({ data, headers, asinColumnIndex, onEdit, rowStatuses }: ProductDescriptionsProps) {
|
||||||
const COLUMNS = useColumns();
|
const COLUMNS = useColumns();
|
||||||
|
const [isFullscreen, setIsFullscreen] = useState(false);
|
||||||
|
|
||||||
// Description columns that should only have Present/Missing filters
|
// Description columns that should only have Present/Missing filters
|
||||||
const DESCRIPTION_COLUMNS = [COLUMNS.LONG_DE, COLUMNS.LONG_EN, COLUMNS.SHORT_DE, COLUMNS.SHORT_EN, COLUMNS.DETAILS_DE, COLUMNS.DETAILS_EN];
|
const DESCRIPTION_COLUMNS = [COLUMNS.LONG_DE, COLUMNS.LONG_EN, COLUMNS.SHORT_DE, COLUMNS.SHORT_EN, COLUMNS.DETAILS_DE, COLUMNS.DETAILS_EN];
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useState, useRef, useEffect } from 'react';
|
import React, { useState, useRef, useEffect } from 'react';
|
||||||
import { Download, LogOut, Undo2, CloudUpload, Loader2, ChevronDown, RotateCcw } from 'lucide-react';
|
import { Download, LogOut, Undo2, CloudUpload, Loader2, ChevronDown, RotateCcw, Maximize2, X } from 'lucide-react';
|
||||||
import { cn } from '../lib/utils';
|
import { cn } from '../lib/utils';
|
||||||
|
|
||||||
interface TopBarProps {
|
interface TopBarProps {
|
||||||
@@ -20,9 +20,11 @@ interface TopBarProps {
|
|||||||
onSaveAll: () => Promise<void>;
|
onSaveAll: () => Promise<void>;
|
||||||
onRevertRow: (articleNo: string) => void;
|
onRevertRow: (articleNo: string) => void;
|
||||||
isSavingAll: boolean;
|
isSavingAll: boolean;
|
||||||
|
isMaximized: boolean;
|
||||||
|
onToggleMaximize: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TopBar({ stats, activeModule, onExport, onRefresh, hasData, hasUnsavedChanges, userEmail, onSignOut, canUndo, onUndo, undoMessage, undoSteps, pendingCount, pendingChanges, onSaveAll, onRevertRow, isSavingAll }: TopBarProps) {
|
export function TopBar({ stats, activeModule, onExport, onRefresh, hasData, hasUnsavedChanges, userEmail, onSignOut, canUndo, onUndo, undoMessage, undoSteps, pendingCount, pendingChanges, onSaveAll, onRevertRow, isSavingAll, isMaximized, onToggleMaximize }: TopBarProps) {
|
||||||
const [showPending, setShowPending] = useState(false);
|
const [showPending, setShowPending] = useState(false);
|
||||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
@@ -179,6 +181,15 @@ export function TopBar({ stats, activeModule, onExport, onRefresh, hasData, hasU
|
|||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={onToggleMaximize}
|
||||||
|
className="flex items-center gap-2 px-4 py-2 rounded-md text-sm font-medium bg-slate-700 hover:bg-slate-600 text-slate-200 transition-colors"
|
||||||
|
title="Maximize - View table in full screen"
|
||||||
|
>
|
||||||
|
<Maximize2 className="w-4 h-4" />
|
||||||
|
MAXIMIZE
|
||||||
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={onUndo}
|
onClick={onUndo}
|
||||||
disabled={!canUndo}
|
disabled={!canUndo}
|
||||||
|
|||||||
Reference in New Issue
Block a user