# Description du programme

Application web de gestion et d'édition de timeway pour installations de traitement de surface (galvanoplastie, phosphatation, etc.).

**Version : 2.4.2** — © 2026 VK Vision SA

---

## Description

VKTimeway permet de définir, visualiser et éditer graphiquement les cycles de transfert d'un ou de plusieurs robots sur une ligne de traitement de surface. L'application gère :

- Les **machines** (lignes de traitement) avec leur historique de versions
- Les **positions** (bains, postes de dépose/prise) par ligne
- Les **robots** (hoists) avec leurs paramètres cinématiques
- Les **séquences** de traitement par produit
- Les **timeway** (diagrammes espace-temps) générés automatiquement ou édités manuellement
- L'**éditeur graphique canvas** interactif des timeway
- Le **simulateur de production** avec visualisation multi-cycles
- La **génération de rapports PDF** pour chaque entité
- Une **source de données Cloud** optionnelle (serveur PocketBase) en plus du stockage local, avec partage de machines entre utilisateurs

---

## Architecture

Application **PWA vanilla JS** sans framework, fonctionnant entièrement côté client.

```
App/
├── index.html                  Point d'entrée unique (SPA)
├── manifest.json               Manifeste PWA
├── sw.js                       Service Worker (cache offline)
└── assets/
    ├── js/
    │   ├── VKApp.js            Routeur SPA + logique de toutes les pages
    │   ├── VKModels.js         Constructeurs des modèles de données
    │   ├── VKData.js           Persistance localStorage
    │   ├── VKCloud.js          Client PocketBase (auth, CRUD, partage)
    │   ├── VKStore.js          Façade de persistance (routage Local/Cloud)
    │   ├── VKDiagram.js        Moteur de rendu canvas du timeway
    │   ├── VKMachine.js        Dessin SVG inline de la machine
    │   ├── VKTimewayGen.js     Générateur automatique de timeway
    │   ├── VKReport.js         Génération de rapports PDF (pdfmake)
    │   ├── VKI18n.js           Internationalisation (FR / EN / DE / ZH)
    │   ├── pdfmake.min.js      Librairie PDF (embarquée, offline)
    │   ├── vfs_fonts.min.js    Polices pdfmake (Roboto, embarquées)
    │   └── pocketbase.umd.js   SDK client PocketBase (embarqué)
    ├── css/
    │   └── app.css             Styles spécifiques à l'application
    └── img/
        └── icons/              Icônes PNG de la barre d'outils de l'éditeur

PocketBase/
└── README.md                   Configuration des collections du serveur PocketBase (source "Cloud")
```

### Modules JS

| Fichier | Rôle |
|---|---|
| `VKApp.js` | Routeur hash (`#/`, `#/machines`, …), rendu des pages, modales d'édition, simulateur |
| `VKModels.js` | Constructeurs `Machine`, `Version`, `Position`, `Hoist`, `Sequence`, `Timeway`, … |
| `VKData.js` | Lecture / écriture dans `localStorage` (source "Local") |
| `VKCloud.js` | Client PocketBase : authentification email/mot de passe, CRUD Machines/Versions, partage (source "Cloud") |
| `VKStore.js` | Façade `Machines`/`Versions` : route vers `VKData` ou `VKCloud` selon la source de données active, transparente pour le reste de l'application |
| `VKDiagram.js` | Rendu canvas : axes, étapes, aides visuelles, détection collision |
| `VKMachine.js` | Dessin SVG inline de la machine (cuves, robots) depuis la définition des positions |
| `VKTimewayGen.js` | Génération automatique d'un timeway depuis une séquence |
| `VKReport.js` | Rapports PDF via pdfmake : machine, séquence, timeway, diagramme |
| `VKI18n.js` | Dictionnaire quadrilingue + injection dans le DOM (`data-i18n`) |

### Modèle de données

```
Machine                  Id, Name, Number, Client, Location, Remarks, CreatedAt, UpdatedAt
└── Version[]            Id, Number, Comment, Remarks, CreatedAt, UpdatedAt
    ├── Positions[]      id, name, tag, flag, group, tank, address, branch, lift, lower, distance
    ├── Hoists[]         id, pi, pf, width, front, back, speed1 (mm/s), accdec (ms), lifts[], lowers[]
    ├── Sequences[]      id, name, remarks, steps[]
    ├── Timeways[]       id, name, remarks, cycletime (ms), visible, steps[]
    ├── LiftTypes[]      10 types de montée nommés (id, name, time)
    └── LowerTypes[]     10 types de descente nommés (id, name, time)
```

En source **Local**, toutes les données sont stockées en JSON dans `localStorage` sous les clés `tw_machines` et `tw_versions`.

En source **Cloud**, le même modèle est persisté sur un serveur PocketBase : `Machine` et `Version` (métadonnées) deviennent chacune une collection, le contenu imbriqué de `Version` (`Positions`, `Hoists`, `Sequences`, `Timeways`, `Programs`, `LiftTypes`, `LowerTypes`) étant conservé tel quel dans un unique champ JSON `data` — voir `PocketBase/README.md`.

---

## Fonctionnalités principales

### Gestion des machines
- Création / modification / suppression de machines
- Champs : nom, numéro, client, localisation, remarques
- Horodatage automatique de création et de dernière modification
- Export / import d'une machine au format JSON (transfert entre postes)

### Export / Import des données
- **Export global** (page d'accueil, source Local uniquement) : télécharge un snapshot complet du `localStorage` (toutes les machines et versions) au format JSON
- **Import global** (page d'accueil, source Local uniquement) : restaure un snapshot précédemment exporté (remplace toutes les données existantes après confirmation)
- **Export machine** : exporte une machine et toutes ses versions dans un fichier JSON individuel (disponible en Local comme en Cloud)
- **Import machine** : importe un fichier machine JSON dans l'application (disponible en Local comme en Cloud)

### Source de données Cloud (PocketBase)
- La page d'accueil permet de choisir la source de données active : **Local** (`localStorage`, comportement historique) ou **Cloud** (serveur PocketBase distant), avec connexion par adresse e-mail + mot de passe
- Une machine créée en Cloud n'est visible que par son créateur, ou par les utilisateurs avec qui elle a été explicitement **partagée** (bouton dédié dans la liste des machines), avec un droit **Lecture** ou **Édition** par utilisateur — la logique d'accès est appliquée côté serveur par les règles API PocketBase, pas par le code client
- **Copie entre sources** : un bouton par machine permet de copier une machine (et toutes ses versions) de la source active vers l'autre, sans modifier l'original
- L'adresse du serveur PocketBase a une valeur par défaut dans le code (`VK_POCKETBASE_URL_DEFAULT` dans `VKCloud.js`) mais reste modifiable depuis le formulaire de connexion, et la dernière adresse utilisée est mémorisée dans le navigateur
- Les sauvegardes/suppressions en Cloud sont optimistes : l'interface reste instantanée, la synchronisation réseau se fait en arrière-plan ; un échec déclenche une notification (toast) sans bloquer l'édition
- Configuration du serveur PocketBase (collections, règles API) : voir `PocketBase/README.md`

### Versions
- Chaque machine peut avoir plusieurs versions indépendantes
- Commentaire et remarques sur chaque version
- Copie et suppression de versions

### Positions, robots (hoists), séquences
- **Édition inline** directement dans le tableau : clic sur une ligne pour éditer en place
- Navigation clavier dans les listes : `Entrée` (enregistrer), `Échap` (annuler), `↑` / `↓` (ligne précédente / suivante)
- Copie de séquences existantes
- Identifiants de séquences auto-incrémentés
- Identifiants de positions calculés automatiquement à l'ajout et recalculés lors des insertions/suppressions

### Visualisation de la machine
- Le plan en coupe de la ligne (cuves, navettes, postes) est dessiné en SVG au-dessus des listes de positions et de robots
- Le dessin se rafraîchit à chaque modification
- Dans l'éditeur de séquence, un clic sur une cuve ajoute une étape automatiquement pré-remplie (position, groupe, types montée/descente)
- Chaque position peut avoir un **tag** libre affiché dans le socle de la cuve (à la place de l'adresse)

### Types montées / descentes
- 10 types nommés par version pour les montées et les descentes
- Le type de montée et de descente est conservé dans chaque étape de timeway généré (`liftType`, `lowerType`)
- Référencés par chaque position et chaque étape de séquence

### Génération automatique de timeway
- Calcul des temps de transfert avec profil de vitesse trapézoïdal :
  - Distance longue : `t = (d/v + ta/2) × 1000` ms
  - Distance courte : `t = √(2 × ta × d / v) × 1000` ms
  - où `v` = vitesse en mm/s, `ta` = temps acc/déc en s
- Attribution automatique des robots aux étapes
- Calcul optionnel des mouvements à vide (retours)

### Éditeur graphique de timeway
- Canvas interactif avec zoom et pan
- **Redimensionnement automatique** du canvas lorsque la fenêtre de l'éditeur change de taille (`ResizeObserver`)
- Sélection du timeway actif via menu déroulant (titre mis à jour dynamiquement)
- Outils : sélection, déplacement, déplacement bloc gauche/droite, ajout d'étapes, effacement, ajustement des temps
- Options d'affichage persistées dans `localStorage` :
  - Grille des temps (`timeGrid`)
  - Grille des positions (`posGrid`)
  - Marques montée/descente (`upDown`)
  - Aides mouvement vides (`emptyMove`)
  - Empreinte bras (`hoistRange`)
  - Liens (`showLink`)
  - **Regroupement des positions** (`collapseGroups`) — les positions d'un même groupe sont affichées sur une seule ligne
- Icônes actives en jaune, inactives en blanc, survol en gris
- Détection visuelle des collisions entre robots
- Cercles d'intersection sur les zones de croisement de temps clés
- Robot sélectionné mis en évidence en jaune (légende mise à jour en conséquence)
- **Curseur interactif** : nom de la position en jaune au survol + ligne verticale pointillée sur l'axe du temps

### Simulateur de production
- Fenêtre plein écran de simulation multi-cycles
- Ajout de diagrammes dans la file de simulation avec vérification des conflits :
  - Détection d'occupation de bain (positions sans groupe)
  - Substitution automatique de position dans un groupe (flag `gf`) si la position cible est occupée
  - Collision de robots entre cycles
- **Algorithme de décalage de cycle** :
  - Les temps `ti`/`tf` sont arrondis à la seconde
  - Si un step dépose dans un groupe (`gf != 0`) et que `tminf > cycleTime`, le temps d'immersion effectif est calculé : `(tf−ti) + floor(tminf / cycleTime) × cycleTime`
  - L'ajout de cycles basé sur `tminf` ne s'applique qu'aux positions avec groupe
- **Positions avec flag 1, 2, 3 ou 7** : le test d'occupation de bain est ignoré
- Visualisation canvas interactive :
  - Axe du temps avec graduation en minutes (`#N` par slot de cycle)
  - Axe des positions avec défilement vertical (clic + glisser)
  - Position survolée mise en évidence en jaune
  - Barres d'immersion colorées (cyan = normal, orange = < tminf, rouge = > tmaxf)
  - Pan temporel par clic + glisser horizontal
  - Zoom temporel par molette de la souris
- Liste des cycles chargeables repliable (panneau latéral)
- Impression du diagramme de simulation

### Rapports PDF

Générés côté client via **pdfmake** (aucun serveur requis, fonctionne offline).
Chaque rapport est téléchargé directement en `.pdf`.

| Rapport | Déclencheur | Contenu |
|---|---|---|
| **Fiche machine** | Bouton dans l'onglet Positions | Positions · Robots · Types montées/descentes · Remarques |
| **Fiche séquence** | Bouton par ligne dans l'onglet Séquences | Étapes (position, groupe, montée, descente, Tmin/Tmax/Tégouttage) · Remarques |
| **Fiche timeway** | Bouton par ligne dans l'onglet Timeway | Étapes avec tous les temps (Ti, Tf, Tégouttage, Tmontée, Tdescente, Ttransfert) · Remarques |
| **Rapport diagramme** | Bouton dans la toolbar de l'éditeur | Capture du canvas + table des étapes · Remarques (orientation paysage si nécessaire) |

Structure commune des rapports :
- Bandeau d'en-tête (machine, version, titre du rapport, client/localisation)
- Tables avec lignes alternées et en-têtes répétés sur chaque page
- Footer : copyright · numéro de page · date de génération

---

## Internationalisation

Quatre langues supportées : **Français** (défaut), **English**, **Deutsch**, **中文** (chinois simplifié).

La langue est sélectionnable depuis la **page d'accueil** et persistée dans `localStorage`. Le changement est instantané sans rechargement de page.

Utilisation dans le HTML : `data-i18n="clé"` sur n'importe quel élément.
Utilisation dans le JS : `VKI18n.t('clé')`.

---

## Installation / Démarrage

### Développement

Aucune dépendance serveur pour la source de données "Local" — l'application fonctionne directement depuis un navigateur (la source "Cloud" nécessite un serveur PocketBase, voir plus bas).

1. Cloner le dépôt
2. Servir le dossier `App/` via un serveur HTTP local (requis pour le Service Worker) :
   ```bash
   npx serve App/
   # ou
   python -m http.server 8080 --directory App/
   ```
3. Ouvrir `http://localhost:8080` dans un navigateur moderne

> **Note :** L'ouverture directe du fichier `index.html` (`file://`) ne fonctionnera pas avec le Service Worker. Un serveur HTTP est nécessaire.

### Build de distribution (version minifiée)

Un script de build génère une version optimisée dans `Dist/` :

```bash
npm install       # une seule fois
npm run build     # génère Dist/
```

Le build effectue :
- Concaténation + minification des fichiers JS applicatifs (dont `VKCloud.js`/`VKStore.js`) → `app.bundle.min.js`
- Minification de `app.css` → `app.min.css`
- Copie des librairies tierces déjà minifiées (dont `pocketbase.umd.js`)
- Mise à jour de `index.html` pour référencer les fichiers minifiés
- Génération d'un `sw.js` avec un nom de cache dédié (`vktimeway-dist-v*`)

> **Important :** le nom du cache du `sw.js` généré (`vktimeway-dist-v*`) est dérivé automatiquement de `VK_VERSION` (tête de `VKApp.js`) — incrémenter cette constante (et `CACHE_NAME` dans `App/sw.js` pour le mode développement) à chaque nouveau déploiement pour forcer la mise à jour du cache chez les utilisateurs.

Pour servir la version de distribution :
```bash
npx serve Dist/
```

### Source de données Cloud (optionnel)

La source "Local" (`localStorage`) fonctionne sans aucune configuration. Pour activer la source "Cloud", un serveur [PocketBase](https://pocketbase.io/) est nécessaire :

1. Configurer les collections `machines`, `versions`, `machine_shares` sur le serveur — voir `PocketBase/README.md` (champs, règles API à copier-coller)
2. Renseigner son adresse dans `VK_POCKETBASE_URL_DEFAULT` (`App/assets/js/VKCloud.js`), ou directement dans le formulaire de connexion de l'application

---

## Dépendances front-end (embarquées)

| Librairie | Version | Usage |
|---|---|---|
| Bootstrap | 5.x | UI composants, grille, modales |
| Font Awesome | 6.x | Icônes vectorielles |
| pdfmake | 0.2.10 | Génération de rapports PDF côté client |
| pocketbase (SDK JS) | 0.28.x | Client de la source de données Cloud (auth, CRUD, partage) |

Toutes les dépendances sont incluses dans `App/assets/` — aucune connexion CDN requise.

### Dépendances de build (développement uniquement)

| Package | Usage |
|---|---|
| `terser` | Minification JavaScript |
| `clean-css` | Minification CSS |
| `fs-extra` | Opérations fichiers avancées |

---

## Structure des routes

| Hash | Page |
|---|---|
| `#/` | Accueil (sélection de langue, source de données Local/Cloud, export/import données) |
| `#/machines` | Liste des machines |
| `#/machines/create` | Nouvelle machine |
| `#/machines/:id` | Détail machine + liste des versions |
| `#/machines/:id/version/:vid` | Édition d'une version (positions, robots, séquences, timeway) |

---

## Versioning de l'application

La version est définie par la constante `VK_VERSION` en tête de `VKApp.js` :

```javascript
var VK_VERSION = 'ver. 2.4.2';
```

Elle s'affiche automatiquement dans le footer de l'application et est intégrée dans les métadonnées et les noms de fichiers des rapports PDF générés.

# Configuration PocketBase — VKTimeway

Ce dossier documente la configuration à effectuer **une seule fois**, dans
l'interface d'administration de votre serveur PocketBase, pour activer la
source de données "Cloud" de l'application (voir `App/assets/js/VKCloud.js`
et `App/assets/js/VKStore.js`).

Testé avec le SDK client `pocketbase` v0.28.1 (vendored dans
`App/assets/js/pocketbase.umd.js`) — utilisez une version de serveur
PocketBase récente (≥ 0.23) qui expose le même format de règles.

## 0. Adresse du serveur

Ouvrez `App/assets/js/VKCloud.js` et remplacez la valeur par défaut :

```js
var VK_POCKETBASE_URL_DEFAULT = 'http://localhost:8090';
```

par l'URL réelle de votre instance PocketBase. Ce n'est qu'une valeur par
défaut : le champ "Adresse du serveur PocketBase" du formulaire de connexion
(page d'accueil, source "Cloud") permet de la modifier à tout moment sans
retoucher le code — la dernière adresse utilisée est mémorisée dans le
navigateur. Changer d'adresse invalide la session en cours (un jeton
d'authentification n'a de sens que pour le serveur qui l'a émis).

## 1. Collection `users` (déjà fournie par PocketBase)

Rien à créer — PocketBase inclut déjà une collection d'authentification
`users` (email + mot de passe). Il faut seulement assouplir ses règles pour
permettre à un utilisateur connecté de retrouver un autre utilisateur par
son adresse e-mail (nécessaire pour partager une machine) :

Dans **Collections → users → Edit → API Rules**, remplacez :

- **List/Search rule** : `id = @request.auth.id` → `@request.auth.id != ""`
- **View rule** : `id = @request.auth.id` → `@request.auth.id != ""`

Ceci permet à tout utilisateur connecté de lister/consulter les comptes
(uniquement les champs publics : email, id — jamais le mot de passe), mais
pas de les modifier ni les supprimer (ces règles restent inchangées).

## 2. Collection `machines`

**Collections → New collection → Base collection**, nom `machines`.

Champs :

| Champ | Type | Requis | Notes |
|---|---|---|---|
| `name` | Text | oui | |
| `number` | Text | non | |
| `client` | Text | non | |
| `location` | Text | non | |
| `remarks` | Editor (ou Text) | non | |
| `owner` | Relation → `users` | oui | Single, cascade delete conseillé |
| `local_id` | Text | non | UUID généré côté client, pour réconcilier avec les données locales |

API Rules :

```
List/Search rule :
  owner = @request.auth.id ||
  machine_shares_via_machine.user ?= @request.auth.id

View rule :  (identique au List rule ci-dessus)

Create rule :
  @request.auth.id != "" && @request.body.owner = @request.auth.id

Update rule :
  owner = @request.auth.id ||
  (machine_shares_via_machine.user ?= @request.auth.id &&
   machine_shares_via_machine.permission = "edit")

Delete rule :
  owner = @request.auth.id
```

## 3. Collection `versions`

**New collection → Base collection**, nom `versions`.

Champs :

| Champ | Type | Requis | Notes |
|---|---|---|---|
| `machine` | Relation → `machines` | oui | Single, cascade delete conseillé |
| `number` | Number | oui | |
| `comment` | Text | non | |
| `remarks` | Editor (ou Text) | non | |
| `data` | JSON | oui | `{ Positions, Hoists, Sequences, Timeways, Programs, LiftTypes, LowerTypes }` |
| `local_id` | Text | non | UUID généré côté client |

API Rules :

```
List/Search rule :
  machine.owner = @request.auth.id ||
  machine.machine_shares_via_machine.user ?= @request.auth.id

View rule :  (identique)

Create rule :
  machine.owner = @request.auth.id ||
  (machine.machine_shares_via_machine.user ?= @request.auth.id &&
   machine.machine_shares_via_machine.permission = "edit")

Update rule :  (identique au Create rule)

Delete rule :
  machine.owner = @request.auth.id
```

## 4. Collection `machine_shares`

**New collection → Base collection**, nom `machine_shares`.

Champs :

| Champ | Type | Requis | Notes |
|---|---|---|---|
| `machine` | Relation → `machines` | oui | Single, cascade delete conseillé |
| `user` | Relation → `users` | oui | Single, cascade delete conseillé |
| `permission` | Select | oui | Options : `view`, `edit` — défaut `view` |

API Rules :

```
List/Search rule :
  machine.owner = @request.auth.id || user = @request.auth.id

View rule :  (identique)

Create rule :
  machine.owner = @request.auth.id

Update rule :
  machine.owner = @request.auth.id

Delete rule :
  machine.owner = @request.auth.id || user = @request.auth.id
```

## 5. Comptes utilisateurs

Créez les comptes depuis **Collections → users → New record** (ou activez
l'inscription publique si vous le souhaitez — non nécessaire pour ce projet,
les comptes peuvent être créés uniquement par l'administrateur).

## Notes / limites connues

- Le contenu détaillé d'une version (positions, robots, séquences,
  diagrammes, programmes) est stocké tel quel dans le champ JSON `data` —
  il n'y a pas de sous-collections dédiées, ce qui reflète la structure déjà
  utilisée en local (`localStorage`).
- `local_id` sert uniquement de repère côté client ; il n'y a pas
  (actuellement) de synchronisation bidirectionnelle automatique entre les
  données locales et le cloud — c'est une source de données alternative que
  l'on choisit sur la page d'accueil, pas une fusion des deux.
- Les sauvegardes/suppressions en mode Cloud sont optimistes : l'interface
  se met à jour immédiatement, puis la requête vers PocketBase part en
  arrière-plan. En cas d'échec (réseau coupé, etc.), une notification
  apparaît mais la modification reste dans le miroir local de la session
  tant qu'un rechargement (`refreshCloud`) n'a pas eu lieu — pensez à
  vérifier la connexion en cas d'erreurs répétées.