mirror of
https://github.com/R0m1k3/LogiFlow.git
synced 2026-10-22 22:48:15 +02:00
feat: add mobile tasks page with creation/edit form and new layout component
This commit is contained in:
1 parent
dd682024fd
commit
69773e3d2f
2 files changed
+54
-48
No files matched your search
@@ -24,7 +24,7 @@ export default function Layout({ children }: LayoutProps) {
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
const { screenSize, isMobileOrTablet, isTablet } = useScreenSize();
|
const { screenSize, isMobileOrTablet, isTablet } = useScreenSize();
|
||||||
|
|
||||||
const [selectedStoreId, setSelectedStoreId] = useState<number | null>(() => {
|
const [selectedStoreId, setSelectedStoreId] = useState<number | null>(() => {
|
||||||
// Restaurer le selectedStoreId depuis localStorage si disponible
|
// Restaurer le selectedStoreId depuis localStorage si disponible
|
||||||
const saved = localStorage.getItem('selectedStoreId');
|
const saved = localStorage.getItem('selectedStoreId');
|
||||||
@@ -32,7 +32,7 @@ export default function Layout({ children }: LayoutProps) {
|
|||||||
console.log('🏪 Layout - Restoring selectedStoreId from localStorage:', { saved, restoredId });
|
console.log('🏪 Layout - Restoring selectedStoreId from localStorage:', { saved, restoredId });
|
||||||
return restoredId;
|
return restoredId;
|
||||||
});
|
});
|
||||||
|
|
||||||
const [storeInitialized, setStoreInitialized] = useState(false);
|
const [storeInitialized, setStoreInitialized] = useState(false);
|
||||||
|
|
||||||
const [sidebarCollapsed, setSidebarCollapsed] = useState<boolean>(() => {
|
const [sidebarCollapsed, setSidebarCollapsed] = useState<boolean>(() => {
|
||||||
@@ -58,7 +58,7 @@ export default function Layout({ children }: LayoutProps) {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (user && stores.length > 0) {
|
if (user && stores.length > 0) {
|
||||||
let needsUpdate = false;
|
let needsUpdate = false;
|
||||||
|
|
||||||
// Pour tous les rôles, vérifier que selectedStoreId est cohérent avec les stores disponibles
|
// Pour tous les rôles, vérifier que selectedStoreId est cohérent avec les stores disponibles
|
||||||
if (selectedStoreId) {
|
if (selectedStoreId) {
|
||||||
const storeExists = stores.find(store => store.id === selectedStoreId);
|
const storeExists = stores.find(store => store.id === selectedStoreId);
|
||||||
@@ -79,7 +79,7 @@ export default function Layout({ children }: LayoutProps) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// IMPORTANT FIX: Pour les rôles directeur/manager, forcer la sélection d'un magasin spécifique
|
// IMPORTANT FIX: Pour les rôles directeur/manager, forcer la sélection d'un magasin spécifique
|
||||||
// si aucun n'est sélectionné et qu'ils n'ont accès qu'à un seul magasin
|
// si aucun n'est sélectionné et qu'ils n'ont accès qu'à un seul magasin
|
||||||
if ((user.role === 'directeur' || user.role === 'manager') && !selectedStoreId && stores.length === 1 && !needsUpdate) {
|
if ((user.role === 'directeur' || user.role === 'manager') && !selectedStoreId && stores.length === 1 && !needsUpdate) {
|
||||||
@@ -95,7 +95,7 @@ export default function Layout({ children }: LayoutProps) {
|
|||||||
setSelectedStoreId(singleStoreId);
|
setSelectedStoreId(singleStoreId);
|
||||||
localStorage.setItem('selectedStoreId', singleStoreId.toString());
|
localStorage.setItem('selectedStoreId', singleStoreId.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
// VALIDATION CRITIQUE: Pour les directeurs/managers, s'assurer qu'un magasin est toujours sélectionné
|
// VALIDATION CRITIQUE: Pour les directeurs/managers, s'assurer qu'un magasin est toujours sélectionné
|
||||||
if ((user.role === 'directeur' || user.role === 'manager') && !selectedStoreId && !needsUpdate) {
|
if ((user.role === 'directeur' || user.role === 'manager') && !selectedStoreId && !needsUpdate) {
|
||||||
console.log('🚨 CRITICAL: Directeur/Manager has no store selected, this will cause empty data display:', {
|
console.log('🚨 CRITICAL: Directeur/Manager has no store selected, this will cause empty data display:', {
|
||||||
@@ -104,10 +104,10 @@ export default function Layout({ children }: LayoutProps) {
|
|||||||
availableStores: stores.map(s => ({ id: s.id, name: s.name }))
|
availableStores: stores.map(s => ({ id: s.id, name: s.name }))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('🏪 Store initialization complete:', {
|
console.log('🏪 Store initialization complete:', {
|
||||||
user: user.role,
|
user: user.role,
|
||||||
selectedStoreId,
|
selectedStoreId,
|
||||||
storesCount: stores.length,
|
storesCount: stores.length,
|
||||||
validatedStore: selectedStoreId ? stores.find(s => s.id === selectedStoreId)?.name : 'None',
|
validatedStore: selectedStoreId ? stores.find(s => s.id === selectedStoreId)?.name : 'None',
|
||||||
autoSelected: (user.role === 'directeur' || user.role === 'manager') && !selectedStoreId && stores.length === 1
|
autoSelected: (user.role === 'directeur' || user.role === 'manager') && !selectedStoreId && stores.length === 1
|
||||||
@@ -125,25 +125,24 @@ export default function Layout({ children }: LayoutProps) {
|
|||||||
<div className="layout-container flex bg-gray-50">
|
<div className="layout-container flex bg-gray-50">
|
||||||
{/* Mobile overlay for normal mode */}
|
{/* Mobile overlay for normal mode */}
|
||||||
{isMobile && mobileMenuOpen && (
|
{isMobile && mobileMenuOpen && (
|
||||||
<div
|
<div
|
||||||
className="fixed inset-0 bg-black bg-opacity-50 z-40"
|
className="fixed inset-0 bg-black bg-opacity-50 z-40"
|
||||||
onClick={() => setMobileMenuOpen(false)}
|
onClick={() => setMobileMenuOpen(false)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Sidebar for normal mode */}
|
{/* Sidebar for normal mode */}
|
||||||
<Sidebar />
|
<Sidebar />
|
||||||
|
|
||||||
<main className={`flex-1 flex flex-col h-full ${
|
<main className={`flex-1 flex flex-col h-full ${
|
||||||
// Ajustement des marges selon le mode
|
// Ajustement des marges selon le mode
|
||||||
isMobile
|
isMobile
|
||||||
? 'ml-0'
|
? 'ml-0'
|
||||||
: (isTablet && !sidebarCollapsed) ? 'ml-48' : 'ml-0'
|
: (isTablet && !sidebarCollapsed) ? 'ml-48' : 'ml-0'
|
||||||
}`}>
|
|
||||||
{/* Header with mobile menu and store selector */}
|
|
||||||
<header className={`h-16 bg-white border-b border-gray-200 flex items-center justify-between ${
|
|
||||||
isMobileOrTablet ? 'px-3' : 'px-6'
|
|
||||||
}`}>
|
}`}>
|
||||||
|
{/* Header with mobile menu and store selector */}
|
||||||
|
<header className={`h-16 bg-white border-b border-gray-200 flex items-center justify-between ${isMobileOrTablet ? 'px-3' : 'px-6'
|
||||||
|
}`}>
|
||||||
<div className="flex items-center gap-2 sm:gap-4 min-w-0 flex-1">
|
<div className="flex items-center gap-2 sm:gap-4 min-w-0 flex-1">
|
||||||
{/* Mobile menu button (only in normal mode) */}
|
{/* Mobile menu button (only in normal mode) */}
|
||||||
{isMobile && (
|
{isMobile && (
|
||||||
@@ -159,9 +158,8 @@ export default function Layout({ children }: LayoutProps) {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Weather and Date widgets - responsive */}
|
{/* Weather and Date widgets - responsive */}
|
||||||
<div className={`flex items-center ${
|
<div className={`flex items-center ${isMobile ? 'gap-1' : 'gap-3'
|
||||||
isMobile ? 'gap-1' : 'gap-3'
|
} min-w-0`}>
|
||||||
} min-w-0`}>
|
|
||||||
<WeatherWidget />
|
<WeatherWidget />
|
||||||
{!isMobile && <DateWidget />}
|
{!isMobile && <DateWidget />}
|
||||||
</div>
|
</div>
|
||||||
@@ -174,23 +172,25 @@ export default function Layout({ children }: LayoutProps) {
|
|||||||
<Select
|
<Select
|
||||||
value={selectedStoreId?.toString() || (user.role === 'admin' ? "all" : "")}
|
value={selectedStoreId?.toString() || (user.role === 'admin' ? "all" : "")}
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
console.log('🏪 Store selector changed:', {
|
console.log('🏪 Store selector changed:', {
|
||||||
value,
|
value,
|
||||||
userRole: user.role,
|
userRole: user.role,
|
||||||
parsed: value === "all" ? null : parseInt(value)
|
parsed: value === "all" ? null : parseInt(value)
|
||||||
});
|
});
|
||||||
const newStoreId = value === "all" ? null : parseInt(value);
|
const newStoreId = value === "all" ? null : parseInt(value);
|
||||||
|
|
||||||
// Invalider toutes les variantes de queryKey pour changement magasin
|
// Invalider toutes les variantes de queryKey pour changement magasin
|
||||||
console.log('🧹 Invalidating data caches for store change');
|
console.log('🧹 Invalidating data caches for store change');
|
||||||
queryClient.invalidateQueries({ predicate: (query) => {
|
queryClient.invalidateQueries({
|
||||||
const key = query.queryKey;
|
predicate: (query) => {
|
||||||
return Boolean(key[0]?.toString().includes('/api/orders') ||
|
const key = query.queryKey;
|
||||||
key[0]?.toString().includes('/api/deliveries') ||
|
return Boolean(key[0]?.toString().includes('/api/orders') ||
|
||||||
key[0]?.toString().includes('/api/stats/monthly') ||
|
key[0]?.toString().includes('/api/deliveries') ||
|
||||||
key[0]?.toString().includes('/api/tasks'));
|
key[0]?.toString().includes('/api/stats/monthly') ||
|
||||||
}});
|
key[0]?.toString().includes('/api/tasks'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Sauvegarder dans localStorage et mettre à jour l'état
|
// Sauvegarder dans localStorage et mettre à jour l'état
|
||||||
if (newStoreId) {
|
if (newStoreId) {
|
||||||
localStorage.setItem('selectedStoreId', newStoreId.toString());
|
localStorage.setItem('selectedStoreId', newStoreId.toString());
|
||||||
@@ -202,11 +202,10 @@ export default function Layout({ children }: LayoutProps) {
|
|||||||
setSelectedStoreId(newStoreId);
|
setSelectedStoreId(newStoreId);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SelectTrigger className={`border border-gray-300 min-h-[44px] ${
|
<SelectTrigger className={`border border-gray-300 min-h-[44px] ${isMobile ? 'w-28 text-xs' :
|
||||||
isMobile ? 'w-28 text-xs' :
|
isTablet ? 'w-40 text-sm' :
|
||||||
isTablet ? 'w-40 text-sm' :
|
'w-64 text-sm'
|
||||||
'w-64 text-sm'
|
}`} data-testid="select-store">
|
||||||
}`} data-testid="select-store">
|
|
||||||
<SelectValue placeholder="Magasin" />
|
<SelectValue placeholder="Magasin" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
@@ -224,8 +223,8 @@ export default function Layout({ children }: LayoutProps) {
|
|||||||
{stores.map((store) => (
|
{stores.map((store) => (
|
||||||
<SelectItem key={store.id} value={store.id.toString()}>
|
<SelectItem key={store.id} value={store.id.toString()}>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div
|
<div
|
||||||
className="w-3 h-3"
|
className="w-3 h-3"
|
||||||
style={{ backgroundColor: store.color || '#gray-400' }}
|
style={{ backgroundColor: store.color || '#gray-400' }}
|
||||||
/>
|
/>
|
||||||
<span className={`${isMobile ? 'text-xs' : 'text-sm'} truncate max-w-[120px]`}>
|
<span className={`${isMobile ? 'text-xs' : 'text-sm'} truncate max-w-[120px]`}>
|
||||||
@@ -257,13 +256,12 @@ export default function Layout({ children }: LayoutProps) {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className={`flex-1 bg-gray-50 h-full overflow-y-auto ${
|
<div className={`flex-1 bg-gray-50 h-full overflow-y-auto overflow-x-hidden ${isMobileOrTablet ? 'p-3' : 'p-6'
|
||||||
isMobileOrTablet ? 'p-3' : 'p-6'
|
}`} style={{ maxWidth: '100%' }}>
|
||||||
}`}>
|
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</StoreProvider>
|
</StoreProvider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -367,9 +367,17 @@ export default function MobileTasksPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-50 pb-24 overflow-hidden" style={{ maxWidth: '100vw' }}>
|
<div
|
||||||
|
className="min-h-screen bg-gray-50 pb-24"
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
maxWidth: '100vw',
|
||||||
|
overflowX: 'hidden',
|
||||||
|
boxSizing: 'border-box'
|
||||||
|
}}
|
||||||
|
>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="bg-white border-b sticky top-0 z-40">
|
<div className="bg-white border-b sticky top-0 z-40" style={{ width: '100%', boxSizing: 'border-box' }}>
|
||||||
<div className="px-3 py-3">
|
<div className="px-3 py-3">
|
||||||
<div className="flex items-center justify-between mb-3">
|
<div className="flex items-center justify-between mb-3">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
|||||||
Reference in new issue
Block a user