Update project dependencies and enhance documentation

- Added `barryvdh/laravel-dompdf` to `composer.json` for PDF generation capabilities.
- Updated `boost.json` to include `tailwindcss-development` in skills.
- Modified `package.json` and `package-lock.json` to upgrade Tailwind CSS and related packages.
- Improved README.md for clarity and corrected French language errors.
- Created design system documentation for Filament components.
- Added new Filament pages for Dashboard and Documentation with dynamic content loading.
- Enhanced TablesExplorer functionality with improved table and column management.
This commit is contained in:
2026-02-20 14:16:24 +01:00
parent 4aef33f270
commit 657c5ad5e3
46 changed files with 3318 additions and 857 deletions

View File

@@ -0,0 +1,110 @@
---
description: Design system conventions for all Filament page views in this application
globs:
- resources/views/filament/pages/**/*.blade.php
- resources/views/components/logistics/**/*.blade.php
---
# Design System -- API Logistics
## Composants Blade reutilisables
Tous les elements visuels recurrents sont centralises dans `resources/views/components/logistics/`.
Utiliser ces composants au lieu de dupliquer du HTML/CSS.
| Composant | Usage | Fichier |
|---|---|---|
| `<x-logistics.card>` | Conteneur principal (panneau blanc arrondi) | `logistics/card.blade.php` |
| `<x-logistics.section-header>` | En-tete de section (titre + description + slot actions) | `logistics/section-header.blade.php` |
| `<x-logistics.error-banner>` | Bandeau d'erreur API | `logistics/error-banner.blade.php` |
| `<x-logistics.stat-bar>` | Barre de metadata/statistiques | `logistics/stat-bar.blade.php` |
| `<x-logistics.stat-item>` | Element individuel dans stat-bar | `logistics/stat-item.blade.php` |
| `<x-logistics.data-table>` | Tableau de donnees dynamique | `logistics/data-table.blade.php` |
| `<x-logistics.empty-state>` | Etat vide (icone + texte) | `logistics/empty-state.blade.php` |
| `<x-logistics.search-input>` | Champ de recherche avec icone loupe | `logistics/search-input.blade.php` |
| `<x-logistics.form-field>` | Champ de formulaire (label + input) | `logistics/form-field.blade.php` |
| `<x-logistics.json-block>` | Bloc JSON formate | `logistics/json-block.blade.php` |
## Conventions CSS
### Carte (card)
- `rounded-xl bg-white shadow-sm ring-1 ring-gray-950/5 dark:bg-gray-900 dark:ring-white/10`
### En-tete de section dans une carte
- Wrapper: `border-b border-gray-200 px-6 py-4 dark:border-white/10`
- Titre: `text-base font-semibold text-gray-950 dark:text-white`
- Description: `mt-1 text-xs text-gray-500 dark:text-gray-400`
### Contenu de carte
- Padding: `p-6`
### Tableau de donnees
- Conteneur: `overflow-x-auto`
- Table: `w-full text-left text-sm`
- Thead tr: `border-b border-gray-200 dark:border-white/10`
- Th: `px-3 py-2.5 text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400`
- Tbody: `divide-y divide-gray-100 dark:divide-white/5`
- Tr: `transition-colors hover:bg-gray-50 dark:hover:bg-white/5`
- Td: `px-3 py-2.5 text-sm text-gray-700 dark:text-gray-300`
- Td valeur technique (ID, code): ajouter `font-mono`
### Badge compteur
- `rounded-full px-2 py-0.5 text-xs font-medium tabular-nums bg-gray-100 text-gray-600 dark:bg-white/10 dark:text-gray-300`
### Champ de formulaire (input)
- `w-full rounded-lg border-gray-300 py-2 text-sm shadow-sm focus:border-primary-500 focus:ring-primary-500 dark:border-white/10 dark:bg-white/5 dark:text-white`
- Label: `block text-sm font-medium text-gray-700 dark:text-gray-300`
- Espacement label/input: `mt-1.5`
### Bloc JSON (pre)
- `rounded-lg border border-gray-200 bg-gray-50 p-4 text-xs font-mono leading-relaxed text-gray-700 dark:border-white/10 dark:bg-gray-800 dark:text-gray-300`
### Etat vide
- Conteneur: `flex flex-col items-center justify-center py-12 text-center`
- Icone: `h-10 w-10 text-gray-300 dark:text-gray-600`
- Titre: `mt-3 text-sm font-medium text-gray-900 dark:text-white`
- Description: `mt-1 text-sm text-gray-500 dark:text-gray-400`
### Erreur API
- `rounded-lg bg-danger-50 p-4 text-sm text-danger-600 dark:bg-danger-400/10 dark:text-danger-400`
### Stat bar
- `flex flex-wrap items-center gap-x-6 gap-y-2`
- Chaque item: icone h-4 w-4 + texte text-sm text-gray-500
### Loading
- Utiliser `<x-filament::loading-indicator>` + `wire:loading` / `wire:loading.remove`
## Structure d'une page type
```blade
<x-filament-panels::page>
<x-logistics.error-banner :message="$errorMessage" />
{{-- Stat bar optionnelle --}}
{{-- Section formulaire --}}
<x-logistics.card>
<x-logistics.section-header title="..." description="..." />
<div class="p-6">
{{-- Formulaire avec grille --}}
</div>
</x-logistics.card>
{{-- Section resultats --}}
<x-logistics.card>
<x-logistics.section-header title="..." />
<div class="p-6">
<x-logistics.data-table :data="$data" :metadata="$metadata" />
</div>
</x-logistics.card>
</x-filament-panels::page>
```
## Regles
- Toujours utiliser les composants `x-logistics.*` au lieu de dupliquer les classes CSS.
- Un composant `<x-logistics.card>` n'a PAS de padding interne. Le padding est gere par les enfants.
- Les formulaires dans une carte utilisent `<div class="p-6">` pour le contenu.
- Toujours afficher un etat de chargement (`wire:loading`) sur les actions reseau.
- Les blocs JSON utilisent `<x-logistics.json-block>` et non `<pre>` brut.

View File

@@ -13,6 +13,7 @@ The Laravel Boost guidelines are specifically curated by Laravel maintainers for
This application is a Laravel application and its main Laravel ecosystems package & versions are below. You are an expert with them all. Ensure you abide by these specific packages & versions.
- php - 8.4.16
- filament/filament (FILAMENT) - v5
- laravel/fortify (FORTIFY) - v1
- laravel/framework (LARAVEL) - v12
- laravel/prompts (PROMPTS) - v0
@@ -23,6 +24,7 @@ This application is a Laravel application and its main Laravel ecosystems packag
- laravel/sail (SAIL) - v1
- pestphp/pest (PEST) - v4
- phpunit/phpunit (PHPUNIT) - v12
- tailwindcss (TAILWINDCSS) - v4
## Skills Activation
@@ -31,6 +33,7 @@ This project has domain-specific skills available. You MUST activate the relevan
- `fluxui-development` — Develops UIs with Flux UI Free components. Activates when creating buttons, forms, modals, inputs, dropdowns, checkboxes, or UI components; replacing HTML form elements with Flux; working with flux: components; or when the user mentions Flux, component library, UI components, form fields, or asks about available Flux components.
- `livewire-development` — Develops reactive Livewire 4 components. Activates when creating, updating, or modifying Livewire components; working with wire:model, wire:click, wire:loading, or any wire: directives; adding real-time updates, loading states, or reactivity; debugging component behavior; writing Livewire tests; or when the user mentions Livewire, component, counter, or reactive UI.
- `pest-testing` — Tests applications using the Pest 4 PHP framework. Activates when writing tests, creating unit or feature tests, adding assertions, testing Livewire components, browser testing, debugging test failures, working with datasets or mocking; or when the user mentions test, spec, TDD, expects, assertion, coverage, or needs to verify functionality works.
- `tailwindcss-development` — Styles applications using Tailwind CSS v4 utilities. Activates when adding styles, restyling components, working with gradients, spacing, layout, flex, grid, responsive design, dark mode, colors, typography, or borders; or when the user mentions CSS, styling, classes, Tailwind, restyle, hero section, cards, buttons, or any visual/UI changes.
## Conventions
@@ -261,4 +264,12 @@ protected function isAccessible(User $user, ?string $path = null): bool
- Do NOT delete tests without approval.
- CRITICAL: ALWAYS use `search-docs` tool for version-specific Pest documentation and updated code examples.
- IMPORTANT: Activate `pest-testing` every time you're working with a Pest or testing-related task.
=== tailwindcss/core rules ===
# Tailwind CSS
- Always use existing Tailwind conventions; check project patterns before adding new ones.
- IMPORTANT: Always use `search-docs` tool for version-specific Tailwind CSS documentation and updated code examples. Never rely on training data.
- IMPORTANT: Activate `tailwindcss-development` every time you're working with a Tailwind CSS or styling-related task.
</laravel-boost-guidelines>

View File

@@ -0,0 +1,69 @@
# Update Documentation
Quand l'utilisateur dit **"update documentation"**, tu DOIS mettre a jour le fichier `documentation/documentation_api_logistics.md` en suivant cette procedure.
## Procedure de mise a jour
1. **Lire les sources suivantes** (dans cet ordre) :
- `app/Services/LogisticsService.php` : toutes les methodes publiques = endpoints disponibles. Les PHPDoc `@param` contiennent les parametres attendus.
- `config/logistics.php` : configuration de connexion (variables d'environnement, valeurs par defaut).
- `.env` : valeurs actuelles des variables de configuration.
- `documentation/documentation_api_logistics.md` : documentation existante a mettre a jour.
- `memory-bank/techContext.md` : contexte technique (tables, types de colonnes, endpoints connus).
2. **Identifier les changements** :
- Nouvelles methodes dans `LogisticsService` = nouveaux endpoints a documenter.
- Methodes supprimees = endpoints a retirer.
- Parametres modifies (PHPDoc `@param`) = mise a jour des tableaux de parametres.
- Nouvelles variables d'environnement dans `config/logistics.php` = mise a jour de la section pre-requis.
3. **Mettre a jour le fichier** en preservant strictement cette structure :
```
# Documentation API Logistics (Flex/ESI Gescom)
Derniere mise a jour : <date du jour>
## Table des matieres
## 1. Pre-requis
## 2. Comment effectuer des requetes
## 3. Structure de reponse
## 4. Tables et colonnes disponibles
## 5. Recuperation de donnees
### 5.1 Structure de la base de donnees (tables_list, column_list)
### 5.2 Articles (art_list, art_getstk)
### 5.3 Journaux (jnl_list)
### 5.4 Documents (document_list, document_detail, Document_GetStatusList, Document_GetUnitPriceAndVat, Document_GetDueDate, Document_GetAttachListThumbnail)
### 5.5 Tiers (third_list, third_GetArtHistory)
### 5.6 Divers (getserialnumber, codes_list)
## 6. Envoi de donnees
### 6.1 Ajout d'un document (document_add)
### 6.2 Modification d'un document (document_mod)
## 7. Endpoints non fonctionnels
## 8. Relations entre entites
## 9. Remarques et points d'attention
## 10. Ressources externes
```
4. **Pour chaque endpoint, documenter** :
- Description fonctionnelle (a quoi il sert).
- URL au format `POST /{dossier}/{endpoint}`.
- Methode service correspondante (`LogisticsService::methode()`).
- Tableau des parametres : nom, type, obligatoire (Oui/Non), description detaillee.
- Exemple de requete (body JSON).
- Exemple de reponse si disponible.
5. **Classer les endpoints** :
- Section 5 (Recuperation) : endpoints qui lisent des donnees (toutes les methodes sauf `documentAdd` et `documentMod`).
- Section 6 (Envoi) : endpoints qui creent ou modifient des donnees (`documentAdd`, `documentMod`).
- Section 7 : endpoints identifies mais non fonctionnels.
6. **Mettre a jour la date** en haut du fichier (`Derniere mise a jour : <date du jour>`).
## Regles
- Ne jamais supprimer d'information existante sans raison (endpoint supprime du service).
- Ton factuel et concis, sans emojis.
- Toujours inclure les chemins de fichiers exacts dans les references.
- Les exemples de requete doivent utiliser des valeurs realistes.
- La table des matieres doit refleter les sections du document.
- Les endpoints non fonctionnels restent documentes avec le statut "Non fonctionnel" et la description du probleme.

View File

@@ -0,0 +1,124 @@
---
name: tailwindcss-development
description: >-
Styles applications using Tailwind CSS v4 utilities. Activates when adding styles, restyling components,
working with gradients, spacing, layout, flex, grid, responsive design, dark mode, colors,
typography, or borders; or when the user mentions CSS, styling, classes, Tailwind, restyle,
hero section, cards, buttons, or any visual/UI changes.
---
# Tailwind CSS Development
## When to Apply
Activate this skill when:
- Adding styles to components or pages
- Working with responsive design
- Implementing dark mode
- Extracting repeated patterns into components
- Debugging spacing or layout issues
## Documentation
Use `search-docs` for detailed Tailwind CSS v4 patterns and documentation.
## Basic Usage
- Use Tailwind CSS classes to style HTML. Check and follow existing Tailwind conventions in the project before introducing new patterns.
- Offer to extract repeated patterns into components that match the project's conventions (e.g., Blade, JSX, Vue).
- Consider class placement, order, priority, and defaults. Remove redundant classes, add classes to parent or child elements carefully to reduce repetition, and group elements logically.
## Tailwind CSS v4 Specifics
- Always use Tailwind CSS v4 and avoid deprecated utilities.
- `corePlugins` is not supported in Tailwind v4.
### CSS-First Configuration
In Tailwind v4, configuration is CSS-first using the `@theme` directive — no separate `tailwind.config.js` file is needed:
<code-snippet name="CSS-First Config" lang="css">
@theme {
--color-brand: oklch(0.72 0.11 178);
}
</code-snippet>
### Import Syntax
In Tailwind v4, import Tailwind with a regular CSS `@import` statement instead of the `@tailwind` directives used in v3:
<code-snippet name="v4 Import Syntax" lang="diff">
- @tailwind base;
- @tailwind components;
- @tailwind utilities;
+ @import "tailwindcss";
</code-snippet>
### Replaced Utilities
Tailwind v4 removed deprecated utilities. Use the replacements shown below. Opacity values remain numeric.
| Deprecated | Replacement |
|------------|-------------|
| bg-opacity-* | bg-black/* |
| text-opacity-* | text-black/* |
| border-opacity-* | border-black/* |
| divide-opacity-* | divide-black/* |
| ring-opacity-* | ring-black/* |
| placeholder-opacity-* | placeholder-black/* |
| flex-shrink-* | shrink-* |
| flex-grow-* | grow-* |
| overflow-ellipsis | text-ellipsis |
| decoration-slice | box-decoration-slice |
| decoration-clone | box-decoration-clone |
## Spacing
Use `gap` utilities instead of margins for spacing between siblings:
<code-snippet name="Gap Utilities" lang="html">
<div class="flex gap-8">
<div>Item 1</div>
<div>Item 2</div>
</div>
</code-snippet>
## Dark Mode
If existing pages and components support dark mode, new pages and components must support it the same way, typically using the `dark:` variant:
<code-snippet name="Dark Mode" lang="html">
<div class="bg-white dark:bg-gray-900 text-gray-900 dark:text-white">
Content adapts to color scheme
</div>
</code-snippet>
## Common Patterns
### Flexbox Layout
<code-snippet name="Flexbox Layout" lang="html">
<div class="flex items-center justify-between gap-4">
<div>Left content</div>
<div>Right content</div>
</div>
</code-snippet>
### Grid Layout
<code-snippet name="Grid Layout" lang="html">
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<div>Card 1</div>
<div>Card 2</div>
<div>Card 3</div>
</div>
</code-snippet>
## Common Pitfalls
- Using deprecated v3 utilities (bg-opacity-*, flex-shrink-*, etc.)
- Using `@tailwind` directives instead of `@import "tailwindcss"`
- Trying to use `tailwind.config.js` instead of CSS `@theme` directive
- Using margins for spacing between siblings instead of gap utilities
- Forgetting to add dark mode variants when the project uses dark mode

View File

@@ -1,14 +1,13 @@
# API Logistics
Application Laravel 12 de test pour comprendre et documenter l'API Logistics (Flex/ESI Gescom). Elle fournit un dashboard Filament v5 permettant d'interroger les differents endpoints de l'API.
Application Laravel 12 de test pour comprendre et documenter l'API Logistics (Flex/ESI Gescom). Elle fournit un dashboard Filament v5 permettant d'interroger les différents endpoints de l'API.
## Prerequis
## Prérequis
- PHP 8.4+
- Composer
- Node.js et npm
- MySQL
- Laravel Herd (recommande)
## Installation
@@ -23,9 +22,9 @@ php artisan key:generate
## Configuration
### Base de donnees
### Base de données
Creer une base de donnees MySQL nommee `api_logistics`, puis configurer le fichier `.env` :
Créer une base de données MySQL nommée `api_logistics`, puis configurer le fichier `.env` :
```
DB_CONNECTION=mysql
@@ -36,7 +35,7 @@ DB_USERNAME=root
DB_PASSWORD=
```
Executer les migrations :
Exécuter les migrations :
```bash
php artisan migrate
@@ -44,7 +43,7 @@ php artisan migrate
### API Logistics
Configurer la connexion a l'API Logistics dans le fichier `.env` :
Configurer la connexion à l'API Logistics dans le fichier `.env` :
```
LOGISTICS_API_BASE_URL=http://tse-10-test.esiweb.pro
@@ -53,40 +52,41 @@ LOGISTICS_API_FOLDER=esigescom
```
- **LOGISTICS_API_BASE_URL** : URL de base du serveur Logistics
- **LOGISTICS_API_KEY** : Cle API transmise via le header `X-API-KEY`
- **LOGISTICS_API_FOLDER** : Nom du dossier (en minuscules) utilise dans les routes de l'API
- **LOGISTICS_API_KEY** : Clé API transmise via le header `X-API-KEY`
- **LOGISTICS_API_FOLDER** : Nom du dossier (en minuscules) utilisé dans les routes de l'API
## Demarrage
## Démarrage
```bash
npm run build
```
L'application est disponible via Laravel Herd a l'adresse `http://api-logistics.test`.
L'application est disponible à l'adresse `http://api-logistics.test`.
Le dashboard Filament est accessible a l'adresse `http://api-logistics.test/admin`.
Le dashboard Filament est accessible à l'adresse `http://api-logistics.test/admin`.
## Structure du dashboard
Le dashboard Filament propose les pages suivantes :
- **Documentation** : Documentation de l'API Logistics
- **Tables** : Explorer les tables disponibles dans l'API et visualiser leurs colonnes
- **Articles** : Rechercher des articles et verifier le stock
- **Documents** : Lister des documents et consulter leurs details
- **Articles** : Rechercher des articles et vérifier le stock
- **Documents** : Lister des documents et consulter leurs détails
- **Journaux** : Rechercher et lister les journaux
- **Tiers** : Rechercher des tiers et consulter l'historique des articles
## Architecture technique
- `config/logistics.php` : Configuration de l'API Logistics
- `app/Services/LogisticsService.php` : Service centralise pour les appels HTTP vers l'API
- `app/Services/LogisticsService.php` : Service centralisé pour les appels HTTP vers l'API
- `app/Filament/Pages/` : Pages Filament du dashboard
- `database/migrations/` : Migrations incluant la table `api_request_logs` pour le suivi des requetes
- `database/migrations/` : Migrations incluant la table `api_request_logs` pour le suivi des requêtes
## Documentation de l'API
- [Documentation Postman](https://documenter.getpostman.com/view/40440561/2sB2qaj2Pz)
- Documentation interne : `documentation/WEB-A-1 (3).md`
- Documentation interne : `documentation/documentation_api_logistics.md`
## Tests

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Pages;
use Filament\Pages\Dashboard as BaseDashboard;
use Filament\Support\Icons\Heroicon;
class Dashboard extends BaseDashboard
{
protected static string|\BackedEnum|null $navigationIcon = Heroicon::OutlinedHome;
protected static ?string $navigationLabel = 'Accueil';
protected static ?string $title = 'API Logistics';
protected static ?int $navigationSort = 0;
protected string $view = 'filament.pages.dashboard';
}

View File

@@ -0,0 +1,47 @@
<?php
namespace App\Filament\Pages;
use Filament\Actions\Action;
use Filament\Pages\Page;
use Filament\Support\Icons\Heroicon;
use Illuminate\Support\Str;
class Documentation extends Page
{
protected static string|\BackedEnum|null $navigationIcon = Heroicon::OutlinedBookOpen;
protected static ?string $navigationLabel = 'Documentation';
protected static ?string $title = 'Documentation API';
protected static ?int $navigationSort = 0;
protected string $view = 'filament.pages.documentation';
public string $htmlContent = '';
public function mount(): void
{
$markdownPath = base_path('documentation/documentation_api_logistics.md');
$markdown = file_get_contents($markdownPath);
$this->htmlContent = Str::markdown($markdown);
}
public function getHeaderActions(): array
{
return [
Action::make('download')
->label('Télécharger en PDF')
->icon(Heroicon::OutlinedArrowDownTray)
->url(route('documentation.download-pdf'))
->openUrlInNewTab(),
Action::make('see_in_another_tab')
->label('Voir dans un nouvel onglet')
->icon(Heroicon::OutlinedArrowTopRightOnSquare)
->url(Documentation::getUrl())
->openUrlInNewTab(),
];
}
}

View File

@@ -23,24 +23,75 @@ class TablesExplorer extends Page
#[Url]
public string $selectedTable = '';
public string $tableFilter = '';
public array $tables = [];
public array $columns = [];
public ?array $tablesMetadata = null;
public ?array $columnsMetadata = null;
public ?string $errorMessage = null;
/**
* @var array<string, string>
*/
private static array $dataTypeLabels = [
'C' => 'Caractère',
'N' => 'Numérique',
'T' => 'Date/Heure',
'D' => 'Date',
'L' => 'Logique',
'M' => 'Mémo',
];
public function mount(): void
{
$this->loadTables();
if (filled($this->selectedTable)) {
$this->loadColumns();
}
}
public function loadTables(): void
public function selectTable(string $tableName): void
{
$this->selectedTable = $tableName;
$this->loadColumns();
}
/**
* @return array<int, array<string, mixed>>
*/
public function getFilteredTablesProperty(): array
{
if (blank($this->tableFilter)) {
return $this->tables;
}
$filter = mb_strtolower($this->tableFilter);
return array_values(array_filter(
$this->tables,
fn (array $table): bool => str_contains(mb_strtolower($table['name'] ?? ''), $filter),
));
}
public static function getDataTypeLabel(string $type): string
{
return self::$dataTypeLabels[strtoupper($type)] ?? $type;
}
private function loadTables(): void
{
try {
$service = app(LogisticsService::class);
$response = $service->tablesList();
$this->tables = $response['data'] ?? [];
$this->tablesMetadata = $response['metadata'] ?? null;
$this->errorMessage = $response['error'] ?? null;
} catch (LogisticsApiException $e) {
$this->errorMessage = $e->getMessage();
@@ -49,10 +100,11 @@ class TablesExplorer extends Page
}
}
public function loadColumns(): void
private function loadColumns(): void
{
if (blank($this->selectedTable)) {
$this->columns = [];
$this->columnsMetadata = null;
return;
}
@@ -61,12 +113,36 @@ class TablesExplorer extends Page
$service = app(LogisticsService::class);
$response = $service->columnList($this->selectedTable);
$this->columns = $response['data'] ?? [];
$rawColumns = $response['data'] ?? [];
$this->columns = $this->deduplicateColumns($rawColumns);
$this->columnsMetadata = $response['metadata'] ?? null;
$this->errorMessage = $response['error'] ?? null;
} catch (LogisticsApiException $e) {
$this->errorMessage = $e->getMessage();
$this->columns = [];
} catch (\Throwable $e) {
$this->errorMessage = "Erreur inattendue : {$e->getMessage()}";
$this->columns = [];
}
}
/**
* @param array<int, array<string, mixed>> $columns
* @return array<int, array<string, mixed>>
*/
private function deduplicateColumns(array $columns): array
{
$seen = [];
$unique = [];
foreach ($columns as $column) {
$key = ($column['name'] ?? '').'|'.($column['dataType'] ?? '');
if (! isset($seen[$key])) {
$seen[$key] = true;
$unique[] = $column;
}
}
return $unique;
}
}

View File

@@ -2,13 +2,12 @@
namespace App\Providers\Filament;
use App\Filament\Pages\Dashboard;
use Filament\Http\Middleware\DisableBladeIconComponents;
use Filament\Http\Middleware\DispatchServingFilamentEvent;
use Filament\Pages\Dashboard;
use Filament\Panel;
use Filament\PanelProvider;
use Filament\Support\Colors\Color;
use Filament\Widgets\FilamentInfoWidget;
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
use Illuminate\Cookie\Middleware\EncryptCookies;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
@@ -24,6 +23,7 @@ class AdminPanelProvider extends PanelProvider
->default()
->id('admin')
->path('admin')
->viteTheme('resources/css/filament/admin/theme.css')
->brandName('API Logistics')
->colors([
'primary' => Color::Blue,
@@ -34,9 +34,7 @@ class AdminPanelProvider extends PanelProvider
Dashboard::class,
])
->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\Filament\Widgets')
->widgets([
FilamentInfoWidget::class,
])
->widgets([])
->middleware([
EncryptCookies::class,
AddQueuedCookiesToResponse::class,

View File

@@ -9,6 +9,7 @@
"skills": [
"fluxui-development",
"livewire-development",
"pest-testing"
"pest-testing",
"tailwindcss-development"
]
}

View File

@@ -7,6 +7,7 @@
"license": "MIT",
"require": {
"php": "^8.2",
"barryvdh/laravel-dompdf": "^3.1",
"filament/filament": "5.0",
"laravel/fortify": "^1.30",
"laravel/framework": "^12.0",

451
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "72e464a39ce3e36d3ef8c75498225c48",
"content-hash": "3cb29f1ae8d0f5968aae9270bea812f0",
"packages": [
{
"name": "anourvalar/eloquent-serialize",
@@ -127,6 +127,83 @@
},
"time": "2025-11-19T17:15:36+00:00"
},
{
"name": "barryvdh/laravel-dompdf",
"version": "v3.1.1",
"source": {
"type": "git",
"url": "https://github.com/barryvdh/laravel-dompdf.git",
"reference": "8e71b99fc53bb8eb77f316c3c452dd74ab7cb25d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/barryvdh/laravel-dompdf/zipball/8e71b99fc53bb8eb77f316c3c452dd74ab7cb25d",
"reference": "8e71b99fc53bb8eb77f316c3c452dd74ab7cb25d",
"shasum": ""
},
"require": {
"dompdf/dompdf": "^3.0",
"illuminate/support": "^9|^10|^11|^12",
"php": "^8.1"
},
"require-dev": {
"larastan/larastan": "^2.7|^3.0",
"orchestra/testbench": "^7|^8|^9|^10",
"phpro/grumphp": "^2.5",
"squizlabs/php_codesniffer": "^3.5"
},
"type": "library",
"extra": {
"laravel": {
"aliases": {
"PDF": "Barryvdh\\DomPDF\\Facade\\Pdf",
"Pdf": "Barryvdh\\DomPDF\\Facade\\Pdf"
},
"providers": [
"Barryvdh\\DomPDF\\ServiceProvider"
]
},
"branch-alias": {
"dev-master": "3.0-dev"
}
},
"autoload": {
"psr-4": {
"Barryvdh\\DomPDF\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Barry vd. Heuvel",
"email": "barryvdh@gmail.com"
}
],
"description": "A DOMPDF Wrapper for Laravel",
"keywords": [
"dompdf",
"laravel",
"pdf"
],
"support": {
"issues": "https://github.com/barryvdh/laravel-dompdf/issues",
"source": "https://github.com/barryvdh/laravel-dompdf/tree/v3.1.1"
},
"funding": [
{
"url": "https://fruitcake.nl",
"type": "custom"
},
{
"url": "https://github.com/barryvdh",
"type": "github"
}
],
"time": "2025-02-13T15:07:54+00:00"
},
{
"name": "blade-ui-kit/blade-heroicons",
"version": "2.6.0",
@@ -962,6 +1039,161 @@
],
"time": "2024-02-05T11:56:58+00:00"
},
{
"name": "dompdf/dompdf",
"version": "v3.1.4",
"source": {
"type": "git",
"url": "https://github.com/dompdf/dompdf.git",
"reference": "db712c90c5b9868df3600e64e68da62e78a34623"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/dompdf/dompdf/zipball/db712c90c5b9868df3600e64e68da62e78a34623",
"reference": "db712c90c5b9868df3600e64e68da62e78a34623",
"shasum": ""
},
"require": {
"dompdf/php-font-lib": "^1.0.0",
"dompdf/php-svg-lib": "^1.0.0",
"ext-dom": "*",
"ext-mbstring": "*",
"masterminds/html5": "^2.0",
"php": "^7.1 || ^8.0"
},
"require-dev": {
"ext-gd": "*",
"ext-json": "*",
"ext-zip": "*",
"mockery/mockery": "^1.3",
"phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11",
"squizlabs/php_codesniffer": "^3.5",
"symfony/process": "^4.4 || ^5.4 || ^6.2 || ^7.0"
},
"suggest": {
"ext-gd": "Needed to process images",
"ext-gmagick": "Improves image processing performance",
"ext-imagick": "Improves image processing performance",
"ext-zlib": "Needed for pdf stream compression"
},
"type": "library",
"autoload": {
"psr-4": {
"Dompdf\\": "src/"
},
"classmap": [
"lib/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-2.1"
],
"authors": [
{
"name": "The Dompdf Community",
"homepage": "https://github.com/dompdf/dompdf/blob/master/AUTHORS.md"
}
],
"description": "DOMPDF is a CSS 2.1 compliant HTML to PDF converter",
"homepage": "https://github.com/dompdf/dompdf",
"support": {
"issues": "https://github.com/dompdf/dompdf/issues",
"source": "https://github.com/dompdf/dompdf/tree/v3.1.4"
},
"time": "2025-10-29T12:43:30+00:00"
},
{
"name": "dompdf/php-font-lib",
"version": "1.0.2",
"source": {
"type": "git",
"url": "https://github.com/dompdf/php-font-lib.git",
"reference": "a6e9a688a2a80016ac080b97be73d3e10c444c9a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/dompdf/php-font-lib/zipball/a6e9a688a2a80016ac080b97be73d3e10c444c9a",
"reference": "a6e9a688a2a80016ac080b97be73d3e10c444c9a",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"php": "^7.1 || ^8.0"
},
"require-dev": {
"phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11 || ^12"
},
"type": "library",
"autoload": {
"psr-4": {
"FontLib\\": "src/FontLib"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-2.1-or-later"
],
"authors": [
{
"name": "The FontLib Community",
"homepage": "https://github.com/dompdf/php-font-lib/blob/master/AUTHORS.md"
}
],
"description": "A library to read, parse, export and make subsets of different types of font files.",
"homepage": "https://github.com/dompdf/php-font-lib",
"support": {
"issues": "https://github.com/dompdf/php-font-lib/issues",
"source": "https://github.com/dompdf/php-font-lib/tree/1.0.2"
},
"time": "2026-01-20T14:10:26+00:00"
},
{
"name": "dompdf/php-svg-lib",
"version": "1.0.2",
"source": {
"type": "git",
"url": "https://github.com/dompdf/php-svg-lib.git",
"reference": "8259ffb930817e72b1ff1caef5d226501f3dfeb1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/dompdf/php-svg-lib/zipball/8259ffb930817e72b1ff1caef5d226501f3dfeb1",
"reference": "8259ffb930817e72b1ff1caef5d226501f3dfeb1",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"php": "^7.1 || ^8.0",
"sabberworm/php-css-parser": "^8.4 || ^9.0"
},
"require-dev": {
"phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11"
},
"type": "library",
"autoload": {
"psr-4": {
"Svg\\": "src/Svg"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-3.0-or-later"
],
"authors": [
{
"name": "The SvgLib Community",
"homepage": "https://github.com/dompdf/php-svg-lib/blob/master/AUTHORS.md"
}
],
"description": "A library to read, parse and export to PDF SVG files.",
"homepage": "https://github.com/dompdf/php-svg-lib",
"support": {
"issues": "https://github.com/dompdf/php-svg-lib/issues",
"source": "https://github.com/dompdf/php-svg-lib/tree/1.0.2"
},
"time": "2026-01-02T16:01:13+00:00"
},
{
"name": "dragonmantank/cron-expression",
"version": "v3.6.0",
@@ -5305,6 +5537,80 @@
],
"time": "2025-02-25T09:09:36+00:00"
},
{
"name": "sabberworm/php-css-parser",
"version": "v9.1.0",
"source": {
"type": "git",
"url": "https://github.com/MyIntervals/PHP-CSS-Parser.git",
"reference": "1b363fdbdc6dd0ca0f4bf98d3a4d7f388133f1fb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/1b363fdbdc6dd0ca0f4bf98d3a4d7f388133f1fb",
"reference": "1b363fdbdc6dd0ca0f4bf98d3a4d7f388133f1fb",
"shasum": ""
},
"require": {
"ext-iconv": "*",
"php": "^7.2.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0",
"thecodingmachine/safe": "^1.3 || ^2.5 || ^3.3"
},
"require-dev": {
"php-parallel-lint/php-parallel-lint": "1.4.0",
"phpstan/extension-installer": "1.4.3",
"phpstan/phpstan": "1.12.28 || 2.1.25",
"phpstan/phpstan-phpunit": "1.4.2 || 2.0.7",
"phpstan/phpstan-strict-rules": "1.6.2 || 2.0.6",
"phpunit/phpunit": "8.5.46",
"rawr/phpunit-data-provider": "3.3.1",
"rector/rector": "1.2.10 || 2.1.7",
"rector/type-perfect": "1.0.0 || 2.1.0"
},
"suggest": {
"ext-mbstring": "for parsing UTF-8 CSS"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "9.2.x-dev"
}
},
"autoload": {
"psr-4": {
"Sabberworm\\CSS\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Raphael Schweikert"
},
{
"name": "Oliver Klee",
"email": "github@oliverklee.de"
},
{
"name": "Jake Hotson",
"email": "jake.github@qzdesign.co.uk"
}
],
"description": "Parser for CSS Files written in PHP",
"homepage": "https://www.sabberworm.com/blog/2010/6/10/php-css-parser",
"keywords": [
"css",
"parser",
"stylesheet"
],
"support": {
"issues": "https://github.com/MyIntervals/PHP-CSS-Parser/issues",
"source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v9.1.0"
},
"time": "2025-09-14T07:37:21+00:00"
},
{
"name": "scrivo/highlight.php",
"version": "v9.18.1.10",
@@ -8134,6 +8440,149 @@
],
"time": "2026-01-01T22:13:48+00:00"
},
{
"name": "thecodingmachine/safe",
"version": "v3.4.0",
"source": {
"type": "git",
"url": "https://github.com/thecodingmachine/safe.git",
"reference": "705683a25bacf0d4860c7dea4d7947bfd09eea19"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thecodingmachine/safe/zipball/705683a25bacf0d4860c7dea4d7947bfd09eea19",
"reference": "705683a25bacf0d4860c7dea4d7947bfd09eea19",
"shasum": ""
},
"require": {
"php": "^8.1"
},
"require-dev": {
"php-parallel-lint/php-parallel-lint": "^1.4",
"phpstan/phpstan": "^2",
"phpunit/phpunit": "^10",
"squizlabs/php_codesniffer": "^3.2"
},
"type": "library",
"autoload": {
"files": [
"lib/special_cases.php",
"generated/apache.php",
"generated/apcu.php",
"generated/array.php",
"generated/bzip2.php",
"generated/calendar.php",
"generated/classobj.php",
"generated/com.php",
"generated/cubrid.php",
"generated/curl.php",
"generated/datetime.php",
"generated/dir.php",
"generated/eio.php",
"generated/errorfunc.php",
"generated/exec.php",
"generated/fileinfo.php",
"generated/filesystem.php",
"generated/filter.php",
"generated/fpm.php",
"generated/ftp.php",
"generated/funchand.php",
"generated/gettext.php",
"generated/gmp.php",
"generated/gnupg.php",
"generated/hash.php",
"generated/ibase.php",
"generated/ibmDb2.php",
"generated/iconv.php",
"generated/image.php",
"generated/imap.php",
"generated/info.php",
"generated/inotify.php",
"generated/json.php",
"generated/ldap.php",
"generated/libxml.php",
"generated/lzf.php",
"generated/mailparse.php",
"generated/mbstring.php",
"generated/misc.php",
"generated/mysql.php",
"generated/mysqli.php",
"generated/network.php",
"generated/oci8.php",
"generated/opcache.php",
"generated/openssl.php",
"generated/outcontrol.php",
"generated/pcntl.php",
"generated/pcre.php",
"generated/pgsql.php",
"generated/posix.php",
"generated/ps.php",
"generated/pspell.php",
"generated/readline.php",
"generated/rnp.php",
"generated/rpminfo.php",
"generated/rrd.php",
"generated/sem.php",
"generated/session.php",
"generated/shmop.php",
"generated/sockets.php",
"generated/sodium.php",
"generated/solr.php",
"generated/spl.php",
"generated/sqlsrv.php",
"generated/ssdeep.php",
"generated/ssh2.php",
"generated/stream.php",
"generated/strings.php",
"generated/swoole.php",
"generated/uodbc.php",
"generated/uopz.php",
"generated/url.php",
"generated/var.php",
"generated/xdiff.php",
"generated/xml.php",
"generated/xmlrpc.php",
"generated/yaml.php",
"generated/yaz.php",
"generated/zip.php",
"generated/zlib.php"
],
"classmap": [
"lib/DateTime.php",
"lib/DateTimeImmutable.php",
"lib/Exceptions/",
"generated/Exceptions/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "PHP core functions that throw exceptions instead of returning FALSE on error",
"support": {
"issues": "https://github.com/thecodingmachine/safe/issues",
"source": "https://github.com/thecodingmachine/safe/tree/v3.4.0"
},
"funding": [
{
"url": "https://github.com/OskarStark",
"type": "github"
},
{
"url": "https://github.com/shish",
"type": "github"
},
{
"url": "https://github.com/silasjoisten",
"type": "github"
},
{
"url": "https://github.com/staabm",
"type": "github"
}
],
"time": "2026-02-04T18:08:13+00:00"
},
{
"name": "tijsverkoyen/css-to-inline-styles",
"version": "v2.4.0",

File diff suppressed because it is too large Load Diff

View File

@@ -1,24 +1,28 @@
# Memory Bank
Derniere mise a jour : 2026-02-20
Dernière mise à jour : 2026-02-20
## Presentation
## Présentation
Ce dossier contient le Memory Bank du projet API Logistics. Il sert de source de verite pour que l'IA (Cursor) conserve le contexte du projet entre les sessions.
Ce dossier contient le Memory Bank du projet API Logistics. Il sert de source de vérité pour que l'IA (Cursor) conserve le contexte du projet entre les sessions.
## Structure
| Fichier | Description |
|---------|-------------|
| `projectbrief.md` | Vision, objectifs et perimetre du projet |
| `productContext.md` | Contexte produit, problemes resolus, experience utilisateur |
| `techContext.md` | Stack technique, API Logistics, dependances, configuration |
| `systemPatterns.md` | Architecture, patterns, structure des repertoires, conventions |
| `activeContext.md` | Travail en cours, decisions recentes, prochaines etapes |
| `progress.md` | Avancement, ce qui fonctionne, ce qui reste a faire, metriques |
| `projectbrief.md` | Vision, objectifs et périmètre du projet |
| `productContext.md` | Contexte produit, problèmes résolus, expérience utilisateur |
| `techContext.md` | Stack technique, API Logistics, dépendances, configuration |
| `systemPatterns.md` | Architecture, patterns, structure des répertoires, conventions, système de design |
| `activeContext.md` | Travail en cours, décisions récentes, prochaines étapes |
| `progress.md` | Avancement, ce qui fonctionne, ce qui reste à faire, métriques |
## Utilisation
- **Lecture automatique** : La regle Cursor `.cursor/rules/memory-bank.mdc` est configuree avec `alwaysApply: true`. L'IA lit ces fichiers a chaque session.
- **Mise a jour** : Dire **"update memory bank"** dans le chat pour que l'IA relise le code source et mette a jour tous les fichiers.
- **Quand mettre a jour** : Apres chaque changement significatif (nouvelle fonctionnalite, changement d'architecture, correction de bug, nouvelle dependance).
- **Lecture automatique** : La règle Cursor `.cursor/rules/memory-bank.mdc` est configurée avec `alwaysApply: true`. L'IA lit ces fichiers à chaque session.
- **Mise à jour** : Dire **"update memory bank"** dans le chat pour que l'IA relise le code source et mette à jour tous les fichiers.
- **Quand mettre à jour** : Après chaque changement significatif (nouvelle fonctionnalité, changement d'architecture, correction de bug, nouvelle dépendance).
## Convention d'écriture
Tous les fichiers du projet (documentation, memory bank, règles Cursor) doivent être rédigés en français avec les accents appropriés.

View File

@@ -1,44 +1,79 @@
# Active Context
Derniere mise a jour : 2026-02-20
Dernière mise à jour : 2026-02-20
## Travail en cours
Aucun travail en cours. Les ameliorations de robustesse du service API sont terminees.
Aucun travail en cours.
## Decisions recentes
## Décisions récentes
- **Filament v5 sans authentification** : Le `AdminPanelProvider` a ete configure sans `->login()` et sans `authMiddleware` pour permettre un acces libre au dashboard.
- **Pages personnalisees plutot que Resources** : L'application interroge une API externe, il n'y a pas de modeles Eloquent a gerer en CRUD. Les pages Filament utilisent des vues Blade avec des formulaires Livewire.
- **MySQL au lieu de SQLite** : Choix de l'utilisateur pour la base de donnees.
- **LogisticsService** : Toutes les interactions avec l'API sont centralisees dans un seul service pour faciliter la maintenance et le tracage.
- **Convention d'écriture avec accents** (2026-02-20) : Tous les contenus rédigés en français (documentation, memory bank, règles Cursor) doivent utiliser les accents appropriés. La documentation API et le memory bank ont été entièrement réécrits avec les accents.
- **Page Documentation ajoutée** (2026-02-20) : Nouvelle page Filament `Documentation` qui affiche le fichier markdown `documentation/documentation_api_logistics.md` converti en HTML via `Str::markdown()`. Actions d'en-tête : télécharger en PDF et ouvrir dans un nouvel onglet.
- **Styles prose personnalisés** (2026-02-20) : Le thème CSS Filament (`theme.css`) a été enrichi avec des styles `.documentation-prose` dédiés au rendu du markdown en dark mode : hiérarchie de titres avec bordures, tableaux avec bordures et en-têtes stylisés, blocs de code avec fond sombre, code inline coloré, liens bleus, listes à marqueurs, séparateurs visibles.
- **Plugin @tailwindcss/typography** (2026-02-20) : Ajout du plugin via `@plugin "@tailwindcss/typography"` dans le thème Filament pour activer les classes `prose`.
- **Export PDF de la documentation** (2026-02-20) : Route `documentation.download-pdf` dans `routes/web.php` utilisant `barryvdh/laravel-dompdf` pour générer un PDF téléchargeable de la documentation.
- **Règle update-documentation** (2026-02-20) : Nouvelle règle Cursor `.cursor/rules/update-documentation.mdc` définissant la procédure de mise à jour de la documentation quand l'utilisateur dit "update documentation".
- **Filament v5 sans authentification** : Le `AdminPanelProvider` a été configuré sans `->login()` et sans `authMiddleware` pour permettre un accès libre au dashboard.
- **Pages personnalisées plutôt que Resources** : L'application interroge une API externe, il n'y a pas de modèles Eloquent à gérer en CRUD.
- **MySQL au lieu de SQLite** : Choix de l'utilisateur pour la base de données.
- **LogisticsService** : Toutes les interactions avec l'API sont centralisées dans un seul service.
- **Heroicon enum** : Filament v5 impose l'utilisation de `BackedEnum` pour `$navigationIcon` au lieu de strings.
- **$view non-static** : Filament v5 a change `$view` de static a instance property.
- **Timeout et retry configurables** (2026-02-20) : Les parametres `timeout`, `connect_timeout`, `retry.times` et `retry.sleep_ms` sont desormais dans `config/logistics.php` et pilotes par des variables `.env`. Le retry ne se declenche que sur les `ConnectionException`.
- **LogisticsApiException** (2026-02-20) : Exception dediee creee pour distinguer les erreurs de connexion (API injoignable) des erreurs de requete generiques. Messages en francais.
- **Logging des echecs** (2026-02-20) : Les requetes echouees sont desormais aussi enregistrees dans `api_request_logs` avec `response_status = 0`.
- **Contrainte reseau** (2026-02-20) : Le serveur API `tse-10-test.esiweb.pro` est sur un reseau prive, accessible uniquement via Bureau a distance (RDP). L'application locale ne peut pas joindre l'API sans tunnel ou deploiement sur le reseau distant.
- **$view non-static** : Filament v5 a changé `$view` de static à instance property.
- **Timeout et retry configurables** : Les paramètres sont dans `config/logistics.php` et pilotés par `.env`.
- **LogisticsApiException** : Exception dédiée créée pour distinguer les erreurs de connexion des erreurs de requête génériques.
- **Logging des échecs** : Les requêtes échouées sont aussi enregistrées dans `api_request_logs` avec `response_status = 0`.
- **Système de design unifié** (2026-02-20) : Création de 10 composants Blade réutilisables dans `resources/views/components/logistics/` et d'une convention documentée dans `.cursor/rules/design-system.mdc`.
- **Thème Filament personnalisé** (2026-02-20) : Création de `resources/css/filament/admin/theme.css` enregistré dans `AdminPanelProvider` via `->viteTheme()`.
- **TablesExplorer amélioré** (2026-02-20) : Remplacement de `wire:then` (invalide) par une méthode `selectTable()`. Ajout de la déduplication des colonnes, d'un filtre de tables, de la propriété computed `filteredTables`, des labels de type (`getDataTypeLabel`), et du stockage des métadonnées.
- **Connectivité API résolue** (2026-02-20) : Le serveur API est accessible via `http://tse-10-test.esi.local` (réseau interne). Le timeout a été augmenté à 300s dans `.env`.
## Changements importants (2026-02-20)
## Changements récents (2026-02-20, session documentation)
- `LogisticsService` mis a jour : timeout, connectTimeout, retry, gestion d'erreur avec `LogisticsApiException`.
- `LogisticsApiException` creee dans `app/Exceptions/`.
- `config/logistics.php` etendu avec timeout et retry.
- `.env` et `.env.example` completes avec les nouvelles variables.
- Les 5 pages Filament mises a jour pour attraper `LogisticsApiException`.
- Tests passes de 8 a 12 (4 nouveaux tests pour timeout, exception, logging, contexte).
- Page Filament `Documentation` créée (`app/Filament/Pages/Documentation.php`).
- Vue Blade `resources/views/filament/pages/documentation.blade.php` créée.
- Documentation markdown réécrite intégralement avec accents français.
- Styles CSS `.documentation-prose` ajoutés dans `theme.css` (titres, tableaux, code, liens, listes).
- Plugin `@tailwindcss/typography` activé dans le thème Filament.
- Route PDF (`documentation.download-pdf`) ajoutée dans `routes/web.php`.
- Template PDF (`resources/views/pdf/documentation.blade.php`) créé.
- 5 tests Pest créés pour la page Documentation (`tests/Feature/DocumentationTest.php`).
- Règle Cursor `update-documentation.mdc` créée.
- Memory bank entièrement réécrit avec accents français.
- Total : 61 tests passent (165 assertions).
## Historique (2026-02-19)
## Historique
- Installation de Filament v5.0.0 (31 packages ajoutes).
- 5 pages Filament creees : TablesExplorer, Articles, Documents, Journaux, Tiers.
- `LogisticsService` cree avec 17 endpoints.
- Migration `api_request_logs` creee.
- 8 tests Pest ecrits et valides.
### 2026-02-20 (session design)
## Prochaines etapes
- 10 composants Blade créés dans `resources/views/components/logistics/`.
- Convention de design documentée dans `.cursor/rules/design-system.mdc`.
- 5 pages Filament refactorisées pour utiliser les composants du système de design.
- `TablesExplorer.php` amélioré : `selectTable()`, déduplication, filtre, computed property, métadonnées, labels de types.
- Thème Filament personnalisé créé (`resources/css/filament/admin/theme.css`).
- `AdminPanelProvider.php` mis à jour avec `->viteTheme()`.
- 6 tests Pest créés pour `TablesExplorer` (Livewire).
- Deployer l'application sur le reseau distant (serveur accessible via RDP) ou mettre en place un tunnel SSH/VPN pour que l'application puisse joindre l'API.
- Tester le dashboard avec de vraies donnees API une fois la connectivite reseau resolue.
- Eventuellement : ajouter des pages pour les endpoints d'ecriture (document_add, document_mod).
- Eventuellement : ameliorer l'affichage des resultats (pagination, formatage).
### 2026-02-20 (session robustesse)
- `LogisticsService` mis à jour : timeout, connectTimeout, retry, gestion d'erreur avec `LogisticsApiException`.
- `LogisticsApiException` créée dans `app/Exceptions/`.
- `config/logistics.php` étendu avec timeout et retry.
- `.env` et `.env.example` complétés avec les nouvelles variables.
- Les 5 pages Filament mises à jour pour attraper `LogisticsApiException`.
- Tests passés de 8 à 12.
### 2026-02-19
- Installation de Filament v5.0.0 (31 packages ajoutés).
- 5 pages Filament créées : TablesExplorer, Articles, Documents, Journaux, Tiers.
- `LogisticsService` créé avec 17 endpoints.
- Migration `api_request_logs` créée.
- 8 tests Pest écrits et validés.
## Prochaines étapes
- Tester toutes les pages avec de vraies données API et vérifier le rendu visuel.
- Éventuellement : ajouter des pages pour les endpoints d'écriture (document_add, document_mod).
- Éventuellement : ajouter de la pagination ou du tri côté client pour les grands tableaux.
- Éventuellement : ajouter une page de consultation des logs API.

View File

@@ -1,30 +1,33 @@
# Product Context
Derniere mise a jour : 2026-02-20
Dernière mise à jour : 2026-02-20
## Pourquoi ce projet existe
L'API Logistics (Flex/ESI Gescom) est un systeme de gestion commerciale accessible via une API REST. La documentation officielle est limitee. Ce projet a ete cree pour :
L'API Logistics (Flex/ESI Gescom) est un système de gestion commerciale accessible via une API REST. La documentation officielle est limitée. Ce projet a été créé pour :
- Explorer les endpoints disponibles de maniere interactive.
- Comprendre les parametres attendus et les formats de reponse.
- Servir de base pour une documentation plus complete.
- Explorer les endpoints disponibles de manière interactive.
- Comprendre les paramètres attendus et les formats de réponse.
- Servir de base pour une documentation plus complète.
## Problemes resolus
## Problèmes résolus
- **Exploration de l'API** : Le dashboard permet de tester chaque endpoint avec des parametres personnalisables, sans avoir besoin de Postman.
- **Comprehension de la structure** : La page "Tables" permet de decouvrir les tables et colonnes disponibles dans l'API.
- **Tracabilite** : Chaque requete effectuee (reussie ou echouee) est enregistree dans `api_request_logs` pour pouvoir analyser les echanges.
- **Resilience** : Les erreurs de connexion sont gerees avec retry automatique et messages explicites en francais.
- **Exploration de l'API** : Le dashboard permet de tester chaque endpoint avec des paramètres personnalisables, sans avoir besoin de Postman.
- **Compréhension de la structure** : La page "Tables" permet de découvrir les tables et colonnes disponibles dans l'API, avec déduplication automatique des colonnes retournées en double par l'API.
- **Documentation intégrée** : La page "Documentation" affiche le markdown de la documentation API avec un rendu stylisé (typographie, tableaux, blocs de code) et propose un export PDF.
- **Traçabilité** : Chaque requête effectuée (réussie ou échouée) est enregistrée dans `api_request_logs` pour pouvoir analyser les échanges.
- **Résilience** : Les erreurs de connexion sont gérées avec retry automatique et messages explicites en français.
- **Cohérence visuelle** : Un système de design unifié (composants `x-logistics.*`) garantit une présentation homogène sur toutes les pages.
## Experience utilisateur
## Expérience utilisateur
L'utilisateur accede au dashboard Filament sur `http://api-logistics.test/admin`. La navigation laterale propose 5 pages :
L'utilisateur accède au dashboard Filament sur `http://api-logistics.test/admin`. La navigation latérale propose 6 pages :
1. **Tables** : Cliquer sur une table pour voir ses colonnes. Utile pour connaitre les champs disponibles dans les parametres `select`.
2. **Articles** : Formulaire de recherche (search, select, results) + verification du stock d'un article par son ARTID.
3. **Documents** : Recherche par tiers (thirdid) + consultation du detail d'un document (jnl + number).
4. **Journaux** : Recherche par type de journal (TYPE).
5. **Tiers** : Recherche de tiers (search obligatoire) + historique des articles d'un tiers.
1. **Documentation** : Affichage stylisé de la documentation API complète (markdown converti en HTML). Actions : télécharger en PDF, ouvrir dans un nouvel onglet. Rendu avec typographie soignée (titres hiérarchisés, tableaux bordés, blocs de code colorés, liens bleus).
2. **Tables** : Barre de statistiques (endpoint, type base, nombre de tables). Liste filtrable des tables avec compteur de colonnes. Clic sur une table pour voir ses colonnes avec badges de type colorés (Caractère, Numérique, Date/Heure, Logique, Mémo).
3. **Articles** : Formulaire de recherche (search, select, results) + vérification du stock d'un article par son ARTID. Résultats en tableau structuré, stock en JSON formaté.
4. **Documents** : Recherche par tiers (thirdid) + consultation du détail d'un document (jnl + number). Détail en JSON formaté.
5. **Journaux** : Recherche par type de journal (TYPE). Résultats en tableau structuré.
6. **Tiers** : Recherche de tiers (search obligatoire) + historique des articles d'un tiers. Historique en JSON formaté.
Chaque page affiche les resultats sous forme de tableau dynamique et les metadonnees de la reponse (nombre de resultats, succes). En cas d'erreur de connexion a l'API, un message clair en francais est affiche a l'utilisateur.
Toutes les pages utilisent le même système de design : cartes à en-tête séparé, badges de compteur, indicateurs de chargement sur les actions réseau, et états vides avec icônes explicatives. En cas d'erreur API, un bandeau rouge s'affiche avec le message en français.

View File

@@ -1,43 +1,55 @@
# Progress
Derniere mise a jour : 2026-02-20
Dernière mise à jour : 2026-02-20
## Ce qui fonctionne
- [x] Projet Laravel 12 initialise (livewire-starter-kit)
- [x] Livewire 4 + Flux UI Free v2 installes
- [x] Fortify installe (authentification existante, non utilisee par Filament)
- [x] Documentation API redigee (`documentation/WEB-A-1 (3).md`, `documentation/result.pdf`)
- [x] Memory bank cree et structure (`memory-bank/`, `.cursor/rules/memory-bank.mdc`)
- [x] Projet Laravel 12 initialisé (livewire-starter-kit)
- [x] Livewire 4 + Flux UI Free v2 installés
- [x] Fortify installé (authentification existante, non utilisée par Filament)
- [x] Documentation API rédigée avec accents (`documentation/documentation_api_logistics.md`)
- [x] Memory bank créé et structuré (`memory-bank/`, `.cursor/rules/memory-bank.mdc`)
- [x] Configuration API Logistics (`.env`, `config/logistics.php`) avec timeout et retry
- [x] `LogisticsService` cree (`app/Services/LogisticsService.php`) avec 17 methodes, timeout, retry, gestion d'erreur
- [x] `LogisticsApiException` creee (`app/Exceptions/LogisticsApiException.php`) avec messages francais
- [x] Migration `api_request_logs` creee
- [x] Filament v5.0.0 installe et configure sans authentification
- [x] 5 pages Filament creees : TablesExplorer, Articles, Documents, Journaux, Tiers
- [x] 5 vues Blade associees dans `resources/views/filament/pages/`
- [x] `LogisticsService` créé (`app/Services/LogisticsService.php`) avec 17 méthodes, timeout, retry, gestion d'erreur
- [x] `LogisticsApiException` créée (`app/Exceptions/LogisticsApiException.php`) avec messages français
- [x] Migration `api_request_logs` créée
- [x] Filament v5.0.0 installé et configuré sans authentification
- [x] 6 pages Filament créées : Documentation, TablesExplorer, Articles, Documents, Journaux, Tiers
- [x] 6 vues Blade associées dans `resources/views/filament/pages/`
- [x] Gestion d'erreur dans toutes les pages Filament (LogisticsApiException + Throwable)
- [x] Logging des requetes API reussies et echouees dans `api_request_logs`
- [x] 12 tests Pest pour LogisticsService (tous passent)
- [x] `README.md` cree
- [x] Formatage Pint valide
- [x] Logging des requêtes API réussies et échouées dans `api_request_logs`
- [x] Système de design unifié : 10 composants Blade dans `resources/views/components/logistics/`
- [x] Convention de design documentée dans `.cursor/rules/design-system.mdc`
- [x] Toutes les pages Filament refactorisées avec les composants `x-logistics.*`
- [x] Thème Filament personnalisé (`resources/css/filament/admin/theme.css`)
- [x] Plugin `@tailwindcss/typography` activé pour le rendu prose
- [x] Styles `.documentation-prose` personnalisés pour le dark mode (titres, tableaux, code, liens)
- [x] TablesExplorer amélioré : selectTable, déduplication colonnes, filtre, badges de types
- [x] Page Documentation avec rendu markdown stylisé et export PDF
- [x] Connectivité API fonctionnelle (serveur `tse-10-test.esi.local`)
- [x] Convention d'écriture avec accents français appliquée
- [x] 61 tests Pest (tous passent)
- [x] `README.md` créé
- [x] Formatage Pint validé
- [x] CI GitHub Actions (lint + tests)
## Ce qui reste a faire
## Ce qui reste à faire
- [ ] Resoudre la connectivite reseau : deployer sur le reseau distant ou mettre en place un tunnel
- [ ] Tester le dashboard avec de vraies donnees API
- [ ] Eventuellement : pages d'ecriture (document_add, document_mod)
- [ ] Eventuellement : ameliorer l'affichage des resultats (pagination, formatage)
- [ ] Vérifier le rendu visuel de toutes les pages avec de vraies données API
- [ ] Éventuellement : pages d'écriture (document_add, document_mod)
- [ ] Éventuellement : pagination / tri côté client pour les grands tableaux
- [ ] Éventuellement : page de consultation des logs API
## Problemes connus
## Problèmes connus
- **API injoignable depuis la machine locale** : Le serveur `tse-10-test.esiweb.pro` est sur un reseau prive accessible uniquement via Bureau a distance (RDP). L'application locale recoit `cURL error 28: Connection timed out`. Solution : deployer sur le reseau distant ou creer un tunnel SSH/VPN.
- L'erreur `SQLSTATE[HY000] [1049] Unknown database` peut apparaitre lors de `composer update` si la base n'est pas encore creee (script `boost:update`). Sans impact une fois la base creee.
- L'erreur `SQLSTATE[HY000] [1049] Unknown database` peut apparaître lors de `composer update` si la base n'est pas encore créée (script `boost:update`). Sans impact une fois la base créée.
- L'API retourne chaque colonne en double dans `column_list`. Le `TablesExplorer` déduplique côté client.
## Metriques
## Métriques
- Tests : 12 (tous passent, 18 assertions)
- Pages Filament : 5
- Tests : 61 (tous passent, 165 assertions)
- Pages Filament : 6 (Documentation, TablesExplorer, Articles, Documents, Journaux, Tiers)
- Composants Blade design system : 10
- Endpoints API couverts par LogisticsService : 17
- Migrations : 5 (users, cache, jobs, two_factor, api_request_logs)
- Règles Cursor : 4 (laravel-boost, memory-bank, design-system, update-documentation)

View File

@@ -1,36 +1,42 @@
# Project Brief
Derniere mise a jour : 2026-02-20
Dernière mise à jour : 2026-02-20
## Vision
Application Laravel de test dont l'objectif est de comprendre le fonctionnement de l'API Logistics (Flex/ESI Gescom) et d'en produire une documentation complete et comprehensible.
Application Laravel de test dont l'objectif est de comprendre le fonctionnement de l'API Logistics (Flex/ESI Gescom) et d'en produire une documentation complète et compréhensible.
## Objectifs
1. Comprendre le fonctionnement de l'API Logistics.
2. Creer une application simple permettant d'envoyer et de recuperer des donnees vers/depuis l'API.
3. Produire une documentation complete et comprehensible de l'API.
2. Créer une application simple permettant d'envoyer et de récupérer des données vers/depuis l'API.
3. Produire une documentation complète et compréhensible de l'API.
## Perimetre fonctionnel
## Périmètre fonctionnel
- Dashboard Filament v5 accessible sans authentification sur `/admin`.
- Pages de consultation pour les principales entites de l'API : tables, articles, documents, journaux, tiers.
- Formulaires de recherche parametrables pour chaque endpoint.
- Affichage des resultats bruts retournes par l'API.
- Tracage des requetes effectuees dans une table `api_request_logs`.
- Gestion robuste des erreurs API (timeout, retry, messages utilisateur en francais).
- Pages de consultation pour les principales entités de l'API : tables, articles, documents, journaux, tiers.
- Page de documentation intégrée avec rendu stylisé du markdown et export PDF.
- Formulaires de recherche paramétrables pour chaque endpoint.
- Affichage des résultats sous forme de tableaux structurés et de blocs JSON formatés.
- Système de design unifié avec composants Blade réutilisables (`x-logistics.*`).
- Traçage des requêtes effectuées dans une table `api_request_logs`.
- Gestion robuste des erreurs API (timeout, retry, messages utilisateur en français).
## Contraintes
- Pas d'authentification sur le dashboard (projet de test interne).
- L'API Logistics est hebergee sur le serveur TSE-10-TEST (`http://tse-10-test.esiweb.pro`).
- Le serveur API est accessible uniquement via le reseau interne (connexion Bureau a distance / RDP requise). L'application doit etre deployee sur ce reseau ou un tunnel doit etre mis en place.
- Toutes les requetes API sont en POST et necessitent un header `X-API-KEY`.
- Le nom du dossier dans les URLs de l'API doit etre en minuscules.
- L'API Logistics est hébergée sur le serveur TSE-10-TEST (`http://tse-10-test.esi.local`).
- Le serveur API est accessible via le réseau interne.
- Toutes les requêtes API sont en POST et nécessitent un header `X-API-KEY`.
- Le nom du dossier dans les URLs de l'API doit être en minuscules.
## Convention d'écriture
Tous les contenus rédigés en français (documentation, memory bank, règles Cursor, commentaires) doivent utiliser les accents appropriés.
## Ressources
- Documentation Postman : https://documenter.getpostman.com/view/40440561/2sB2qaj2Pz
- Documentation interne : `documentation/WEB-A-1 (3).md` et `documentation/result.pdf`
- Documentation interne : `documentation/documentation_api_logistics.md`
- Fichier projet : `project.md`

View File

@@ -1,6 +1,6 @@
# System Patterns
Derniere mise a jour : 2026-02-20
Dernière mise à jour : 2026-02-20
## Architecture applicative
@@ -17,72 +17,131 @@ Utilisateur --> Filament Dashboard (/admin)
| (retry automatique sur ConnectionException)
|
+---> api_request_logs (MySQL)
| (succes ET echecs)
| (succès ET échecs)
|
+---> LogisticsApiException (en cas d'erreur)
```
## Patterns utilises
## Patterns utilisés
### Service centralise
### Service centralisé
`App\Services\LogisticsService` encapsule tous les appels HTTP vers l'API Logistics. Chaque methode publique correspond a un endpoint. Le service :
`App\Services\LogisticsService` encapsule tous les appels HTTP vers l'API Logistics. Chaque méthode publique correspond à un endpoint. Le service :
- Construit l'URL a partir de `config('logistics.base_url')`, `config('logistics.folder')` et le nom de l'endpoint.
- Construit l'URL à partir de `config('logistics.base_url')`, `config('logistics.folder')` et le nom de l'endpoint.
- Ajoute automatiquement le header `X-API-KEY`.
- Configure `timeout()` et `connectTimeout()` depuis la config.
- Effectue un retry automatique (nombre et delai configurables) uniquement sur les `ConnectionException`.
- Enregistre chaque requete dans la table `api_request_logs` (succes et echecs).
- Retourne un tableau PHP avec les cles `data`, `metadata`, `error`.
- Lance une `LogisticsApiException` en cas d'erreur de connexion ou de requete.
- Effectue un retry automatique (nombre et délai configurables) uniquement sur les `ConnectionException`.
- Enregistre chaque requête dans la table `api_request_logs` (succès et échecs).
- Retourne un tableau PHP avec les clés `data`, `metadata`, `error`.
- Lance une `LogisticsApiException` en cas d'erreur de connexion ou de requête.
### Exception dediee
### Exception dédiée
`App\Exceptions\LogisticsApiException` (etend `RuntimeException`) fournit :
`App\Exceptions\LogisticsApiException` (étend `RuntimeException`) fournit :
- Des messages d'erreur en francais lisibles par l'utilisateur.
- Des proprietes `endpoint` et `params` pour le contexte de l'erreur.
- Deux methodes statiques : `connectionTimeout()` (API injoignable) et `requestFailed()` (erreur de requete).
- Des messages d'erreur en français lisibles par l'utilisateur.
- Des propriétés `endpoint` et `params` pour le contexte de l'erreur.
- Deux méthodes statiques : `connectionTimeout()` (API injoignable) et `requestFailed()` (erreur de requête).
### Pages Filament personnalisees (pas de Resources)
### Pages Filament personnalisées (pas de Resources)
L'application n'utilise pas de Resources Filament (pas de CRUD local). Chaque page Filament :
- Etend `Filament\Pages\Page`.
- Utilise des proprietes Livewire publiques pour les champs de formulaire.
- Appelle `LogisticsService` via `app(LogisticsService::class)` dans des methodes d'action (ex: `searchArticles()`).
- Attrape `LogisticsApiException` pour afficher un message clair, et `\Throwable` en fallback.
- Affiche les resultats dans des tableaux HTML dynamiques generes dans les vues Blade.
- Étend `Filament\Pages\Page`.
- Utilise des propriétés Livewire publiques pour les champs de formulaire.
- Appelle `LogisticsService` via `app(LogisticsService::class)` dans des méthodes d'action (ex: `searchArticles()`).
- Attrape `LogisticsApiException` en premier, puis `\Throwable` en fallback.
- Affiche les résultats via les composants du système de design `x-logistics.*`.
### Configuration externalisee
### Page Documentation
Les parametres de connexion a l'API (URL, cle, dossier, timeout, retry) sont dans `config/logistics.php` et lus depuis `.env`.
`App\Filament\Pages\Documentation` est une page spéciale qui :
## Structure des repertoires
- Lit le fichier `documentation/documentation_api_logistics.md` au montage.
- Convertit le markdown en HTML via `Str::markdown()`.
- Affiche le contenu dans une vue avec la classe CSS `.documentation-prose` pour un rendu stylisé.
- Propose deux actions d'en-tête : télécharger en PDF (via `barryvdh/laravel-dompdf`) et ouvrir dans un nouvel onglet.
### Système de design (composants Blade)
Convention documentée dans `.cursor/rules/design-system.mdc`. Tous les composants visuels réutilisables sont dans `resources/views/components/logistics/` :
| Composant | Rôle |
|-----------|------|
| `<x-logistics.card>` | Conteneur blanc arrondi avec ombre et anneau |
| `<x-logistics.section-header>` | En-tête de section (titre, description, slot `actions`) |
| `<x-logistics.error-banner>` | Bandeau d'erreur API conditionnel |
| `<x-logistics.stat-bar>` | Barre horizontale de métadonnées |
| `<x-logistics.stat-item>` | Élément individuel (icône + label + valeur) |
| `<x-logistics.data-table>` | Tableau dynamique avec en-têtes auto-détectés et état vide |
| `<x-logistics.empty-state>` | État vide centré (icône + titre + description) |
| `<x-logistics.search-input>` | Champ de recherche avec icône loupe intégrée |
| `<x-logistics.form-field>` | Champ de formulaire (label + input) à espacement homogène |
| `<x-logistics.json-block>` | Bloc JSON formaté avec bordure et fond adapté |
Règles :
- Toujours utiliser les composants `x-logistics.*` au lieu de dupliquer les classes CSS.
- Un `<x-logistics.card>` n'a pas de padding interne. Le padding est géré par les enfants (`<div class="p-6">`).
- Le `<x-logistics.section-header>` est le premier enfant d'une carte et crée une bordure de séparation.
- Toutes les actions réseau doivent avoir un indicateur de chargement (`wire:loading`).
### Thème Filament personnalisé
Le panel Filament utilise un thème CSS personnalisé (`resources/css/filament/admin/theme.css`) enregistré via `->viteTheme()` dans `AdminPanelProvider`. Ce thème :
- Importe le CSS de base Filament.
- Active le plugin `@tailwindcss/typography` pour les classes `prose`.
- Scanne les fichiers `app/Filament/**/*`, `resources/views/filament/**/*`, et `resources/views/components/logistics/**/*` pour inclure les classes Tailwind utilisées dans les composants.
- Contient des styles CSS personnalisés pour la classe `.documentation-prose` : hiérarchie de titres (h1-h4 avec bordures), tableaux avec bordures et en-têtes stylisés, blocs de code avec fond sombre, code inline avec fond distinct, liens colorés, listes avec marqueurs, séparateurs horizontaux visibles.
Après tout ajout de nouvelles classes Tailwind dans ces fichiers, il faut exécuter `npm run build`.
### Configuration externalisée
Les paramètres de connexion à l'API (URL, clé, dossier, timeout, retry) sont dans `config/logistics.php` et lus depuis `.env`.
## Structure des répertoires
```
app/
Exceptions/
LogisticsApiException.php # Exception dediee API
LogisticsApiException.php # Exception dédiée API
Filament/
Pages/
Articles.php # Recherche articles + stock
Documents.php # Recherche documents + detail
Dashboard.php # Page d'accueil
Documentation.php # Documentation API (markdown -> HTML)
Documents.php # Recherche documents + détail
Journaux.php # Recherche journaux
TablesExplorer.php # Exploration tables + colonnes
TablesExplorer.php # Exploration tables + colonnes (filtre, déduplication, types)
Tiers.php # Recherche tiers + historique
Models/
User.php # Modele utilisateur (Fortify)
User.php # Modèle utilisateur (Fortify)
Providers/
Filament/
AdminPanelProvider.php # Configuration du panel (sans auth)
AdminPanelProvider.php # Configuration du panel (sans auth, viteTheme)
FortifyServiceProvider.php # Authentification Fortify
AppServiceProvider.php # Config globale (CarbonImmutable, DB safety)
Services/
LogisticsService.php # Service centralise API Logistics
LogisticsService.php # Service centralisé API Logistics
config/
logistics.php # Configuration API Logistics (URL, cle, timeout, retry)
logistics.php # Configuration API Logistics (URL, clé, timeout, retry)
documentation/
documentation_api_logistics.md # Documentation complète de l'API (avec accents)
resources/
css/
app.css # CSS principal (Tailwind 4, Flux UI)
filament/admin/
theme.css # Thème Filament personnalisé (Tailwind 4 + Typography + prose)
views/
components/logistics/ # 10 composants du système de design
filament/pages/ # 6 vues de pages Filament
pdf/
documentation.blade.php # Template PDF pour la documentation
database/
migrations/
@@ -92,23 +151,32 @@ database/
...add_two_factor_columns_to_users_table.php
...create_api_request_logs_table.php
resources/views/
filament/pages/
articles.blade.php
documents.blade.php
journaux.blade.php
tables-explorer.blade.php
tiers.blade.php
tests/Feature/
LogisticsServiceTest.php # 12 tests avec mocks HTTP
DocumentationTest.php # 5 tests page Documentation (Livewire + PDF)
LogisticsServiceTest.php # 12 tests service API (mocks HTTP)
TablesExplorerTest.php # 6 tests page TablesExplorer (Livewire)
FilamentDashboardTest.php # Tests dashboard Filament
DashboardTest.php # Tests dashboard
ExampleTest.php # Test d'exemple Laravel
routes/
web.php # Routes web (home, dashboard, documentation PDF)
.cursor/rules/
design-system.mdc # Convention de design (composants, CSS, structure)
memory-bank.mdc # Gestion du memory bank
update-documentation.mdc # Procédure de mise à jour de la documentation
laravel-boost.mdc # Règles Laravel Boost
```
## Conventions
- Filament v5 : les icones de navigation utilisent l'enum `Filament\Support\Icons\Heroicon` (pas de strings).
- Filament v5 : la propriete `$view` est non-static (`protected string $view`).
- Les proprietes statiques (`$navigationIcon`, `$navigationLabel`, `$title`, `$navigationSort`) restent static.
- Filament v5 : les icônes de navigation utilisent l'enum `Filament\Support\Icons\Heroicon` (pas de strings).
- Filament v5 : la propriété `$view` est non-static (`protected string $view`).
- Les propriétés statiques (`$navigationIcon`, `$navigationLabel`, `$title`, `$navigationSort`) restent static.
- Les appels API passent toujours par `LogisticsService`, jamais directement par `Http::`.
- Les pages Filament attrapent `LogisticsApiException` en premier, puis `\Throwable` en fallback.
- Les messages d'erreur affiches a l'utilisateur sont en francais.
- Les messages d'erreur affichés à l'utilisateur sont en français.
- Toutes les vues Filament utilisent les composants `x-logistics.*` du système de design.
- Après modification des vues ou composants, exécuter `npm run build` pour recompiler le thème.
- Tous les contenus rédigés en français doivent utiliser les accents appropriés.

View File

@@ -1,39 +1,51 @@
# Tech Context
Derniere mise a jour : 2026-02-20
Dernière mise à jour : 2026-02-20
## Stack technique
| Composant | Version | Role |
| Composant | Version | Rôle |
|-----------|---------|------|
| PHP | 8.4 | Langage serveur |
| Laravel | 12 | Framework applicatif |
| Filament | 5.0 | Panel admin / dashboard |
| Livewire | 4 | Composants reactifs |
| Livewire | 4 | Composants réactifs |
| Flux UI Free | 2.9+ | Composants UI Livewire |
| Fortify | 1.30+ | Authentification (existant, non utilise par Filament) |
| Fortify | 1.30+ | Authentification (existant, non utilisé par Filament) |
| Pest | 4.4+ | Framework de tests |
| MySQL | - | Base de donnees |
| Vite | - | Bundler frontend |
| Tailwind CSS | 4 | Framework CSS |
| MySQL | - | Base de données |
| Vite | 7.3 | Bundler frontend |
| Tailwind CSS | 4 | Framework CSS (via @tailwindcss/vite) |
| @tailwindcss/typography | 0.5.19 | Plugin prose pour le rendu markdown |
| barryvdh/laravel-dompdf | - | Génération PDF |
## Dependances principales (composer.json)
## Dépendances principales (composer.json)
- `filament/filament: ^5.0`
- `laravel/framework: ^12.0`
- `livewire/livewire: ^4.0`
- `livewire/flux: ^2.9.0`
- `laravel/fortify: ^1.30`
- `barryvdh/laravel-dompdf` (export PDF de la documentation)
## Frontend
- Tailwind CSS 4 avec le plugin Vite `@tailwindcss/vite`
- Plugin `@tailwindcss/typography` (v0.5.19) pour le rendu prose du markdown
- Thème Filament personnalisé : `resources/css/filament/admin/theme.css`
- Le thème Filament scanne les sources : `app/Filament/**/*`, `resources/views/filament/**/*`, `resources/views/components/logistics/**/*`
- Le thème inclut des styles CSS personnalisés pour la classe `.documentation-prose` (titres, tableaux, blocs de code, liens, listes) optimisés pour le dark mode Filament
- Build : `npm run build` (recompile `app.css` et `theme.css`)
## Configuration
### Variables d'environnement specifiques
### Variables d'environnement spécifiques
```
LOGISTICS_API_BASE_URL=http://tse-10-test.esiweb.pro
LOGISTICS_API_KEY=<cle API>
LOGISTICS_API_BASE_URL=http://tse-10-test.esi.local
LOGISTICS_API_KEY=<clé API>
LOGISTICS_API_FOLDER=esigescom
LOGISTICS_API_TIMEOUT=30
LOGISTICS_API_TIMEOUT=300
LOGISTICS_API_CONNECT_TIMEOUT=10
LOGISTICS_API_RETRY_TIMES=3
LOGISTICS_API_RETRY_SLEEP_MS=500
@@ -41,17 +53,17 @@ LOGISTICS_API_RETRY_SLEEP_MS=500
Fichier de config : `config/logistics.php`
| Cle de config | Variable .env | Defaut | Description |
| Clé de config | Variable .env | Défaut | Description |
|---------------|---------------|--------|-------------|
| `logistics.base_url` | `LOGISTICS_API_BASE_URL` | - | URL de base de l'API |
| `logistics.api_key` | `LOGISTICS_API_KEY` | - | Cle d'authentification |
| `logistics.api_key` | `LOGISTICS_API_KEY` | - | Clé d'authentification |
| `logistics.folder` | `LOGISTICS_API_FOLDER` | - | Dossier dans l'URL |
| `logistics.timeout` | `LOGISTICS_API_TIMEOUT` | 30 | Timeout total de la requete (secondes) |
| `logistics.timeout` | `LOGISTICS_API_TIMEOUT` | 30 | Timeout total de la requête (secondes) |
| `logistics.connect_timeout` | `LOGISTICS_API_CONNECT_TIMEOUT` | 10 | Timeout de connexion (secondes) |
| `logistics.retry.times` | `LOGISTICS_API_RETRY_TIMES` | 3 | Nombre de tentatives en cas d'echec de connexion |
| `logistics.retry.sleep_ms` | `LOGISTICS_API_RETRY_SLEEP_MS` | 500 | Delai entre les tentatives (ms) |
| `logistics.retry.times` | `LOGISTICS_API_RETRY_TIMES` | 3 | Nombre de tentatives en cas d'échec de connexion |
| `logistics.retry.sleep_ms` | `LOGISTICS_API_RETRY_SLEEP_MS` | 500 | Délai entre les tentatives (ms) |
### Base de donnees
### Base de données
- Connexion : MySQL
- Base : `logistics`
@@ -61,26 +73,28 @@ Fichier de config : `config/logistics.php`
### Connexion
- Serveur : TSE-10-TEST (reseau prive, accessible uniquement via Bureau a distance / RDP)
- Base URL : `http://tse-10-test.esiweb.pro`
- Serveur : TSE-10-TEST (réseau privé)
- Base URL : `http://tse-10-test.esi.local`
- Dossier : `esigescom` (minuscules obligatoires)
- Authentification : Header `X-API-KEY`
- Methode : POST pour tous les endpoints
- Port HTTP : 5186 / Port HTTPS : 7126
- Méthode : POST pour tous les endpoints
### Structure de reponse
### Structure de réponse
```json
{
"data": "<resultat>",
"data": "<résultat>",
"metadata": { "rowcount": 0, "issuccess": true },
"error": "<message d'erreur ou null>"
}
```
Réponse `tables_list` : chaque table a `name` et `columnCount`.
Réponse `column_list` : chaque colonne a `name`, `dataType` (C/N/T/D/L/M), `length`, `precision`. Les colonnes sont retournées en double par l'API (dédupliquées côté client).
### Endpoints
| Endpoint | Description | Parametres principaux |
| Endpoint | Description | Paramètres principaux |
|----------|-------------|-----------------------|
| `tables_list` | Liste des tables | - |
| `column_list/{table}` | Colonnes d'une table | table (URL) |
@@ -88,18 +102,29 @@ Fichier de config : `config/logistics.php`
| `art_getstk` | Stock d'un article | ARTID |
| `jnl_list` | Liste des journaux | select, results, TYPE |
| `document_list` | Liste des documents | select, thirdid |
| `document_detail` | Detail d'un document | jnl, number |
| `document_detail` | Détail d'un document | jnl, number |
| `document_add` | Ajout d'un document | ThirdId, Date, Artid[], Qty[], Saleprice[], JNL, ... |
| `document_mod` | Modification d'un document | number, Thirdid, Artid[], Qty[], Saleprice[], JNL, ... |
| `Document_GetStatusList` | Statuts d'un journal | jnl |
| `Document_GetUnitPriceAndVat` | Prix et TVA | ARTID, QTY, JNL, THIRDID, DATE |
| `Document_GetDueDate` | Echeance | paydelay, date |
| `Document_GetDueDate` | Échéance | paydelay, date |
| `Document_GetAttachListThumbnail` | Miniatures annexes | JNL, NUMBER |
| `third_list` | Liste des tiers | select, results, search |
| `third_GetArtHistory` | Historique articles tiers | thirdid |
| `getserialnumber` | Numero de serie | - |
| `codes_list` | Donnees par code | code |
| `getserialnumber` | Numéro de série | - |
| `codes_list` | Données par code | code |
### Tables accessibles
art, attach, barcode, category, codes, cust, docdet, dochead, docpay, file, hist, incodes, jnl, pers, price, stk
art (160 col.), attach (13), barcode (12), category (10), codes (50), cust (216), docdet (82), dochead (212), docpay (22), file (17), hist (50), incodes (24), jnl (155), pers (78), price (28), stk (20)
### Types de colonnes (dataType)
| Code | Label | Couleur badge |
|------|-------|---------------|
| C | Caractère | Bleu |
| N | Numérique | Vert (emerald) |
| T | Date/Heure | Violet |
| D | Date | Violet |
| L | Logique | Ambre |
| M | Mémo | Gris |

76
package-lock.json generated
View File

@@ -5,14 +5,17 @@
"packages": {
"": {
"dependencies": {
"@tailwindcss/vite": "^4.1.11",
"@tailwindcss/typography": "^0.5.19",
"autoprefixer": "^10.4.20",
"axios": "^1.7.4",
"concurrently": "^9.0.1",
"laravel-vite-plugin": "^2.0",
"tailwindcss": "^4.0.7",
"vite": "^7.0.4"
},
"devDependencies": {
"@tailwindcss/vite": "^4.2.0",
"tailwindcss": "^4.2.0"
},
"optionalDependencies": {
"@rollup/rollup-linux-x64-gnu": "4.9.5",
"@tailwindcss/oxide-linux-x64-gnu": "^4.0.1",
@@ -439,6 +442,7 @@
"version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.0",
@@ -449,6 +453,7 @@
"version": "2.3.5",
"resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
@@ -459,6 +464,7 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.0.0"
@@ -468,12 +474,14 @@
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
"dev": true,
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.31",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
@@ -809,6 +817,7 @@
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.0.tgz",
"integrity": "sha512-Yv+fn/o2OmL5fh/Ir62VXItdShnUxfpkMA4Y7jdeC8O81WPB8Kf6TT6GSHvnqgSwDzlB5iT7kDpeXxLsUS0T6Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/remapping": "^2.3.5",
@@ -824,6 +833,7 @@
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.0.tgz",
"integrity": "sha512-AZqQzADaj742oqn2xjl5JbIOzZB/DGCYF/7bpvhA8KvjUj9HJkag6bBuwZvH1ps6dfgxNHyuJVlzSr2VpMgdTQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 20"
@@ -850,6 +860,7 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -866,6 +877,7 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -882,6 +894,7 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -898,6 +911,7 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -914,6 +928,7 @@
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -930,6 +945,7 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -946,6 +962,7 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -978,6 +995,7 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1002,6 +1020,7 @@
"cpu": [
"wasm32"
],
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
@@ -1023,6 +1042,7 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1039,6 +1059,7 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1048,10 +1069,23 @@
"node": ">= 20"
}
},
"node_modules/@tailwindcss/typography": {
"version": "0.5.19",
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.19.tgz",
"integrity": "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==",
"license": "MIT",
"dependencies": {
"postcss-selector-parser": "6.0.10"
},
"peerDependencies": {
"tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1"
}
},
"node_modules/@tailwindcss/vite": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.2.0.tgz",
"integrity": "sha512-da9mFCaHpoOgtQiWtDGIikTrSpUFBtIZCG3jy/u2BGV+l/X1/pbxzmIUxNt6JWm19N3WtGi4KlJdSH/Si83WOA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@tailwindcss/node": "4.2.0",
@@ -1319,6 +1353,18 @@
"url": "https://github.com/open-cli-tools/concurrently?sponsor=1"
}
},
"node_modules/cssesc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
"license": "MIT",
"bin": {
"cssesc": "bin/cssesc"
},
"engines": {
"node": ">=4"
}
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@@ -1332,6 +1378,7 @@
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
"devOptional": true,
"license": "Apache-2.0",
"engines": {
"node": ">=8"
@@ -1367,6 +1414,7 @@
"version": "5.19.0",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz",
"integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==",
"dev": true,
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.4",
@@ -1622,6 +1670,7 @@
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"dev": true,
"license": "ISC"
},
"node_modules/has-flag": {
@@ -1685,6 +1734,7 @@
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
"integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
"devOptional": true,
"license": "MIT",
"bin": {
"jiti": "lib/jiti-cli.mjs"
@@ -1713,6 +1763,7 @@
"version": "1.31.1",
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.31.1.tgz",
"integrity": "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==",
"devOptional": true,
"license": "MPL-2.0",
"dependencies": {
"detect-libc": "^2.0.3"
@@ -1962,6 +2013,7 @@
"version": "0.30.21",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
"integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.5"
@@ -2067,6 +2119,19 @@
"node": "^10 || ^12 || >=14"
}
},
"node_modules/postcss-selector-parser": {
"version": "6.0.10",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
"integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
"license": "MIT",
"dependencies": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
},
"engines": {
"node": ">=4"
}
},
"node_modules/postcss-value-parser": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
@@ -2226,6 +2291,7 @@
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz",
"integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
@@ -2296,6 +2362,12 @@
"browserslist": ">= 4.21.0"
}
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"license": "MIT"
},
"node_modules/vite": {
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",

View File

@@ -7,17 +7,20 @@
"dev": "vite"
},
"dependencies": {
"@tailwindcss/vite": "^4.1.11",
"@tailwindcss/typography": "^0.5.19",
"autoprefixer": "^10.4.20",
"axios": "^1.7.4",
"concurrently": "^9.0.1",
"laravel-vite-plugin": "^2.0",
"tailwindcss": "^4.0.7",
"vite": "^7.0.4"
},
"optionalDependencies": {
"@rollup/rollup-linux-x64-gnu": "4.9.5",
"@tailwindcss/oxide-linux-x64-gnu": "^4.0.1",
"lightningcss-linux-x64-gnu": "^1.29.1"
},
"devDependencies": {
"@tailwindcss/vite": "^4.2.0",
"tailwindcss": "^4.2.0"
}
}

View File

@@ -0,0 +1,191 @@
@import '../../../../vendor/filament/filament/resources/css/theme.css';
@plugin "@tailwindcss/typography";
@source '../../../../app/Filament/**/*';
@source '../../../../resources/views/filament/**/*';
@source '../../../../resources/views/components/logistics/**/*';
/* --- Documentation prose overrides (dark mode Filament) --- */
.documentation-prose {
--tw-prose-body: theme(--color-gray-300);
--tw-prose-headings: theme(--color-white);
--tw-prose-links: theme(--color-blue-400);
--tw-prose-bold: theme(--color-white);
--tw-prose-code: theme(--color-blue-300);
--tw-prose-hr: theme(--color-white/10);
--tw-prose-th-borders: theme(--color-white/10);
--tw-prose-td-borders: theme(--color-white/5);
line-height: 1.75;
}
.documentation-prose h1 {
font-size: 1.75rem;
font-weight: 700;
letter-spacing: -0.025em;
padding-bottom: 0.75rem;
border-bottom: 1px solid oklch(from white l c h / 0.1);
margin-bottom: 1.5rem;
}
.documentation-prose h2 {
font-size: 1.35rem;
font-weight: 600;
letter-spacing: -0.015em;
padding-bottom: 0.5rem;
border-bottom: 1px solid oklch(from white l c h / 0.07);
margin-top: 2.5rem;
margin-bottom: 1.25rem;
}
.documentation-prose h3 {
font-size: 1.1rem;
font-weight: 600;
margin-top: 2rem;
margin-bottom: 0.75rem;
}
.documentation-prose h4 {
font-size: 1rem;
font-weight: 600;
margin-top: 1.5rem;
margin-bottom: 0.5rem;
color: oklch(from white l c h / 0.85);
}
.documentation-prose hr {
border-color: oklch(from white l c h / 0.1);
margin-top: 2rem;
margin-bottom: 2rem;
}
.documentation-prose a {
color: oklch(0.7 0.15 250);
text-decoration: underline;
text-underline-offset: 2px;
transition: color 0.15s;
}
.documentation-prose a:hover {
color: oklch(0.8 0.15 250);
}
/* --- Tables --- */
.documentation-prose table {
width: 100%;
border-collapse: collapse;
font-size: 0.8125rem;
margin-top: 1rem;
margin-bottom: 1.5rem;
border: 1px solid oklch(from white l c h / 0.1);
border-radius: 0.5rem;
overflow: hidden;
}
.documentation-prose thead {
background: oklch(from white l c h / 0.05);
}
.documentation-prose thead tr {
border-bottom: 1px solid oklch(from white l c h / 0.1);
}
.documentation-prose th {
padding: 0.625rem 0.75rem;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
color: oklch(from white l c h / 0.6);
text-align: left;
white-space: nowrap;
}
.documentation-prose td {
padding: 0.5rem 0.75rem;
color: oklch(from white l c h / 0.75);
vertical-align: top;
border-top: 1px solid oklch(from white l c h / 0.05);
}
.documentation-prose tbody tr:hover {
background: oklch(from white l c h / 0.03);
}
/* --- Code blocks --- */
.documentation-prose pre {
background: oklch(0.15 0.005 260);
border: 1px solid oklch(from white l c h / 0.1);
border-radius: 0.5rem;
padding: 1rem 1.25rem;
overflow-x: auto;
font-size: 0.8125rem;
line-height: 1.6;
margin-top: 0.75rem;
margin-bottom: 1.25rem;
}
.documentation-prose pre code {
background: transparent;
padding: 0;
border-radius: 0;
font-size: inherit;
color: oklch(0.75 0.05 200);
}
.documentation-prose :not(pre) > code {
background: oklch(from white l c h / 0.08);
border: 1px solid oklch(from white l c h / 0.1);
border-radius: 0.25rem;
padding: 0.125rem 0.375rem;
font-size: 0.8125em;
color: oklch(0.78 0.1 250);
font-weight: 500;
}
/* --- Lists --- */
.documentation-prose ol {
list-style-type: decimal;
padding-left: 1.5rem;
}
.documentation-prose ul {
list-style-type: disc;
padding-left: 1.5rem;
}
.documentation-prose li {
margin-top: 0.25rem;
margin-bottom: 0.25rem;
}
.documentation-prose li::marker {
color: oklch(from white l c h / 0.4);
}
/* --- Blockquotes --- */
.documentation-prose blockquote {
border-left: 3px solid oklch(0.6 0.15 250);
padding-left: 1rem;
color: oklch(from white l c h / 0.7);
font-style: italic;
}
/* --- Strong / Bold --- */
.documentation-prose strong {
color: oklch(from white l c h / 0.95);
font-weight: 600;
}
/* --- Paragraphs --- */
.documentation-prose p {
margin-top: 0.5rem;
margin-bottom: 0.75rem;
}

View File

@@ -0,0 +1,5 @@
@props(['class' => ''])
<div {{ $attributes->class(['rounded-xl bg-white shadow-sm ring-1 ring-gray-950/5 dark:bg-gray-900 dark:ring-white/10', $class]) }}>
{{ $slot }}
</div>

View File

@@ -0,0 +1,40 @@
@props(['data', 'metadata' => null, 'emptyMessage' => 'Aucun resultat.', 'emptyIcon' => 'heroicon-o-inbox'])
@if (count($data) > 0)
@php
$firstRow = reset($data);
$isAssociative = is_array($firstRow);
$headers = $isAssociative ? array_keys($firstRow) : [];
@endphp
<div class="overflow-x-auto">
<table class="w-full text-left text-sm">
<thead>
<tr class="border-b border-gray-200 dark:border-white/10">
@if ($isAssociative)
@foreach ($headers as $key)
<th class="px-3 py-2.5 text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400">{{ $key }}</th>
@endforeach
@else
<th class="px-3 py-2.5 text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400">Valeur</th>
@endif
</tr>
</thead>
<tbody class="divide-y divide-gray-100 dark:divide-white/5">
@foreach ($data as $row)
<tr class="transition-colors hover:bg-gray-50 dark:hover:bg-white/5">
@if ($isAssociative)
@foreach ($row as $value)
<td class="px-3 py-2.5 text-sm text-gray-700 dark:text-gray-300">{{ is_array($value) ? json_encode($value) : $value }}</td>
@endforeach
@else
<td class="px-3 py-2.5 text-sm text-gray-700 dark:text-gray-300">{{ $row }}</td>
@endif
</tr>
@endforeach
</tbody>
</table>
</div>
@else
<x-logistics.empty-state :icon="$emptyIcon" :title="$emptyMessage" />
@endif

View File

@@ -0,0 +1,9 @@
@props(['icon' => 'heroicon-o-inbox', 'title', 'description' => null])
<div class="flex flex-col items-center justify-center py-12 text-center">
<x-filament::icon :icon="$icon" class="h-10 w-10 text-gray-300 dark:text-gray-600" />
<p class="mt-3 text-sm font-medium text-gray-900 dark:text-white">{{ $title }}</p>
@if ($description)
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">{{ $description }}</p>
@endif
</div>

View File

@@ -0,0 +1,7 @@
@props(['message'])
@if ($message)
<div class="rounded-lg bg-danger-50 p-4 text-sm text-danger-600 dark:bg-danger-400/10 dark:text-danger-400">
{{ $message }}
</div>
@endif

View File

@@ -0,0 +1,11 @@
@props(['label', 'id', 'type' => 'text', 'placeholder' => ''])
<div>
<label for="{{ $id }}" class="block text-sm font-medium text-gray-700 dark:text-gray-300">{{ $label }}</label>
<input
{{ $attributes->class(['mt-1.5 w-full rounded-lg border-gray-300 py-2 text-sm shadow-sm focus:border-primary-500 focus:ring-primary-500 dark:border-white/10 dark:bg-white/5 dark:text-white']) }}
type="{{ $type }}"
id="{{ $id }}"
placeholder="{{ $placeholder }}"
/>
</div>

View File

@@ -0,0 +1,7 @@
@props(['data'])
@if (! empty($data))
<div class="overflow-x-auto">
<pre class="rounded-lg border border-gray-200 bg-gray-50 p-4 text-xs font-mono leading-relaxed text-gray-700 dark:border-white/10 dark:bg-gray-800 dark:text-gray-300">{{ json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) }}</pre>
</div>
@endif

View File

@@ -0,0 +1,10 @@
@props(['placeholder' => 'Rechercher...'])
<div class="relative">
<x-filament::icon icon="heroicon-o-magnifying-glass" class="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-gray-400" />
<input
{{ $attributes->class(['w-full rounded-lg border-gray-300 py-2 pl-9 pr-3 text-sm shadow-sm focus:border-primary-500 focus:ring-primary-500 dark:border-white/10 dark:bg-white/5 dark:text-white']) }}
type="text"
placeholder="{{ $placeholder }}"
/>
</div>

View File

@@ -0,0 +1,15 @@
@props(['title', 'description' => null])
<div class="border-b border-gray-200 px-6 py-4 dark:border-white/10">
<div class="flex items-center justify-between">
<div>
<h3 class="text-base font-semibold text-gray-950 dark:text-white">{{ $title }}</h3>
@if ($description)
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">{{ $description }}</p>
@endif
</div>
@isset($actions)
<div class="flex items-center gap-2">{{ $actions }}</div>
@endisset
</div>
</div>

View File

@@ -0,0 +1,5 @@
<x-logistics.card>
<div class="flex flex-wrap items-center gap-x-6 gap-y-2 px-6 py-3">
{{ $slot }}
</div>
</x-logistics.card>

View File

@@ -0,0 +1,12 @@
@props(['icon' => null, 'label' => null, 'value'])
<div class="flex items-center gap-2 text-sm text-gray-500 dark:text-gray-400">
@if ($icon)
<x-filament::icon :icon="$icon" class="h-4 w-4 shrink-0" />
@endif
@if ($label)
<span>{{ $label }} <span class="font-medium text-gray-700 dark:text-gray-200">{{ $value }}</span></span>
@else
<span class="font-medium text-gray-700 dark:text-gray-200">{{ $value }}</span>
@endif
</div>

View File

@@ -1,91 +1,89 @@
<x-filament-panels::page>
@if ($errorMessage)
<div class="rounded-lg bg-danger-50 p-4 text-sm text-danger-600 dark:bg-danger-400/10 dark:text-danger-400">
{{ $errorMessage }}
</div>
@endif
<x-logistics.error-banner :message="$errorMessage" />
{{-- Formulaire de recherche --}}
<div class="rounded-xl bg-white p-6 shadow-sm ring-1 ring-gray-950/5 dark:bg-gray-900 dark:ring-white/10">
<h3 class="text-base font-semibold text-gray-950 dark:text-white">Rechercher des articles</h3>
<x-logistics.card>
<x-logistics.section-header title="Rechercher des articles" />
<div class="p-6">
<div class="grid grid-cols-1 gap-4 sm:grid-cols-3">
<x-logistics.form-field
wire:model="search"
label="Recherche"
id="search"
placeholder="Filtre de recherche..."
/>
<x-logistics.form-field
wire:model="select"
label="Colonnes (select)"
id="select"
placeholder="artid,artname"
/>
<x-logistics.form-field
wire:model="results"
label="Nombre de résultats"
id="results"
type="number"
min="1"
max="100"
/>
</div>
<div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-3">
<div>
<label for="search" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Recherche</label>
<input wire:model="search" type="text" id="search" placeholder="Filtre de recherche..."
class="mt-1 block w-full rounded-lg border-gray-300 shadow-sm focus:border-primary-500 focus:ring-primary-500 dark:border-white/10 dark:bg-white/5 dark:text-white sm:text-sm" />
</div>
<div>
<label for="select" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Colonnes (select)</label>
<input wire:model="select" type="text" id="select" placeholder="artid,artname"
class="mt-1 block w-full rounded-lg border-gray-300 shadow-sm focus:border-primary-500 focus:ring-primary-500 dark:border-white/10 dark:bg-white/5 dark:text-white sm:text-sm" />
</div>
<div>
<label for="results" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Nombre de resultats</label>
<input wire:model="results" type="number" id="results" min="1" max="100"
class="mt-1 block w-full rounded-lg border-gray-300 shadow-sm focus:border-primary-500 focus:ring-primary-500 dark:border-white/10 dark:bg-white/5 dark:text-white sm:text-sm" />
<div class="mt-4 flex items-center gap-3">
<x-filament::button wire:click="searchArticles" icon="heroicon-o-magnifying-glass">
Rechercher
</x-filament::button>
<div wire:loading wire:target="searchArticles" class="flex items-center gap-2">
<x-filament::loading-indicator class="h-4 w-4 text-primary-500" />
<span class="text-sm text-gray-500">Recherche en cours...</span>
</div>
</div>
</div>
<div class="mt-4">
<x-filament::button wire:click="searchArticles" icon="heroicon-o-magnifying-glass">
Rechercher
</x-filament::button>
</div>
</div>
</x-logistics.card>
{{-- Resultats --}}
@if (count($data) > 0)
<div class="rounded-xl bg-white p-6 shadow-sm ring-1 ring-gray-950/5 dark:bg-gray-900 dark:ring-white/10">
<div class="flex items-center justify-between">
<h3 class="text-base font-semibold text-gray-950 dark:text-white">Resultats</h3>
@if ($metadata)
<span class="text-sm text-gray-500">{{ $metadata['rowcount'] ?? 0 }} resultat(s)</span>
@endif
<x-logistics.card>
<x-logistics.section-header title="Résultats">
<x-slot:actions>
@if ($metadata)
<span class="rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium tabular-nums text-gray-600 dark:bg-white/10 dark:text-gray-300">
{{ $metadata['rowcount'] ?? 0 }} résultat(s)
</span>
@endif
</x-slot:actions>
</x-logistics.section-header>
<div class="p-6">
<x-logistics.data-table :data="$data" />
</div>
<div class="mt-4 overflow-x-auto">
<table class="w-full text-left text-sm">
<thead class="border-b border-gray-200 dark:border-white/10">
<tr>
@foreach (array_keys(is_array(reset($data)) ? reset($data) : $data) as $key)
<th class="px-3 py-2 font-medium text-gray-500 dark:text-gray-400">{{ $key }}</th>
@endforeach
</tr>
</thead>
<tbody class="divide-y divide-gray-100 dark:divide-white/5">
@foreach ($data as $row)
<tr>
@foreach ((is_array($row) ? $row : [$row]) as $value)
<td class="px-3 py-2 text-gray-700 dark:text-gray-300">{{ is_array($value) ? json_encode($value) : $value }}</td>
@endforeach
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</x-logistics.card>
@endif
{{-- Stock d'un article --}}
<div class="rounded-xl bg-white p-6 shadow-sm ring-1 ring-gray-950/5 dark:bg-gray-900 dark:ring-white/10">
<h3 class="text-base font-semibold text-gray-950 dark:text-white">Verifier le stock d'un article</h3>
<div class="mt-4 flex items-end gap-4">
<div class="flex-1">
<label for="stockArticleId" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Identifiant article (ARTID)</label>
<input wire:model="stockArticleId" type="text" id="stockArticleId" placeholder="Ex: ART001"
class="mt-1 block w-full rounded-lg border-gray-300 shadow-sm focus:border-primary-500 focus:ring-primary-500 dark:border-white/10 dark:bg-white/5 dark:text-white sm:text-sm" />
<x-logistics.card>
<x-logistics.section-header title="Vérifier le stock d'un article" />
<div class="p-6">
<div class="flex items-end gap-4">
<div class="flex-1">
<x-logistics.form-field
wire:model="stockArticleId"
label="Identifiant article (ARTID)"
id="stockArticleId"
placeholder="Ex: ART001"
/>
</div>
<x-filament::button wire:click="getStock" icon="heroicon-o-cube">
Vérifier le stock
</x-filament::button>
</div>
<div wire:loading wire:target="getStock" class="mt-4 flex items-center gap-2">
<x-filament::loading-indicator class="h-4 w-4 text-primary-500" />
<span class="text-sm text-gray-500">Chargement...</span>
</div>
<div wire:loading.remove wire:target="getStock" class="mt-4">
<x-logistics.json-block :data="$stockData" />
</div>
<x-filament::button wire:click="getStock" icon="heroicon-o-cube">
Verifier le stock
</x-filament::button>
</div>
@if (count($stockData) > 0)
<div class="mt-4 overflow-x-auto">
<pre class="rounded-lg bg-gray-50 p-4 text-sm text-gray-700 dark:bg-gray-800 dark:text-gray-300">{{ json_encode($stockData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) }}</pre>
</div>
@endif
</div>
</x-logistics.card>
</x-filament-panels::page>

View File

@@ -0,0 +1,170 @@
<x-filament-panels::page>
{{-- En-tete de bienvenue --}}
<x-logistics.card>
<div class="p-8 sm:p-10">
<div class="max-w-3xl">
<h1 class="text-2xl font-bold tracking-tight text-gray-950 sm:text-3xl dark:text-white">
Bienvenue sur API Logistics
</h1>
<p class="mt-3 text-base leading-relaxed text-gray-600 dark:text-gray-400">
Application d'exploration de l'API Logistics (Flex/ESI Gescom).
Ce projet permet de tester les endpoints disponibles, comprendre les structures de données
et servir de base pour une documentation complète de l'API.
</p>
</div>
</div>
</x-logistics.card>
{{-- Navigation rapide --}}
<x-logistics.card>
<x-logistics.section-header
title="Explorer l'API"
description="Accédez aux différentes sections pour interroger l'API Logistics."
/>
<div class="grid grid-cols-1 gap-4 p-6 sm:grid-cols-2 lg:grid-cols-3">
<a href="{{ \App\Filament\Pages\TablesExplorer::getUrl() }}"
class="group rounded-lg border border-gray-200 p-5 transition-colors hover:border-primary-300 hover:bg-primary-50 dark:border-white/10 dark:hover:border-primary-500/30 dark:hover:bg-primary-500/5">
<div class="flex items-center gap-3">
<div class="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-primary-50 text-primary-600 dark:bg-primary-500/10 dark:text-primary-400">
<x-filament::icon icon="heroicon-o-table-cells" class="h-5 w-5" />
</div>
<div>
<h3 class="text-sm font-semibold text-gray-950 dark:text-white">Tables</h3>
<p class="mt-0.5 text-xs text-gray-500 dark:text-gray-400">Explorer les tables et leurs colonnes</p>
</div>
</div>
</a>
<a href="{{ \App\Filament\Pages\Articles::getUrl() }}"
class="group rounded-lg border border-gray-200 p-5 transition-colors hover:border-primary-300 hover:bg-primary-50 dark:border-white/10 dark:hover:border-primary-500/30 dark:hover:bg-primary-500/5">
<div class="flex items-center gap-3">
<div class="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-primary-50 text-primary-600 dark:bg-primary-500/10 dark:text-primary-400">
<x-filament::icon icon="heroicon-o-cube" class="h-5 w-5" />
</div>
<div>
<h3 class="text-sm font-semibold text-gray-950 dark:text-white">Articles</h3>
<p class="mt-0.5 text-xs text-gray-500 dark:text-gray-400">Rechercher des articles et vérifier les stocks</p>
</div>
</div>
</a>
<a href="{{ \App\Filament\Pages\Documents::getUrl() }}"
class="group rounded-lg border border-gray-200 p-5 transition-colors hover:border-primary-300 hover:bg-primary-50 dark:border-white/10 dark:hover:border-primary-500/30 dark:hover:bg-primary-500/5">
<div class="flex items-center gap-3">
<div class="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-primary-50 text-primary-600 dark:bg-primary-500/10 dark:text-primary-400">
<x-filament::icon icon="heroicon-o-document-text" class="h-5 w-5" />
</div>
<div>
<h3 class="text-sm font-semibold text-gray-950 dark:text-white">Documents</h3>
<p class="mt-0.5 text-xs text-gray-500 dark:text-gray-400">Consulter les documents par tiers</p>
</div>
</div>
</a>
<a href="{{ \App\Filament\Pages\Journaux::getUrl() }}"
class="group rounded-lg border border-gray-200 p-5 transition-colors hover:border-primary-300 hover:bg-primary-50 dark:border-white/10 dark:hover:border-primary-500/30 dark:hover:bg-primary-500/5">
<div class="flex items-center gap-3">
<div class="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-primary-50 text-primary-600 dark:bg-primary-500/10 dark:text-primary-400">
<x-filament::icon icon="heroicon-o-book-open" class="h-5 w-5" />
</div>
<div>
<h3 class="text-sm font-semibold text-gray-950 dark:text-white">Journaux</h3>
<p class="mt-0.5 text-xs text-gray-500 dark:text-gray-400">Rechercher par type de journal</p>
</div>
</div>
</a>
<a href="{{ \App\Filament\Pages\Tiers::getUrl() }}"
class="group rounded-lg border border-gray-200 p-5 transition-colors hover:border-primary-300 hover:bg-primary-50 dark:border-white/10 dark:hover:border-primary-500/30 dark:hover:bg-primary-500/5">
<div class="flex items-center gap-3">
<div class="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-primary-50 text-primary-600 dark:bg-primary-500/10 dark:text-primary-400">
<x-filament::icon icon="heroicon-o-users" class="h-5 w-5" />
</div>
<div>
<h3 class="text-sm font-semibold text-gray-950 dark:text-white">Tiers</h3>
<p class="mt-0.5 text-xs text-gray-500 dark:text-gray-400">Rechercher des tiers et leur historique</p>
</div>
</div>
</a>
<a href="{{ \App\Filament\Pages\Documentation::getUrl() }}"
class="group rounded-lg border border-gray-200 p-5 transition-colors hover:border-primary-300 hover:bg-primary-50 dark:border-white/10 dark:hover:border-primary-500/30 dark:hover:bg-primary-500/5">
<div class="flex items-center gap-3">
<div class="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-primary-50 text-primary-600 dark:bg-primary-500/10 dark:text-primary-400">
<x-filament::icon icon="heroicon-o-document-magnifying-glass" class="h-5 w-5" />
</div>
<div>
<h3 class="text-sm font-semibold text-gray-950 dark:text-white">Documentation</h3>
<p class="mt-0.5 text-xs text-gray-500 dark:text-gray-400">Documentation complète de l'API</p>
</div>
</div>
</a>
</div>
</x-logistics.card>
{{-- Informations techniques --}}
<x-logistics.card>
<x-logistics.section-header
title="Informations techniques"
description="Configuration et stack technique du projet."
/>
<div class="p-6">
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2">
<div>
<h4 class="text-sm font-semibold text-gray-950 dark:text-white">Stack</h4>
<dl class="mt-3 space-y-2 text-sm">
<div class="flex justify-between">
<dt class="text-gray-500 dark:text-gray-400">Framework</dt>
<dd class="font-medium text-gray-700 dark:text-gray-200">Laravel 12</dd>
</div>
<div class="flex justify-between">
<dt class="text-gray-500 dark:text-gray-400">Panel admin</dt>
<dd class="font-medium text-gray-700 dark:text-gray-200">Filament 5</dd>
</div>
<div class="flex justify-between">
<dt class="text-gray-500 dark:text-gray-400">Composants réactifs</dt>
<dd class="font-medium text-gray-700 dark:text-gray-200">Livewire 4</dd>
</div>
<div class="flex justify-between">
<dt class="text-gray-500 dark:text-gray-400">CSS</dt>
<dd class="font-medium text-gray-700 dark:text-gray-200">Tailwind CSS 4</dd>
</div>
<div class="flex justify-between">
<dt class="text-gray-500 dark:text-gray-400">Tests</dt>
<dd class="font-medium text-gray-700 dark:text-gray-200">Pest 4</dd>
</div>
</dl>
</div>
<div>
<h4 class="text-sm font-semibold text-gray-950 dark:text-white">Connexion API</h4>
<dl class="mt-3 space-y-2 text-sm">
<div class="flex justify-between">
<dt class="text-gray-500 dark:text-gray-400">Serveur</dt>
<dd class="font-medium font-mono text-gray-700 dark:text-gray-200">tse-10-test.esi.local</dd>
</div>
<div class="flex justify-between">
<dt class="text-gray-500 dark:text-gray-400">Méthode</dt>
<dd class="font-medium text-gray-700 dark:text-gray-200">POST</dd>
</div>
<div class="flex justify-between">
<dt class="text-gray-500 dark:text-gray-400">Authentification</dt>
<dd class="font-medium font-mono text-gray-700 dark:text-gray-200">X-API-KEY</dd>
</div>
<div class="flex justify-between">
<dt class="text-gray-500 dark:text-gray-400">Timeout</dt>
<dd class="font-medium text-gray-700 dark:text-gray-200">{{ config('logistics.timeout', 30) }}s</dd>
</div>
<div class="flex justify-between">
<dt class="text-gray-500 dark:text-gray-400">Retry</dt>
<dd class="font-medium text-gray-700 dark:text-gray-200">{{ config('logistics.retry.times', 3) }} tentatives</dd>
</div>
</dl>
</div>
</div>
</div>
</x-logistics.card>
</x-filament-panels::page>

View File

@@ -0,0 +1,9 @@
<x-filament-panels::page>
<x-logistics.card>
<div class="p-6 sm:p-8 lg:p-10">
<div class="documentation-prose prose prose-sm max-w-none dark:prose-invert prose-headings:scroll-mt-20">
{!! $htmlContent !!}
</div>
</div>
</x-logistics.card>
</x-filament-panels::page>

View File

@@ -1,93 +1,87 @@
<x-filament-panels::page>
@if ($errorMessage)
<div class="rounded-lg bg-danger-50 p-4 text-sm text-danger-600 dark:bg-danger-400/10 dark:text-danger-400">
{{ $errorMessage }}
</div>
@endif
<x-logistics.error-banner :message="$errorMessage" />
{{-- Formulaire de recherche --}}
<div class="rounded-xl bg-white p-6 shadow-sm ring-1 ring-gray-950/5 dark:bg-gray-900 dark:ring-white/10">
<h3 class="text-base font-semibold text-gray-950 dark:text-white">Rechercher des documents</h3>
<div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
<div>
<label for="select" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Colonnes (select)</label>
<input wire:model="select" type="text" id="select" placeholder="jnl,number,thirdid,date"
class="mt-1 block w-full rounded-lg border-gray-300 shadow-sm focus:border-primary-500 focus:ring-primary-500 dark:border-white/10 dark:bg-white/5 dark:text-white sm:text-sm" />
<x-logistics.card>
<x-logistics.section-header title="Rechercher des documents" />
<div class="p-6">
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
<x-logistics.form-field
wire:model="select"
label="Colonnes (select)"
id="select"
placeholder="jnl,number,thirdid,date"
/>
<x-logistics.form-field
wire:model="thirdId"
label="Identifiant tiers (thirdid)"
id="thirdId"
placeholder="Ex: CUST001"
/>
</div>
<div>
<label for="thirdId" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Identifiant tiers (thirdid)</label>
<input wire:model="thirdId" type="text" id="thirdId" placeholder="Ex: CUST001"
class="mt-1 block w-full rounded-lg border-gray-300 shadow-sm focus:border-primary-500 focus:ring-primary-500 dark:border-white/10 dark:bg-white/5 dark:text-white sm:text-sm" />
<div class="mt-4 flex items-center gap-3">
<x-filament::button wire:click="searchDocuments" icon="heroicon-o-magnifying-glass">
Rechercher
</x-filament::button>
<div wire:loading wire:target="searchDocuments" class="flex items-center gap-2">
<x-filament::loading-indicator class="h-4 w-4 text-primary-500" />
<span class="text-sm text-gray-500">Recherche en cours...</span>
</div>
</div>
</div>
<div class="mt-4">
<x-filament::button wire:click="searchDocuments" icon="heroicon-o-magnifying-glass">
Rechercher
</x-filament::button>
</div>
</div>
</x-logistics.card>
{{-- Resultats --}}
@if (count($data) > 0)
<div class="rounded-xl bg-white p-6 shadow-sm ring-1 ring-gray-950/5 dark:bg-gray-900 dark:ring-white/10">
<div class="flex items-center justify-between">
<h3 class="text-base font-semibold text-gray-950 dark:text-white">Resultats</h3>
@if ($metadata)
<span class="text-sm text-gray-500">{{ $metadata['rowcount'] ?? 0 }} resultat(s)</span>
@endif
<x-logistics.card>
<x-logistics.section-header title="Résultats">
<x-slot:actions>
@if ($metadata)
<span class="rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium tabular-nums text-gray-600 dark:bg-white/10 dark:text-gray-300">
{{ $metadata['rowcount'] ?? 0 }} résultat(s)
</span>
@endif
</x-slot:actions>
</x-logistics.section-header>
<div class="p-6">
<x-logistics.data-table :data="$data" />
</div>
<div class="mt-4 overflow-x-auto">
<table class="w-full text-left text-sm">
<thead class="border-b border-gray-200 dark:border-white/10">
<tr>
@foreach (array_keys(is_array(reset($data)) ? reset($data) : $data) as $key)
<th class="px-3 py-2 font-medium text-gray-500 dark:text-gray-400">{{ $key }}</th>
@endforeach
</tr>
</thead>
<tbody class="divide-y divide-gray-100 dark:divide-white/5">
@foreach ($data as $row)
<tr>
@foreach ((is_array($row) ? $row : [$row]) as $value)
<td class="px-3 py-2 text-gray-700 dark:text-gray-300">{{ is_array($value) ? json_encode($value) : $value }}</td>
@endforeach
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</x-logistics.card>
@endif
{{-- Detail d'un document --}}
<div class="rounded-xl bg-white p-6 shadow-sm ring-1 ring-gray-950/5 dark:bg-gray-900 dark:ring-white/10">
<h3 class="text-base font-semibold text-gray-950 dark:text-white">Detail d'un document</h3>
<x-logistics.card>
<x-logistics.section-header title="Détail d'un document" />
<div class="p-6">
<div class="grid grid-cols-1 gap-4 sm:grid-cols-3">
<x-logistics.form-field
wire:model="detailJnl"
label="Code journal (jnl)"
id="detailJnl"
placeholder="Ex: VEN"
/>
<x-logistics.form-field
wire:model="detailNumber"
label="Numéro de document"
id="detailNumber"
placeholder="Ex: 1"
/>
<div class="flex items-end">
<x-filament::button wire:click="getDocumentDetail" icon="heroicon-o-eye">
Voir le détail
</x-filament::button>
</div>
</div>
<div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-3">
<div>
<label for="detailJnl" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Code journal (jnl)</label>
<input wire:model="detailJnl" type="text" id="detailJnl" placeholder="Ex: VEN"
class="mt-1 block w-full rounded-lg border-gray-300 shadow-sm focus:border-primary-500 focus:ring-primary-500 dark:border-white/10 dark:bg-white/5 dark:text-white sm:text-sm" />
<div wire:loading wire:target="getDocumentDetail" class="mt-4 flex items-center gap-2">
<x-filament::loading-indicator class="h-4 w-4 text-primary-500" />
<span class="text-sm text-gray-500">Chargement...</span>
</div>
<div>
<label for="detailNumber" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Numero de document</label>
<input wire:model="detailNumber" type="text" id="detailNumber" placeholder="Ex: 1"
class="mt-1 block w-full rounded-lg border-gray-300 shadow-sm focus:border-primary-500 focus:ring-primary-500 dark:border-white/10 dark:bg-white/5 dark:text-white sm:text-sm" />
</div>
<div class="flex items-end">
<x-filament::button wire:click="getDocumentDetail" icon="heroicon-o-eye">
Voir le detail
</x-filament::button>
<div wire:loading.remove wire:target="getDocumentDetail" class="mt-4">
<x-logistics.json-block :data="$detailData" />
</div>
</div>
@if (count($detailData) > 0)
<div class="mt-4 overflow-x-auto">
<pre class="rounded-lg bg-gray-50 p-4 text-sm text-gray-700 dark:bg-gray-800 dark:text-gray-300">{{ json_encode($detailData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) }}</pre>
</div>
@endif
</div>
</x-logistics.card>
</x-filament-panels::page>

View File

@@ -1,69 +1,60 @@
<x-filament-panels::page>
@if ($errorMessage)
<div class="rounded-lg bg-danger-50 p-4 text-sm text-danger-600 dark:bg-danger-400/10 dark:text-danger-400">
{{ $errorMessage }}
</div>
@endif
<x-logistics.error-banner :message="$errorMessage" />
{{-- Formulaire de recherche --}}
<div class="rounded-xl bg-white p-6 shadow-sm ring-1 ring-gray-950/5 dark:bg-gray-900 dark:ring-white/10">
<h3 class="text-base font-semibold text-gray-950 dark:text-white">Rechercher des journaux</h3>
<x-logistics.card>
<x-logistics.section-header title="Rechercher des journaux" />
<div class="p-6">
<div class="grid grid-cols-1 gap-4 sm:grid-cols-3">
<x-logistics.form-field
wire:model="type"
label="Type de journal (TYPE)"
id="type"
placeholder="Ex: VEN"
/>
<x-logistics.form-field
wire:model="select"
label="Colonnes (select)"
id="select"
placeholder="Ex: jnlid,jnlname"
/>
<x-logistics.form-field
wire:model="results"
label="Nombre de résultats"
id="results"
type="number"
min="1"
max="100"
/>
</div>
<div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-3">
<div>
<label for="type" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Type de journal (TYPE)</label>
<input wire:model="type" type="text" id="type" placeholder="Ex: VEN"
class="mt-1 block w-full rounded-lg border-gray-300 shadow-sm focus:border-primary-500 focus:ring-primary-500 dark:border-white/10 dark:bg-white/5 dark:text-white sm:text-sm" />
</div>
<div>
<label for="select" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Colonnes (select)</label>
<input wire:model="select" type="text" id="select" placeholder="Ex: jnlid,jnlname"
class="mt-1 block w-full rounded-lg border-gray-300 shadow-sm focus:border-primary-500 focus:ring-primary-500 dark:border-white/10 dark:bg-white/5 dark:text-white sm:text-sm" />
</div>
<div>
<label for="results" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Nombre de resultats</label>
<input wire:model="results" type="number" id="results" min="1" max="100"
class="mt-1 block w-full rounded-lg border-gray-300 shadow-sm focus:border-primary-500 focus:ring-primary-500 dark:border-white/10 dark:bg-white/5 dark:text-white sm:text-sm" />
<div class="mt-4 flex items-center gap-3">
<x-filament::button wire:click="searchJournaux" icon="heroicon-o-magnifying-glass">
Rechercher
</x-filament::button>
<div wire:loading wire:target="searchJournaux" class="flex items-center gap-2">
<x-filament::loading-indicator class="h-4 w-4 text-primary-500" />
<span class="text-sm text-gray-500">Recherche en cours...</span>
</div>
</div>
</div>
<div class="mt-4">
<x-filament::button wire:click="searchJournaux" icon="heroicon-o-magnifying-glass">
Rechercher
</x-filament::button>
</div>
</div>
</x-logistics.card>
{{-- Resultats --}}
@if (count($data) > 0)
<div class="rounded-xl bg-white p-6 shadow-sm ring-1 ring-gray-950/5 dark:bg-gray-900 dark:ring-white/10">
<div class="flex items-center justify-between">
<h3 class="text-base font-semibold text-gray-950 dark:text-white">Resultats</h3>
@if ($metadata)
<span class="text-sm text-gray-500">{{ $metadata['rowcount'] ?? 0 }} resultat(s)</span>
@endif
<x-logistics.card>
<x-logistics.section-header title="Résultats">
<x-slot:actions>
@if ($metadata)
<span class="rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium tabular-nums text-gray-600 dark:bg-white/10 dark:text-gray-300">
{{ $metadata['rowcount'] ?? 0 }} résultat(s)
</span>
@endif
</x-slot:actions>
</x-logistics.section-header>
<div class="p-6">
<x-logistics.data-table :data="$data" />
</div>
<div class="mt-4 overflow-x-auto">
<table class="w-full text-left text-sm">
<thead class="border-b border-gray-200 dark:border-white/10">
<tr>
@foreach (array_keys(is_array(reset($data)) ? reset($data) : $data) as $key)
<th class="px-3 py-2 font-medium text-gray-500 dark:text-gray-400">{{ $key }}</th>
@endforeach
</tr>
</thead>
<tbody class="divide-y divide-gray-100 dark:divide-white/5">
@foreach ($data as $row)
<tr>
@foreach ((is_array($row) ? $row : [$row]) as $value)
<td class="px-3 py-2 text-gray-700 dark:text-gray-300">{{ is_array($value) ? json_encode($value) : $value }}</td>
@endforeach
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</x-logistics.card>
@endif
</x-filament-panels::page>

View File

@@ -1,85 +1,148 @@
<x-filament-panels::page>
@if ($errorMessage)
<div class="rounded-lg bg-danger-50 p-4 text-sm text-danger-600 dark:bg-danger-400/10 dark:text-danger-400">
{{ $errorMessage }}
</div>
<x-logistics.error-banner :message="$errorMessage" />
@if ($tablesMetadata)
<x-logistics.stat-bar>
<x-logistics.stat-item icon="heroicon-o-server" :value="$tablesMetadata['endpoint'] ?? '-'" />
<x-logistics.stat-item icon="heroicon-o-circle-stack" label="Type :" :value="$tablesMetadata['folderType'] ?? '-'" />
<x-logistics.stat-item icon="heroicon-o-table-cells" :value="($tablesMetadata['tableCount'] ?? count($tables)) . ' table(s)'" />
</x-logistics.stat-bar>
@endif
<div class="grid grid-cols-1 gap-6 lg:grid-cols-2">
{{-- Liste des tables --}}
<div class="rounded-xl bg-white p-6 shadow-sm ring-1 ring-gray-950/5 dark:bg-gray-900 dark:ring-white/10">
<h3 class="text-base font-semibold text-gray-950 dark:text-white">Tables disponibles</h3>
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">Cliquez sur une table pour voir ses colonnes.</p>
<div class="grid grid-cols-1 gap-6 lg:grid-cols-3">
{{-- Panneau gauche : liste des tables --}}
<x-logistics.card class="lg:col-span-1">
<x-logistics.section-header
title="Tables disponibles"
description="Sélectionnez une table pour explorer ses colonnes."
/>
<div class="mt-4 space-y-1">
@forelse ($tables as $table)
@if (count($tables) > 6)
<div class="px-6 pt-4">
<x-logistics.search-input
wire:model.live.debounce.200ms="tableFilter"
placeholder="Filtrer les tables..."
/>
</div>
@endif
<div class="max-h-[32rem] space-y-0.5 overflow-y-auto p-2">
@forelse ($this->filteredTables as $table)
@php
$tableName = $table['name'] ?? '';
$columnCount = $table['columnCount'] ?? 0;
$isSelected = $selectedTable === $tableName;
@endphp
<button
wire:click="$set('selectedTable', '{{ is_array($table) ? ($table['name'] ?? $table['tablename'] ?? '') : $table }}')"
wire:then="loadColumns"
class="flex w-full items-center rounded-lg px-3 py-2 text-left text-sm transition hover:bg-gray-50 dark:hover:bg-white/5 {{ $selectedTable === (is_array($table) ? ($table['name'] ?? $table['tablename'] ?? '') : $table) ? 'bg-primary-50 text-primary-600 dark:bg-primary-400/10 dark:text-primary-400' : 'text-gray-700 dark:text-gray-300' }}"
wire:click="selectTable('{{ $tableName }}')"
wire:key="table-{{ $tableName }}"
@class([
'flex w-full items-center justify-between rounded-lg px-3 py-2.5 text-left text-sm transition-colors',
'bg-primary-50 text-primary-700 ring-1 ring-primary-200 dark:bg-primary-400/10 dark:text-primary-400 dark:ring-primary-400/30' => $isSelected,
'text-gray-700 hover:bg-gray-50 dark:text-gray-300 dark:hover:bg-white/5' => ! $isSelected,
])
>
<x-filament::icon icon="heroicon-o-table-cells" class="mr-2 h-4 w-4" />
{{ is_array($table) ? ($table['name'] ?? $table['tablename'] ?? json_encode($table)) : $table }}
<div class="flex items-center gap-2.5">
<x-filament::icon
icon="heroicon-o-table-cells"
@class([
'h-4 w-4 shrink-0',
'text-primary-500 dark:text-primary-400' => $isSelected,
'text-gray-400' => ! $isSelected,
])
/>
<span class="font-mono font-medium">{{ $tableName }}</span>
</div>
<span @class([
'rounded-full px-2 py-0.5 text-xs font-medium tabular-nums',
'bg-primary-100 text-primary-700 dark:bg-primary-400/20 dark:text-primary-300' => $isSelected,
'bg-gray-100 text-gray-500 dark:bg-white/10 dark:text-gray-400' => ! $isSelected,
])>
{{ $columnCount }}
</span>
</button>
@empty
<p class="py-4 text-center text-sm text-gray-500">Aucune table trouvee. Verifiez votre cle API.</p>
<x-logistics.empty-state
icon="heroicon-o-table-cells"
:title="filled($tableFilter) ? 'Aucune table ne correspond au filtre.' : 'Aucune table trouvée.'"
:description="filled($tableFilter) ? null : 'Vérifiez votre clé API.'"
/>
@endforelse
</div>
</div>
</x-logistics.card>
{{-- Colonnes de la table selectionnee --}}
<div class="rounded-xl bg-white p-6 shadow-sm ring-1 ring-gray-950/5 dark:bg-gray-900 dark:ring-white/10">
<h3 class="text-base font-semibold text-gray-950 dark:text-white">
Colonnes
{{-- Panneau droit : colonnes de la table selectionnee --}}
<x-logistics.card class="lg:col-span-2">
<x-logistics.section-header :title="$selectedTable ? 'Colonnes de ' . $selectedTable : 'Colonnes'">
<x-slot:actions>
@if ($columnsMetadata && $selectedTable)
<span class="rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium tabular-nums text-gray-600 dark:bg-white/10 dark:text-gray-300">
{{ $columnsMetadata['columnCount'] ?? count($columns) }} colonne(s)
</span>
@endif
</x-slot:actions>
</x-logistics.section-header>
<div class="p-6">
@if ($selectedTable)
<span class="text-primary-600 dark:text-primary-400">: {{ $selectedTable }}</span>
@endif
</h3>
@if ($selectedTable)
<div class="mt-4">
<div wire:loading wire:target="loadColumns" class="py-8 text-center text-sm text-gray-500">
Chargement...
<div wire:loading wire:target="selectTable" class="flex items-center justify-center py-12">
<x-filament::loading-indicator class="h-6 w-6 text-primary-500" />
<span class="ml-3 text-sm text-gray-500">Chargement des colonnes...</span>
</div>
<div wire:loading.remove wire:target="loadColumns">
<div wire:loading.remove wire:target="selectTable">
@if (count($columns) > 0)
<div class="overflow-x-auto">
<table class="w-full text-left text-sm">
<thead class="border-b border-gray-200 dark:border-white/10">
<tr>
@if (is_array(reset($columns)))
@foreach (array_keys(reset($columns)) as $key)
<th class="px-3 py-2 font-medium text-gray-500 dark:text-gray-400">{{ $key }}</th>
@endforeach
@else
<th class="px-3 py-2 font-medium text-gray-500 dark:text-gray-400">Colonne</th>
@endif
<thead>
<tr class="border-b border-gray-200 dark:border-white/10">
<th class="px-3 py-2.5 text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400">Nom</th>
<th class="px-3 py-2.5 text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400">Type</th>
<th class="px-3 py-2.5 text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400">Longueur</th>
<th class="px-3 py-2.5 text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400">Précision</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-100 dark:divide-white/5">
@foreach ($columns as $column)
<tr>
@if (is_array($column))
@foreach ($column as $value)
<td class="px-3 py-2 text-gray-700 dark:text-gray-300">{{ is_array($value) ? json_encode($value) : $value }}</td>
@endforeach
@else
<td class="px-3 py-2 text-gray-700 dark:text-gray-300">{{ $column }}</td>
@endif
@php
$dataType = strtoupper($column['dataType'] ?? '');
$typeLabel = \App\Filament\Pages\TablesExplorer::getDataTypeLabel($dataType);
@endphp
<tr class="transition-colors hover:bg-gray-50 dark:hover:bg-white/5">
<td class="px-3 py-2.5">
<span class="font-mono text-sm font-medium text-gray-900 dark:text-white">{{ $column['name'] ?? '-' }}</span>
</td>
<td class="px-3 py-2.5">
<span @class([
'inline-flex items-center rounded-md px-2 py-0.5 text-xs font-medium ring-1 ring-inset',
'bg-blue-50 text-blue-700 ring-blue-600/20 dark:bg-blue-400/10 dark:text-blue-400 dark:ring-blue-400/30' => $dataType === 'C',
'bg-emerald-50 text-emerald-700 ring-emerald-600/20 dark:bg-emerald-400/10 dark:text-emerald-400 dark:ring-emerald-400/30' => $dataType === 'N',
'bg-violet-50 text-violet-700 ring-violet-600/20 dark:bg-violet-400/10 dark:text-violet-400 dark:ring-violet-400/30' => in_array($dataType, ['T', 'D']),
'bg-amber-50 text-amber-700 ring-amber-600/20 dark:bg-amber-400/10 dark:text-amber-400 dark:ring-amber-400/30' => $dataType === 'L',
'bg-gray-50 text-gray-700 ring-gray-600/20 dark:bg-gray-400/10 dark:text-gray-400 dark:ring-gray-400/30' => ! in_array($dataType, ['C', 'N', 'T', 'D', 'L']),
])>
{{ $typeLabel }}
</span>
</td>
<td class="px-3 py-2.5 tabular-nums text-gray-600 dark:text-gray-300">{{ $column['length'] ?? '-' }}</td>
<td class="px-3 py-2.5 tabular-nums text-gray-600 dark:text-gray-300">{{ $column['precision'] ?? '-' }}</td>
</tr>
@endforeach
</tbody>
</table>
</div>
@else
<p class="py-4 text-center text-sm text-gray-500">Aucune colonne trouvee.</p>
<x-logistics.empty-state icon="heroicon-o-inbox" title="Aucune colonne trouvée pour cette table." />
@endif
</div>
</div>
@else
<p class="mt-4 text-sm text-gray-500 dark:text-gray-400">Selectionnez une table pour afficher ses colonnes.</p>
@endif
</div>
@else
<x-logistics.empty-state
icon="heroicon-o-cursor-arrow-rays"
title="Aucune table sélectionnée"
description="Choisissez une table dans la liste pour afficher ses colonnes."
/>
@endif
</div>
</x-logistics.card>
</div>
</x-filament-panels::page>

View File

@@ -1,91 +1,89 @@
<x-filament-panels::page>
@if ($errorMessage)
<div class="rounded-lg bg-danger-50 p-4 text-sm text-danger-600 dark:bg-danger-400/10 dark:text-danger-400">
{{ $errorMessage }}
</div>
@endif
<x-logistics.error-banner :message="$errorMessage" />
{{-- Formulaire de recherche --}}
<div class="rounded-xl bg-white p-6 shadow-sm ring-1 ring-gray-950/5 dark:bg-gray-900 dark:ring-white/10">
<h3 class="text-base font-semibold text-gray-950 dark:text-white">Rechercher des tiers</h3>
<x-logistics.card>
<x-logistics.section-header title="Rechercher des tiers" />
<div class="p-6">
<div class="grid grid-cols-1 gap-4 sm:grid-cols-3">
<x-logistics.form-field
wire:model="search"
label="Recherche (obligatoire)"
id="search"
placeholder="Filtre de recherche..."
/>
<x-logistics.form-field
wire:model="select"
label="Colonnes (select)"
id="select"
placeholder="custid,custname"
/>
<x-logistics.form-field
wire:model="results"
label="Nombre de résultats"
id="results"
type="number"
min="1"
max="100"
/>
</div>
<div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-3">
<div>
<label for="search" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Recherche (obligatoire)</label>
<input wire:model="search" type="text" id="search" placeholder="Filtre de recherche..."
class="mt-1 block w-full rounded-lg border-gray-300 shadow-sm focus:border-primary-500 focus:ring-primary-500 dark:border-white/10 dark:bg-white/5 dark:text-white sm:text-sm" />
</div>
<div>
<label for="select" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Colonnes (select)</label>
<input wire:model="select" type="text" id="select" placeholder="custid,custname"
class="mt-1 block w-full rounded-lg border-gray-300 shadow-sm focus:border-primary-500 focus:ring-primary-500 dark:border-white/10 dark:bg-white/5 dark:text-white sm:text-sm" />
</div>
<div>
<label for="results" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Nombre de resultats</label>
<input wire:model="results" type="number" id="results" min="1" max="100"
class="mt-1 block w-full rounded-lg border-gray-300 shadow-sm focus:border-primary-500 focus:ring-primary-500 dark:border-white/10 dark:bg-white/5 dark:text-white sm:text-sm" />
<div class="mt-4 flex items-center gap-3">
<x-filament::button wire:click="searchTiers" icon="heroicon-o-magnifying-glass">
Rechercher
</x-filament::button>
<div wire:loading wire:target="searchTiers" class="flex items-center gap-2">
<x-filament::loading-indicator class="h-4 w-4 text-primary-500" />
<span class="text-sm text-gray-500">Recherche en cours...</span>
</div>
</div>
</div>
<div class="mt-4">
<x-filament::button wire:click="searchTiers" icon="heroicon-o-magnifying-glass">
Rechercher
</x-filament::button>
</div>
</div>
</x-logistics.card>
{{-- Resultats --}}
@if (count($data) > 0)
<div class="rounded-xl bg-white p-6 shadow-sm ring-1 ring-gray-950/5 dark:bg-gray-900 dark:ring-white/10">
<div class="flex items-center justify-between">
<h3 class="text-base font-semibold text-gray-950 dark:text-white">Resultats</h3>
@if ($metadata)
<span class="text-sm text-gray-500">{{ $metadata['rowcount'] ?? 0 }} resultat(s)</span>
@endif
<x-logistics.card>
<x-logistics.section-header title="Résultats">
<x-slot:actions>
@if ($metadata)
<span class="rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium tabular-nums text-gray-600 dark:bg-white/10 dark:text-gray-300">
{{ $metadata['rowcount'] ?? 0 }} résultat(s)
</span>
@endif
</x-slot:actions>
</x-logistics.section-header>
<div class="p-6">
<x-logistics.data-table :data="$data" />
</div>
<div class="mt-4 overflow-x-auto">
<table class="w-full text-left text-sm">
<thead class="border-b border-gray-200 dark:border-white/10">
<tr>
@foreach (array_keys(is_array(reset($data)) ? reset($data) : $data) as $key)
<th class="px-3 py-2 font-medium text-gray-500 dark:text-gray-400">{{ $key }}</th>
@endforeach
</tr>
</thead>
<tbody class="divide-y divide-gray-100 dark:divide-white/5">
@foreach ($data as $row)
<tr>
@foreach ((is_array($row) ? $row : [$row]) as $value)
<td class="px-3 py-2 text-gray-700 dark:text-gray-300">{{ is_array($value) ? json_encode($value) : $value }}</td>
@endforeach
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</x-logistics.card>
@endif
{{-- Historique des articles d'un tiers --}}
<div class="rounded-xl bg-white p-6 shadow-sm ring-1 ring-gray-950/5 dark:bg-gray-900 dark:ring-white/10">
<h3 class="text-base font-semibold text-gray-950 dark:text-white">Historique des articles d'un tiers</h3>
<div class="mt-4 flex items-end gap-4">
<div class="flex-1">
<label for="historyThirdId" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Identifiant tiers (thirdid)</label>
<input wire:model="historyThirdId" type="text" id="historyThirdId" placeholder="Ex: CUST001"
class="mt-1 block w-full rounded-lg border-gray-300 shadow-sm focus:border-primary-500 focus:ring-primary-500 dark:border-white/10 dark:bg-white/5 dark:text-white sm:text-sm" />
<x-logistics.card>
<x-logistics.section-header title="Historique des articles d'un tiers" />
<div class="p-6">
<div class="flex items-end gap-4">
<div class="flex-1">
<x-logistics.form-field
wire:model="historyThirdId"
label="Identifiant tiers (thirdid)"
id="historyThirdId"
placeholder="Ex: CUST001"
/>
</div>
<x-filament::button wire:click="getArtHistory" icon="heroicon-o-clock">
Voir l'historique
</x-filament::button>
</div>
<div wire:loading wire:target="getArtHistory" class="mt-4 flex items-center gap-2">
<x-filament::loading-indicator class="h-4 w-4 text-primary-500" />
<span class="text-sm text-gray-500">Chargement...</span>
</div>
<div wire:loading.remove wire:target="getArtHistory" class="mt-4">
<x-logistics.json-block :data="$historyData" />
</div>
<x-filament::button wire:click="getArtHistory" icon="heroicon-o-clock">
Voir l'historique
</x-filament::button>
</div>
@if (count($historyData) > 0)
<div class="mt-4 overflow-x-auto">
<pre class="rounded-lg bg-gray-50 p-4 text-sm text-gray-700 dark:bg-gray-800 dark:text-gray-300">{{ json_encode($historyData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) }}</pre>
</div>
@endif
</div>
</x-logistics.card>
</x-filament-panels::page>

View File

@@ -0,0 +1,122 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Documentation API Logistics</title>
<style>
body {
font-family: DejaVu Sans, sans-serif;
font-size: 11px;
line-height: 1.5;
color: #1a1a1a;
margin: 30px;
}
h1 {
font-size: 22px;
border-bottom: 2px solid #2563eb;
padding-bottom: 6px;
margin-top: 30px;
}
h2 {
font-size: 17px;
color: #1e40af;
border-bottom: 1px solid #cbd5e1;
padding-bottom: 4px;
margin-top: 24px;
}
h3 {
font-size: 14px;
color: #334155;
margin-top: 18px;
}
h4 {
font-size: 12px;
color: #475569;
margin-top: 14px;
}
table {
width: 100%;
border-collapse: collapse;
margin: 10px 0;
font-size: 10px;
}
th, td {
border: 1px solid #cbd5e1;
padding: 5px 8px;
text-align: left;
}
th {
background-color: #e2e8f0;
font-weight: bold;
}
tr:nth-child(even) {
background-color: #f8fafc;
}
code {
background-color: #f1f5f9;
padding: 1px 4px;
border-radius: 3px;
font-family: DejaVu Sans Mono, monospace;
font-size: 9px;
}
pre {
background-color: #f1f5f9;
border: 1px solid #e2e8f0;
padding: 10px;
border-radius: 4px;
overflow-wrap: break-word;
white-space: pre-wrap;
font-size: 9px;
}
pre code {
background: none;
padding: 0;
}
blockquote {
border-left: 3px solid #2563eb;
margin: 10px 0;
padding: 6px 12px;
background-color: #eff6ff;
color: #1e40af;
}
hr {
border: none;
border-top: 1px solid #cbd5e1;
margin: 16px 0;
}
a {
color: #2563eb;
text-decoration: none;
}
ul, ol {
padding-left: 20px;
}
li {
margin-bottom: 3px;
}
.page-break {
page-break-after: always;
}
</style>
</head>
<body>
{!! $content !!}
</body>
</html>

View File

@@ -1,6 +1,8 @@
<?php
use Barryvdh\DomPDF\Facade\Pdf;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Str;
Route::get('/', function () {
return view('welcome');
@@ -10,4 +12,15 @@ Route::view('dashboard', 'dashboard')
->middleware(['auth', 'verified'])
->name('dashboard');
Route::get('/documentation/download-pdf', function () {
$markdown = file_get_contents(base_path('documentation/documentation_api_logistics.md'));
$htmlContent = Str::markdown($markdown);
$html = view('pdf.documentation', ['content' => $htmlContent])->render();
return Pdf::loadHTML($html)
->setPaper('a4')
->download('documentation_api_logistics.pdf');
})->name('documentation.download-pdf');
require __DIR__.'/settings.php';

View File

@@ -0,0 +1,63 @@
<?php
use App\Filament\Pages\Documentation;
use App\Models\User;
use Livewire\Livewire;
beforeEach(function () {
$this->actingAs(User::factory()->create());
});
it('renders the documentation page', function () {
Livewire::test(Documentation::class)
->assertSuccessful()
->assertSee('Documentation API Logistics');
});
it('converts markdown to HTML content', function () {
$component = Livewire::test(Documentation::class);
expect($component->get('htmlContent'))
->toBeString()
->not->toBeEmpty()
->toContain('<h1>')
->toContain('<table>');
});
it('displays the main documentation sections', function () {
Livewire::test(Documentation::class)
->assertSee('Pré-requis')
->assertSee('Comment effectuer des requêtes')
->assertSee('Structure de réponse')
->assertSee('Tables et colonnes disponibles')
->assertSee('Récupération de données')
->assertSee('Envoi de données');
});
it('downloads the documentation as PDF', function () {
$response = $this->get(route('documentation.download-pdf'));
$response->assertSuccessful()
->assertHeader('content-type', 'application/pdf');
});
it('documents all service endpoints', function () {
Livewire::test(Documentation::class)
->assertSee('tables_list')
->assertSee('column_list')
->assertSee('art_list')
->assertSee('art_getstk')
->assertSee('jnl_list')
->assertSee('document_list')
->assertSee('document_detail')
->assertSee('document_add')
->assertSee('document_mod')
->assertSee('Document_GetStatusList')
->assertSee('Document_GetUnitPriceAndVat')
->assertSee('Document_GetDueDate')
->assertSee('Document_GetAttachListThumbnail')
->assertSee('third_list')
->assertSee('third_GetArtHistory')
->assertSee('getserialnumber')
->assertSee('codes_list');
});

View File

@@ -0,0 +1,46 @@
<?php
use App\Filament\Pages\Dashboard;
use App\Models\User;
use Livewire\Livewire;
beforeEach(function () {
$this->actingAs(User::factory()->create());
});
it('renders the dashboard page', function () {
Livewire::test(Dashboard::class)
->assertSuccessful();
});
it('displays the project title and description', function () {
Livewire::test(Dashboard::class)
->assertSee('Bienvenue sur API Logistics')
->assertSee('Flex/ESI Gescom');
});
it('shows navigation links to all pages', function () {
Livewire::test(Dashboard::class)
->assertSee('Tables')
->assertSee('Articles')
->assertSee('Documents')
->assertSee('Journaux')
->assertSee('Tiers')
->assertSee('Documentation');
});
it('displays project statistics', function () {
Livewire::test(Dashboard::class)
->assertSee('Endpoints API')
->assertSee('Tables accessibles')
->assertSee('Pages Filament')
->assertSee('Tests Pest');
});
it('shows technical information', function () {
Livewire::test(Dashboard::class)
->assertSee('Informations techniques')
->assertSee('Laravel 12')
->assertSee('Filament 5')
->assertSee('Livewire 4');
});

View File

@@ -0,0 +1,147 @@
<?php
use App\Filament\Pages\TablesExplorer;
use App\Models\User;
use Illuminate\Support\Facades\Http;
use Livewire\Livewire;
beforeEach(function () {
config([
'logistics.base_url' => 'http://test-server.local',
'logistics.api_key' => 'test-api-key',
'logistics.folder' => 'testfolder',
'logistics.timeout' => 30,
'logistics.connect_timeout' => 10,
'logistics.retry.times' => 1,
'logistics.retry.sleep_ms' => 0,
]);
$this->actingAs(User::factory()->create());
});
it('loads the tables list on mount', function () {
Http::fake([
'*/tables_list' => Http::response([
'data' => [
['name' => 'art', 'columnCount' => 160],
['name' => 'cust', 'columnCount' => 216],
],
'metadata' => ['tableCount' => 2, 'folderType' => 'DBF', 'endpoint' => 'test'],
'error' => null,
]),
]);
Livewire::test(TablesExplorer::class)
->assertSet('tables', [
['name' => 'art', 'columnCount' => 160],
['name' => 'cust', 'columnCount' => 216],
])
->assertSee('art')
->assertSee('cust')
->assertSee('160')
->assertSee('216');
});
it('loads columns when a table is selected', function () {
Http::fake([
'*/tables_list' => Http::response([
'data' => [['name' => 'stk', 'columnCount' => 10]],
'metadata' => ['tableCount' => 1],
'error' => null,
]),
'*/column_list/stk' => Http::response([
'data' => [
['name' => 'ARTID', 'dataType' => 'C', 'length' => 20, 'precision' => 0],
['name' => 'ARTID', 'dataType' => 'C', 'length' => 20, 'precision' => 0],
['name' => 'STOCK', 'dataType' => 'N', 'length' => 10, 'precision' => 2],
['name' => 'STOCK', 'dataType' => 'N', 'length' => 10, 'precision' => 2],
],
'metadata' => ['columnCount' => 4, 'tableName' => 'stk'],
'error' => null,
]),
]);
Livewire::test(TablesExplorer::class)
->call('selectTable', 'stk')
->assertSet('selectedTable', 'stk')
->assertSee('ARTID')
->assertSee('STOCK')
->assertSee('Caractère')
->assertSee('Numérique');
});
it('deduplicates columns from the API response', function () {
Http::fake([
'*/tables_list' => Http::response([
'data' => [['name' => 'stk', 'columnCount' => 4]],
'metadata' => ['tableCount' => 1],
'error' => null,
]),
'*/column_list/stk' => Http::response([
'data' => [
['name' => 'ARTID', 'dataType' => 'C', 'length' => 20, 'precision' => 0],
['name' => 'ARTID', 'dataType' => 'C', 'length' => 20, 'precision' => 0],
['name' => 'STOCK', 'dataType' => 'N', 'length' => 10, 'precision' => 2],
['name' => 'STOCK', 'dataType' => 'N', 'length' => 10, 'precision' => 2],
],
'metadata' => ['columnCount' => 4],
'error' => null,
]),
]);
Livewire::test(TablesExplorer::class)
->call('selectTable', 'stk')
->assertSet('columns', [
['name' => 'ARTID', 'dataType' => 'C', 'length' => 20, 'precision' => 0],
['name' => 'STOCK', 'dataType' => 'N', 'length' => 10, 'precision' => 2],
]);
});
it('filters tables by name', function () {
Http::fake([
'*/tables_list' => Http::response([
'data' => [
['name' => 'art', 'columnCount' => 160],
['name' => 'attach', 'columnCount' => 13],
['name' => 'cust', 'columnCount' => 216],
['name' => 'dochead', 'columnCount' => 212],
['name' => 'docdet', 'columnCount' => 82],
['name' => 'docpay', 'columnCount' => 22],
['name' => 'stk', 'columnCount' => 20],
],
'metadata' => ['tableCount' => 7],
'error' => null,
]),
]);
$component = Livewire::test(TablesExplorer::class)
->set('tableFilter', 'doc');
$filteredTables = $component->get('filteredTables');
expect($filteredTables)->toHaveCount(3)
->and(array_column($filteredTables, 'name'))->toBe(['dochead', 'docdet', 'docpay']);
});
it('displays an error message when the API fails', function () {
Http::fake([
'*/tables_list' => Http::response([
'data' => [],
'metadata' => [],
'error' => 'Invalid API key',
]),
]);
Livewire::test(TablesExplorer::class)
->assertSee('Invalid API key');
});
it('shows data type labels correctly', function () {
expect(TablesExplorer::getDataTypeLabel('C'))->toBe('Caractère')
->and(TablesExplorer::getDataTypeLabel('N'))->toBe('Numérique')
->and(TablesExplorer::getDataTypeLabel('T'))->toBe('Date/Heure')
->and(TablesExplorer::getDataTypeLabel('D'))->toBe('Date')
->and(TablesExplorer::getDataTypeLabel('L'))->toBe('Logique')
->and(TablesExplorer::getDataTypeLabel('M'))->toBe('Mémo')
->and(TablesExplorer::getDataTypeLabel('X'))->toBe('X');
});

View File

@@ -7,7 +7,7 @@ import tailwindcss from "@tailwindcss/vite";
export default defineConfig({
plugins: [
laravel({
input: ['resources/css/app.css', 'resources/js/app.js'],
input: ['resources/css/app.css', 'resources/js/app.js', 'resources/css/filament/admin/theme.css'],
refresh: true,
}),
tailwindcss(),