Import initail de calcul-astreintes v0.9.4 pour passage en paycheck 1.0
38
.gitignore
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
# --- Build / Wails ---
|
||||
bin/
|
||||
build/
|
||||
dist/
|
||||
*.exe
|
||||
*.app
|
||||
*.dmg
|
||||
!build/appicon.png
|
||||
!build/windows/
|
||||
!build/windows/icon.ico
|
||||
|
||||
# Wails generated
|
||||
# frontend/src/wailsjs/
|
||||
|
||||
# --- Node ---
|
||||
frontend/node_modules/
|
||||
frontend/dist/*
|
||||
!frontend/dist/.keep
|
||||
frontend/.vite/
|
||||
|
||||
# --- Archives / exports lourds ---
|
||||
*.zip
|
||||
*.tar
|
||||
*.tar.gz
|
||||
*.7z
|
||||
|
||||
# --- PDF / exports ---
|
||||
*.pdf
|
||||
|
||||
# --- Diffs / patches ---
|
||||
*.diff
|
||||
*.patch
|
||||
|
||||
# --- OS / IDE ---
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
.vscode/
|
||||
.idea/
|
||||
17
LICENSE
Normal file
@@ -0,0 +1,17 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2026 Alban
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
161
README.md
Normal file
@@ -0,0 +1,161 @@
|
||||
# Calcul Astreintes
|
||||
|
||||
Outil de calcul de rémunération des astreintes et interventions, basé sur un import de fichier Excel mensuel et un **remplissage automatique** des données par agent.
|
||||
|
||||
---
|
||||
|
||||
## Fonctionnalités
|
||||
|
||||
- Import d’un fichier Excel mensuel (format `YYYY-MM`)
|
||||
- Lecture automatique des heures d’astreinte et d’intervention
|
||||
- Sélection d’un agent par **nom / prénom / matricule**
|
||||
- **Remplissage automatique** des champs de calcul à partir du fichier Excel
|
||||
- Saisie manuelle possible (sans import Excel)
|
||||
- Calcul **manuel** via un bouton « Calculer » (comportement volontairement sécurisé)
|
||||
- Export PDF du calcul (A4) (nom de fichier : `calcul-astreintes-YYYY-MM-NOM-Prenom.pdf`)
|
||||
- Application graphique multiplateforme basée sur **Wails** (Go + Web)
|
||||
|
||||
Champs remplis automatiquement :
|
||||
- Heures d’intervention de jour (code 456)
|
||||
- Heures d’intervention de nuit (code 459)
|
||||
- Heures d’intervention dimanche / jours fériés (code 458)
|
||||
- Total des heures d’astreinte du mois (code 471)
|
||||
|
||||
---
|
||||
|
||||
## Principe de fonctionnement
|
||||
|
||||
1. L’utilisateur importe le fichier Excel mensuel des astreintes
|
||||
2. Le logiciel analyse automatiquement la feuille correspondant au mois (`YYYY-MM`)
|
||||
3. L’utilisateur sélectionne un agent dans la liste (nom, prénom, matricule)
|
||||
4. Les champs de calcul sont **remplis automatiquement** à partir de la ligne « Total Agent »
|
||||
5. L’utilisateur vérifie / complète si nécessaire (ex. nombre de dimanches / jours fériés)
|
||||
6. Le calcul est lancé manuellement via le bouton « Calculer »
|
||||
|
||||
---
|
||||
|
||||
## Format du fichier Excel attendu
|
||||
|
||||
- Une feuille par mois, nommée `YYYY-MM` (exemple : `2026-01`)
|
||||
- Les agents sont identifiés par :
|
||||
- Un **matricule unique**
|
||||
- Le nom et le prénom dans une seule cellule (format : `NOM Prénom`)
|
||||
- Les valeurs utilisées sont celles de la ligne **Total Agent**, située juste au-dessus des lignes détaillées
|
||||
|
||||
Colonnes exploitées sur la ligne « Total Agent » :
|
||||
- Total des heures d’astreinte du mois
|
||||
- Heures d’intervention de jour
|
||||
- Heures d’intervention de nuit
|
||||
- Heures d’intervention dimanche / jour férié
|
||||
|
||||
Les lignes détaillées (types d’intervention) ne sont pas utilisées.
|
||||
|
||||
---
|
||||
|
||||
## Installation – Debian 13 (Linux)
|
||||
|
||||
### Dépendances système
|
||||
|
||||
```bash
|
||||
sudo apt update
|
||||
sudo apt install -y \
|
||||
build-essential pkg-config \
|
||||
libgtk-3-dev \
|
||||
libwebkit2gtk-4.1-dev
|
||||
```
|
||||
|
||||
### Go (≥ 1.21)
|
||||
|
||||
```bash
|
||||
sudo apt install -y golang
|
||||
```
|
||||
|
||||
### Node.js (Node 20 LTS – recommandé)
|
||||
|
||||
Ajout du dépôt NodeSource :
|
||||
|
||||
```bash
|
||||
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
|
||||
sudo apt install -y nodejs
|
||||
```
|
||||
|
||||
### Wails
|
||||
|
||||
```bash
|
||||
go install github.com/wailsapp/wails/v2/cmd/wails@latest
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Lancement en mode développement (Linux)
|
||||
|
||||
```bash
|
||||
wails dev -tags webkit2_41
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Compilation Linux
|
||||
|
||||
```bash
|
||||
wails build -tags webkit2_41
|
||||
```
|
||||
|
||||
Le binaire généré se trouve dans le dossier `build/`.
|
||||
|
||||
---
|
||||
|
||||
## Compilation Windows (préparation / roadmap)
|
||||
|
||||
La compilation Windows est prévue prochainement. Le projet est déjà compatible Wails.
|
||||
|
||||
### Prérequis Windows
|
||||
|
||||
- Windows 10 ou 11 (64 bits)
|
||||
- Go ≥ 1.21
|
||||
- Node.js 20 LTS
|
||||
- Outils de compilation Microsoft :
|
||||
- **Visual Studio Build Tools**
|
||||
- Composant « Développement Desktop en C++ »
|
||||
|
||||
### Étapes prévues pour compiler sous Windows
|
||||
|
||||
1. Installer Go et Node.js
|
||||
2. Installer les Visual Studio Build Tools (C++ requis)
|
||||
3. Installer Wails :
|
||||
|
||||
```powershell
|
||||
go install github.com/wailsapp/wails/v2/cmd/wails@latest
|
||||
```
|
||||
|
||||
4. Depuis un terminal PowerShell dans le projet :
|
||||
|
||||
```powershell
|
||||
wails build
|
||||
```
|
||||
|
||||
Un binaire Windows (`.exe`) sera alors généré.
|
||||
|
||||
> Remarque : aucune dépendance GTK/WebKit n’est nécessaire sous Windows.
|
||||
|
||||
---
|
||||
|
||||
## Licence
|
||||
|
||||
Ce projet est distribué sous licence **GNU GPL v3**.
|
||||
|
||||
---
|
||||
|
||||
## Statut du projet
|
||||
|
||||
- Version actuelle : **v0.2**
|
||||
- Projet en cours de développement
|
||||
|
||||
### Évolutions prévues
|
||||
|
||||
- Amélioration de l’ergonomie de l’interface
|
||||
- Gestion de plusieurs profils utilisateurs
|
||||
- Export des résultats (PDF / CSV)
|
||||
- Calcul du net à partir du brut
|
||||
- Compilation et distribution Windows
|
||||
|
||||
214
app/app.go
Normal file
@@ -0,0 +1,214 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
goruntime "runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||
"github.com/xuri/excelize/v2"
|
||||
|
||||
"git.dumerain.org/alban/calcul-astreintes/internal/calc"
|
||||
"git.dumerain.org/alban/calcul-astreintes/internal/importer"
|
||||
"git.dumerain.org/alban/calcul-astreintes/internal/models"
|
||||
"git.dumerain.org/alban/calcul-astreintes/internal/pdf"
|
||||
"git.dumerain.org/alban/calcul-astreintes/internal/profiles"
|
||||
)
|
||||
|
||||
var ymRe = regexp.MustCompile(`^(\d{4})-(\d{2})$|^(\d{2})-(\d{4})$`)
|
||||
|
||||
func normalizeYearMonth(sheet string) (string, bool) {
|
||||
sheet = strings.TrimSpace(sheet)
|
||||
m := ymRe.FindStringSubmatch(sheet)
|
||||
if m == nil {
|
||||
return "", false
|
||||
}
|
||||
// YYYY-MM
|
||||
if m[1] != "" && m[2] != "" {
|
||||
return fmt.Sprintf("%s-%s", m[1], m[2]), true
|
||||
}
|
||||
// MM-YYYY
|
||||
if m[3] != "" && m[4] != "" {
|
||||
return fmt.Sprintf("%s-%s", m[4], m[3]), true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
type App struct {
|
||||
ctx context.Context
|
||||
|
||||
repo *profiles.Repo
|
||||
rules *models.GlobalRules
|
||||
|
||||
importMu sync.RWMutex
|
||||
agents []models.Agent
|
||||
totals map[string]models.AgentTotals
|
||||
|
||||
lastImportYearMonth string
|
||||
lastImportFileBase string
|
||||
}
|
||||
|
||||
func NewApp() *App {
|
||||
repo, rules, err := profiles.NewEmbeddedRepo()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return &App{
|
||||
repo: repo,
|
||||
rules: rules,
|
||||
totals: map[string]models.AgentTotals{},
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) Startup(ctx context.Context) {
|
||||
a.ctx = ctx
|
||||
}
|
||||
|
||||
func (a *App) ListProfiles() ([]models.ProfileListItem, error) {
|
||||
return a.repo.List()
|
||||
}
|
||||
|
||||
func (a *App) Calculate(req models.CalculateRequest) (models.CalculateResponse, error) {
|
||||
profile, err := a.repo.Get(req.ProfileID)
|
||||
if err != nil {
|
||||
return models.CalculateResponse{}, err
|
||||
}
|
||||
return calc.Compute(req, profile, *a.rules), nil
|
||||
}
|
||||
|
||||
// ImportMonthlyXLSX : import Excel + stockage en mémoire
|
||||
// ✅ Mois = nom de feuille (YYYY-MM ou MM-YYYY) normalisé en YYYY-MM
|
||||
func (a *App) ImportMonthlyXLSX() ([]models.Agent, error) {
|
||||
if a.ctx == nil {
|
||||
return nil, fmt.Errorf("contexte Wails non initialisé")
|
||||
}
|
||||
|
||||
path, err := runtime.OpenFileDialog(a.ctx, runtime.OpenDialogOptions{
|
||||
Title: "Importer le fichier Excel des astreintes",
|
||||
Filters: []runtime.FileFilter{
|
||||
{DisplayName: "Excel (.xlsx)", Pattern: "*.xlsx"},
|
||||
},
|
||||
})
|
||||
if err != nil || path == "" {
|
||||
return nil, fmt.Errorf("import annulé")
|
||||
}
|
||||
|
||||
// Détection feuille
|
||||
f, err := excelize.OpenFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() { _ = f.Close() }()
|
||||
|
||||
sheet, err := importer.FindMonthSheet(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
yearMonth, ok := normalizeYearMonth(sheet)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("nom de feuille invalide pour le mois: %s (attendu YYYY-MM ou MM-YYYY)", sheet)
|
||||
}
|
||||
|
||||
agents, totals, err := importer.ParseAgentsAndTotals(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
a.importMu.Lock()
|
||||
a.agents = agents
|
||||
a.totals = totals
|
||||
a.lastImportYearMonth = yearMonth // ✅ toujours en YYYY-MM
|
||||
a.lastImportFileBase = filepath.Base(path)
|
||||
a.importMu.Unlock()
|
||||
|
||||
return agents, nil
|
||||
}
|
||||
|
||||
func (a *App) GetImportedTotals(matricule string) (models.AgentTotals, error) {
|
||||
a.importMu.RLock()
|
||||
defer a.importMu.RUnlock()
|
||||
|
||||
t, ok := a.totals[matricule]
|
||||
if !ok {
|
||||
return models.AgentTotals{}, fmt.Errorf("matricule introuvable: %s", matricule)
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func (a *App) GetLastImportInfo() (map[string]string, error) {
|
||||
a.importMu.RLock()
|
||||
defer a.importMu.RUnlock()
|
||||
|
||||
return map[string]string{
|
||||
"yearMonth": a.lastImportYearMonth, // ✅ normalisé
|
||||
"fileBase": a.lastImportFileBase,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *App) ExportPDF(payload models.ExportPDFRequest) (models.ExportPDFResponse, error) {
|
||||
profile, err := a.repo.Get(payload.Request.ProfileID)
|
||||
if err != nil {
|
||||
return models.ExportPDFResponse{}, err
|
||||
}
|
||||
|
||||
res := calc.Compute(payload.Request, profile, *a.rules)
|
||||
defaultName := defaultPDFNameV051(payload.Meta)
|
||||
|
||||
savePath, err := runtime.SaveFileDialog(a.ctx, runtime.SaveDialogOptions{
|
||||
Title: "Exporter le calcul en PDF",
|
||||
DefaultFilename: defaultName,
|
||||
Filters: []runtime.FileFilter{
|
||||
{DisplayName: "PDF (*.pdf)", Pattern: "*.pdf"},
|
||||
},
|
||||
})
|
||||
if err != nil || savePath == "" {
|
||||
return models.ExportPDFResponse{}, fmt.Errorf("export annulé")
|
||||
}
|
||||
|
||||
if filepath.Ext(strings.ToLower(savePath)) != ".pdf" {
|
||||
savePath += ".pdf"
|
||||
}
|
||||
|
||||
data, err := pdf.BuildPDF(payload.Meta, profile.Label, payload.Request, res)
|
||||
if err != nil {
|
||||
return models.ExportPDFResponse{}, err
|
||||
}
|
||||
|
||||
if err := os.WriteFile(savePath, data, 0o644); err != nil {
|
||||
return models.ExportPDFResponse{}, err
|
||||
}
|
||||
|
||||
_ = openWithDefaultApp(savePath)
|
||||
return models.ExportPDFResponse{Path: savePath}, nil
|
||||
}
|
||||
|
||||
func defaultPDFNameV051(meta models.ExportPDFMeta) string {
|
||||
ts := time.Now().Format("2006-01-02-15h04")
|
||||
return fmt.Sprintf(
|
||||
"%s-%s-%s-%s.pdf",
|
||||
ts,
|
||||
pdf.SanitizeFilename(meta.Nom),
|
||||
pdf.SanitizeFilename(meta.Prenom),
|
||||
pdf.SanitizeFilename(meta.Matricule),
|
||||
)
|
||||
}
|
||||
|
||||
func openWithDefaultApp(path string) error {
|
||||
switch goruntime.GOOS {
|
||||
case "windows":
|
||||
return exec.Command("cmd", "/c", "start", "", path).Start()
|
||||
case "darwin":
|
||||
return exec.Command("open", path).Start()
|
||||
default:
|
||||
return exec.Command("xdg-open", path).Start()
|
||||
}
|
||||
}
|
||||
BIN
assets/icon/export/paybackcheck-icon-alt.png
Normal file
|
After Width: | Height: | Size: 289 KiB |
BIN
assets/icon/export/paybackcheck-icon-piggy.ico
Normal file
|
After Width: | Height: | Size: 110 KiB |
BIN
assets/icon/export/paybackcheck-icon-primary.png
Normal file
|
After Width: | Height: | Size: 341 KiB |
BIN
assets/icon/export/paybackcheck-logo-eurocheck.ico
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
assets/icon/export/paybackcheck-logo-eurocheck.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
3
assets/icon/src/paybackcheck-logo-eurocheck (1).svg
Normal file
|
After Width: | Height: | Size: 38 KiB |
79
assets/icon/src/paybackcheck-logo-eurocheck.svg
Normal file
@@ -0,0 +1,79 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" role="img" aria-label="PayBackCheck Euro check logo">
|
||||
<!-- Transparent background by default (no rect) -->
|
||||
<defs>
|
||||
<!-- Pastel Euro gradient -->
|
||||
<linearGradient id="euroFill" x1="128" y1="96" x2="384" y2="416" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#CFF6E2"/>
|
||||
<stop offset="0.55" stop-color="#8FDFC0"/>
|
||||
<stop offset="1" stop-color="#6BCF9D"/>
|
||||
</linearGradient>
|
||||
|
||||
<!-- Darker check gradient -->
|
||||
<linearGradient id="checkFill" x1="220" y1="220" x2="420" y2="420" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#2F8B5F"/>
|
||||
<stop offset="1" stop-color="#1F6A49"/>
|
||||
</linearGradient>
|
||||
|
||||
<!-- Soft highlight -->
|
||||
<radialGradient id="highlight" cx="320" cy="170" r="210" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#FFFFFF" stop-opacity="0.55"/>
|
||||
<stop offset="1" stop-color="#FFFFFF" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
|
||||
<filter id="softShadow" x="-20%" y="-20%" width="140%" height="140%" color-interpolation-filters="sRGB">
|
||||
<feDropShadow dx="0" dy="10" stdDeviation="10" flood-color="#000000" flood-opacity="0.18"/>
|
||||
</filter>
|
||||
</defs>
|
||||
|
||||
<!-- Euro symbol -->
|
||||
<g filter="url(#softShadow)">
|
||||
<!-- Main euro curve (simple, icon-friendly) -->
|
||||
<path fill="url(#euroFill)" d="
|
||||
M332 106
|
||||
C254 74 162 114 134 198
|
||||
C120 240 120 288 134 330
|
||||
C162 414 254 454 332 422
|
||||
C352 414 370 402 386 386
|
||||
C396 376 396 360 386 350
|
||||
C376 340 360 340 350 350
|
||||
C338 362 324 372 308 378
|
||||
C250 402 188 368 170 308
|
||||
C160 276 160 252 160 224
|
||||
C160 196 160 172 170 140
|
||||
C188 80 250 46 308 70
|
||||
C324 76 338 86 350 98
|
||||
C360 108 376 108 386 98
|
||||
C396 88 396 72 386 62
|
||||
C370 46 352 34 332 26
|
||||
Z"/>
|
||||
|
||||
<!-- Euro bars -->
|
||||
<path fill="url(#euroFill)" d="M118 214 H276 c12 0 22 10 22 22s-10 22-22 22H118c-12 0-22-10-22-22s10-22 22-22Z"/>
|
||||
<path fill="url(#euroFill)" d="M118 286 H276 c12 0 22 10 22 22s-10 22-22 22H118c-12 0-22-10-22-22s10-22 22-22Z"/>
|
||||
|
||||
<!-- subtle highlight overlay -->
|
||||
<path fill="url(#highlight)" d="M332 106 C254 74 162 114 134 198 C120 240 120 288 134 330 C162 414 254 454 332 422 C352 414 370 402 386 386 C396 376 396 360 386 350 C376 340 360 340 350 350 C338 362 324 372 308 378 C250 402 188 368 170 308 C160 276 160 252 160 224 C160 196 160 172 170 140 C188 80 250 46 308 70 C324 76 338 86 350 98 C360 108 376 108 386 98 C396 88 396 72 386 62 C370 46 352 34 332 26 Z"/>
|
||||
</g>
|
||||
|
||||
<!-- Check mark (slightly larger, overlaps bottom-right) -->
|
||||
<g filter="url(#softShadow)">
|
||||
<path fill="url(#checkFill)" d="
|
||||
M334 224
|
||||
c12-12 32-12 44 0
|
||||
c12 12 12 32 0 44
|
||||
l-128 128
|
||||
c-12 12-32 12-44 0
|
||||
l-64-64
|
||||
c-12-12-12-32 0-44
|
||||
c12-12 32-12 44 0
|
||||
l42 42
|
||||
l106-106
|
||||
Z"/>
|
||||
|
||||
<!-- thin bright edge for contrast -->
|
||||
<path fill="none" stroke="#EFFFF7" stroke-opacity="0.55" stroke-width="8" stroke-linecap="round" stroke-linejoin="round" d="
|
||||
M206 334
|
||||
L248 376
|
||||
L354 270"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.1 KiB |
3
assets/icon/src/paybackcheck-logo-eurocheck2.svg
Normal file
|
After Width: | Height: | Size: 304 KiB |
28
cmd/checkprofiles/main.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"sort"
|
||||
|
||||
"git.dumerain.org/alban/calcul-astreintes/internal/profiles"
|
||||
)
|
||||
|
||||
func main() {
|
||||
repo, rules, err := profiles.NewEmbeddedRepo()
|
||||
if err != nil {
|
||||
log.Fatalf("Erreur chargement profils: %v", err)
|
||||
}
|
||||
items, _ := repo.List()
|
||||
fmt.Printf("OK: %d profil(s) charge(s) - forfait=%.2f\n", len(items), rules.ForfaitDimFerie)
|
||||
|
||||
keys := make([]string, 0, len(items))
|
||||
for _, it := range items {
|
||||
keys = append(keys, it.ID)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, id := range keys {
|
||||
p, _ := repo.Get(id)
|
||||
fmt.Printf("- %s: label=%q t456=%.2f t458=%.2f t459=%.2f t471=%.2f\n", id, p.Label, p.T456, p.T458, p.T459, p.T471)
|
||||
}
|
||||
}
|
||||
12
frontend/index.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<!doctype html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Calcul astreintes</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
987
frontend/package-lock.json
generated
Normal file
@@ -0,0 +1,987 @@
|
||||
{
|
||||
"name": "calcul-astreintes-frontend",
|
||||
"version": "0.0.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "calcul-astreintes-frontend",
|
||||
"version": "0.0.1",
|
||||
"devDependencies": {
|
||||
"vite": "^5.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
|
||||
"integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"aix"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
|
||||
"integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
|
||||
"integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-x64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
|
||||
"integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
|
||||
"integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
|
||||
"integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
|
||||
"integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
|
||||
"integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
|
||||
"integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
|
||||
"integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
|
||||
"integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
|
||||
"integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
|
||||
"integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
|
||||
"integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
|
||||
"integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
|
||||
"integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
|
||||
"integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
|
||||
"integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
|
||||
"integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
|
||||
"integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
|
||||
"integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
|
||||
"integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
|
||||
"integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.55.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.55.1.tgz",
|
||||
"integrity": "sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm64": {
|
||||
"version": "4.55.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.55.1.tgz",
|
||||
"integrity": "sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||
"version": "4.55.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.55.1.tgz",
|
||||
"integrity": "sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-x64": {
|
||||
"version": "4.55.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.55.1.tgz",
|
||||
"integrity": "sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-arm64": {
|
||||
"version": "4.55.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.55.1.tgz",
|
||||
"integrity": "sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-x64": {
|
||||
"version": "4.55.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.55.1.tgz",
|
||||
"integrity": "sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||
"version": "4.55.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.55.1.tgz",
|
||||
"integrity": "sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||
"version": "4.55.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.55.1.tgz",
|
||||
"integrity": "sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||
"version": "4.55.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.55.1.tgz",
|
||||
"integrity": "sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||
"version": "4.55.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.55.1.tgz",
|
||||
"integrity": "sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-loong64-gnu": {
|
||||
"version": "4.55.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.55.1.tgz",
|
||||
"integrity": "sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-loong64-musl": {
|
||||
"version": "4.55.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.55.1.tgz",
|
||||
"integrity": "sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
|
||||
"version": "4.55.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.55.1.tgz",
|
||||
"integrity": "sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-ppc64-musl": {
|
||||
"version": "4.55.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.55.1.tgz",
|
||||
"integrity": "sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||
"version": "4.55.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.55.1.tgz",
|
||||
"integrity": "sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
||||
"version": "4.55.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.55.1.tgz",
|
||||
"integrity": "sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||
"version": "4.55.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.55.1.tgz",
|
||||
"integrity": "sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||
"version": "4.55.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.55.1.tgz",
|
||||
"integrity": "sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||
"version": "4.55.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.55.1.tgz",
|
||||
"integrity": "sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-openbsd-x64": {
|
||||
"version": "4.55.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.55.1.tgz",
|
||||
"integrity": "sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-openharmony-arm64": {
|
||||
"version": "4.55.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.55.1.tgz",
|
||||
"integrity": "sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openharmony"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||
"version": "4.55.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.55.1.tgz",
|
||||
"integrity": "sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||
"version": "4.55.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.55.1.tgz",
|
||||
"integrity": "sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-gnu": {
|
||||
"version": "4.55.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.55.1.tgz",
|
||||
"integrity": "sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||
"version": "4.55.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.55.1.tgz",
|
||||
"integrity": "sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
||||
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
|
||||
"integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/aix-ppc64": "0.21.5",
|
||||
"@esbuild/android-arm": "0.21.5",
|
||||
"@esbuild/android-arm64": "0.21.5",
|
||||
"@esbuild/android-x64": "0.21.5",
|
||||
"@esbuild/darwin-arm64": "0.21.5",
|
||||
"@esbuild/darwin-x64": "0.21.5",
|
||||
"@esbuild/freebsd-arm64": "0.21.5",
|
||||
"@esbuild/freebsd-x64": "0.21.5",
|
||||
"@esbuild/linux-arm": "0.21.5",
|
||||
"@esbuild/linux-arm64": "0.21.5",
|
||||
"@esbuild/linux-ia32": "0.21.5",
|
||||
"@esbuild/linux-loong64": "0.21.5",
|
||||
"@esbuild/linux-mips64el": "0.21.5",
|
||||
"@esbuild/linux-ppc64": "0.21.5",
|
||||
"@esbuild/linux-riscv64": "0.21.5",
|
||||
"@esbuild/linux-s390x": "0.21.5",
|
||||
"@esbuild/linux-x64": "0.21.5",
|
||||
"@esbuild/netbsd-x64": "0.21.5",
|
||||
"@esbuild/openbsd-x64": "0.21.5",
|
||||
"@esbuild/sunos-x64": "0.21.5",
|
||||
"@esbuild/win32-arm64": "0.21.5",
|
||||
"@esbuild/win32-ia32": "0.21.5",
|
||||
"@esbuild/win32-x64": "0.21.5"
|
||||
}
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.11",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
||||
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"nanoid": "bin/nanoid.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.6",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
|
||||
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/postcss/"
|
||||
},
|
||||
{
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/postcss"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.11",
|
||||
"picocolors": "^1.1.1",
|
||||
"source-map-js": "^1.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "4.55.1",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.55.1.tgz",
|
||||
"integrity": "sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree": "1.0.8"
|
||||
},
|
||||
"bin": {
|
||||
"rollup": "dist/bin/rollup"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0",
|
||||
"npm": ">=8.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rollup/rollup-android-arm-eabi": "4.55.1",
|
||||
"@rollup/rollup-android-arm64": "4.55.1",
|
||||
"@rollup/rollup-darwin-arm64": "4.55.1",
|
||||
"@rollup/rollup-darwin-x64": "4.55.1",
|
||||
"@rollup/rollup-freebsd-arm64": "4.55.1",
|
||||
"@rollup/rollup-freebsd-x64": "4.55.1",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.55.1",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.55.1",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.55.1",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.55.1",
|
||||
"@rollup/rollup-linux-loong64-gnu": "4.55.1",
|
||||
"@rollup/rollup-linux-loong64-musl": "4.55.1",
|
||||
"@rollup/rollup-linux-ppc64-gnu": "4.55.1",
|
||||
"@rollup/rollup-linux-ppc64-musl": "4.55.1",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.55.1",
|
||||
"@rollup/rollup-linux-riscv64-musl": "4.55.1",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.55.1",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.55.1",
|
||||
"@rollup/rollup-linux-x64-musl": "4.55.1",
|
||||
"@rollup/rollup-openbsd-x64": "4.55.1",
|
||||
"@rollup/rollup-openharmony-arm64": "4.55.1",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.55.1",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.55.1",
|
||||
"@rollup/rollup-win32-x64-gnu": "4.55.1",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.55.1",
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "5.4.21",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
|
||||
"integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"esbuild": "^0.21.3",
|
||||
"postcss": "^8.4.43",
|
||||
"rollup": "^4.20.0"
|
||||
},
|
||||
"bin": {
|
||||
"vite": "bin/vite.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.0.0 || >=20.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/vitejs/vite?sponsor=1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "~2.3.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/node": "^18.0.0 || >=20.0.0",
|
||||
"less": "*",
|
||||
"lightningcss": "^1.21.0",
|
||||
"sass": "*",
|
||||
"sass-embedded": "*",
|
||||
"stylus": "*",
|
||||
"sugarss": "*",
|
||||
"terser": "^5.4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/node": {
|
||||
"optional": true
|
||||
},
|
||||
"less": {
|
||||
"optional": true
|
||||
},
|
||||
"lightningcss": {
|
||||
"optional": true
|
||||
},
|
||||
"sass": {
|
||||
"optional": true
|
||||
},
|
||||
"sass-embedded": {
|
||||
"optional": true
|
||||
},
|
||||
"stylus": {
|
||||
"optional": true
|
||||
},
|
||||
"sugarss": {
|
||||
"optional": true
|
||||
},
|
||||
"terser": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
14
frontend/package.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "calcul-astreintes-frontend",
|
||||
"private": true,
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview --port 4173"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "^5.4.0"
|
||||
}
|
||||
}
|
||||
1
frontend/package.json.md5
Executable file
@@ -0,0 +1 @@
|
||||
58af01706385cb79d0824bdff23f5bc8
|
||||
531
frontend/src/main.js
Normal file
@@ -0,0 +1,531 @@
|
||||
import './style.css';
|
||||
|
||||
import {
|
||||
ListProfiles,
|
||||
Calculate,
|
||||
ImportMonthlyXLSX,
|
||||
GetImportedTotals,
|
||||
GetLastImportInfo,
|
||||
ExportPDF
|
||||
} from './wailsjs/go/app/App';
|
||||
|
||||
const app = document.querySelector('#app');
|
||||
|
||||
/* ---------- helpers ---------- */
|
||||
|
||||
function euro(n) {
|
||||
if (typeof n !== 'number' || Number.isNaN(n)) return '';
|
||||
return new Intl.NumberFormat('fr-FR', { style: 'currency', currency: 'EUR' }).format(n);
|
||||
}
|
||||
|
||||
function numberOrZero(s) {
|
||||
const cleaned = String(s ?? '').trim().replace(/\s/g, '').replace(',', '.');
|
||||
const n = Number(cleaned);
|
||||
return Number.isFinite(n) ? n : 0;
|
||||
}
|
||||
|
||||
function intOrZero(s) {
|
||||
const n = parseInt(String(s ?? '').trim(), 10);
|
||||
return Number.isFinite(n) ? n : 0;
|
||||
}
|
||||
|
||||
function parseAgentDisplay(display) {
|
||||
// "NOM Prenom (123456)" => { nom:"NOM", prenom:"Prenom" }
|
||||
const s = String(display || '').trim();
|
||||
const withoutMat = s.replace(/\(\s*\d+\s*\)\s*$/, '').trim();
|
||||
const parts = withoutMat.split(/\s+/).filter(Boolean);
|
||||
if (parts.length === 0) return { nom: '', prenom: '' };
|
||||
if (parts.length === 1) return { nom: parts[0], prenom: '' };
|
||||
return { nom: parts[0], prenom: parts.slice(1).join(' ') };
|
||||
}
|
||||
|
||||
function sectionDetails(sectionKey, title, status, isOpen, innerHTML) {
|
||||
return `
|
||||
<details class="section" data-section="${sectionKey}" ${isOpen ? 'open' : ''}>
|
||||
<summary>
|
||||
<span class="section-title">
|
||||
${title}
|
||||
<span class="section-status">— ${status}</span>
|
||||
</span>
|
||||
<span class="chevron" aria-hidden="true"></span>
|
||||
</summary>
|
||||
<div class="section-body">
|
||||
${innerHTML}
|
||||
</div>
|
||||
</details>
|
||||
`;
|
||||
}
|
||||
|
||||
function autoSelectProfileForMatricule(state, matricule) {
|
||||
if (!matricule) {
|
||||
state.profileId = '';
|
||||
return;
|
||||
}
|
||||
const match = state.profiles.find(p => String(p.matricule || '') === String(matricule));
|
||||
if (match) {
|
||||
state.profileId = match.id;
|
||||
return;
|
||||
}
|
||||
// fallback
|
||||
const hasTest = state.profiles.some(p => p.id === 'test');
|
||||
state.profileId = hasTest ? 'test' : '';
|
||||
}
|
||||
|
||||
/* ---------- auto-load + auto-calc (v0.8) ---------- */
|
||||
|
||||
async function recalcNow(state) {
|
||||
// Recalcule uniquement si profil OK
|
||||
if (!state.profileId) {
|
||||
state.rows = [];
|
||||
state.total = 0;
|
||||
state.error = "Sélectionne un profil (ou choisis un agent pour auto-match).";
|
||||
return;
|
||||
}
|
||||
state.error = '';
|
||||
|
||||
const res = await Calculate({
|
||||
profileId: state.profileId,
|
||||
q456: numberOrZero(state.q456),
|
||||
q458: numberOrZero(state.q458),
|
||||
q459: numberOrZero(state.q459),
|
||||
q471: numberOrZero(state.q471),
|
||||
nbDimFerie: intOrZero(state.nbDimFerie),
|
||||
});
|
||||
|
||||
state.rows = res.lines || [];
|
||||
state.total = res.total || 0;
|
||||
state.sections.details = true;
|
||||
}
|
||||
|
||||
async function loadSelectedAgentIntoForm(state) {
|
||||
if (!state.selectedMatricule) return;
|
||||
|
||||
const t = await GetImportedTotals(state.selectedMatricule);
|
||||
|
||||
state.q456 = String(t.q456 ?? 0);
|
||||
state.q458 = String(t.q458 ?? 0);
|
||||
state.q459 = String(t.q459 ?? 0);
|
||||
state.q471 = String(t.q471 ?? 0);
|
||||
state.nbDimFerie = String(t.nbDimFerie ?? 0);
|
||||
|
||||
const agent = state.importedAgents.find(a => a.matricule === state.selectedMatricule);
|
||||
if (agent) {
|
||||
const { nom, prenom } = parseAgentDisplay(agent.display);
|
||||
state.nom = nom;
|
||||
state.prenom = prenom;
|
||||
state.matricule = state.selectedMatricule;
|
||||
} else {
|
||||
state.matricule = state.selectedMatricule;
|
||||
}
|
||||
}
|
||||
|
||||
async function onAgentChanged(state) {
|
||||
// 1) Auto profil (match matricule → sinon test)
|
||||
autoSelectProfileForMatricule(state, state.selectedMatricule);
|
||||
|
||||
// 2) Auto load valeurs (si import présent)
|
||||
if (state.selectedMatricule) {
|
||||
await loadSelectedAgentIntoForm(state);
|
||||
} else {
|
||||
// reset si aucun agent
|
||||
state.profileId = '';
|
||||
state.q456 = state.q458 = state.q459 = state.q471 = '0';
|
||||
state.nbDimFerie = '0';
|
||||
state.rows = [];
|
||||
state.total = 0;
|
||||
state.error = '';
|
||||
}
|
||||
|
||||
// 3) Auto calc
|
||||
if (state.selectedMatricule && state.profileId) {
|
||||
await recalcNow(state);
|
||||
} else {
|
||||
state.rows = [];
|
||||
state.total = 0;
|
||||
}
|
||||
}
|
||||
|
||||
async function onProfileChanged(state) {
|
||||
// Recalcul “à chaud” si on a déjà des valeurs
|
||||
if (state.selectedMatricule && state.profileId) {
|
||||
await recalcNow(state);
|
||||
} else {
|
||||
state.rows = [];
|
||||
state.total = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- UI state ---------- */
|
||||
|
||||
function computeStatuses(state) {
|
||||
let importStatus = 'non importé';
|
||||
if (state.importedAgents.length > 0) {
|
||||
if (state.selectedMatricule) {
|
||||
const agent = state.importedAgents.find(a => a.matricule === state.selectedMatricule);
|
||||
importStatus = agent ? `agent : ${agent.display}` : `agent : ${state.selectedMatricule}`;
|
||||
} else {
|
||||
importStatus = `${state.importedAgents.length} agents chargés`;
|
||||
}
|
||||
}
|
||||
|
||||
const currentProfile = state.profiles.find(p => p.id === state.profileId);
|
||||
const dim = intOrZero(state.nbDimFerie);
|
||||
const profileStatus = currentProfile
|
||||
? `${currentProfile.label} • Dim/JF : ${dim}`
|
||||
: 'non sélectionné';
|
||||
|
||||
const values = [
|
||||
numberOrZero(state.q456),
|
||||
numberOrZero(state.q458),
|
||||
numberOrZero(state.q459),
|
||||
numberOrZero(state.q471)
|
||||
];
|
||||
const filled = values.filter(v => v !== 0).length;
|
||||
const inputsStatus = filled === 0 ? 'vides' : `${filled} valeur${filled > 1 ? 's' : ''}`;
|
||||
|
||||
const detailsStatus =
|
||||
state.rows.length === 0
|
||||
? 'non calculé'
|
||||
: `${state.rows.length} ligne${state.rows.length > 1 ? 's' : ''} • total : ${euro(state.total)}`;
|
||||
|
||||
return { importStatus, profileStatus, inputsStatus, detailsStatus };
|
||||
}
|
||||
|
||||
/* ---------- render ---------- */
|
||||
|
||||
function render(state) {
|
||||
const st = computeStatuses(state);
|
||||
|
||||
app.innerHTML = `
|
||||
<div class="container">
|
||||
|
||||
<div class="card header-card">
|
||||
<div class="header-top">
|
||||
<div>
|
||||
<h2>Paychek - Outil de calcul de rémunération d'astreintes</h2>
|
||||
</div>
|
||||
|
||||
<div class="header-actions">
|
||||
<button id="toggleAll">Tout afficher</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
|
||||
${sectionDetails(
|
||||
'import',
|
||||
'Import Excel',
|
||||
st.importStatus,
|
||||
state.sections.import,
|
||||
`
|
||||
<button id="importXlsx" class="primary">Importer fichier Excel (.xlsx)</button>
|
||||
|
||||
<div class="row" style="margin-top:12px;">
|
||||
<div>
|
||||
<label>Agent (Nom Prénom / Matricule)</label>
|
||||
<select id="agentSelect">
|
||||
<option value="">— Choisir —</option>
|
||||
${state.importedAgents.map(a => `
|
||||
<option value="${a.matricule}" ${a.matricule === state.selectedMatricule ? 'selected' : ''}>
|
||||
${a.display}
|
||||
</option>
|
||||
`).join('')}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top:12px; display:flex; gap:10px;">
|
||||
<button id="loadAgent">Recharger les valeurs</button>
|
||||
<button id="clearAgent">Effacer</button>
|
||||
</div>
|
||||
|
||||
${state.importError ? `<div class="error">${state.importError}</div>` : ''}
|
||||
`
|
||||
)}
|
||||
|
||||
${sectionDetails(
|
||||
'profile',
|
||||
'Profil',
|
||||
st.profileStatus,
|
||||
state.sections.profile,
|
||||
`
|
||||
<div class="row">
|
||||
<div>
|
||||
<label>Profil</label>
|
||||
<select id="profile"></select>
|
||||
</div>
|
||||
<div>
|
||||
<label>Nombre de dimanches / jours fériés (forfait 120 €/jour)</label>
|
||||
<input id="nbDimFerie" inputmode="numeric" value="${state.nbDimFerie}">
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
|
||||
${sectionDetails(
|
||||
'inputs',
|
||||
'Interventions (heures)',
|
||||
st.inputsStatus,
|
||||
state.sections.inputs,
|
||||
`
|
||||
<div class="row">
|
||||
<div>
|
||||
<label>456 — Jour</label>
|
||||
<input id="q456" value="${state.q456}">
|
||||
</div>
|
||||
<div>
|
||||
<label>458 — Dimanche / férié</label>
|
||||
<input id="q458" value="${state.q458}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div>
|
||||
<label>459 — Nuit</label>
|
||||
<input id="q459" value="${state.q459}">
|
||||
</div>
|
||||
<div>
|
||||
<label>471 — Total heures astreinte</label>
|
||||
<input id="q471" value="${state.q471}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button id="calc" class="primary" style="margin-top:12px;">Calculer</button>
|
||||
${state.error ? `<div class="error">${state.error}</div>` : ''}
|
||||
`
|
||||
)}
|
||||
|
||||
${sectionDetails(
|
||||
'details',
|
||||
'Détail',
|
||||
st.detailsStatus,
|
||||
state.sections.details,
|
||||
`
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Code</th>
|
||||
<th>Libellé</th>
|
||||
<th class="right">Base</th>
|
||||
<th class="right">Quantité</th>
|
||||
<th class="right">Total</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${state.rows.map(r => `
|
||||
<tr>
|
||||
<td>${r.code}</td>
|
||||
<td>${r.label}</td>
|
||||
<td class="right">${Number(r.rate).toFixed(2)}</td>
|
||||
<td class="right">${Number(r.quantity).toFixed(2)} ${r.unit}</td>
|
||||
<td class="right">${euro(r.amount)}</td>
|
||||
</tr>
|
||||
`).join('')}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="4" class="right total">TOTAL BRUT</td>
|
||||
<td class="right total">${euro(state.total)}</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
`
|
||||
)}
|
||||
|
||||
</div>
|
||||
|
||||
<div class="export-bar">
|
||||
<button id="exportPdf" class="primary">Exporter en PDF</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
`;
|
||||
|
||||
/* ---------- section state ---------- */
|
||||
|
||||
document.querySelectorAll('details.section[data-section]').forEach(d => {
|
||||
d.ontoggle = () => {
|
||||
const key = d.getAttribute('data-section');
|
||||
state.sections[key] = d.open;
|
||||
};
|
||||
});
|
||||
|
||||
const toggleBtn = document.getElementById('toggleAll');
|
||||
if (toggleBtn) {
|
||||
toggleBtn.onclick = () => {
|
||||
const sections = document.querySelectorAll('details');
|
||||
const allOpen = Array.from(sections).every(d => d.open);
|
||||
|
||||
sections.forEach(d => {
|
||||
d.open = !allOpen;
|
||||
});
|
||||
|
||||
toggleBtn.textContent = allOpen
|
||||
? 'Tout afficher'
|
||||
: 'Tout masquer';
|
||||
const toggleAllBtn = document.getElementById('toggleAll');
|
||||
if (toggleAllBtn) {
|
||||
const sections = document.querySelectorAll('details');
|
||||
const allOpen =
|
||||
sections.length > 0 &&
|
||||
Array.from(sections).every(d => d.open);
|
||||
|
||||
toggleAllBtn.textContent = allOpen
|
||||
? 'Tout masquer'
|
||||
: 'Tout afficher';
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/* ---------- profile ---------- */
|
||||
|
||||
const profileSel = document.getElementById('profile');
|
||||
profileSel.innerHTML = `
|
||||
<option value="">— Choisir —</option>
|
||||
${state.profiles.map(p =>
|
||||
`<option value="${p.id}" ${p.id === state.profileId ? 'selected' : ''}>${p.label}</option>`
|
||||
).join('')}
|
||||
`;
|
||||
|
||||
profileSel.onchange = async (e) => {
|
||||
state.profileId = e.target.value;
|
||||
try {
|
||||
await onProfileChanged(state); // ✅ auto-calc “à chaud”
|
||||
render(state);
|
||||
} catch (err) {
|
||||
state.error = String(err);
|
||||
render(state);
|
||||
}
|
||||
};
|
||||
|
||||
/* ---------- import ---------- */
|
||||
|
||||
document.getElementById('importXlsx').onclick = async () => {
|
||||
try {
|
||||
state.importedAgents = await ImportMonthlyXLSX() || [];
|
||||
state.importError = '';
|
||||
|
||||
// 🔧 Récupère le mois normalisé détecté par le backend (utilisé ensuite par l'export PDF)
|
||||
try {
|
||||
const info = await GetLastImportInfo();
|
||||
if (info && info.yearMonth) {
|
||||
state.yearMonth = info.yearMonth;
|
||||
}
|
||||
} catch (_) {
|
||||
// Ne pas bloquer l'import si l'info n'est pas dispo
|
||||
}
|
||||
|
||||
// Si un agent est déjà sélectionné, on re-joue la logique (profil + load + calc)
|
||||
if (state.selectedMatricule) {
|
||||
await onAgentChanged(state);
|
||||
}
|
||||
render(state);
|
||||
} catch (e) {
|
||||
state.importError = String(e);
|
||||
render(state);
|
||||
}
|
||||
};
|
||||
|
||||
document.getElementById('agentSelect').onchange = async (e) => {
|
||||
state.selectedMatricule = e.target.value;
|
||||
try {
|
||||
await onAgentChanged(state); // ✅ auto profil + auto load + auto calc
|
||||
render(state);
|
||||
} catch (err) {
|
||||
state.error = String(err);
|
||||
render(state);
|
||||
}
|
||||
};
|
||||
|
||||
document.getElementById('loadAgent').onclick = async () => {
|
||||
if (!state.selectedMatricule) return;
|
||||
try {
|
||||
await loadSelectedAgentIntoForm(state);
|
||||
if (state.profileId) {
|
||||
await recalcNow(state); // ✅ rechargement => recalcul
|
||||
}
|
||||
render(state);
|
||||
} catch (e) {
|
||||
state.error = String(e);
|
||||
render(state);
|
||||
}
|
||||
};
|
||||
|
||||
document.getElementById('clearAgent').onclick = () => {
|
||||
state.selectedMatricule = '';
|
||||
state.profileId = '';
|
||||
state.q456 = state.q458 = state.q459 = state.q471 = '0';
|
||||
state.nbDimFerie = '0';
|
||||
state.rows = [];
|
||||
state.total = 0;
|
||||
state.error = '';
|
||||
render(state);
|
||||
};
|
||||
|
||||
/* ---------- manual calculation button ---------- */
|
||||
|
||||
document.getElementById('calc').onclick = async () => {
|
||||
try {
|
||||
await recalcNow(state);
|
||||
render(state);
|
||||
} catch (e) {
|
||||
state.error = String(e);
|
||||
render(state);
|
||||
}
|
||||
};
|
||||
|
||||
/* ---------- export ---------- */
|
||||
|
||||
document.getElementById('exportPdf').onclick = async () => {
|
||||
try {
|
||||
if (!state.profileId) {
|
||||
alert("Sélectionne un profil (ou choisis un agent pour auto-match).");
|
||||
return;
|
||||
}
|
||||
await ExportPDF({
|
||||
meta: {
|
||||
yearMonth: state.yearMonth,
|
||||
nom: state.nom,
|
||||
prenom: state.prenom,
|
||||
matricule: state.matricule
|
||||
},
|
||||
request: {
|
||||
profileId: state.profileId,
|
||||
q456: numberOrZero(state.q456),
|
||||
q458: numberOrZero(state.q458),
|
||||
q459: numberOrZero(state.q459),
|
||||
q471: numberOrZero(state.q471),
|
||||
nbDimFerie: intOrZero(state.nbDimFerie),
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
alert(String(e));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/* ---------- boot ---------- */
|
||||
|
||||
async function boot() {
|
||||
const state = {
|
||||
profiles: [],
|
||||
profileId: '',
|
||||
|
||||
q456: '0', q458: '0', q459: '0', q471: '0',
|
||||
nbDimFerie: '0',
|
||||
|
||||
rows: [], total: 0, error: '',
|
||||
|
||||
importedAgents: [], selectedMatricule: '', importError: '',
|
||||
|
||||
yearMonth: '', nom: '', prenom: '', matricule: '',
|
||||
|
||||
sections: { import: true, profile: false, inputs: false, details: false }
|
||||
};
|
||||
|
||||
state.profiles = await ListProfiles();
|
||||
render(state);
|
||||
}
|
||||
|
||||
boot();
|
||||
213
frontend/src/style.css
Normal file
@@ -0,0 +1,213 @@
|
||||
/* CSS volontairement simple : lisible, pas de framework. */
|
||||
:root {
|
||||
font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
|
||||
line-height: 1.35;
|
||||
color: #111;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 980px;
|
||||
margin: 24px auto;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: #fff;
|
||||
border-radius: 14px;
|
||||
padding: 16px;
|
||||
box-shadow: 0 6px 18px rgba(0,0,0,.06);
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
/* =========================
|
||||
HEADER
|
||||
========================= */
|
||||
|
||||
.header-card {
|
||||
padding: 14px 16px;
|
||||
}
|
||||
|
||||
.header-top {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center; /* ✅ alignement vertical corrigé */
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.header-top h2 {
|
||||
margin: 0;
|
||||
flex: 1; /* le titre prend l’espace */
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
flex-shrink: 0; /* le bouton ne se compresse pas */
|
||||
}
|
||||
|
||||
/* ========================= */
|
||||
|
||||
.row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
@media (max-width: 820px) {
|
||||
.row {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
font-size: .92rem;
|
||||
margin-bottom: 6px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
input,
|
||||
select {
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid #d8d8d8;
|
||||
border-radius: 10px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 10px 14px;
|
||||
border: 0;
|
||||
border-radius: 10px;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
button.primary {
|
||||
background: #111;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
text-align: left;
|
||||
padding: 10px 8px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.muted {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.total {
|
||||
font-weight: 700;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: #b00020;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.success {
|
||||
color: #0a7a2f;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
/* ---------------------------
|
||||
Sections repliables
|
||||
---------------------------- */
|
||||
|
||||
.section {
|
||||
border: 1px solid #eee;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Supprime le marker natif */
|
||||
.section > summary {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.section > summary::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.section > summary {
|
||||
cursor: pointer;
|
||||
padding: 12px 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
user-select: none;
|
||||
background: #fafafa;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.section[open] > summary {
|
||||
background: #f7f7f7;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-weight: 650;
|
||||
}
|
||||
|
||||
.section-body {
|
||||
padding: 14px 12px;
|
||||
}
|
||||
|
||||
/* Chevron simple (sans dépendance) */
|
||||
.chevron::before {
|
||||
content: "▸";
|
||||
display: inline-block;
|
||||
transform: rotate(0deg);
|
||||
transition: transform 120ms ease;
|
||||
color: #444;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.section[open] .chevron::before {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
/* Légère amélioration tactile */
|
||||
.section > summary:hover {
|
||||
background: #f3f3f3;
|
||||
}
|
||||
|
||||
.section-status {
|
||||
font-weight: 500;
|
||||
color: #666;
|
||||
margin-left: 8px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* ---------------------------
|
||||
Barre d’export PDF
|
||||
---------------------------- */
|
||||
|
||||
.export-bar {
|
||||
position: sticky;
|
||||
bottom: 16px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 18px;
|
||||
}
|
||||
15
frontend/src/wailsjs/go/app/App.d.ts
vendored
Executable file
@@ -0,0 +1,15 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
import {models} from '../models';
|
||||
|
||||
export function Calculate(arg1:models.CalculateRequest):Promise<models.CalculateResponse>;
|
||||
|
||||
export function ExportPDF(arg1:models.ExportPDFRequest):Promise<models.ExportPDFResponse>;
|
||||
|
||||
export function GetImportedTotals(arg1:string):Promise<models.AgentTotals>;
|
||||
|
||||
export function GetLastImportInfo():Promise<Record<string, string>>;
|
||||
|
||||
export function ImportMonthlyXLSX():Promise<Array<models.Agent>>;
|
||||
|
||||
export function ListProfiles():Promise<Array<models.ProfileListItem>>;
|
||||
27
frontend/src/wailsjs/go/app/App.js
Executable file
@@ -0,0 +1,27 @@
|
||||
// @ts-check
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
export function Calculate(arg1) {
|
||||
return window['go']['app']['App']['Calculate'](arg1);
|
||||
}
|
||||
|
||||
export function ExportPDF(arg1) {
|
||||
return window['go']['app']['App']['ExportPDF'](arg1);
|
||||
}
|
||||
|
||||
export function GetImportedTotals(arg1) {
|
||||
return window['go']['app']['App']['GetImportedTotals'](arg1);
|
||||
}
|
||||
|
||||
export function GetLastImportInfo() {
|
||||
return window['go']['app']['App']['GetLastImportInfo']();
|
||||
}
|
||||
|
||||
export function ImportMonthlyXLSX() {
|
||||
return window['go']['app']['App']['ImportMonthlyXLSX']();
|
||||
}
|
||||
|
||||
export function ListProfiles() {
|
||||
return window['go']['app']['App']['ListProfiles']();
|
||||
}
|
||||
200
frontend/src/wailsjs/go/models.ts
Executable file
@@ -0,0 +1,200 @@
|
||||
export namespace models {
|
||||
|
||||
export class Agent {
|
||||
matricule: string;
|
||||
nom: string;
|
||||
prenom: string;
|
||||
display: string;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new Agent(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.matricule = source["matricule"];
|
||||
this.nom = source["nom"];
|
||||
this.prenom = source["prenom"];
|
||||
this.display = source["display"];
|
||||
}
|
||||
}
|
||||
export class AgentTotals {
|
||||
matricule: string;
|
||||
q471: number;
|
||||
q456: number;
|
||||
q459: number;
|
||||
q458: number;
|
||||
nbDimFerie: number;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new AgentTotals(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.matricule = source["matricule"];
|
||||
this.q471 = source["q471"];
|
||||
this.q456 = source["q456"];
|
||||
this.q459 = source["q459"];
|
||||
this.q458 = source["q458"];
|
||||
this.nbDimFerie = source["nbDimFerie"];
|
||||
}
|
||||
}
|
||||
export class CalculateRequest {
|
||||
profileId: string;
|
||||
q456: number;
|
||||
q458: number;
|
||||
q459: number;
|
||||
q471: number;
|
||||
nbDimFerie: number;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new CalculateRequest(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.profileId = source["profileId"];
|
||||
this.q456 = source["q456"];
|
||||
this.q458 = source["q458"];
|
||||
this.q459 = source["q459"];
|
||||
this.q471 = source["q471"];
|
||||
this.nbDimFerie = source["nbDimFerie"];
|
||||
}
|
||||
}
|
||||
export class Line {
|
||||
code: string;
|
||||
label: string;
|
||||
rate: number;
|
||||
quantity: number;
|
||||
amount: number;
|
||||
unit: string;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new Line(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.code = source["code"];
|
||||
this.label = source["label"];
|
||||
this.rate = source["rate"];
|
||||
this.quantity = source["quantity"];
|
||||
this.amount = source["amount"];
|
||||
this.unit = source["unit"];
|
||||
}
|
||||
}
|
||||
export class CalculateResponse {
|
||||
lines: Line[];
|
||||
total: number;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new CalculateResponse(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.lines = this.convertValues(source["lines"], Line);
|
||||
this.total = source["total"];
|
||||
}
|
||||
|
||||
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
||||
if (!a) {
|
||||
return a;
|
||||
}
|
||||
if (a.slice && a.map) {
|
||||
return (a as any[]).map(elem => this.convertValues(elem, classs));
|
||||
} else if ("object" === typeof a) {
|
||||
if (asMap) {
|
||||
for (const key of Object.keys(a)) {
|
||||
a[key] = new classs(a[key]);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
return new classs(a);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
}
|
||||
export class ExportPDFMeta {
|
||||
yearMonth: string;
|
||||
nom: string;
|
||||
prenom: string;
|
||||
matricule: string;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new ExportPDFMeta(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.yearMonth = source["yearMonth"];
|
||||
this.nom = source["nom"];
|
||||
this.prenom = source["prenom"];
|
||||
this.matricule = source["matricule"];
|
||||
}
|
||||
}
|
||||
export class ExportPDFRequest {
|
||||
meta: ExportPDFMeta;
|
||||
request: CalculateRequest;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new ExportPDFRequest(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.meta = this.convertValues(source["meta"], ExportPDFMeta);
|
||||
this.request = this.convertValues(source["request"], CalculateRequest);
|
||||
}
|
||||
|
||||
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
||||
if (!a) {
|
||||
return a;
|
||||
}
|
||||
if (a.slice && a.map) {
|
||||
return (a as any[]).map(elem => this.convertValues(elem, classs));
|
||||
} else if ("object" === typeof a) {
|
||||
if (asMap) {
|
||||
for (const key of Object.keys(a)) {
|
||||
a[key] = new classs(a[key]);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
return new classs(a);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
}
|
||||
export class ExportPDFResponse {
|
||||
path: string;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new ExportPDFResponse(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.path = source["path"];
|
||||
}
|
||||
}
|
||||
|
||||
export class ProfileListItem {
|
||||
id: string;
|
||||
label: string;
|
||||
matricule?: string;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new ProfileListItem(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.id = source["id"];
|
||||
this.label = source["label"];
|
||||
this.matricule = source["matricule"];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
24
frontend/src/wailsjs/runtime/package.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "@wailsapp/runtime",
|
||||
"version": "2.0.0",
|
||||
"description": "Wails Javascript runtime library",
|
||||
"main": "runtime.js",
|
||||
"types": "runtime.d.ts",
|
||||
"scripts": {
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/wailsapp/wails.git"
|
||||
},
|
||||
"keywords": [
|
||||
"Wails",
|
||||
"Javascript",
|
||||
"Go"
|
||||
],
|
||||
"author": "Lea Anthony <lea.anthony@gmail.com>",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/wailsapp/wails/issues"
|
||||
},
|
||||
"homepage": "https://github.com/wailsapp/wails#readme"
|
||||
}
|
||||
249
frontend/src/wailsjs/runtime/runtime.d.ts
vendored
Normal file
@@ -0,0 +1,249 @@
|
||||
/*
|
||||
_ __ _ __
|
||||
| | / /___ _(_) /____
|
||||
| | /| / / __ `/ / / ___/
|
||||
| |/ |/ / /_/ / / (__ )
|
||||
|__/|__/\__,_/_/_/____/
|
||||
The electron alternative for Go
|
||||
(c) Lea Anthony 2019-present
|
||||
*/
|
||||
|
||||
export interface Position {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
export interface Size {
|
||||
w: number;
|
||||
h: number;
|
||||
}
|
||||
|
||||
export interface Screen {
|
||||
isCurrent: boolean;
|
||||
isPrimary: boolean;
|
||||
width : number
|
||||
height : number
|
||||
}
|
||||
|
||||
// Environment information such as platform, buildtype, ...
|
||||
export interface EnvironmentInfo {
|
||||
buildType: string;
|
||||
platform: string;
|
||||
arch: string;
|
||||
}
|
||||
|
||||
// [EventsEmit](https://wails.io/docs/reference/runtime/events#eventsemit)
|
||||
// emits the given event. Optional data may be passed with the event.
|
||||
// This will trigger any event listeners.
|
||||
export function EventsEmit(eventName: string, ...data: any): void;
|
||||
|
||||
// [EventsOn](https://wails.io/docs/reference/runtime/events#eventson) sets up a listener for the given event name.
|
||||
export function EventsOn(eventName: string, callback: (...data: any) => void): () => void;
|
||||
|
||||
// [EventsOnMultiple](https://wails.io/docs/reference/runtime/events#eventsonmultiple)
|
||||
// sets up a listener for the given event name, but will only trigger a given number times.
|
||||
export function EventsOnMultiple(eventName: string, callback: (...data: any) => void, maxCallbacks: number): () => void;
|
||||
|
||||
// [EventsOnce](https://wails.io/docs/reference/runtime/events#eventsonce)
|
||||
// sets up a listener for the given event name, but will only trigger once.
|
||||
export function EventsOnce(eventName: string, callback: (...data: any) => void): () => void;
|
||||
|
||||
// [EventsOff](https://wails.io/docs/reference/runtime/events#eventsoff)
|
||||
// unregisters the listener for the given event name.
|
||||
export function EventsOff(eventName: string, ...additionalEventNames: string[]): void;
|
||||
|
||||
// [EventsOffAll](https://wails.io/docs/reference/runtime/events#eventsoffall)
|
||||
// unregisters all listeners.
|
||||
export function EventsOffAll(): void;
|
||||
|
||||
// [LogPrint](https://wails.io/docs/reference/runtime/log#logprint)
|
||||
// logs the given message as a raw message
|
||||
export function LogPrint(message: string): void;
|
||||
|
||||
// [LogTrace](https://wails.io/docs/reference/runtime/log#logtrace)
|
||||
// logs the given message at the `trace` log level.
|
||||
export function LogTrace(message: string): void;
|
||||
|
||||
// [LogDebug](https://wails.io/docs/reference/runtime/log#logdebug)
|
||||
// logs the given message at the `debug` log level.
|
||||
export function LogDebug(message: string): void;
|
||||
|
||||
// [LogError](https://wails.io/docs/reference/runtime/log#logerror)
|
||||
// logs the given message at the `error` log level.
|
||||
export function LogError(message: string): void;
|
||||
|
||||
// [LogFatal](https://wails.io/docs/reference/runtime/log#logfatal)
|
||||
// logs the given message at the `fatal` log level.
|
||||
// The application will quit after calling this method.
|
||||
export function LogFatal(message: string): void;
|
||||
|
||||
// [LogInfo](https://wails.io/docs/reference/runtime/log#loginfo)
|
||||
// logs the given message at the `info` log level.
|
||||
export function LogInfo(message: string): void;
|
||||
|
||||
// [LogWarning](https://wails.io/docs/reference/runtime/log#logwarning)
|
||||
// logs the given message at the `warning` log level.
|
||||
export function LogWarning(message: string): void;
|
||||
|
||||
// [WindowReload](https://wails.io/docs/reference/runtime/window#windowreload)
|
||||
// Forces a reload by the main application as well as connected browsers.
|
||||
export function WindowReload(): void;
|
||||
|
||||
// [WindowReloadApp](https://wails.io/docs/reference/runtime/window#windowreloadapp)
|
||||
// Reloads the application frontend.
|
||||
export function WindowReloadApp(): void;
|
||||
|
||||
// [WindowSetAlwaysOnTop](https://wails.io/docs/reference/runtime/window#windowsetalwaysontop)
|
||||
// Sets the window AlwaysOnTop or not on top.
|
||||
export function WindowSetAlwaysOnTop(b: boolean): void;
|
||||
|
||||
// [WindowSetSystemDefaultTheme](https://wails.io/docs/next/reference/runtime/window#windowsetsystemdefaulttheme)
|
||||
// *Windows only*
|
||||
// Sets window theme to system default (dark/light).
|
||||
export function WindowSetSystemDefaultTheme(): void;
|
||||
|
||||
// [WindowSetLightTheme](https://wails.io/docs/next/reference/runtime/window#windowsetlighttheme)
|
||||
// *Windows only*
|
||||
// Sets window to light theme.
|
||||
export function WindowSetLightTheme(): void;
|
||||
|
||||
// [WindowSetDarkTheme](https://wails.io/docs/next/reference/runtime/window#windowsetdarktheme)
|
||||
// *Windows only*
|
||||
// Sets window to dark theme.
|
||||
export function WindowSetDarkTheme(): void;
|
||||
|
||||
// [WindowCenter](https://wails.io/docs/reference/runtime/window#windowcenter)
|
||||
// Centers the window on the monitor the window is currently on.
|
||||
export function WindowCenter(): void;
|
||||
|
||||
// [WindowSetTitle](https://wails.io/docs/reference/runtime/window#windowsettitle)
|
||||
// Sets the text in the window title bar.
|
||||
export function WindowSetTitle(title: string): void;
|
||||
|
||||
// [WindowFullscreen](https://wails.io/docs/reference/runtime/window#windowfullscreen)
|
||||
// Makes the window full screen.
|
||||
export function WindowFullscreen(): void;
|
||||
|
||||
// [WindowUnfullscreen](https://wails.io/docs/reference/runtime/window#windowunfullscreen)
|
||||
// Restores the previous window dimensions and position prior to full screen.
|
||||
export function WindowUnfullscreen(): void;
|
||||
|
||||
// [WindowIsFullscreen](https://wails.io/docs/reference/runtime/window#windowisfullscreen)
|
||||
// Returns the state of the window, i.e. whether the window is in full screen mode or not.
|
||||
export function WindowIsFullscreen(): Promise<boolean>;
|
||||
|
||||
// [WindowSetSize](https://wails.io/docs/reference/runtime/window#windowsetsize)
|
||||
// Sets the width and height of the window.
|
||||
export function WindowSetSize(width: number, height: number): void;
|
||||
|
||||
// [WindowGetSize](https://wails.io/docs/reference/runtime/window#windowgetsize)
|
||||
// Gets the width and height of the window.
|
||||
export function WindowGetSize(): Promise<Size>;
|
||||
|
||||
// [WindowSetMaxSize](https://wails.io/docs/reference/runtime/window#windowsetmaxsize)
|
||||
// Sets the maximum window size. Will resize the window if the window is currently larger than the given dimensions.
|
||||
// Setting a size of 0,0 will disable this constraint.
|
||||
export function WindowSetMaxSize(width: number, height: number): void;
|
||||
|
||||
// [WindowSetMinSize](https://wails.io/docs/reference/runtime/window#windowsetminsize)
|
||||
// Sets the minimum window size. Will resize the window if the window is currently smaller than the given dimensions.
|
||||
// Setting a size of 0,0 will disable this constraint.
|
||||
export function WindowSetMinSize(width: number, height: number): void;
|
||||
|
||||
// [WindowSetPosition](https://wails.io/docs/reference/runtime/window#windowsetposition)
|
||||
// Sets the window position relative to the monitor the window is currently on.
|
||||
export function WindowSetPosition(x: number, y: number): void;
|
||||
|
||||
// [WindowGetPosition](https://wails.io/docs/reference/runtime/window#windowgetposition)
|
||||
// Gets the window position relative to the monitor the window is currently on.
|
||||
export function WindowGetPosition(): Promise<Position>;
|
||||
|
||||
// [WindowHide](https://wails.io/docs/reference/runtime/window#windowhide)
|
||||
// Hides the window.
|
||||
export function WindowHide(): void;
|
||||
|
||||
// [WindowShow](https://wails.io/docs/reference/runtime/window#windowshow)
|
||||
// Shows the window, if it is currently hidden.
|
||||
export function WindowShow(): void;
|
||||
|
||||
// [WindowMaximise](https://wails.io/docs/reference/runtime/window#windowmaximise)
|
||||
// Maximises the window to fill the screen.
|
||||
export function WindowMaximise(): void;
|
||||
|
||||
// [WindowToggleMaximise](https://wails.io/docs/reference/runtime/window#windowtogglemaximise)
|
||||
// Toggles between Maximised and UnMaximised.
|
||||
export function WindowToggleMaximise(): void;
|
||||
|
||||
// [WindowUnmaximise](https://wails.io/docs/reference/runtime/window#windowunmaximise)
|
||||
// Restores the window to the dimensions and position prior to maximising.
|
||||
export function WindowUnmaximise(): void;
|
||||
|
||||
// [WindowIsMaximised](https://wails.io/docs/reference/runtime/window#windowismaximised)
|
||||
// Returns the state of the window, i.e. whether the window is maximised or not.
|
||||
export function WindowIsMaximised(): Promise<boolean>;
|
||||
|
||||
// [WindowMinimise](https://wails.io/docs/reference/runtime/window#windowminimise)
|
||||
// Minimises the window.
|
||||
export function WindowMinimise(): void;
|
||||
|
||||
// [WindowUnminimise](https://wails.io/docs/reference/runtime/window#windowunminimise)
|
||||
// Restores the window to the dimensions and position prior to minimising.
|
||||
export function WindowUnminimise(): void;
|
||||
|
||||
// [WindowIsMinimised](https://wails.io/docs/reference/runtime/window#windowisminimised)
|
||||
// Returns the state of the window, i.e. whether the window is minimised or not.
|
||||
export function WindowIsMinimised(): Promise<boolean>;
|
||||
|
||||
// [WindowIsNormal](https://wails.io/docs/reference/runtime/window#windowisnormal)
|
||||
// Returns the state of the window, i.e. whether the window is normal or not.
|
||||
export function WindowIsNormal(): Promise<boolean>;
|
||||
|
||||
// [WindowSetBackgroundColour](https://wails.io/docs/reference/runtime/window#windowsetbackgroundcolour)
|
||||
// Sets the background colour of the window to the given RGBA colour definition. This colour will show through for all transparent pixels.
|
||||
export function WindowSetBackgroundColour(R: number, G: number, B: number, A: number): void;
|
||||
|
||||
// [ScreenGetAll](https://wails.io/docs/reference/runtime/window#screengetall)
|
||||
// Gets the all screens. Call this anew each time you want to refresh data from the underlying windowing system.
|
||||
export function ScreenGetAll(): Promise<Screen[]>;
|
||||
|
||||
// [BrowserOpenURL](https://wails.io/docs/reference/runtime/browser#browseropenurl)
|
||||
// Opens the given URL in the system browser.
|
||||
export function BrowserOpenURL(url: string): void;
|
||||
|
||||
// [Environment](https://wails.io/docs/reference/runtime/intro#environment)
|
||||
// Returns information about the environment
|
||||
export function Environment(): Promise<EnvironmentInfo>;
|
||||
|
||||
// [Quit](https://wails.io/docs/reference/runtime/intro#quit)
|
||||
// Quits the application.
|
||||
export function Quit(): void;
|
||||
|
||||
// [Hide](https://wails.io/docs/reference/runtime/intro#hide)
|
||||
// Hides the application.
|
||||
export function Hide(): void;
|
||||
|
||||
// [Show](https://wails.io/docs/reference/runtime/intro#show)
|
||||
// Shows the application.
|
||||
export function Show(): void;
|
||||
|
||||
// [ClipboardGetText](https://wails.io/docs/reference/runtime/clipboard#clipboardgettext)
|
||||
// Returns the current text stored on clipboard
|
||||
export function ClipboardGetText(): Promise<string>;
|
||||
|
||||
// [ClipboardSetText](https://wails.io/docs/reference/runtime/clipboard#clipboardsettext)
|
||||
// Sets a text on the clipboard
|
||||
export function ClipboardSetText(text: string): Promise<boolean>;
|
||||
|
||||
// [OnFileDrop](https://wails.io/docs/reference/runtime/draganddrop#onfiledrop)
|
||||
// OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings.
|
||||
export function OnFileDrop(callback: (x: number, y: number ,paths: string[]) => void, useDropTarget: boolean) :void
|
||||
|
||||
// [OnFileDropOff](https://wails.io/docs/reference/runtime/draganddrop#dragandddropoff)
|
||||
// OnFileDropOff removes the drag and drop listeners and handlers.
|
||||
export function OnFileDropOff() :void
|
||||
|
||||
// Check if the file path resolver is available
|
||||
export function CanResolveFilePaths(): boolean;
|
||||
|
||||
// Resolves file paths for an array of files
|
||||
export function ResolveFilePaths(files: File[]): void
|
||||
242
frontend/src/wailsjs/runtime/runtime.js
Normal file
@@ -0,0 +1,242 @@
|
||||
/*
|
||||
_ __ _ __
|
||||
| | / /___ _(_) /____
|
||||
| | /| / / __ `/ / / ___/
|
||||
| |/ |/ / /_/ / / (__ )
|
||||
|__/|__/\__,_/_/_/____/
|
||||
The electron alternative for Go
|
||||
(c) Lea Anthony 2019-present
|
||||
*/
|
||||
|
||||
export function LogPrint(message) {
|
||||
window.runtime.LogPrint(message);
|
||||
}
|
||||
|
||||
export function LogTrace(message) {
|
||||
window.runtime.LogTrace(message);
|
||||
}
|
||||
|
||||
export function LogDebug(message) {
|
||||
window.runtime.LogDebug(message);
|
||||
}
|
||||
|
||||
export function LogInfo(message) {
|
||||
window.runtime.LogInfo(message);
|
||||
}
|
||||
|
||||
export function LogWarning(message) {
|
||||
window.runtime.LogWarning(message);
|
||||
}
|
||||
|
||||
export function LogError(message) {
|
||||
window.runtime.LogError(message);
|
||||
}
|
||||
|
||||
export function LogFatal(message) {
|
||||
window.runtime.LogFatal(message);
|
||||
}
|
||||
|
||||
export function EventsOnMultiple(eventName, callback, maxCallbacks) {
|
||||
return window.runtime.EventsOnMultiple(eventName, callback, maxCallbacks);
|
||||
}
|
||||
|
||||
export function EventsOn(eventName, callback) {
|
||||
return EventsOnMultiple(eventName, callback, -1);
|
||||
}
|
||||
|
||||
export function EventsOff(eventName, ...additionalEventNames) {
|
||||
return window.runtime.EventsOff(eventName, ...additionalEventNames);
|
||||
}
|
||||
|
||||
export function EventsOffAll() {
|
||||
return window.runtime.EventsOffAll();
|
||||
}
|
||||
|
||||
export function EventsOnce(eventName, callback) {
|
||||
return EventsOnMultiple(eventName, callback, 1);
|
||||
}
|
||||
|
||||
export function EventsEmit(eventName) {
|
||||
let args = [eventName].slice.call(arguments);
|
||||
return window.runtime.EventsEmit.apply(null, args);
|
||||
}
|
||||
|
||||
export function WindowReload() {
|
||||
window.runtime.WindowReload();
|
||||
}
|
||||
|
||||
export function WindowReloadApp() {
|
||||
window.runtime.WindowReloadApp();
|
||||
}
|
||||
|
||||
export function WindowSetAlwaysOnTop(b) {
|
||||
window.runtime.WindowSetAlwaysOnTop(b);
|
||||
}
|
||||
|
||||
export function WindowSetSystemDefaultTheme() {
|
||||
window.runtime.WindowSetSystemDefaultTheme();
|
||||
}
|
||||
|
||||
export function WindowSetLightTheme() {
|
||||
window.runtime.WindowSetLightTheme();
|
||||
}
|
||||
|
||||
export function WindowSetDarkTheme() {
|
||||
window.runtime.WindowSetDarkTheme();
|
||||
}
|
||||
|
||||
export function WindowCenter() {
|
||||
window.runtime.WindowCenter();
|
||||
}
|
||||
|
||||
export function WindowSetTitle(title) {
|
||||
window.runtime.WindowSetTitle(title);
|
||||
}
|
||||
|
||||
export function WindowFullscreen() {
|
||||
window.runtime.WindowFullscreen();
|
||||
}
|
||||
|
||||
export function WindowUnfullscreen() {
|
||||
window.runtime.WindowUnfullscreen();
|
||||
}
|
||||
|
||||
export function WindowIsFullscreen() {
|
||||
return window.runtime.WindowIsFullscreen();
|
||||
}
|
||||
|
||||
export function WindowGetSize() {
|
||||
return window.runtime.WindowGetSize();
|
||||
}
|
||||
|
||||
export function WindowSetSize(width, height) {
|
||||
window.runtime.WindowSetSize(width, height);
|
||||
}
|
||||
|
||||
export function WindowSetMaxSize(width, height) {
|
||||
window.runtime.WindowSetMaxSize(width, height);
|
||||
}
|
||||
|
||||
export function WindowSetMinSize(width, height) {
|
||||
window.runtime.WindowSetMinSize(width, height);
|
||||
}
|
||||
|
||||
export function WindowSetPosition(x, y) {
|
||||
window.runtime.WindowSetPosition(x, y);
|
||||
}
|
||||
|
||||
export function WindowGetPosition() {
|
||||
return window.runtime.WindowGetPosition();
|
||||
}
|
||||
|
||||
export function WindowHide() {
|
||||
window.runtime.WindowHide();
|
||||
}
|
||||
|
||||
export function WindowShow() {
|
||||
window.runtime.WindowShow();
|
||||
}
|
||||
|
||||
export function WindowMaximise() {
|
||||
window.runtime.WindowMaximise();
|
||||
}
|
||||
|
||||
export function WindowToggleMaximise() {
|
||||
window.runtime.WindowToggleMaximise();
|
||||
}
|
||||
|
||||
export function WindowUnmaximise() {
|
||||
window.runtime.WindowUnmaximise();
|
||||
}
|
||||
|
||||
export function WindowIsMaximised() {
|
||||
return window.runtime.WindowIsMaximised();
|
||||
}
|
||||
|
||||
export function WindowMinimise() {
|
||||
window.runtime.WindowMinimise();
|
||||
}
|
||||
|
||||
export function WindowUnminimise() {
|
||||
window.runtime.WindowUnminimise();
|
||||
}
|
||||
|
||||
export function WindowSetBackgroundColour(R, G, B, A) {
|
||||
window.runtime.WindowSetBackgroundColour(R, G, B, A);
|
||||
}
|
||||
|
||||
export function ScreenGetAll() {
|
||||
return window.runtime.ScreenGetAll();
|
||||
}
|
||||
|
||||
export function WindowIsMinimised() {
|
||||
return window.runtime.WindowIsMinimised();
|
||||
}
|
||||
|
||||
export function WindowIsNormal() {
|
||||
return window.runtime.WindowIsNormal();
|
||||
}
|
||||
|
||||
export function BrowserOpenURL(url) {
|
||||
window.runtime.BrowserOpenURL(url);
|
||||
}
|
||||
|
||||
export function Environment() {
|
||||
return window.runtime.Environment();
|
||||
}
|
||||
|
||||
export function Quit() {
|
||||
window.runtime.Quit();
|
||||
}
|
||||
|
||||
export function Hide() {
|
||||
window.runtime.Hide();
|
||||
}
|
||||
|
||||
export function Show() {
|
||||
window.runtime.Show();
|
||||
}
|
||||
|
||||
export function ClipboardGetText() {
|
||||
return window.runtime.ClipboardGetText();
|
||||
}
|
||||
|
||||
export function ClipboardSetText(text) {
|
||||
return window.runtime.ClipboardSetText(text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for OnFileDrop returns a slice of file path strings when a drop is finished.
|
||||
*
|
||||
* @export
|
||||
* @callback OnFileDropCallback
|
||||
* @param {number} x - x coordinate of the drop
|
||||
* @param {number} y - y coordinate of the drop
|
||||
* @param {string[]} paths - A list of file paths.
|
||||
*/
|
||||
|
||||
/**
|
||||
* OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings.
|
||||
*
|
||||
* @export
|
||||
* @param {OnFileDropCallback} callback - Callback for OnFileDrop returns a slice of file path strings when a drop is finished.
|
||||
* @param {boolean} [useDropTarget=true] - Only call the callback when the drop finished on an element that has the drop target style. (--wails-drop-target)
|
||||
*/
|
||||
export function OnFileDrop(callback, useDropTarget) {
|
||||
return window.runtime.OnFileDrop(callback, useDropTarget);
|
||||
}
|
||||
|
||||
/**
|
||||
* OnFileDropOff removes the drag and drop listeners and handlers.
|
||||
*/
|
||||
export function OnFileDropOff() {
|
||||
return window.runtime.OnFileDropOff();
|
||||
}
|
||||
|
||||
export function CanResolveFilePaths() {
|
||||
return window.runtime.CanResolveFilePaths();
|
||||
}
|
||||
|
||||
export function ResolveFilePaths(files) {
|
||||
return window.runtime.ResolveFilePaths(files);
|
||||
}
|
||||
14
frontend/vite.config.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
/**
|
||||
* Configuration minimale Vite :
|
||||
* - base './' pour que les assets fonctionnent en mode "embarqué"
|
||||
* - outDir = 'dist' (Wails embarque frontend/dist dans le binaire)
|
||||
*/
|
||||
export default defineConfig({
|
||||
base: './',
|
||||
build: {
|
||||
outDir: 'dist',
|
||||
emptyOutDir: true
|
||||
}
|
||||
});
|
||||
46
go.mod
Normal file
@@ -0,0 +1,46 @@
|
||||
module git.dumerain.org/alban/calcul-astreintes
|
||||
|
||||
go 1.24.0
|
||||
|
||||
toolchain go1.24.4
|
||||
|
||||
require (
|
||||
github.com/phpdave11/gofpdf v1.4.2
|
||||
github.com/wailsapp/wails/v2 v2.11.0
|
||||
github.com/xuri/excelize/v2 v2.10.0
|
||||
golang.org/x/text v0.33.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/bep/debounce v1.2.1 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.3 // indirect
|
||||
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect
|
||||
github.com/labstack/echo/v4 v4.13.3 // indirect
|
||||
github.com/labstack/gommon v0.4.2 // indirect
|
||||
github.com/leaanthony/go-ansi-parser v1.6.1 // indirect
|
||||
github.com/leaanthony/gosod v1.0.4 // indirect
|
||||
github.com/leaanthony/slicer v1.6.0 // indirect
|
||||
github.com/leaanthony/u v1.1.1 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/richardlehane/mscfb v1.0.4 // indirect
|
||||
github.com/richardlehane/msoleps v1.0.4 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/samber/lo v1.49.1 // indirect
|
||||
github.com/tiendc/go-deepcopy v1.7.1 // indirect
|
||||
github.com/tkrajina/go-reflector v0.5.8 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||
github.com/wailsapp/go-webview2 v1.0.22 // indirect
|
||||
github.com/wailsapp/mimetype v1.4.1 // indirect
|
||||
github.com/xuri/efp v0.0.1 // indirect
|
||||
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 // indirect
|
||||
golang.org/x/crypto v0.43.0 // indirect
|
||||
golang.org/x/net v0.46.0 // indirect
|
||||
golang.org/x/sys v0.37.0 // indirect
|
||||
)
|
||||
107
go.sum
Normal file
@@ -0,0 +1,107 @@
|
||||
github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
|
||||
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
|
||||
github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
||||
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck=
|
||||
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=
|
||||
github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
|
||||
github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY=
|
||||
github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g=
|
||||
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
|
||||
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
|
||||
github.com/leaanthony/debme v1.2.1 h1:9Tgwf+kjcrbMQ4WnPcEIUcQuIZYqdWftzZkBr+i/oOc=
|
||||
github.com/leaanthony/debme v1.2.1/go.mod h1:3V+sCm5tYAgQymvSOfYQ5Xx2JCr+OXiD9Jkw3otUjiA=
|
||||
github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A=
|
||||
github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU=
|
||||
github.com/leaanthony/gosod v1.0.4 h1:YLAbVyd591MRffDgxUOU1NwLhT9T1/YiwjKZpkNFeaI=
|
||||
github.com/leaanthony/gosod v1.0.4/go.mod h1:GKuIL0zzPj3O1SdWQOdgURSuhkF+Urizzxh26t9f1cw=
|
||||
github.com/leaanthony/slicer v1.6.0 h1:1RFP5uiPJvT93TAHi+ipd3NACobkW53yUiBqZheE/Js=
|
||||
github.com/leaanthony/slicer v1.6.0/go.mod h1:o/Iz29g7LN0GqH3aMjWAe90381nyZlDNquK+mtH2Fj8=
|
||||
github.com/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M=
|
||||
github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI=
|
||||
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
|
||||
github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ=
|
||||
github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/phpdave11/gofpdf v1.4.2 h1:KPKiIbfwbvC/wOncwhrpRdXVj2CZTCFlw4wnoyjtHfQ=
|
||||
github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY=
|
||||
github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM=
|
||||
github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk=
|
||||
github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
|
||||
github.com/richardlehane/msoleps v1.0.4 h1:WuESlvhX3gH2IHcd8UqyCuFY5yiq/GR/yqaSM/9/g00=
|
||||
github.com/richardlehane/msoleps v1.0.4/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w=
|
||||
github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew=
|
||||
github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/tiendc/go-deepcopy v1.7.1 h1:LnubftI6nYaaMOcaz0LphzwraqN8jiWTwm416sitff4=
|
||||
github.com/tiendc/go-deepcopy v1.7.1/go.mod h1:4bKjNC2r7boYOkD2IOuZpYjmlDdzjbpTRyCx+goBCJQ=
|
||||
github.com/tkrajina/go-reflector v0.5.8 h1:yPADHrwmUbMq4RGEyaOUpz2H90sRsETNVpjzo3DLVQQ=
|
||||
github.com/tkrajina/go-reflector v0.5.8/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
|
||||
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||
github.com/wailsapp/go-webview2 v1.0.22 h1:YT61F5lj+GGaat5OB96Aa3b4QA+mybD0Ggq6NZijQ58=
|
||||
github.com/wailsapp/go-webview2 v1.0.22/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc=
|
||||
github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=
|
||||
github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o=
|
||||
github.com/wailsapp/wails/v2 v2.11.0 h1:seLacV8pqupq32IjS4Y7V8ucab0WZwtK6VvUVxSBtqQ=
|
||||
github.com/wailsapp/wails/v2 v2.11.0/go.mod h1:jrf0ZaM6+GBc1wRmXsM8cIvzlg0karYin3erahI4+0k=
|
||||
github.com/xuri/efp v0.0.1 h1:fws5Rv3myXyYni8uwj2qKjVaRP30PdjeYe2Y6FDsCL8=
|
||||
github.com/xuri/efp v0.0.1/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
|
||||
github.com/xuri/excelize/v2 v2.10.0 h1:8aKsP7JD39iKLc6dH5Tw3dgV3sPRh8uRVXu/fMstfW4=
|
||||
github.com/xuri/excelize/v2 v2.10.0/go.mod h1:SC5TzhQkaOsTWpANfm+7bJCldzcnU/jrhqkTi/iBHBU=
|
||||
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 h1:+C0TIdyyYmzadGaL/HBLbf3WdLgC29pgyhTjAT/0nuE=
|
||||
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
|
||||
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
|
||||
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
|
||||
golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ=
|
||||
golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs=
|
||||
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
|
||||
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
|
||||
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
||||
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
||||
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
74
internal/calc/compute.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package calc
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"git.dumerain.org/alban/calcul-astreintes/internal/models"
|
||||
)
|
||||
|
||||
/*
|
||||
Le moteur de calcul est volontairement isolé du reste.
|
||||
Ça facilite :
|
||||
- les tests unitaires
|
||||
- l'évolution des règles
|
||||
- le support de nouveaux codes
|
||||
*/
|
||||
|
||||
func round2(v float64) float64 {
|
||||
return math.Round(v*100) / 100
|
||||
}
|
||||
|
||||
func Compute(req models.CalculateRequest, profile models.Profile, rules models.GlobalRules) models.CalculateResponse {
|
||||
// Chaque ligne = taux * quantité, arrondi au centime.
|
||||
// Libellés volontairement "fiche de paie" (courts) pour que UI + PDF collent.
|
||||
lines := []models.Line{
|
||||
{
|
||||
Code: "456",
|
||||
Label: "Interv.astreinte Jour",
|
||||
Rate: profile.T456,
|
||||
Quantity: req.Q456,
|
||||
Unit: "h",
|
||||
Amount: round2(profile.T456 * req.Q456),
|
||||
},
|
||||
{
|
||||
Code: "458",
|
||||
Label: "Interv.astreinte Dim & JF",
|
||||
Rate: profile.T458,
|
||||
Quantity: req.Q458,
|
||||
Unit: "h",
|
||||
Amount: round2(profile.T458 * req.Q458),
|
||||
},
|
||||
{
|
||||
Code: "459",
|
||||
Label: "Interv.astreinte Nuit",
|
||||
Rate: profile.T459,
|
||||
Quantity: req.Q459,
|
||||
Unit: "h",
|
||||
Amount: round2(profile.T459 * req.Q459),
|
||||
},
|
||||
{
|
||||
Code: "471",
|
||||
Label: "Astreinte",
|
||||
Rate: profile.T471,
|
||||
Quantity: req.Q471,
|
||||
Unit: "h",
|
||||
Amount: round2(profile.T471 * req.Q471),
|
||||
},
|
||||
{
|
||||
Code: "480",
|
||||
Label: "Ind. Forfait. Dim & JF",
|
||||
Rate: rules.ForfaitDimFerie,
|
||||
Quantity: float64(req.NbDimFerie),
|
||||
Unit: "j",
|
||||
Amount: round2(rules.ForfaitDimFerie * float64(req.NbDimFerie)),
|
||||
},
|
||||
}
|
||||
|
||||
// Total = somme des montants déjà arrondis (comportement typique paie).
|
||||
total := 0.0
|
||||
for _, l := range lines {
|
||||
total += l.Amount
|
||||
}
|
||||
|
||||
return models.CalculateResponse{Lines: lines, Total: round2(total)}
|
||||
}
|
||||
205
internal/importer/xlsx.go
Normal file
@@ -0,0 +1,205 @@
|
||||
package importer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/xuri/excelize/v2"
|
||||
|
||||
"git.dumerain.org/alban/calcul-astreintes/internal/models"
|
||||
)
|
||||
|
||||
// Feuille de mois acceptée :
|
||||
// - YYYY-MM (ex: 2025-01)
|
||||
// - MM-YYYY (ex: 01-2025)
|
||||
var sheetRe = regexp.MustCompile(`^(\d{4})-(\d{2})$|^(\d{2})-(\d{4})$`)
|
||||
|
||||
func normalizeYearMonth(sheet string) (string, bool) {
|
||||
sheet = strings.TrimSpace(sheet)
|
||||
m := sheetRe.FindStringSubmatch(sheet)
|
||||
if m == nil {
|
||||
return "", false
|
||||
}
|
||||
// YYYY-MM
|
||||
if m[1] != "" && m[2] != "" {
|
||||
return fmt.Sprintf("%s-%s", m[1], m[2]), true
|
||||
}
|
||||
// MM-YYYY
|
||||
if m[3] != "" && m[4] != "" {
|
||||
return fmt.Sprintf("%s-%s", m[4], m[3]), true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
// parseNumberFR convertit "1,92" / "1.92" / "" -> float64
|
||||
func parseNumberFR(s string) float64 {
|
||||
s = strings.TrimSpace(s)
|
||||
if s == "" {
|
||||
return 0
|
||||
}
|
||||
s = strings.ReplaceAll(s, "\u00a0", "")
|
||||
s = strings.ReplaceAll(s, " ", "")
|
||||
s = strings.ReplaceAll(s, ",", ".")
|
||||
f, _ := strconv.ParseFloat(s, 64)
|
||||
return f
|
||||
}
|
||||
|
||||
func isMatriculeCell(s string) bool {
|
||||
s = strings.TrimSpace(s)
|
||||
if s == "" {
|
||||
return false
|
||||
}
|
||||
for _, r := range s {
|
||||
if r < '0' || r > '9' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// FindMonthSheet: prend la première feuille qui match YYYY-MM ou MM-YYYY, sinon la première feuille.
|
||||
func FindMonthSheet(f *excelize.File) (string, error) {
|
||||
sheets := f.GetSheetList()
|
||||
for _, sh := range sheets {
|
||||
if _, ok := normalizeYearMonth(sh); ok {
|
||||
return strings.TrimSpace(sh), nil
|
||||
}
|
||||
}
|
||||
if len(sheets) == 0 {
|
||||
return "", fmt.Errorf("aucune feuille trouvée dans le fichier")
|
||||
}
|
||||
return sheets[0], nil
|
||||
}
|
||||
|
||||
// ParseAgentsAndTotals lit la feuille détectée et renvoie agents + totaux.
|
||||
// (Le mois est géré côté app/app.go via normalizeYearMonth)
|
||||
func ParseAgentsAndTotals(path string) ([]models.Agent, map[string]models.AgentTotals, error) {
|
||||
f, err := excelize.OpenFile(path)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer func() { _ = f.Close() }()
|
||||
|
||||
sheet, err := FindMonthSheet(f)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
rows, err := f.GetRows(sheet)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
agents := []models.Agent{}
|
||||
totals := map[string]models.AgentTotals{}
|
||||
|
||||
// Dedup P_DIM-JF par agent (par date)
|
||||
dimDates := map[string]map[string]struct{}{}
|
||||
currentMatricule := ""
|
||||
|
||||
cell := func(col string, excelRow int) string {
|
||||
v, _ := f.GetCellValue(sheet, fmt.Sprintf("%s%d", col, excelRow))
|
||||
return v
|
||||
}
|
||||
|
||||
for i := 0; i < len(rows); i++ {
|
||||
row := rows[i]
|
||||
|
||||
colA := ""
|
||||
if len(row) >= 1 {
|
||||
colA = row[0]
|
||||
}
|
||||
|
||||
if isMatriculeCell(colA) {
|
||||
currentMatricule = strings.TrimSpace(colA)
|
||||
|
||||
if _, ok := totals[currentMatricule]; !ok {
|
||||
fullName := ""
|
||||
if len(row) >= 2 {
|
||||
fullName = strings.TrimSpace(row[1]) // Col B = "NOM Prenom"
|
||||
}
|
||||
|
||||
nom, prenom := "", ""
|
||||
if fullName != "" {
|
||||
parts := strings.Fields(fullName)
|
||||
if len(parts) >= 1 {
|
||||
nom = parts[0]
|
||||
}
|
||||
if len(parts) >= 2 {
|
||||
prenom = strings.Join(parts[1:], " ")
|
||||
}
|
||||
}
|
||||
|
||||
displayName := strings.TrimSpace(strings.Join([]string{nom, prenom}, " "))
|
||||
if displayName == "" {
|
||||
displayName = currentMatricule
|
||||
}
|
||||
display := fmt.Sprintf("%s (%s)", displayName, currentMatricule)
|
||||
|
||||
if i == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
totalExcelRow := i // ligne au-dessus en Excel (cf logique actuelle)
|
||||
|
||||
q471 := parseNumberFR(cell("G", totalExcelRow))
|
||||
q456 := parseNumberFR(cell("L", totalExcelRow))
|
||||
q459 := parseNumberFR(cell("M", totalExcelRow))
|
||||
q458 := parseNumberFR(cell("N", totalExcelRow))
|
||||
|
||||
agents = append(agents, models.Agent{
|
||||
Matricule: currentMatricule,
|
||||
Nom: nom,
|
||||
Prenom: prenom,
|
||||
Display: display,
|
||||
})
|
||||
|
||||
totals[currentMatricule] = models.AgentTotals{
|
||||
Matricule: currentMatricule,
|
||||
Q471: q471,
|
||||
Q456: q456,
|
||||
Q459: q459,
|
||||
Q458: q458,
|
||||
NbDimFerie: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if currentMatricule != "" {
|
||||
typeLigne := ""
|
||||
if len(row) >= 3 {
|
||||
typeLigne = strings.TrimSpace(row[2]) // Col C
|
||||
}
|
||||
if typeLigne == "P_DIM-JF" {
|
||||
dateStr := ""
|
||||
if len(row) >= 4 {
|
||||
dateStr = strings.TrimSpace(row[3]) // Col D
|
||||
}
|
||||
if _, ok := dimDates[currentMatricule]; !ok {
|
||||
dimDates[currentMatricule] = map[string]struct{}{}
|
||||
}
|
||||
if dateStr != "" {
|
||||
dimDates[currentMatricule][dateStr] = struct{}{}
|
||||
} else {
|
||||
dimDates[currentMatricule][fmt.Sprintf("row-%d", i+1)] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for mat, t := range totals {
|
||||
if s, ok := dimDates[mat]; ok {
|
||||
t.NbDimFerie = len(s)
|
||||
totals[mat] = t
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(agents, func(i, j int) bool {
|
||||
return strings.ToLower(agents[i].Display) < strings.ToLower(agents[j].Display)
|
||||
})
|
||||
|
||||
return agents, totals, nil
|
||||
}
|
||||
21
internal/models/import.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package models
|
||||
|
||||
// Agent = une personne trouvée dans le fichier (affichage + clé matricule)
|
||||
type Agent struct {
|
||||
Matricule string `json:"matricule"`
|
||||
Nom string `json:"nom"`
|
||||
Prenom string `json:"prenom"`
|
||||
Display string `json:"display"` // "NOM Prenom (matricule)"
|
||||
}
|
||||
|
||||
// AgentTotals = les totaux "Total Agent" (ligne du dessus)
|
||||
type AgentTotals struct {
|
||||
Matricule string `json:"matricule"`
|
||||
Q471 float64 `json:"q471"` // Total heures astreinte (col G)
|
||||
Q456 float64 `json:"q456"` // Intervention jour (col L)
|
||||
Q459 float64 `json:"q459"` // Intervention nuit (col M)
|
||||
Q458 float64 `json:"q458"` // Intervention dimanche/ferié (col N)
|
||||
|
||||
// ✅ Nouveau: nombre de dimanches / jours fériés détectés via les lignes P_DIM-JF
|
||||
NbDimFerie int `json:"nbDimFerie"`
|
||||
}
|
||||
57
internal/models/models.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package models
|
||||
|
||||
// GlobalRules = constantes communes à tous les utilisateurs.
|
||||
type GlobalRules struct {
|
||||
// ForfaitDimFerie = forfait par dimanche / jour férié (€/jour)
|
||||
ForfaitDimFerie float64 `json:"forfait_dim_ferie"`
|
||||
}
|
||||
|
||||
// Profile = taux dépendants de la personne (grade, etc.)
|
||||
type Profile struct {
|
||||
ID string `json:"id"`
|
||||
Label string `json:"label"`
|
||||
Matricule string `json:"matricule,omitempty"` // ✅ pour auto-match avec l'agent importé
|
||||
|
||||
// Taux horaires (€/h)
|
||||
T456 float64 `json:"t456"` // Interventions jour hors dimanche/férié
|
||||
T458 float64 `json:"t458"` // Interventions dimanche/jour férié
|
||||
T459 float64 `json:"t459"` // Interventions nuit hors dimanche/férié
|
||||
T471 float64 `json:"t471"` // Astreinte: heures totales du mois
|
||||
}
|
||||
|
||||
// ProfileListItem = ce qu'on expose à l'UI (id + label + matricule pour auto-match)
|
||||
type ProfileListItem struct {
|
||||
ID string `json:"id"`
|
||||
Label string `json:"label"`
|
||||
Matricule string `json:"matricule,omitempty"`
|
||||
}
|
||||
|
||||
// CalculateRequest = saisies mensuelles par l'utilisateur.
|
||||
type CalculateRequest struct {
|
||||
ProfileID string `json:"profileId"`
|
||||
|
||||
// Quantités en heures
|
||||
Q456 float64 `json:"q456"` // heures interventions jour hors dim/férié
|
||||
Q458 float64 `json:"q458"` // heures interventions dimanche/férié
|
||||
Q459 float64 `json:"q459"` // heures interventions nuit hors dim/férié
|
||||
Q471 float64 `json:"q471"` // heures totales astreinte du mois
|
||||
|
||||
// ✅ Nombre de dimanches / jours fériés "au forfait" (auto-rempli à l'import, modifiable)
|
||||
NbDimFerie int `json:"nbDimFerie"`
|
||||
}
|
||||
|
||||
// Line = une ligne de la grille (456/458/459/471/480).
|
||||
type Line struct {
|
||||
Code string `json:"code"`
|
||||
Label string `json:"label"`
|
||||
Rate float64 `json:"rate"`
|
||||
Quantity float64 `json:"quantity"`
|
||||
Amount float64 `json:"amount"`
|
||||
Unit string `json:"unit"` // "h" ou "j"
|
||||
}
|
||||
|
||||
// CalculateResponse = résultat du calcul (lignes + total)
|
||||
type CalculateResponse struct {
|
||||
Lines []Line `json:"lines"`
|
||||
Total float64 `json:"total"`
|
||||
}
|
||||
24
internal/models/pdf.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package models
|
||||
|
||||
// ExportPDFMeta = métadonnées utilisées pour nommer le fichier et afficher l'entête du PDF.
|
||||
//
|
||||
// YearMonth doit être au format YYYY-MM.
|
||||
type ExportPDFMeta struct {
|
||||
YearMonth string `json:"yearMonth"`
|
||||
Nom string `json:"nom"`
|
||||
Prenom string `json:"prenom"`
|
||||
Matricule string `json:"matricule"`
|
||||
}
|
||||
|
||||
// ExportPDFRequest = données nécessaires à la génération du PDF.
|
||||
//
|
||||
// Note: on envoie les inputs (CalculateRequest) et on recalcule côté backend
|
||||
// pour garantir que le PDF correspond exactement aux règles/taux du profil.
|
||||
type ExportPDFRequest struct {
|
||||
Meta ExportPDFMeta `json:"meta"`
|
||||
Request CalculateRequest `json:"request"`
|
||||
}
|
||||
|
||||
type ExportPDFResponse struct {
|
||||
Path string `json:"path"`
|
||||
}
|
||||
BIN
internal/pdf/assets/paychek-logo.png
Normal file
|
After Width: | Height: | Size: 341 KiB |
BIN
internal/pdf/fonts/DejaVuSans-Bold.ttf
Normal file
BIN
internal/pdf/fonts/DejaVuSans.ttf
Normal file
237
internal/pdf/pdf.go
Normal file
@@ -0,0 +1,237 @@
|
||||
package pdf
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"embed"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/phpdave11/gofpdf"
|
||||
|
||||
"git.dumerain.org/alban/calcul-astreintes/internal/models"
|
||||
)
|
||||
|
||||
//go:embed fonts/DejaVuSans.ttf fonts/DejaVuSans-Bold.ttf assets/paychek-logo.png
|
||||
var assetFS embed.FS
|
||||
|
||||
func BuildPDF(
|
||||
meta models.ExportPDFMeta,
|
||||
profileLabel string,
|
||||
req models.CalculateRequest,
|
||||
res models.CalculateResponse,
|
||||
) ([]byte, error) {
|
||||
|
||||
regularFont, err := assetFS.ReadFile("fonts/DejaVuSans.ttf")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
boldFont, err := assetFS.ReadFile("fonts/DejaVuSans-Bold.ttf")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logoBytes, logoErr := assetFS.ReadFile("assets/paychek-logo.png")
|
||||
hasLogo := logoErr == nil && len(logoBytes) > 0
|
||||
|
||||
pdf := gofpdf.New("P", "mm", "A4", "")
|
||||
if hasLogo {
|
||||
pdf.RegisterImageOptionsReader(
|
||||
"paychek-logo",
|
||||
gofpdf.ImageOptions{ImageType: "PNG"},
|
||||
bytes.NewReader(logoBytes),
|
||||
)
|
||||
}
|
||||
// ✅ IMPORTANT : on désactive l'auto page-break pour éviter une 2e page
|
||||
pdf.SetMargins(12, 14, 12)
|
||||
pdf.SetAutoPageBreak(false, 0)
|
||||
pdf.AddPage()
|
||||
|
||||
pdf.AddUTF8FontFromBytes("DejaVu", "", regularFont)
|
||||
pdf.AddUTF8FontFromBytes("DejaVu", "B", boldFont)
|
||||
|
||||
// A4 en mm
|
||||
pageW := 210.0
|
||||
contentW := 170.0
|
||||
left := (pageW - contentW) / 2.0
|
||||
|
||||
created := time.Now().Format("02/01/2006 15:04")
|
||||
|
||||
fullName := strings.TrimSpace(strings.Join([]string{
|
||||
meta.Nom,
|
||||
meta.Prenom,
|
||||
}, " "))
|
||||
if fullName == "" {
|
||||
fullName = "(nom non renseigné)"
|
||||
}
|
||||
|
||||
// ---------------- TITRE ----------------
|
||||
titleY := pdf.GetY()
|
||||
logoSize := 10.0 // mm
|
||||
gap := 2.5 // espace texte ↔ logo
|
||||
|
||||
pdf.SetFont("DejaVu", "B", 18)
|
||||
|
||||
// Largeur du texte
|
||||
title := "Paychek"
|
||||
textW := pdf.GetStringWidth(title)
|
||||
|
||||
// Largeur totale du bloc (texte + logo)
|
||||
blockW := textW
|
||||
if hasLogo {
|
||||
blockW += gap + logoSize
|
||||
}
|
||||
|
||||
// Position X pour centrer le bloc
|
||||
startX := left + (contentW-blockW)/2
|
||||
|
||||
// Texte
|
||||
pdf.SetXY(startX, titleY)
|
||||
pdf.CellFormat(textW, 10, title, "", 0, "L", false, 0, "")
|
||||
|
||||
// Logo à droite du texte
|
||||
if hasLogo {
|
||||
pdf.ImageOptions(
|
||||
"paychek-logo",
|
||||
startX+textW+gap,
|
||||
titleY,
|
||||
logoSize,
|
||||
logoSize,
|
||||
false,
|
||||
gofpdf.ImageOptions{ImageType: "PNG"},
|
||||
0,
|
||||
"",
|
||||
)
|
||||
}
|
||||
|
||||
pdf.Ln(10)
|
||||
|
||||
pdf.SetFont("DejaVu", "", 11)
|
||||
pdf.SetX(left)
|
||||
pdf.CellFormat(contentW, 6, "Give me my fuc** money", "", 1, "C", false, 0, "")
|
||||
|
||||
// ✅ Séparation franche sous le titre
|
||||
pdf.Ln(6)
|
||||
|
||||
// ✅ CENTRAGE VERTICAL : on place le "bloc principal" à une hauteur stable
|
||||
// (A4, contenu fixe : 2 colonnes + tableau 5 lignes + légende)
|
||||
// -> évite un rendu collé en haut.
|
||||
startY := 48.0
|
||||
pdf.SetY(startY)
|
||||
|
||||
// ---------------- IDENTITÉ / VALEURS ----------------
|
||||
pdf.SetFont("DejaVu", "B", 12)
|
||||
pdf.SetX(left)
|
||||
pdf.CellFormat(contentW/2-5, 6, "Identité", "", 0, "L", false, 0, "")
|
||||
pdf.CellFormat(contentW/2-5, 6, "Valeurs importées", "", 1, "L", false, 0, "")
|
||||
|
||||
pdf.SetFont("DejaVu", "", 11)
|
||||
|
||||
y := pdf.GetY()
|
||||
pdf.SetXY(left, y)
|
||||
pdf.CellFormat(contentW/2-5, 6, "Mois : "+meta.YearMonth, "", 1, "L", false, 0, "")
|
||||
pdf.SetX(left)
|
||||
pdf.CellFormat(contentW/2-5, 6, "Agent : "+fullName, "", 1, "L", false, 0, "")
|
||||
if strings.TrimSpace(meta.Matricule) != "" {
|
||||
pdf.SetX(left)
|
||||
pdf.CellFormat(contentW/2-5, 6, "Matricule : "+meta.Matricule, "", 1, "L", false, 0, "")
|
||||
} else {
|
||||
pdf.SetX(left)
|
||||
pdf.CellFormat(contentW/2-5, 6, "Matricule : (non renseigné)", "", 1, "L", false, 0, "")
|
||||
}
|
||||
pdf.SetX(left)
|
||||
pdf.CellFormat(contentW/2-5, 6, "Profil : "+profileLabel, "", 1, "L", false, 0, "")
|
||||
|
||||
rightX := left + contentW/2 + 5
|
||||
pdf.SetXY(rightX, y)
|
||||
pdf.CellFormat(contentW/2-5, 6, fmt.Sprintf("456 : %.2f h", req.Q456), "", 1, "L", false, 0, "")
|
||||
pdf.SetX(rightX)
|
||||
pdf.CellFormat(contentW/2-5, 6, fmt.Sprintf("458 : %.2f h", req.Q458), "", 1, "L", false, 0, "")
|
||||
pdf.SetX(rightX)
|
||||
pdf.CellFormat(contentW/2-5, 6, fmt.Sprintf("459 : %.2f h", req.Q459), "", 1, "L", false, 0, "")
|
||||
pdf.SetX(rightX)
|
||||
pdf.CellFormat(contentW/2-5, 6, fmt.Sprintf("471 : %.2f h", req.Q471), "", 1, "L", false, 0, "")
|
||||
pdf.SetX(rightX)
|
||||
pdf.CellFormat(contentW/2-5, 6, fmt.Sprintf("Dim/JF : %d j", req.NbDimFerie), "", 1, "L", false, 0, "")
|
||||
|
||||
pdf.Ln(8)
|
||||
|
||||
// ---------------- TABLEAU ----------------
|
||||
pdf.SetFont("DejaVu", "B", 12)
|
||||
pdf.SetX(left)
|
||||
pdf.CellFormat(contentW, 7, "Résultat", "", 1, "L", false, 0, "")
|
||||
|
||||
// Largeurs adaptées (total = 170)
|
||||
colW := []float64{24, 52, 38, 22, 34}
|
||||
headerColor := []int{197, 227, 242}
|
||||
|
||||
pdf.SetFont("DejaVu", "B", 9)
|
||||
pdf.SetFillColor(headerColor[0], headerColor[1], headerColor[2])
|
||||
pdf.SetX(left)
|
||||
headers := []string{"Code paie", "Libellé", "Nombre", "Base", "À payer"}
|
||||
for i, h := range headers {
|
||||
align := "L"
|
||||
if i >= 2 {
|
||||
align = "R"
|
||||
}
|
||||
pdf.CellFormat(colW[i], 7, h, "1", 0, align, true, 0, "")
|
||||
}
|
||||
pdf.Ln(-1)
|
||||
|
||||
pdf.SetFont("DejaVu", "", 9)
|
||||
for _, l := range res.Lines {
|
||||
pdf.SetX(left)
|
||||
pdf.CellFormat(colW[0], 7, l.Code, "1", 0, "L", false, 0, "")
|
||||
pdf.CellFormat(colW[1], 7, l.Label, "1", 0, "L", false, 0, "")
|
||||
pdf.CellFormat(colW[2], 7, fmt.Sprintf("%.2f %s", l.Quantity, l.Unit), "1", 0, "R", false, 0, "")
|
||||
pdf.CellFormat(colW[3], 7, fmt.Sprintf("%.2f", l.Rate), "1", 0, "R", false, 0, "")
|
||||
pdf.CellFormat(colW[4], 7, fmt.Sprintf("%.2f", l.Amount), "1", 0, "R", false, 0, "")
|
||||
pdf.Ln(-1)
|
||||
}
|
||||
|
||||
// TOTAL BRUT (fond jaune clair)
|
||||
pdf.SetFont("DejaVu", "B", 10)
|
||||
pdf.SetFillColor(255, 249, 196)
|
||||
pdf.SetX(left)
|
||||
pdf.CellFormat(colW[0]+colW[1]+colW[2]+colW[3], 7, "TOTAL BRUT", "1", 0, "L", true, 0, "")
|
||||
pdf.CellFormat(colW[4], 7, fmt.Sprintf("%.2f", res.Total), "1", 1, "R", true, 0, "")
|
||||
|
||||
pdf.Ln(6)
|
||||
|
||||
// ---------------- LÉGENDE ----------------
|
||||
pdf.SetFont("DejaVu", "B", 9)
|
||||
pdf.SetX(left)
|
||||
pdf.CellFormat(contentW, 5, "Correspondance code paie / fichier Excel", "", 1, "L", false, 0, "")
|
||||
|
||||
pdf.SetFont("DejaVu", "", 8)
|
||||
legend := "" +
|
||||
"• 456 → colonne L (Interv.astreinte Jour)\n" +
|
||||
"• 458 → colonne N (Interv.astreinte Dim & JF)\n" +
|
||||
"• 459 → colonne M (Interv.astreinte Nuit)\n" +
|
||||
"• 471 → colonne G (Astreinte = total d'heures du mois)\n" +
|
||||
"• 480 → déduit des lignes \"P_DIM-JF\" (colonne C) → compteur Dim/JF\n"
|
||||
pdf.SetX(left)
|
||||
pdf.MultiCell(contentW, 4.2, legend, "", "L", false)
|
||||
|
||||
// ---------------- FOOTER (toujours sur la page 1) ----------------
|
||||
// ✅ Position absolue proche du bas (A4=297mm) + marges
|
||||
pdf.SetFont("DejaVu", "", 8)
|
||||
pdf.SetTextColor(120, 120, 120)
|
||||
pdf.SetXY(12, 284)
|
||||
pdf.CellFormat(
|
||||
186,
|
||||
6,
|
||||
fmt.Sprintf("Paychek - Made with \u2665 in Go by Flooze Corp \u00B7 A Niarmud Nablax Company - Exporté le %s", created),
|
||||
"",
|
||||
0,
|
||||
"C",
|
||||
false,
|
||||
0,
|
||||
"",
|
||||
)
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := pdf.Output(&buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
50
internal/pdf/sanitize.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package pdf
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"golang.org/x/text/unicode/norm"
|
||||
)
|
||||
|
||||
// SanitizeFilename transforme une chaîne Unicode en nom de fichier sûr :
|
||||
// - supprime les accents (Clément → Clement)
|
||||
// - remplace les espaces par des tirets
|
||||
// - supprime les caractères interdits (Windows / Unix)
|
||||
// - évite les noms vides
|
||||
func SanitizeFilename(input string) string {
|
||||
if input == "" {
|
||||
return "inconnu"
|
||||
}
|
||||
|
||||
// 1) Normalisation Unicode (NFD) pour séparer lettres + accents
|
||||
t := norm.NFD.String(input)
|
||||
|
||||
// 2) Suppression des marques diacritiques (accents)
|
||||
sb := strings.Builder{}
|
||||
for _, r := range t {
|
||||
if unicode.Is(unicode.Mn, r) {
|
||||
continue
|
||||
}
|
||||
sb.WriteRune(r)
|
||||
}
|
||||
out := sb.String()
|
||||
|
||||
// 3) Remplacements simples
|
||||
out = strings.ReplaceAll(out, " ", "-")
|
||||
out = strings.ReplaceAll(out, "_", "-")
|
||||
|
||||
// 4) Suppression des caractères interdits dans les noms de fichiers
|
||||
// Windows + Unix
|
||||
re := regexp.MustCompile(`[<>:"/\\|?*\x00-\x1F]`)
|
||||
out = re.ReplaceAllString(out, "")
|
||||
|
||||
// 5) Nettoyage final
|
||||
out = strings.Trim(out, "-.")
|
||||
if out == "" {
|
||||
return "inconnu"
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
98
internal/profiles/loader.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package profiles
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"git.dumerain.org/alban/calcul-astreintes/internal/models"
|
||||
)
|
||||
|
||||
//go:embed profiles.json
|
||||
var embeddedFS embed.FS
|
||||
|
||||
type profileJSON struct {
|
||||
Label string `json:"label"`
|
||||
Matricule string `json:"matricule,omitempty"` // ✅ pour auto-match avec l'agent importé
|
||||
Rates map[string]float64 `json:"rates"`
|
||||
ForfaitDimFerie float64 `json:"forfait_dim_ferie"`
|
||||
}
|
||||
|
||||
type Repo struct {
|
||||
mu sync.RWMutex
|
||||
profiles map[string]models.Profile
|
||||
rules models.GlobalRules
|
||||
}
|
||||
|
||||
// NewEmbeddedRepo charge les profils depuis profiles.json (embarqué).
|
||||
func NewEmbeddedRepo() (*Repo, *models.GlobalRules, error) {
|
||||
b, err := embeddedFS.ReadFile("profiles.json")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var raw map[string]profileJSON
|
||||
if err := json.Unmarshal(b, &raw); err != nil {
|
||||
return nil, nil, fmt.Errorf("profiles.json invalide: %w", err)
|
||||
}
|
||||
|
||||
repo := &Repo{profiles: map[string]models.Profile{}}
|
||||
|
||||
for id, p := range raw {
|
||||
repo.profiles[id] = models.Profile{
|
||||
ID: id,
|
||||
Label: p.Label,
|
||||
Matricule: p.Matricule,
|
||||
T456: p.Rates["456"],
|
||||
T458: p.Rates["458"],
|
||||
T459: p.Rates["459"],
|
||||
T471: p.Rates["471"],
|
||||
}
|
||||
if repo.rules.ForfaitDimFerie == 0 {
|
||||
repo.rules.ForfaitDimFerie = p.ForfaitDimFerie
|
||||
}
|
||||
}
|
||||
|
||||
// Valeur par défaut de sécurité si le JSON n'a aucun profil
|
||||
if repo.rules.ForfaitDimFerie == 0 {
|
||||
repo.rules.ForfaitDimFerie = 120
|
||||
}
|
||||
|
||||
return repo, &repo.rules, nil
|
||||
}
|
||||
|
||||
// List renvoie les profils triés par label (dropdown stable).
|
||||
func (r *Repo) List() ([]models.ProfileListItem, error) {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
|
||||
items := make([]models.ProfileListItem, 0, len(r.profiles))
|
||||
for _, p := range r.profiles {
|
||||
items = append(items, models.ProfileListItem{
|
||||
ID: p.ID,
|
||||
Label: p.Label,
|
||||
Matricule: p.Matricule,
|
||||
})
|
||||
}
|
||||
sort.Slice(items, func(i, j int) bool { return items[i].Label < items[j].Label })
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func (r *Repo) Get(id string) (models.Profile, error) {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
|
||||
p, ok := r.profiles[id]
|
||||
if !ok {
|
||||
return models.Profile{}, fmt.Errorf("profil introuvable: %s", id)
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (r *Repo) Rules() models.GlobalRules {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
return r.rules
|
||||
}
|
||||
46
internal/profiles/profiles.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"test": {
|
||||
"label": "TEST (fallback)",
|
||||
"matricule": "",
|
||||
"rates": {
|
||||
"456": 1,
|
||||
"458": 1,
|
||||
"459": 1,
|
||||
"471": 1
|
||||
},
|
||||
"forfait_dim_ferie": 120
|
||||
},
|
||||
"alban": {
|
||||
"label": "DUMERAIN Alban (165004)",
|
||||
"matricule": "165004",
|
||||
"rates": {
|
||||
"456": 18.03,
|
||||
"458": 30.05,
|
||||
"459": 36.06,
|
||||
"471": 3.58
|
||||
},
|
||||
"forfait_dim_ferie": 120
|
||||
},
|
||||
"lydie": {
|
||||
"label": "ROUQUIER Lydie (266340)",
|
||||
"matricule": "266340",
|
||||
"rates": {
|
||||
"456": 15.24,
|
||||
"458": 25.4,
|
||||
"459": 30.48,
|
||||
"471": 3.03
|
||||
},
|
||||
"forfait_dim_ferie": 120
|
||||
},
|
||||
"clement": {
|
||||
"label": "DUFETELLE Clement (178549)",
|
||||
"matricule": "178549",
|
||||
"rates": {
|
||||
"456": 19.05,
|
||||
"458": 31.75,
|
||||
"459": 38.1,
|
||||
"471": 3.78
|
||||
},
|
||||
"forfait_dim_ferie": 120
|
||||
}
|
||||
}
|
||||
15
internal/store/profiles.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"rules": {
|
||||
"forfait_dim_ferie": 120.0
|
||||
},
|
||||
"profiles": [
|
||||
{
|
||||
"id": "alban",
|
||||
"label": "Alban",
|
||||
"t456": 18.03,
|
||||
"t458": 30.05,
|
||||
"t459": 36.06,
|
||||
"t471": 3.58
|
||||
}
|
||||
]
|
||||
}
|
||||
69
internal/store/repo.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"git.dumerain.org/alban/calcul-astreintes/internal/models"
|
||||
)
|
||||
|
||||
/*
|
||||
On embarque internal/store/profiles.json dans le binaire.
|
||||
Ça simplifie énormément le "standalone": pas de fichier externe obligatoire.
|
||||
*/
|
||||
//go:embed profiles.json
|
||||
var embeddedFS embed.FS
|
||||
|
||||
type ProfileRepo struct {
|
||||
mu sync.RWMutex
|
||||
profiles map[string]models.Profile
|
||||
}
|
||||
|
||||
type embeddedData struct {
|
||||
Rules models.GlobalRules `json:"rules"`
|
||||
Profiles []models.Profile `json:"profiles"`
|
||||
}
|
||||
|
||||
func NewEmbeddedRepo() (*ProfileRepo, *models.GlobalRules, error) {
|
||||
b, err := embeddedFS.ReadFile("profiles.json")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
var data embeddedData
|
||||
if err := json.Unmarshal(b, &data); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
repo := &ProfileRepo{profiles: map[string]models.Profile{}}
|
||||
for _, p := range data.Profiles {
|
||||
repo.profiles[p.ID] = p
|
||||
}
|
||||
return repo, &data.Rules, nil
|
||||
}
|
||||
|
||||
// List : profils triés par label (pour un dropdown stable).
|
||||
func (r *ProfileRepo) List() ([]models.ProfileListItem, error) {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
|
||||
items := make([]models.ProfileListItem, 0, len(r.profiles))
|
||||
for _, p := range r.profiles {
|
||||
items = append(items, models.ProfileListItem{ID: p.ID, Label: p.Label})
|
||||
}
|
||||
sort.Slice(items, func(i, j int) bool { return items[i].Label < items[j].Label })
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func (r *ProfileRepo) Get(id string) (models.Profile, error) {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
|
||||
p, ok := r.profiles[id]
|
||||
if !ok {
|
||||
return models.Profile{}, fmt.Errorf("profil introuvable: %s", id)
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
50
main.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"log"
|
||||
|
||||
"git.dumerain.org/alban/calcul-astreintes/app"
|
||||
|
||||
"github.com/wailsapp/wails/v2"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
|
||||
)
|
||||
|
||||
/*
|
||||
Wails embarque des fichiers statiques (le build du frontend) dans le binaire Go.
|
||||
⚠️ Go exige que le pattern //go:embed matche au moins 1 fichier.
|
||||
C'est pourquoi on garde un fichier placeholder: frontend/dist/.keep
|
||||
*/
|
||||
//go:embed frontend/dist/*
|
||||
var assets embed.FS
|
||||
|
||||
func main() {
|
||||
// Instance de l'application Wails (backend Go)
|
||||
application := app.NewApp()
|
||||
|
||||
// Lancement de la fenêtre desktop + serveur d'assets
|
||||
err := wails.Run(&options.App{
|
||||
Title: "Paychek - Give me my fuc** money",
|
||||
Width: 980,
|
||||
Height: 720,
|
||||
MinWidth: 880,
|
||||
MinHeight: 640,
|
||||
|
||||
AssetServer: &assetserver.Options{
|
||||
Assets: assets,
|
||||
},
|
||||
|
||||
BackgroundColour: &options.RGBA{R: 245, G: 245, B: 245, A: 1},
|
||||
|
||||
OnStartup: application.Startup,
|
||||
|
||||
// Méthodes exportées au frontend via "wailsjs"
|
||||
Bind: []interface{}{
|
||||
application,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
12
wails.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"$schema": "https://wails.io/schemas/config.v2.json",
|
||||
"name": "calcul-astreintes",
|
||||
"title": "Paychek - Give me my money",
|
||||
"outputfilename": "calcul-astreintes",
|
||||
"frontend:install": "npm install",
|
||||
"frontend:build": "npm run build",
|
||||
"frontend:dev:watcher": "npm run dev",
|
||||
"frontend:dev:serverUrl": "auto",
|
||||
"frontend:dir": "frontend",
|
||||
"wailsjsdir": "frontend/src"
|
||||
}
|
||||