Merge pull request #532 from R0m1k3/1.1

feat: add mobile tasks page with creation/edit form and new layout co…
This commit is contained in:
LogiFlow authored and GitHub committed 2025-12-10 13:25:52 +01:00
commit 25b6372e7c
2 files changed
+54 -48

No files matched your search

+44 -46
View File
@@ -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>
); );
+10 -2
View File
@@ -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">