diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..0a24edc --- /dev/null +++ b/.dockerignore @@ -0,0 +1,96 @@ +# Dependencies +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Coverage directory used by tools like istanbul +coverage/ +*.lcov + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Compiled binary addons +build/Release + +# Dependency directories +jspm_packages/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env.local +.env.development.local +.env.test.local +.env.production.local + +# Development files +.vscode/ +.idea/ +*.swp +*.swo + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Git +.git +.gitignore +README.md + +# Docker files +Dockerfile* +docker-compose* +.dockerignore + +# Development assets +attached_assets/ +uploads/ + +# Temporary files +tmp/ +temp/ + +# SSL certificates (if any) +ssl/ +*.pem +*.key +*.crt + +# Backup files +*.bak +*.backup \ No newline at end of file diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..82a1cea --- /dev/null +++ b/.env.example @@ -0,0 +1,24 @@ +# Environment Variables for LogiFlow Production + +# Database Configuration (préconfigurés pour Docker) +DATABASE_URL=postgresql://logiflow_admin:LogiFlow2025!@postgres:5432/logiflow_db + +# Session Configuration +SESSION_SECRET=LogiFlow_Super_Secret_Session_Key_2025_Production + +# Application Configuration +NODE_ENV=production +PORT=5000 + +# Authentication Configuration +USE_LOCAL_AUTH=true + +# Configuration de la base de données +POSTGRES_DB=logiflow_db +POSTGRES_USER=logiflow_admin +POSTGRES_PASSWORD=LogiFlow2025! +POSTGRES_PORT=5434 + +# Note de sécurité: +# Les identifiants sont préconfigurés pour simplifier le déploiement. +# En production, vous pouvez les modifier si nécessaire. \ No newline at end of file diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..cbde1cc --- /dev/null +++ b/.env.production @@ -0,0 +1 @@ +NODE_ENV=production diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f9ba7f8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +node_modules +dist +.DS_Store +server/public +vite.config.ts.* +*.tar.gz \ No newline at end of file diff --git a/.replit b/.replit new file mode 100644 index 0000000..f103f71 --- /dev/null +++ b/.replit @@ -0,0 +1,37 @@ +modules = ["nodejs-20", "web", "postgresql-16"] +run = "npm run dev" +hidden = [".config", ".git", "generated-icon.png", "node_modules", "dist"] + +[nix] +channel = "stable-24_05" +packages = ["jq"] + +[deployment] +deploymentTarget = "autoscale" +build = ["npm", "run", "build"] +run = ["npm", "run", "start"] + +[[ports]] +localPort = 5000 +externalPort = 80 + +[workflows] +runButton = "Project" + +[[workflows.workflow]] +name = "Project" +mode = "parallel" +author = "agent" + +[[workflows.workflow.tasks]] +task = "workflow.run" +args = "Start application" + +[[workflows.workflow]] +name = "Start application" +author = "agent" + +[[workflows.workflow.tasks]] +task = "shell.exec" +args = "npm run dev" +waitForPort = 5000 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..bd9deb7 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,103 @@ +# Multi-stage build for optimized production image +FROM node:20-alpine AS build + +# Set working directory +WORKDIR /app + +# Copy package files +COPY package*.json ./ + +# Install all dependencies (including dev dependencies needed for build) +RUN npm ci && npm cache clean --force + +# Copy source code (excluding .env to avoid conflicts) +COPY package*.json ./ +COPY tsconfig.json ./ +COPY drizzle.config.ts ./ +COPY vite.config.ts ./ +COPY tailwind.config.ts ./ +COPY postcss.config.js ./ +COPY components.json ./ +COPY client/ client/ +COPY server/ server/ +COPY shared/ shared/ + +# Vérifier la structure +RUN ls -la server/ + +# Build the application +# Build frontend first +RUN npx vite build + +# Vérifier que les fichiers sont construits +RUN echo "=== BUILD VERIFICATION ===" && \ + ls -la dist/ && \ + echo "Frontend files in dist/public:" && \ + ls -la dist/public/ && \ + echo "index.html exists:" && \ + ls -la dist/public/index.html + +# Build backend avec tous les modules externes +RUN npx esbuild server/index.production.ts \ + --platform=node \ + --bundle \ + --format=esm \ + --outfile=dist/index.js \ + --external:vite \ + --external:@vitejs/* \ + --external:@replit/* \ + --external:tsx \ + --external:openid-client \ + --external:@neondatabase/serverless \ + --external:ws \ + --external:drizzle-orm \ + --external:pg \ + --external:express \ + --external:connect-pg-simple \ + --external:passport \ + --external:passport-local \ + --external:express-session \ + --external:zod \ + --external:express-rate-limit \ + --external:memoizee \ + --external:nanoid \ + --external:date-fns + +# Production stage +FROM node:20-alpine AS production + +# Create non-root user for security +RUN addgroup -g 1001 -S nodejs && \ + adduser -S nextjs -u 1001 + +# Set working directory +WORKDIR /app + +# Install production dependencies only +COPY --from=build /app/package*.json ./ +RUN npm ci --only=production && npm cache clean --force + +# Copy built application from build stage +COPY --from=build --chown=nextjs:nodejs /app/dist ./dist +COPY --from=build --chown=nextjs:nodejs /app/shared ./shared + +# Create uploads directory with proper permissions +RUN mkdir -p /app/uploads && chown -R nextjs:nodejs /app/uploads + +# Switch to non-root user +USER nextjs + +# Expose port +EXPOSE 3000 + +# Install wget for health check +USER root +RUN apk add --no-cache wget +USER nextjs + +# Health check +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD wget --no-verbose --tries=1 --spider http://localhost:3000/api/health || exit 1 + +# Start the application +CMD ["node", "dist/index.js"] \ No newline at end of file diff --git a/FORCE_DEPLOY_v2.txt b/FORCE_DEPLOY_v2.txt new file mode 100644 index 0000000..e5a0a67 --- /dev/null +++ b/FORCE_DEPLOY_v2.txt @@ -0,0 +1,11 @@ +Force deploy version 2 - Critical task completion fix + +This file forces a redeploy of the production application to apply the critical +fix for task completion functionality that was added to initDatabase.production.ts + +Changes: +- Added CRITICAL FIX v2 in initDatabase.production.ts +- Forces recreation of completed_at and completed_by columns on startup +- Should resolve persistent "column completed_at does not exist" error in production + +Deploy timestamp: 2025-07-18 20:05:00 UTC \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..addb04c --- /dev/null +++ b/README.md @@ -0,0 +1,200 @@ +# LogiFlow - Plateforme de Gestion Logistique + +LogiFlow est une application web complète de gestion logistique conçue spécialement pour les magasins de la chaîne La Foir'Fouille. Elle centralise la gestion des commandes, livraisons, stocks, utilisateurs et bien plus à travers une interface moderne et intuitive. + +## 🎯 Fonctionnalités Principales + +### 📦 Gestion des Commandes et Livraisons +- **Commandes Fournisseurs** : Création, suivi et gestion des commandes avec planification des dates de livraison +- **Livraisons** : Suivi des livraisons avec statuts (en attente, planifiée, livrée) et gestion des bons de livraison +- **Réconciliation BL** : Rapprochement automatique entre bons de livraison et factures +- **Tableau de Bord** : Vue d'ensemble avec statistiques en temps réel + +### 🏪 Gestion Multi-Magasins +- **Sélection de Magasin** : Interface permettant de filtrer les données par magasin +- **Magasins Personnalisés** : Support des magasins "Frouard" et "Houdemont" +- **Permissions par Magasin** : Contrôle d'accès granulaire selon les magasins assignés + +### 📅 Gestion DLC (Dates Limites de Consommation) +- **Suivi des Produits** : Gestion des produits avec dates d'expiration (DLC, DDM, DLUO) +- **Alertes Automatiques** : Notifications pour produits expirant sous 15 jours +- **Filtres Avancés** : Tri par statut (validés, expire bientôt, expirés) +- **Génération de Codes** : Support des codes EAN13 pour les produits +- **Impression** : Rapports PDF des produits expirant bientôt ou expirés + +### 👥 Gestion des Utilisateurs et Rôles +- **Système de Rôles** : 4 rôles (Admin, Manager, Employé, Directeur) avec 54 permissions +- **Permissions Granulaires** : Contrôle d'accès précis pour chaque fonctionnalité +- **Interface de Gestion** : Assignation et modification des permissions en temps réel +- **Traçabilité** : Suivi des actions utilisateurs + +### 📋 Gestion des Tâches +- **Planification** : Création et assignation de tâches avec dates d'échéance +- **Calendrier Intégré** : Vue calendrier pour la planification des tâches +- **Suivi de Progression** : Statuts et validation des tâches terminées +- **Filtres** : Tri par priorité, statut et dates + +### 🛒 Commandes Clients +- **Point de Vente** : Gestion des commandes clients avec génération de codes-barres +- **Suivi Livraison** : Statuts de préparation et livraison +- **Historique** : Archive complète des commandes clients + +### 📢 Gestion des Publicités +- **Campagnes Marketing** : Planification et suivi des campagnes publicitaires +- **Participation Magasins** : Gestion de la participation par magasin +- **Calendrier** : Vue hebdomadaire et mensuelle des campagnes + +## 🛠️ Technologies Utilisées + +### Frontend +- **React 18** avec TypeScript pour une interface moderne et typée +- **Vite** pour un développement rapide et des builds optimisés +- **Shadcn/ui** composants UI modernes basés sur Radix UI +- **Tailwind CSS** pour un design responsive et cohérent +- **TanStack Query** pour la gestion d'état serveur +- **Wouter** pour le routage léger +- **React Hook Form + Zod** pour la validation des formulaires + +### Backend +- **Express.js** avec TypeScript pour l'API REST +- **PostgreSQL** base de données relationnelle robuste +- **Drizzle ORM** pour les requêtes typées et sécurisées +- **Passport.js** pour l'authentification locale et production +- **Express Sessions** avec stockage PostgreSQL + +### Infrastructure +- **Docker** pour le déploiement en production +- **Replit** pour le développement collaboratif +- **Environment adaptatif** : détection automatique développement/production + +## 🚀 Installation et Démarrage + +### Prérequis +- Node.js 18+ +- PostgreSQL +- npm ou yarn + +### Installation +```bash +# Cloner le projet +git clone [url-du-projet] +cd logiflow + +# Installer les dépendances +npm install + +# Configurer la base de données +cp .env.example .env +# Modifier DATABASE_URL dans .env + +# Démarrer l'application +npm run dev +``` + +L'application sera accessible sur `http://localhost:5000` + +### Comptes par Défaut +- **Administrateur** : admin / admin +- **Base de données** : Initialisation automatique avec données de test + +## 🔐 Système de Permissions + +L'application dispose d'un système de permissions granulaire avec 54 permissions réparties en 12 catégories : + +### Catégories de Permissions +- **Tableau de Bord** : Accès aux statistiques et vues d'ensemble +- **Magasins** : Gestion des magasins et groupes +- **Fournisseurs** : Gestion des contacts fournisseurs +- **Commandes** : Création et suivi des commandes +- **Livraisons** : Gestion des livraisons et réceptions +- **Publicités** : Campagnes marketing et communication +- **Commandes Clients** : Point de vente et suivi client +- **Gestion DLC** : Suivi des dates d'expiration +- **Gestion des Tâches** : Planification et suivi des tâches +- **Utilisateurs** : Gestion des comptes utilisateurs +- **Gestion des Rôles** : Administration des permissions +- **Administration** : Configuration système et maintenance + +### Rôles Prédéfinis +- **Admin** : Accès complet à toutes les fonctionnalités +- **Directeur** : Accès étendu avec supervision multi-magasins +- **Manager** : Gestion opérationnelle d'un magasin +- **Employé** : Accès aux opérations quotidiennes + +## 📊 Fonctionnalités Avancées + +### Pagination Intelligente +- **Pagination adaptative** : 10 éléments pour les vues détaillées, 20 pour les vues de synthèse +- **Filtres intégrés** : Recherche et tri sans perte de pagination +- **Performance optimisée** : Pagination côté client pour la réactivité + +### Système de Logs +- **Traçabilité complète** : Logs détaillés pour toutes les opérations critiques +- **Debug en production** : Outils de diagnostic intégrés +- **Monitoring** : Suivi des performances et erreurs + +### Sécurité +- **Authentification robuste** : Support développement (Replit Auth) et production (local) +- **Hachage sécurisé** : PBKDF2 pour les mots de passe +- **Sessions sécurisées** : Stockage PostgreSQL avec rotation automatique +- **Validation stricte** : Zod pour la validation côté client et serveur + +## 🗂️ Structure du Projet + +``` +logiflow/ +├── client/ # Frontend React +│ ├── src/ +│ │ ├── components/ # Composants réutilisables +│ │ ├── pages/ # Pages de l'application +│ │ ├── hooks/ # Hooks personnalisés +│ │ └── lib/ # Utilitaires et configuration +├── server/ # Backend Express +│ ├── routes.ts # Routes API principales +│ ├── storage.ts # Interface de stockage +│ └── auth/ # Système d'authentification +├── shared/ # Code partagé frontend/backend +│ └── schema.ts # Schémas de données Drizzle +└── migrations/ # Migrations de base de données +``` + +## 🔧 Configuration + +### Variables d'Environnement +```env +DATABASE_URL=postgresql://user:password@localhost:5432/logiflow +SESSION_SECRET=your-session-secret-key +NODE_ENV=development|production +``` + +### Déploiement Production +```bash +# Build de production +npm run build + +# Démarrage production +npm run start +``` + +## 📈 Métriques et Performance + +- **Base de données** : PostgreSQL avec contraintes et index optimisés +- **Cache intelligent** : TanStack Query avec invalidation automatique +- **Responsive design** : Interface adaptée mobile et desktop +- **Accessibilité** : Composants Radix UI conformes WCAG + +## 🤝 Contribution + +L'application est conçue pour évoluer facilement : +- **Architecture modulaire** : Ajout simple de nouvelles fonctionnalités +- **Types TypeScript** : Sécurité et auto-complétion complètes +- **Tests intégrés** : Validation automatique des modifications +- **Documentation** : Code auto-documenté avec TypeScript + +## 📝 Licence + +Application propriétaire développée pour Frodis. + +--- + +**LogiFlow** - Optimisation logistique pour un commerce moderne diff --git a/client/src/components/ui/sheet.tsx b/client/src/components/ui/sheet.tsx new file mode 100644 index 0000000..a37f17b --- /dev/null +++ b/client/src/components/ui/sheet.tsx @@ -0,0 +1,140 @@ +"use client" + +import * as React from "react" +import * as SheetPrimitive from "@radix-ui/react-dialog" +import { cva, type VariantProps } from "class-variance-authority" +import { X } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Sheet = SheetPrimitive.Root + +const SheetTrigger = SheetPrimitive.Trigger + +const SheetClose = SheetPrimitive.Close + +const SheetPortal = SheetPrimitive.Portal + +const SheetOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SheetOverlay.displayName = SheetPrimitive.Overlay.displayName + +const sheetVariants = cva( + "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500", + { + variants: { + side: { + top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top", + bottom: + "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom", + left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm", + right: + "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm", + }, + }, + defaultVariants: { + side: "right", + }, + } +) + +interface SheetContentProps + extends React.ComponentPropsWithoutRef, + VariantProps {} + +const SheetContent = React.forwardRef< + React.ElementRef, + SheetContentProps +>(({ side = "right", className, children, ...props }, ref) => ( + + + + {children} + + + Close + + + +)) +SheetContent.displayName = SheetPrimitive.Content.displayName + +const SheetHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +SheetHeader.displayName = "SheetHeader" + +const SheetFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +SheetFooter.displayName = "SheetFooter" + +const SheetTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SheetTitle.displayName = SheetPrimitive.Title.displayName + +const SheetDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SheetDescription.displayName = SheetPrimitive.Description.displayName + +export { + Sheet, + SheetPortal, + SheetOverlay, + SheetTrigger, + SheetClose, + SheetContent, + SheetHeader, + SheetFooter, + SheetTitle, + SheetDescription, +} diff --git a/client/src/components/ui/sidebar.tsx b/client/src/components/ui/sidebar.tsx new file mode 100644 index 0000000..d17b4a3 --- /dev/null +++ b/client/src/components/ui/sidebar.tsx @@ -0,0 +1,771 @@ +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { VariantProps, cva } from "class-variance-authority" +import { PanelLeft } from "lucide-react" + +import { useIsMobile } from "@/hooks/use-mobile" +import { cn } from "@/lib/utils" +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import { Separator } from "@/components/ui/separator" +import { + Sheet, + SheetContent, + SheetDescription, + SheetHeader, + SheetTitle, +} from "@/components/ui/sheet" +import { Skeleton } from "@/components/ui/skeleton" +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip" + +const SIDEBAR_COOKIE_NAME = "sidebar_state" +const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7 +const SIDEBAR_WIDTH = "16rem" +const SIDEBAR_WIDTH_MOBILE = "18rem" +const SIDEBAR_WIDTH_ICON = "3rem" +const SIDEBAR_KEYBOARD_SHORTCUT = "b" + +type SidebarContextProps = { + state: "expanded" | "collapsed" + open: boolean + setOpen: (open: boolean) => void + openMobile: boolean + setOpenMobile: (open: boolean) => void + isMobile: boolean + toggleSidebar: () => void +} + +const SidebarContext = React.createContext(null) + +function useSidebar() { + const context = React.useContext(SidebarContext) + if (!context) { + throw new Error("useSidebar must be used within a SidebarProvider.") + } + + return context +} + +const SidebarProvider = React.forwardRef< + HTMLDivElement, + React.ComponentProps<"div"> & { + defaultOpen?: boolean + open?: boolean + onOpenChange?: (open: boolean) => void + } +>( + ( + { + defaultOpen = true, + open: openProp, + onOpenChange: setOpenProp, + className, + style, + children, + ...props + }, + ref + ) => { + const isMobile = useIsMobile() + const [openMobile, setOpenMobile] = React.useState(false) + + // This is the internal state of the sidebar. + // We use openProp and setOpenProp for control from outside the component. + const [_open, _setOpen] = React.useState(defaultOpen) + const open = openProp ?? _open + const setOpen = React.useCallback( + (value: boolean | ((value: boolean) => boolean)) => { + const openState = typeof value === "function" ? value(open) : value + if (setOpenProp) { + setOpenProp(openState) + } else { + _setOpen(openState) + } + + // This sets the cookie to keep the sidebar state. + document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}` + }, + [setOpenProp, open] + ) + + // Helper to toggle the sidebar. + const toggleSidebar = React.useCallback(() => { + return isMobile + ? setOpenMobile((open) => !open) + : setOpen((open) => !open) + }, [isMobile, setOpen, setOpenMobile]) + + // Adds a keyboard shortcut to toggle the sidebar. + React.useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + if ( + event.key === SIDEBAR_KEYBOARD_SHORTCUT && + (event.metaKey || event.ctrlKey) + ) { + event.preventDefault() + toggleSidebar() + } + } + + window.addEventListener("keydown", handleKeyDown) + return () => window.removeEventListener("keydown", handleKeyDown) + }, [toggleSidebar]) + + // We add a state so that we can do data-state="expanded" or "collapsed". + // This makes it easier to style the sidebar with Tailwind classes. + const state = open ? "expanded" : "collapsed" + + const contextValue = React.useMemo( + () => ({ + state, + open, + setOpen, + isMobile, + openMobile, + setOpenMobile, + toggleSidebar, + }), + [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar] + ) + + return ( + + +
+ {children} +
+
+
+ ) + } +) +SidebarProvider.displayName = "SidebarProvider" + +const Sidebar = React.forwardRef< + HTMLDivElement, + React.ComponentProps<"div"> & { + side?: "left" | "right" + variant?: "sidebar" | "floating" | "inset" + collapsible?: "offcanvas" | "icon" | "none" + } +>( + ( + { + side = "left", + variant = "sidebar", + collapsible = "offcanvas", + className, + children, + ...props + }, + ref + ) => { + const { isMobile, state, openMobile, setOpenMobile } = useSidebar() + + if (collapsible === "none") { + return ( +
+ {children} +
+ ) + } + + if (isMobile) { + return ( + + + + Sidebar + Displays the mobile sidebar. + +
{children}
+
+
+ ) + } + + return ( +
+ {/* This is what handles the sidebar gap on desktop */} +
+ +
+ ) + } +) +Sidebar.displayName = "Sidebar" + +const SidebarTrigger = React.forwardRef< + React.ElementRef, + React.ComponentProps +>(({ className, onClick, ...props }, ref) => { + const { toggleSidebar } = useSidebar() + + return ( + + ) +}) +SidebarTrigger.displayName = "SidebarTrigger" + +const SidebarRail = React.forwardRef< + HTMLButtonElement, + React.ComponentProps<"button"> +>(({ className, ...props }, ref) => { + const { toggleSidebar } = useSidebar() + + return ( +