{"_id":"@bobdivx/mf3d","_rev":"2-4022c9d949d7646f1eced1212d0d498c","name":"@bobdivx/mf3d","dist-tags":{"latest":"1.0.3"},"versions":{"1.0.2":{"name":"@bobdivx/mf3d","version":"1.0.2","_id":"@bobdivx/mf3d@1.0.2","maintainers":[{"name":"bobdivx","email":"bobdivx@gmail.com"}],"dist":{"shasum":"c811b708de1cca40ffde248e41a762c6149de460","tarball":"https://registry.npmjs.org/@bobdivx/mf3d/-/mf3d-1.0.2.tgz","fileCount":89,"integrity":"sha512-jqSrSkMQUU8nnvli3Ghx06ImQpDQxf1zicaQ7WObUFLEWu9E2qDVFqBJKuTvmA84TjDn7zRnu7yKYD9OLjR09w==","signatures":[{"sig":"MEUCIH+gB2m04VIPBkNJnj/gaVAPkmjjQRB5Q8MCGXzG8NzmAiEA6Uomt2pGMrfYCSMOt6Y1cJoMA5eyJK6BnIXK0ZttdmA=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1786923},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","gitHead":"df102d0b0ab2184098afd7ead9995fe75c44dedc","private":false,"scripts":{"dev":"astro dev --host","lint":"eslint . --ext .ts,.tsx --fix","test":"playwright test","astro":"astro","build":"astro check && astro build","start":"astro dev","backup":"tsx src/scripts/backup.ts","format":"prettier --write .","import":"tsx src/scripts/import-data.ts","db:init":"tsx src/db/scripts/init-db.ts","init:db":"tsx src/scripts/init-db.ts","migrate":"tsx src/lib/db/migrate.ts","preview":"astro preview","test:ui":"playwright test --ui","check-db":"tsx scripts/check-db.ts","check:db":"tsx src/scripts/check-db.ts","coverage":"vitest run --coverage","db:reset":"tsx src/db/scripts/reset-db.ts","backup:db":"tsx src/scripts/backup-db.ts","db:backup":"tsx src/db/scripts/backup-db.ts","dev:turso":"npm run use-turso-db && npm run dev","test:sync":"tsx src/scripts/test-sync.ts","typecheck":"tsc --noEmit","db:restore":"tsx src/db/scripts/restore-db.ts","fix:unused":"tsx scripts/fix-unused-vars.js","import:csv":"tsx src/scripts/import-csv.ts","lint:check":"eslint . --ext .ts,.tsx","restore:db":"tsx src/scripts/restore-db.ts","test:debug":"playwright test --debug","turso:init":"tsx src/scripts/init-turso.ts","build:check":"powershell -File scripts/build-check.ps1","convert:csv":"tsx src/scripts/convert-sql-to-csv.ts","build:errors":"astro build > build-errors.log 2>&1","check:schema":"tsx src/scripts/check-schema.ts","list:backups":"tsx src/scripts/list-backups.ts","turso:verify":"tsx src/scripts/verify-turso-data.ts","use-local-db":"tsx src/scripts/use-local-db.ts","use-turso-db":"tsx src/scripts/use-turso-db.ts","migrate:reset":"tsx src/db/reset-db.ts","migrate:users":"tsx src/scripts/migrate-users.ts","set-passwords":"tsx scripts/set-initial-passwords.ts","remove:columns":"tsx src/scripts/remove-columns.ts","check-db-source":"tsx src/scripts/check-db-source.ts","check-passwords":"tsx scripts/check-passwords.ts","db:add-quantity":"tsx src/db/scripts/add-quantity-column.ts","migrate:qrcodes":"tsx src/scripts/migrate-qrcodes.ts","reset-passwords":"tsx scripts/reset-passwords.ts","test-connection":"tsx scripts/test-connection.ts","create-test-user":"tsx src/scripts/create-test-user.ts","db:restore-users":"tsx src/db/scripts/restore-users.ts","migrate:all-data":"tsx scripts/migrate-all-data.ts","migrate:database":"tsx src/scripts/migrate-database.ts","migrate:profiles":"tsx src/scripts/migrate-profiles.ts","migrate:to-turso":"tsx src/scripts/migrate-to-turso.ts","add-test-filament":"tsx src/scripts/add-test-filament.ts","db:update-passwords":"tsx src/db/scripts/update-passwords.ts","migrate:foreign-keys":"tsx scripts/migrate-foreign-keys.ts","add-test-notification":"tsx scripts/add-test-notification.ts","turso:test-connection":"tsx src/scripts/test-turso-connection.ts","db:add-filament-history":"tsx src/db/scripts/add-filament-history.ts","turso:add-missing-users":"tsx src/scripts/create-missing-users.ts","turso:migrate-filaments":"tsx src/scripts/migrate-filaments.ts"},"_npmUser":{"name":"bobdivx","email":"bobdivx@gmail.com"},"_npmVersion":"11.2.0","description":"Le projet utilise un système de traduction basé sur TypeScript pour gérer le contenu multilingue. Les traductions sont stockées dans le dossier `src/i18n/locales/`.","directories":{},"resolutions":{"chart.js":"^4.4.8","preact-chartjs-2":"^1.0.3"},"_nodeVersion":"20.17.0","dependencies":{"jsqr":"^1.4.0","uuid":"^11.1.0","vite":"^6.2.5","astro":"^5.6.1","dexie":"^4.0.11","jsdom":"^26.1.0","dotenv":"^16.4.5","marked":"^15.0.8","preact":"^10.26.5","qrcode":"^1.5.4","sqlite":"^5.1.1","daisyui":"^5.0.19","esbuild":"^0.25.2","mermaid":"^11.6.0","sqlite3":"^5.1.7","bcryptjs":"^2.4.3","chart.js":"^4.4.9","csv-parse":"^5.6.0","dompurify":"^3.2.5","jsbarcode":"^3.11.6","astro-icon":"^1.1.5","handlebars":"^4.7.8","nodemailer":"^6.10.1","preact-iso":"^2.9.1","@astrojs/db":"^0.14.10","@types/uuid":"^10.0.0","tailwindcss":"^3.4.1","@astrojs/mdx":"^4.2.4","@types/jsdom":"^21.1.7","html5-qrcode":"^2.3.8","jsonwebtoken":"^9.0.2","@astrojs/node":"^9.1.3","@types/bcrypt":"^5.0.2","@types/marked":"^5.0.2","@types/qrcode":"^1.5.5","lucide-preact":"^0.488.0","preact-router":"^4.1.2","@astrojs/check":"^0.9.4","@libsql/client":"^0.15.4","@zxing/browser":"^0.1.5","@zxing/library":"^0.21.3","astro-i18n-aut":"^0.7.3","better-sqlite3":"^11.9.1","@astrojs/preact":"^4.0.10","@astrojs/vercel":"^8.1.3","@preact/signals":"^1.2.2","@types/chart.js":"^2.9.41","@types/dompurify":"^3.0.5","@vercel/postgres":"^0.10.0","preact-chartjs-2":"^1.0.3","qrcode-generator":"^1.4.4","@astrojs/tailwind":"^5.1.0","@types/handlebars":"^4.0.40","@types/nodemailer":"^6.4.17","@ericblade/quagga2":"^1.8.4","@types/jsonwebtoken":"^9.0.9","@tanstack/react-table":"^8.21.2","@mermaid-js/mermaid-cli":"^11.4.2"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.3","glob":"^11.0.1","eslint":"^8.57.0","ts-node":"^10.9.2","prettier":"^3.2.5","typescript":"^5.3.3","@types/node":"^20.11.25","@types/bcryptjs":"^2.4.6","@playwright/test":"^1.51.1","eslint-plugin-astro":"^0.30.0","@types/better-sqlite3":"^7.6.13","prettier-plugin-astro":"^0.13.0","@typescript-eslint/parser":"^7.0.0","eslint-plugin-unused-imports":"^4.1.4","@typescript-eslint/eslint-plugin":"^7.0.0"},"_npmOperationalInternal":{"tmp":"tmp/mf3d_1.0.2_1745500121930_0.3874220686603109","host":"s3://npm-registry-packages-npm-production"}},"1.0.3":{"name":"@bobdivx/mf3d","type":"module","version":"1.0.3","private":false,"main":"dist/index.js","types":"dist/index.d.ts","publishConfig":{"access":"public"},"scripts":{"dev":"astro dev --host","start":"astro dev","build":"astro check && astro build","build:errors":"astro build > build-errors.log 2>&1","build:check":"powershell -File scripts/build-check.ps1","preview":"astro preview","astro":"astro","test":"playwright test","test:ui":"playwright test --ui","test:debug":"playwright test --debug","coverage":"vitest run --coverage","lint":"eslint . --ext .ts,.tsx --fix","lint:check":"eslint . --ext .ts,.tsx","format":"prettier --write .","typecheck":"tsc --noEmit","migrate":"tsx src/lib/db/migrate.ts","migrate:reset":"tsx src/db/reset-db.ts","migrate:all-data":"tsx scripts/migrate-all-data.ts","migrate:users":"tsx src/scripts/migrate-users.ts","migrate:profiles":"tsx src/scripts/migrate-profiles.ts","set-passwords":"tsx scripts/set-initial-passwords.ts","reset-passwords":"tsx scripts/reset-passwords.ts","check-passwords":"tsx scripts/check-passwords.ts","add-test-filament":"tsx src/scripts/add-test-filament.ts","create-test-user":"tsx src/scripts/create-test-user.ts","check-db":"tsx scripts/check-db.ts","add-test-notification":"tsx scripts/add-test-notification.ts","migrate:foreign-keys":"tsx scripts/migrate-foreign-keys.ts","backup":"tsx src/scripts/backup.ts","import":"tsx src/scripts/import-data.ts","check:db":"tsx src/scripts/check-db.ts","migrate:database":"tsx src/scripts/migrate-database.ts","check:schema":"tsx src/scripts/check-schema.ts","init:db":"tsx src/scripts/init-db.ts","convert:csv":"tsx src/scripts/convert-sql-to-csv.ts","import:csv":"tsx src/scripts/import-csv.ts","backup:db":"tsx src/scripts/backup-db.ts","list:backups":"tsx src/scripts/list-backups.ts","restore:db":"tsx src/scripts/restore-db.ts","db:init":"tsx src/db/scripts/init-db.ts","db:backup":"tsx src/db/scripts/backup-db.ts","db:restore":"tsx src/db/scripts/restore-db.ts","db:reset":"tsx src/db/scripts/reset-db.ts","db:restore-users":"tsx src/db/scripts/restore-users.ts","db:update-passwords":"tsx src/db/scripts/update-passwords.ts","db:add-quantity":"tsx src/db/scripts/add-quantity-column.ts","db:add-filament-history":"tsx src/db/scripts/add-filament-history.ts","migrate:to-turso":"tsx src/scripts/migrate-to-turso.ts","test:sync":"tsx src/scripts/test-sync.ts","turso:init":"tsx src/scripts/init-turso.ts","turso:add-missing-users":"tsx src/scripts/create-missing-users.ts","turso:migrate-filaments":"tsx src/scripts/migrate-filaments.ts","turso:verify":"tsx src/scripts/verify-turso-data.ts","turso:test-connection":"tsx src/scripts/test-turso-connection.ts","use-turso-db":"tsx src/scripts/use-turso-db.ts","use-local-db":"tsx src/scripts/use-local-db.ts","check-db-source":"tsx src/scripts/check-db-source.ts","dev:turso":"npm run use-turso-db && npm run dev","fix:unused":"tsx scripts/fix-unused-vars.js","test-connection":"tsx scripts/test-connection.ts","migrate:qrcodes":"tsx src/scripts/migrate-qrcodes.ts","remove:columns":"tsx src/scripts/remove-columns.ts"},"dependencies":{"@astrojs/check":"^0.9.4","@astrojs/db":"^0.14.10","@astrojs/mdx":"^4.2.4","@astrojs/node":"^9.1.3","@astrojs/preact":"^4.0.10","@astrojs/tailwind":"^5.1.0","@astrojs/vercel":"^8.1.3","@ericblade/quagga2":"^1.8.4","@libsql/client":"^0.15.4","@mermaid-js/mermaid-cli":"^11.4.2","@preact/signals":"^1.2.2","@tanstack/react-table":"^8.21.2","@types/bcrypt":"^5.0.2","@types/chart.js":"^2.9.41","@types/dompurify":"^3.0.5","@types/handlebars":"^4.0.40","@types/jsdom":"^21.1.7","@types/jsonwebtoken":"^9.0.9","@types/marked":"^5.0.2","@types/nodemailer":"^6.4.17","@types/qrcode":"^1.5.5","@types/uuid":"^10.0.0","@vercel/postgres":"^0.10.0","@zxing/browser":"^0.1.5","@zxing/library":"^0.21.3","astro":"^5.6.1","astro-i18n-aut":"^0.7.3","astro-icon":"^1.1.5","bcryptjs":"^2.4.3","better-sqlite3":"^11.9.1","chart.js":"^4.4.9","csv-parse":"^5.6.0","daisyui":"^5.0.19","dexie":"^4.0.11","dompurify":"^3.2.5","dotenv":"^16.4.5","esbuild":"^0.25.2","handlebars":"^4.7.8","html5-qrcode":"^2.3.8","jsbarcode":"^3.11.6","jsdom":"^26.1.0","jsonwebtoken":"^9.0.2","jsqr":"^1.4.0","lucide-preact":"^0.488.0","marked":"^15.0.8","mermaid":"^11.6.0","nodemailer":"^6.10.1","preact":"^10.26.5","preact-chartjs-2":"^1.0.3","preact-iso":"^2.9.1","preact-router":"^4.1.2","qrcode":"^1.5.4","qrcode-generator":"^1.4.4","sqlite":"^5.1.1","sqlite3":"^5.1.7","tailwindcss":"^3.4.1","uuid":"^11.1.0","vite":"^6.2.5"},"devDependencies":{"@playwright/test":"^1.51.1","@types/bcryptjs":"^2.4.6","@types/better-sqlite3":"^7.6.13","@types/node":"^20.11.25","@typescript-eslint/eslint-plugin":"^7.0.0","@typescript-eslint/parser":"^7.0.0","eslint":"^8.57.0","eslint-plugin-astro":"^0.30.0","eslint-plugin-unused-imports":"^4.1.4","glob":"^11.0.1","prettier":"^3.2.5","prettier-plugin-astro":"^0.13.0","ts-node":"^10.9.2","tsx":"^4.19.3","typescript":"^5.3.3"},"resolutions":{"chart.js":"^4.4.8","preact-chartjs-2":"^1.0.3"},"_id":"@bobdivx/mf3d@1.0.3","gitHead":"df102d0b0ab2184098afd7ead9995fe75c44dedc","description":"Le projet utilise un système de traduction basé sur TypeScript pour gérer le contenu multilingue. Les traductions sont stockées dans le dossier `src/i18n/locales/`.","_nodeVersion":"20.17.0","_npmVersion":"11.2.0","dist":{"integrity":"sha512-f/m++NpTt/50ETOiKzneIsQ4zj35TOChTicmtCJPyxT7so1flDqJh3JTSCtjl3r/rhyC5tanI5JR/wIAnetHTg==","shasum":"18bfbd9fd3c3f8661c62baa0432baaab45b4f7ef","tarball":"https://registry.npmjs.org/@bobdivx/mf3d/-/mf3d-1.0.3.tgz","fileCount":533,"unpackedSize":32305709,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDuQc8ZGuS6C3xvL1vghONpS/usEE7T3XmT4SmtNIO5tAIhAJymCmAxRzVPRQ2N/QvE1/wXRjpsYAliG1vYce6adokg"}]},"_npmUser":{"name":"bobdivx","email":"bobdivx@gmail.com"},"directories":{},"maintainers":[{"name":"bobdivx","email":"bobdivx@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/mf3d_1.0.3_1745500841799_0.10736857842984127"},"_hasShrinkwrap":false}},"time":{"created":"2025-04-24T13:08:41.795Z","modified":"2025-04-24T13:20:42.521Z","1.0.2":"2025-04-24T13:08:42.200Z","1.0.3":"2025-04-24T13:20:42.345Z"},"description":"Le projet utilise un système de traduction basé sur TypeScript pour gérer le contenu multilingue. Les traductions sont stockées dans le dossier `src/i18n/locales/`.","maintainers":[{"name":"bobdivx","email":"bobdivx@gmail.com"}],"readme":"# Filament Manager\r\n\r\n## Gestion des traductions\r\n\r\nLe projet utilise un système de traduction basé sur TypeScript pour gérer le contenu multilingue. Les traductions sont stockées dans le dossier `src/i18n/locales/`.\r\n\r\n### Structure des traductions\r\n\r\n```\r\nsrc/i18n/\r\n├── locales/\r\n│   ├── en/         # Dossier des traductions anglaises\r\n│   │   ├── stats.ts\r\n│   │   ├── filaments.ts\r\n│   │   └── ...\r\n│   ├── fr/         # Dossier des traductions françaises\r\n│   │   ├── stats.ts\r\n│   │   ├── filaments.ts\r\n│   │   └── ...\r\n│   ├── en.ts       # Fichier principal des traductions anglaises\r\n│   └── fr.ts       # Fichier principal des traductions françaises\r\n├── types.ts        # Types pour les traductions\r\n├── config.ts       # Configuration et utilitaires de traduction\r\n└── utils.ts        # Utilitaires (getLanguageFromURL, etc.)\r\n```\r\n\r\n### Organisation des traductions\r\n\r\nLes traductions sont organisées par modules fonctionnels. Par exemple, pour les statistiques :\r\n\r\n```typescript\r\n// src/i18n/locales/fr/stats.ts\r\nexport const stats = {\r\n  title: \"Statistiques\",\r\n  description: \"Statistiques des filaments\",\r\n  // Statistiques générales\r\n  total_filaments: \"Nombre total de filaments\",\r\n  total_weight: \"Poids total\",\r\n  // Actions\r\n  actions: {\r\n    refresh: \"Rafraîchir\",\r\n    print: \"Imprimer\",\r\n  },\r\n  // Sous-sections\r\n  filaments_stats: {\r\n    title: \"Filaments\",\r\n    // ...\r\n  },\r\n  prints_stats: {\r\n    title: \"Impressions\",\r\n    // ...\r\n  },\r\n};\r\n```\r\n\r\n### Procédure d'utilisation des traductions\r\n\r\n#### 1. Dans une page Astro\r\n\r\n1. **Imports nécessaires** :\r\n\r\n   ```astro\r\n   ---\r\n   import Layout from '@/layouts/Layout.astro';\r\n   import { getLangFromUrl, useTranslations } from '@/i18n/config';\r\n   ---\r\n   ```\r\n\r\n2. **Récupération de la langue et utilisation des traductions** :\r\n\r\n   ```astro\r\n   ---\r\n   const lang = getLangFromUrl(Astro.url);\r\n   const t = useTranslations(lang);\r\n   ---\r\n   ```\r\n\r\n3. **Structure de la page** :\r\n   ```astro\r\n   <Layout title={t('stats.title')} description={t('stats.description')} lang={lang}>\r\n     <div>\r\n       <h1>{t('stats.title')}</h1>\r\n       <p>{t('stats.description')}</p>\r\n     </div>\r\n   </Layout>\r\n   ```\r\n\r\n#### 2. Dans un composant Preact\r\n\r\n1. **Import du hook** :\r\n\r\n   ```tsx\r\n   import { useTranslations } from \"@/i18n/config\";\r\n   ```\r\n\r\n2. **Utilisation dans le composant** :\r\n\r\n   ```tsx\r\n   interface Props {\r\n     lang: Language;\r\n   }\r\n\r\n   export default function MonComposant({ lang }: Props) {\r\n     const t = useTranslations(lang);\r\n\r\n     return (\r\n       <div>\r\n         <h1>{t(\"stats.title\")}</h1>\r\n         <p>{t(\"stats.description\")}</p>\r\n       </div>\r\n     );\r\n   }\r\n   ```\r\n\r\n### Ajout de nouvelles traductions\r\n\r\n1. **Structure des fichiers de traduction** :\r\n\r\n   - Chaque module (ex: stats, filaments) a son propre fichier dans les dossiers `en/` et `fr/`\r\n   - Les fichiers principaux `en.ts` et `fr.ts` importent et agrègent tous les modules\r\n   - Toujours ajouter les traductions dans les deux langues (FR et EN)\r\n\r\n2. **Exemple d'ajout d'une nouvelle section** :\r\n\r\n   ```typescript\r\n   // Dans src/i18n/locales/fr/stats.ts\r\n   export const stats = {\r\n     // ... traductions existantes\r\n     nouvelle_section: {\r\n       title: \"Titre de la section\",\r\n       description: \"Description de la section\",\r\n       // ... autres traductions\r\n     },\r\n   };\r\n\r\n   // Dans src/i18n/locales/en/stats.ts\r\n   export const stats = {\r\n     // ... existing translations\r\n     nouvelle_section: {\r\n       title: \"Section title\",\r\n       description: \"Section description\",\r\n       // ... other translations\r\n     },\r\n   };\r\n   ```\r\n\r\n3. **Bonnes pratiques** :\r\n   - Utiliser des clés descriptives et cohérentes\r\n   - Organiser les traductions de manière hiérarchique\r\n   - Maintenir la même structure dans toutes les langues\r\n   - Vérifier que toutes les clés existent dans toutes les langues\r\n   - Utiliser des valeurs appropriées pour chaque langue\r\n\r\n### Utilisation des traductions dans le code\r\n\r\n1. **Accès simple** :\r\n\r\n   ```typescript\r\n   t(\"stats.title\");\r\n   ```\r\n\r\n2. **Avec des variables** :\r\n\r\n   ```typescript\r\n   `${value} ${t(\"stats.unit\")}`;\r\n   ```\r\n\r\n3. **Pour les actions** :\r\n   ```typescript\r\n   const actions = {\r\n     label: t(\"stats.actions.refresh\"),\r\n     onClickId: \"refresh-stats\",\r\n   };\r\n   ```\r\n\r\n### Maintenance des traductions\r\n\r\n1. **Vérification des clés manquantes** :\r\n\r\n   - S'assurer que chaque clé existe dans toutes les langues\r\n   - Maintenir la même structure hiérarchique\r\n\r\n2. **Mise à jour des traductions** :\r\n\r\n   - Modifier les fichiers de traduction correspondants\r\n   - Tester l'affichage dans toutes les langues\r\n   - Vérifier la cohérence des traductions\r\n\r\n3. **Tests** :\r\n   - Vérifier l'affichage dans l'interface\r\n   - Tester le changement de langue\r\n   - Valider les traductions avec les utilisateurs\r\n\r\n## Design System\r\n\r\n### Page Layout\r\n\r\nChaque page principale suit une structure cohérente :\r\n\r\n1. **Bandeau d'en-tête (Header Banner)**\r\n\r\n   - Dégradé de couleur personnalisable via les variables CSS\r\n   - Titre principal et sous-titre\r\n   - Actions principales\r\n   - Classes : `header-banner`, `header-banner-content`, `header-banner-title`\r\n\r\n2. **Sections de contenu**\r\n   - Cartes avec ombres et coins arrondis\r\n   - Icônes dans des cercles avec couleurs pastels\r\n   - Classes : `section-card`, `section-card-title`\r\n\r\n### Composants\r\n\r\n1. **Icônes**\r\n\r\n   - Toujours affichées dans des cercles\r\n   - Tailles standardisées :\r\n     - Grande : `section-icon` (40x40px)\r\n     - Petite : `section-icon-sm` (32x32px)\r\n   - Couleurs pastels prédéfinies :\r\n     - `section-icon-blue`\r\n     - `section-icon-purple`\r\n     - `section-icon-green`\r\n     - `section-icon-yellow`\r\n     - `section-icon-amber`\r\n\r\n2. **Boutons**\r\n   - Style principal : `header-banner-button`\r\n   - Avec icône : `header-banner-button-icon`\r\n\r\n### Thème et Personnalisation\r\n\r\nLes couleurs du bandeau sont personnalisables via les variables CSS :\r\n\r\n```css\r\n:root {\r\n  --banner-from-color: theme(\"colors.blue.500\");\r\n  --banner-to-color: theme(\"colors.purple.600\");\r\n}\r\n```\r\n\r\nPour le thème sombre :\r\n\r\n```css\r\n[data-theme=\"dark\"] {\r\n  --banner-from-color: theme(\"colors.blue.600\");\r\n  --banner-to-color: theme(\"colors.purple.700\");\r\n}\r\n```\r\n\r\n### Utilisation\r\n\r\nExemple de structure de page :\r\n\r\n```astro\r\n<div class=\"header-banner\">\r\n  <div class=\"header-banner-content\">\r\n    <h1 class=\"header-banner-title\">Titre de la page</h1>\r\n    <p class=\"header-banner-subtitle\">Description</p>\r\n  </div>\r\n</div>\r\n\r\n<div class=\"section-card\">\r\n  <h2 class=\"section-card-title\">\r\n    <div class=\"section-icon section-icon-blue\">\r\n      <Icon />\r\n    </div>\r\n    Titre de section\r\n  </h2>\r\n  <!-- Contenu -->\r\n</div>\r\n```\r\n\r\n### Gestion des types pour les traductions\r\n\r\n1. **Types de base** :\r\n\r\n   - Les traductions retournent un type `TranslationValue` qui peut être soit une `string` soit une fonction retournant une `string`\r\n   - Pour convertir une traduction en string, utilisez la fonction `getTranslationString()` :\r\n\r\n   ```typescript\r\n   import { getTranslationString } from \"@/lib/utils/translation\";\r\n\r\n   // Dans un composant\r\n   const t = useTranslations(lang);\r\n   const title = getTranslationString(t(\"page.title\"));\r\n   ```\r\n\r\n2. **Utilisation dans les composants** :\r\n\r\n   - Pour les props qui attendent une string, utilisez toujours `getTranslationString()` :\r\n\r\n   ```typescript\r\n   <Button disabled={isLoading}>\r\n     {getTranslationString(t('common.save'))}\r\n   </Button>\r\n   ```\r\n\r\n3. **Bonnes pratiques** :\r\n   - Utilisez `getTranslationString()` pour les props qui attendent une string\r\n   - Pour les textes affichés directement dans le JSX, vous pouvez utiliser directement `t()`\r\n   - Vérifiez les types TypeScript pour éviter les erreurs de compilation\r\n\r\n## Gestion des stocks de filaments\r\n\r\nLe système de gestion des stocks de filaments permet d'ajouter et de retirer des bobines entières de filament, en tenant compte du poids défini pour chaque type de filament.\r\n\r\n### Structure du système\r\n\r\n1. **Composants principaux** :\r\n\r\n   - `SpoolManager` : Interface utilisateur pour gérer le stock\r\n   - `TargetSpoolsManager` : Interface pour définir le stock cible\r\n   - `FilamentHistory` : Affichage de l'historique des modifications\r\n\r\n2. **API** :\r\n   - `/api/filaments/[id]/spool` : Endpoint pour ajouter/retirer du stock\r\n   - `/api/filaments/[id]/target` : Endpoint pour définir le stock cible\r\n\r\n### Fonctionnement du `SpoolManager`\r\n\r\nLe composant `SpoolManager` affiche l'état actuel du stock et permet d'ajouter ou de retirer des bobines complètes.\r\n\r\n```tsx\r\n<SpoolManager\r\n  filamentId={filament.id}\r\n  currentStock={remainingWeight}\r\n  targetStock={targetTotalWeight}\r\n  _lang={lang}\r\n  client:load\r\n/>\r\n```\r\n\r\n#### Propriétés importantes :\r\n\r\n- `filamentId` : Identifiant unique du filament\r\n- `currentStock` : Poids actuel restant en grammes\r\n- `targetStock` : Poids cible total en grammes\r\n- `_lang` : Langue courante pour les traductions\r\n\r\n#### Caractéristiques :\r\n\r\n1. **Affichage** :\r\n\r\n   - Poids en kilogrammes (pour plus de lisibilité)\r\n   - Nombre de bobines actuelles et cibles\r\n   - Pourcentage du stock par rapport à l'objectif\r\n   - Radar visuel montrant la proportion du stock\r\n\r\n2. **Actions** :\r\n   - Bouton pour ajouter une bobine entière\r\n   - Bouton pour retirer une bobine entière\r\n   - Validations pour empêcher le stock négatif\r\n\r\n### API de gestion du stock\r\n\r\nL'API `/api/filaments/[id]/spool` est conçue pour gérer les ajouts et retraits de stock.\r\n\r\n#### Requête :\r\n\r\n```typescript\r\n// Exemple d'appel pour ajouter du stock\r\nfetch(`/api/filaments/${filamentId}/spool`, {\r\n  method: \"POST\",\r\n  headers: {\r\n    \"Content-Type\": \"application/json\",\r\n  },\r\n  body: JSON.stringify({ weight: 1000 }), // Poids positif pour ajouter\r\n});\r\n\r\n// Exemple d'appel pour retirer du stock\r\nfetch(`/api/filaments/${filamentId}/spool`, {\r\n  method: \"POST\",\r\n  headers: {\r\n    \"Content-Type\": \"application/json\",\r\n  },\r\n  body: JSON.stringify({ weight: -1000 }), // Poids négatif pour retirer\r\n});\r\n```\r\n\r\n#### Fonctionnement interne :\r\n\r\n1. **Récupération du contexte** :\r\n\r\n   - Poids actuel du filament (`remaining_weight`)\r\n   - Poids d'une bobine individuelle (`weight`)\r\n\r\n2. **Calcul du nouveau poids** :\r\n\r\n   - Addition du poids fourni au poids actuel\r\n   - Validation que le résultat n'est pas négatif\r\n\r\n3. **Mise à jour de la base de données** :\r\n\r\n   - Mise à jour du champ `remaining_weight`\r\n   - Ajout d'une entrée dans l'historique avec l'opération et la quantité\r\n\r\n4. **Réponse** :\r\n   ```json\r\n   {\r\n     \"success\": true,\r\n     \"previousWeight\": 1000,\r\n     \"newWeight\": 2000,\r\n     \"spoolWeight\": 1000,\r\n     \"difference\": 1000,\r\n     \"totalSpools\": 2\r\n   }\r\n   ```\r\n\r\n### Historique des modifications\r\n\r\nChaque modification de stock est enregistrée dans la table `filament_history` avec les informations suivantes :\r\n\r\n- `filament_id` : ID du filament concerné\r\n- `operation` : Type d'opération ('add' ou 'remove')\r\n- `quantity` : Quantité ajoutée ou retirée (toujours positive)\r\n- `user_id` : ID de l'utilisateur ayant effectué l'opération\r\n- `created_at` : Date et heure de l'opération\r\n\r\n### Bonnes pratiques\r\n\r\n1. **Opérations par bobines entières** :\r\n\r\n   - Toujours ajouter ou retirer des bobines entières\r\n   - Le poids d'une bobine est défini dans la base de données pour chaque filament\r\n\r\n2. **Validation des données** :\r\n\r\n   - Vérifier que le stock ne devient pas négatif\r\n   - Valider les entrées utilisateur\r\n   - Utiliser des valeurs par défaut appropriées\r\n\r\n3. **Affichage des informations** :\r\n\r\n   - Afficher les poids en kilogrammes (plus lisible)\r\n   - Afficher également le nombre de bobines\r\n   - Utiliser des indicateurs visuels (comme le radar de stock)\r\n\r\n4. **Gestion des erreurs** :\r\n   - Afficher des messages d'erreur clairs\r\n   - Utiliser le composant Toast pour les notifications\r\n   - Logger les erreurs pour le débogage\r\n\r\n### Exemple complet\r\n\r\n```tsx\r\n// Composant SpoolManager (version simplifiée)\r\nfunction SpoolManager({ filamentId, currentStock, targetStock, _lang }) {\r\n  const t = useTranslations(_lang);\r\n  const handleUpdateStock = async (action) => {\r\n    try {\r\n      const weightChange = action === \"add\" ? spoolWeight : -spoolWeight;\r\n      await fetch(`/api/filaments/${filamentId}/spool`, {\r\n        method: \"POST\",\r\n        headers: { \"Content-Type\": \"application/json\" },\r\n        body: JSON.stringify({ weight: weightChange }),\r\n      });\r\n      // Notification et rafraîchissement\r\n    } catch (error) {\r\n      // Gestion des erreurs\r\n    }\r\n  };\r\n\r\n  return (\r\n    <div>\r\n      {/* Affichage du stock */}\r\n      <div>\r\n        {currentStockKg} kg ({currentSpools} bobines)\r\n      </div>\r\n\r\n      {/* Boutons d'action */}\r\n      <Button onClick={() => handleUpdateStock(\"add\")}>\r\n        Ajouter une bobine\r\n      </Button>\r\n      <Button onClick={() => handleUpdateStock(\"remove\")}>\r\n        Retirer une bobine\r\n      </Button>\r\n    </div>\r\n  );\r\n}\r\n```\r\n\r\n## Authentification et Comptes de Test\r\n\r\nLe système d'authentification utilise bcrypt pour le hachage sécurisé des mots de passe et des sessions basées sur des tokens.\r\n\r\n### Comptes de Test Disponibles\r\n\r\nDeux comptes de test sont disponibles pour le développement :\r\n\r\n1. **Compte Administrateur**\r\n\r\n   - Email : bobdivx@gmail.com\r\n   - Mot de passe : 8tc6vr89\r\n   - Rôle : admin\r\n\r\n2. **Compte Utilisateur Test**\r\n   - Email : test@exemple.com\r\n   - Mot de passe : password\r\n   - Rôle : user\r\n\r\n### Processus d'Authentification\r\n\r\n1. **Connexion**\r\n\r\n   - Les identifiants sont envoyés à `/api/auth/login`\r\n   - Le serveur vérifie l'existence de l'utilisateur\r\n   - Le mot de passe est vérifié avec bcrypt\r\n   - Une session est créée avec un token unique\r\n   - Un cookie sécurisé est défini avec le token\r\n\r\n2. **Sécurité**\r\n\r\n   - Les mots de passe sont hachés avec bcrypt (10 rounds)\r\n   - Les sessions expirent après 7 jours\r\n   - Les cookies sont configurés avec :\r\n     - HttpOnly\r\n     - SameSite=Lax\r\n     - Secure en production\r\n     - Path=/\r\n\r\n3. **Déconnexion**\r\n   - La session est supprimée de la base de données\r\n   - Le cookie est expiré\r\n\r\n### Dépannage de l'Authentification\r\n\r\nSi vous rencontrez des problèmes de connexion :\r\n\r\n1. **Vérifiez les logs**\r\n\r\n   - Les logs détaillés sont disponibles dans la console du navigateur\r\n   - Les logs serveur montrent le processus d'authentification\r\n\r\n2. **Vérifiez les cookies**\r\n\r\n   - Le cookie `turso-token` doit être présent après la connexion\r\n   - Les options du cookie doivent correspondre à la configuration\r\n\r\n3. **Vérifiez la base de données**\r\n   - L'utilisateur doit exister dans la table `auth_users`\r\n   - Le mot de passe doit être correctement haché\r\n   - La session doit être créée dans la table `sessions`\r\n","readmeFilename":"README.md"}