Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 79b831367c | |||
| 715aad58e1 | |||
| c3e1d4378c | |||
| 5acc6ff5c8 | |||
| 2e6ac29e95 | |||
| a1860e9462 | |||
| 6f55663984 | |||
| 8efb7e30df | |||
| 3faa74640d | |||
| f530f55577 |
@@ -0,0 +1,70 @@
|
||||
# Changelog
|
||||
|
||||
Toutes les modifications notables de MesRelevés sont documentées dans ce fichier.
|
||||
Format : [Keep a Changelog](https://keepachangelog.com/fr/1.0.0/) — versionnage [Semver](https://semver.org/lang/fr/).
|
||||
|
||||
---
|
||||
|
||||
## [1.0.2] — 2026-06-04
|
||||
|
||||
### Corrigé
|
||||
|
||||
- **Assistant d'installation — conflit de connexion pgsql/mysql** : un fichier `bootstrap/cache/config.php` résiduel (produit par `php artisan optimize` sur le poste de développement) était lu en priorité sur le `.env` réécrit par le wizard, forçant une connexion PostgreSQL même quand MySQL était sélectionné. Un appel à `config:clear` est maintenant effectué entre l'écriture du `.env` et l'exécution des migrations.
|
||||
- **Archives de déploiement** : `bootstrap/cache/*.php` exclu du build rsync pour éviter qu'un cache de configuration de développement ne soit embarqué dans les distributables.
|
||||
|
||||
---
|
||||
|
||||
## [1.0.1] — 2026-06-04
|
||||
|
||||
### Ajouté
|
||||
|
||||
- **Page Carte** — carte interactive (Leaflet + OpenStreetMap) affichant les lieux géolocalisés ayant des relevés ; marqueurs proportionnels au nombre de sources, popups avec détail des sources et lien vers la recherche ; compatible mode sombre
|
||||
- **Mode sombre** — détection automatique de la préférence système ; sélecteur clair / sombre / automatique dans la barre de navigation (mémorisé dans `localStorage`, sans flash au chargement) ; couverture complète des vues et composants
|
||||
- **2FA par e-mail** — code PIN à 6 chiffres envoyé à la connexion dès qu'un serveur SMTP est configuré ; code valable 10 minutes, renvoi possible
|
||||
- **Configuration SMTP depuis l'interface** — formulaire dans Administration → Paramètres du site avec test de connexion en temps réel ; activation du 2FA automatique à l'enregistrement
|
||||
- **Import / export CSV utilisateurs** — import en masse avec détection automatique du séparateur (`;`/`,`), validation par ligne, génération de mot de passe aléatoire ; export filtré au format CSV UTF-8 BOM (compatible Excel)
|
||||
- **Filtre actif / inactif** sur la liste des utilisateurs
|
||||
- **Titre du site configurable** depuis les paramètres (sans éditer `.env`)
|
||||
- **Version affichée** dans Administration → Paramètres du site avec indicateur de mise à jour disponible
|
||||
- **Option de désactivation des mises à jour automatiques** dans les paramètres
|
||||
- **Sélecteur d'utilisateur avec recherche** — fenêtre modale remplaçant les `<select>` pour l'ajout de membres aux sections et sources (adapté à plusieurs centaines d'utilisateurs)
|
||||
- **`public/servercheck.php`** — outil de diagnostic serveur autonome (PHP, extensions, répertoires, test BDD) pour les environnements de test
|
||||
|
||||
### Modifié
|
||||
|
||||
- **Assistant d'installation** — auto-création du `.env` minimal (clé temporaire, drivers fichier) à la première requête si aucun `.env` n'existe, évitant l'erreur 500 sur un serveur vierge
|
||||
- **`.htaccess`** — ajout de `FallbackResource /index.php` en fallback pour les hébergements sans `mod_rewrite`
|
||||
- **Logo dans la navigation** — contraint par `max-height` inline pour s'adapter à la hauteur de la barre sans débordement
|
||||
- **Panneau "Paramètres généraux"** remonté en première position dans la page de paramètres
|
||||
- **README** — procédure d'installation mutualisée (PHP + MySQL) mise en avant ; procédure Docker corrigée (stack réelle : PHP-FPM + Nginx + PostgreSQL, sans Redis)
|
||||
|
||||
### Corrigé
|
||||
|
||||
- Champs de formulaire illisibles en mode sombre (texte clair sur fond clair) — règle CSS globale `@layer base` couvrant tous les `<input>`, `<select>`, `<textarea>`
|
||||
- Composant `lieu-picker` entièrement sans style en mode sombre
|
||||
- Erreur 500 à l'ouverture de `/setup` sur un serveur sans `.env` (`MissingAppKeyException`)
|
||||
- Menu Administration débordant hors de la zone visible en haut de l'écran
|
||||
|
||||
---
|
||||
|
||||
## [1.0.0] — 2026-05-29
|
||||
|
||||
### Ajouté
|
||||
|
||||
- Scaffold Laravel 12 + PostgreSQL + authentification Breeze
|
||||
- Modèle de données complet : lieux (hiérarchie récursive), sections, dépôts, types de sources, sources, relevés (JSONB), utilisateurs et rôles
|
||||
- CRUD Lieux avec arbre hiérarchique et calcul automatique de `nom_long`
|
||||
- CRUD Sections, Dépôts, Types de sources (admin)
|
||||
- CRUD Sources avec assignation de membres et workflow de statut (`à_faire → en_cours → à_valider → terminé`)
|
||||
- Formulaire de saisie dynamique des relevés piloté par `source_type_fields`
|
||||
- Calendriers grégorien, julien et républicain avec conversion automatique
|
||||
- Recherche plein texte sur les relevés avec filtres (type, lieu, plage d'années)
|
||||
- Export GEDCOM 5.5.1 par source ou par sélection de recherche
|
||||
- Notifications in-app et par e-mail lors des transitions de statut
|
||||
- Interface d'administration : tableau de bord, gestion utilisateurs (actifs/inactifs), sections, types de lieux
|
||||
- Comptes actifs / inactifs avec statistiques de section dans le tableau de bord
|
||||
- Logo du site, favicon et contrôle des inscriptions publiques
|
||||
- Assistant d'installation web en 5 étapes (`/setup`)
|
||||
- Système de versioning et de mise à jour automatique (`app:update`, `app:rollback`)
|
||||
- Compatibilité MySQL / MariaDB en plus de PostgreSQL
|
||||
- Déploiement Docker (PHP-FPM + Nginx + PostgreSQL) via `docker-compose.prod.yml`
|
||||
@@ -3,32 +3,167 @@
|
||||
Application web de saisie et de recherche de relevés généalogiques pour associations.
|
||||
Permet la saisie collaborative de relevés d'actes (naissance, mariage, décès, etc.), la recherche plein texte et l'export au format GEDCOM 5.5.1.
|
||||
|
||||
---
|
||||
|
||||
## Installation sur hébergement mutualisé (recommandé)
|
||||
|
||||
### Prérequis serveur
|
||||
|
||||
| Composant | Version minimale |
|
||||
|---|---|
|
||||
| PHP | 8.2 avec extensions : `pdo_mysql`, `mbstring`, `openssl`, `tokenizer`, `xml`, `ctype`, `fileinfo`, `bcmath`, `curl` |
|
||||
| MySQL / MariaDB | MySQL 8.0+ ou MariaDB 10.5+ |
|
||||
| Serveur web | Apache (mod_rewrite activé) ou Nginx |
|
||||
| Accès fichier | Écriture sur `storage/` et `bootstrap/cache/` |
|
||||
|
||||
> **PostgreSQL** est également supporté et recommandé pour les grandes bases de relevés (full-text search natif, JSONB). Voir la section [Docker](#docker) pour une installation avec PostgreSQL.
|
||||
|
||||
---
|
||||
|
||||
### Étape 1 — Télécharger et décompresser l'archive
|
||||
|
||||
Récupérez la dernière archive depuis la page des [releases](https://git.barbel.synology.me/CGL/mesreleves-php/releases) :
|
||||
|
||||
```
|
||||
mesreleves-X.Y.Z.tar.gz
|
||||
```
|
||||
|
||||
Décompressez-la dans le dossier de votre hébergement (via FTP ou le gestionnaire de fichiers du panneau de contrôle) :
|
||||
|
||||
```bash
|
||||
tar -xzf mesreleves-X.Y.Z.tar.gz
|
||||
```
|
||||
|
||||
Cela crée un dossier `mesreleves/` contenant toute l'application.
|
||||
|
||||
---
|
||||
|
||||
### Étape 2 — Faire pointer le site vers `public/`
|
||||
|
||||
Le point d'entrée de l'application est le dossier `public/`.
|
||||
**Le dossier racine de votre site (document root) doit pointer sur `public/`, pas sur la racine de l'archive.**
|
||||
|
||||
**Exemples de configurations :**
|
||||
|
||||
<details>
|
||||
<summary>Apache — <code>.htaccess</code> (déjà inclus dans <code>public/</code>)</summary>
|
||||
|
||||
Rien à faire : le `.htaccess` livré avec l'application configure automatiquement la réécriture d'URL. Vérifiez que `mod_rewrite` est activé sur votre hébergement.
|
||||
|
||||
Si vous ne pouvez pas modifier le document root, vous pouvez déposer les fichiers directement dans le dossier `public_html/` de votre hébergement en copiant le contenu de `public/` à sa racine et en adaptant le chemin dans `index.php` :
|
||||
|
||||
```php
|
||||
// public/index.php — adapter le chemin si l'arborescence est modifiée
|
||||
require __DIR__.'/../mesreleves/vendor/autoload.php';
|
||||
$app = require_once __DIR__.'/../mesreleves/bootstrap/app.php';
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Nginx</summary>
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name votre-domaine.fr;
|
||||
root /var/www/mesreleves/public;
|
||||
|
||||
index index.php;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.php?$query_string;
|
||||
}
|
||||
|
||||
location ~ \.php$ {
|
||||
fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
|
||||
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
|
||||
include fastcgi_params;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
### Étape 3 — Lancer l'assistant d'installation
|
||||
|
||||
Ouvrez votre navigateur sur `https://votre-domaine.fr/setup`.
|
||||
|
||||
L'assistant vous guide à travers **5 étapes** :
|
||||
|
||||
1. **Prérequis** — vérification automatique des extensions PHP et des droits d'écriture
|
||||
2. **Base de données** — saisie des paramètres MySQL/PostgreSQL ; un bouton « Tester la connexion » valide la configuration avant de continuer
|
||||
3. **Application** — nom du site, URL de base, activation des inscriptions publiques
|
||||
4. **Compte administrateur** — création du premier compte admin
|
||||
5. **Installation** — migrations, génération de la clé d'application, résumé
|
||||
|
||||
> L'assistant crée automatiquement le fichier `.env` et exécute toutes les migrations.
|
||||
> Une fois terminé, la page `/setup` est désactivée.
|
||||
|
||||
---
|
||||
|
||||
### Étape 4 — Configurer les tâches planifiées (optionnel)
|
||||
|
||||
Pour activer les notifications par e-mail et la vérification automatique des mises à jour, ajoutez cette ligne à votre `crontab` (via le panneau de contrôle de l'hébergement → **Tâches planifiées / Cron**) :
|
||||
|
||||
```
|
||||
* * * * * php /chemin/absolu/vers/mesreleves/artisan schedule:run >> /dev/null 2>&1
|
||||
```
|
||||
|
||||
Remplacez `/chemin/absolu/vers/mesreleves/` par le chemin réel sur votre serveur (visible dans le gestionnaire de fichiers, généralement `/home/user/www/mesreleves/`).
|
||||
|
||||
---
|
||||
|
||||
### Mise à jour
|
||||
|
||||
Dans **Administration → Paramètres du site → Version du logiciel**, le tableau de bord signale automatiquement les nouvelles versions disponibles (si la vérification n'est pas désactivée et que les tâches planifiées sont actives).
|
||||
|
||||
**Pour appliquer une mise à jour manuellement :**
|
||||
|
||||
1. Télécharger la nouvelle archive `mesreleves-X.Y.Z.tar.gz`
|
||||
2. Décompresser et écraser les fichiers existants (**sauf** `.env` et `storage/`)
|
||||
3. Exécuter les migrations depuis un terminal SSH :
|
||||
```bash
|
||||
php artisan migrate --force
|
||||
php artisan optimize:clear
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Fonctionnalités
|
||||
|
||||
- **Saisie collaborative** — formulaires dynamiques pilotés par des types de sources configurables ; champs libres stockés en JSONB PostgreSQL
|
||||
- **Saisie collaborative** — formulaires dynamiques pilotés par des types de sources configurables
|
||||
- **Calendriers** — saisie en calendrier grégorien, julien ou républicain avec conversion automatique
|
||||
- **Recherche plein texte** — recherche PostgreSQL native sur les relevés, filtrable par type de source, lieu (avec subdivisions récursives) et plage d'années
|
||||
- **Gestion des lieux** — arbre hiérarchique (pays → région → département → commune…), types de lieux configurables, recherche par picker contextuel
|
||||
- **Recherche plein texte** — filtrable par type de source, lieu (avec subdivisions récursives) et plage d'années
|
||||
- **Gestion des lieux** — arbre hiérarchique (pays → région → département → commune…), types configurables
|
||||
- **Workflow de validation** — statuts `à faire → en cours → à valider → terminé`, notifications mail + in-app
|
||||
- **Export GEDCOM 5.5.1** — par source ou par sélection de recherche, conversion des dates vers le grégorien
|
||||
- **Gestion des rôles** — administrateur, responsable de section, membre ; autorisations granulaires par source
|
||||
- **Interface admin** — tableau de bord statistiques, gestion des utilisateurs, sections, dépôts, types de sources et de lieux
|
||||
- **Mises à jour automatiques** — vérification quotidienne via l'API Gitea, application sans rebuild Docker
|
||||
- **Interface admin** — tableau de bord, gestion des utilisateurs, sections, dépôts d'archives, types de sources
|
||||
- **Import / export CSV** — gestion en masse des utilisateurs
|
||||
- **Mode sombre** — détection automatique, commutable manuellement (clair / sombre / automatique)
|
||||
- **2FA par e-mail** — code PIN à 6 chiffres à la connexion, activé dès qu'un serveur SMTP est configuré
|
||||
|
||||
---
|
||||
|
||||
## Stack technique
|
||||
|
||||
| Composant | Technologie |
|
||||
|---|---|
|
||||
| Backend | PHP 8.5 · Laravel 12 |
|
||||
| Base de données | PostgreSQL 18 (JSONB, full-text search, CTE récursives) |
|
||||
| Cache / Sessions | Redis 7 |
|
||||
| Backend | PHP 8.2+ · Laravel 12 |
|
||||
| Base de données | MySQL 8.0+ / MariaDB 10.5+ · ou PostgreSQL 16+ |
|
||||
| Frontend | Blade · Alpine.js · Tailwind CSS |
|
||||
| Auth | Laravel Breeze (sessions) |
|
||||
| Conteneurs | Docker + Docker Compose |
|
||||
|
||||
---
|
||||
|
||||
## Installation
|
||||
## Docker
|
||||
|
||||
Installation avec Docker Compose : **PHP-FPM + Nginx + PostgreSQL** (pas de Redis — cache, sessions et queue utilisent la base de données).
|
||||
|
||||
> La stack Docker utilise exclusivement **PostgreSQL**. Pour MySQL, utilisez l'installation manuelle sur hébergement mutualisé décrite ci-dessus.
|
||||
|
||||
### Prérequis
|
||||
|
||||
@@ -37,100 +172,91 @@ Permet la saisie collaborative de relevés d'actes (naissance, mariage, décès,
|
||||
|
||||
### Première installation
|
||||
|
||||
**1. Télécharger la dernière release**
|
||||
|
||||
Récupérez l'archive `mesreleves-X.Y.Z.tar.gz` depuis la page des [releases](https://git.barbel.synology.me/CGL/mesreleves-php/releases) et extrayez-la :
|
||||
**1. Extraire l'archive**
|
||||
|
||||
```bash
|
||||
tar -xzf mesreleves-X.Y.Z.tar.gz
|
||||
cd mesreleves-X.Y.Z
|
||||
cd mesreleves
|
||||
```
|
||||
|
||||
**2. Lancer le script d'installation**
|
||||
**2. Premier lancement — création du `.env`**
|
||||
|
||||
```bash
|
||||
./install.sh
|
||||
```
|
||||
|
||||
Le script crée un `.env` depuis `.env.example` puis s'arrête pour vous laisser le configurer.
|
||||
Éditez `.env` avec vos paramètres :
|
||||
Le script détecte l'absence de `.env`, le crée depuis `.env.example` et s'arrête pour vous laisser le configurer.
|
||||
|
||||
**3. Configurer `.env`**
|
||||
|
||||
Ouvrez `.env` et renseignez au minimum :
|
||||
|
||||
```env
|
||||
APP_URL=https://votre-domaine.fr
|
||||
APP_KEY= # généré automatiquement au démarrage
|
||||
|
||||
DB_PASSWORD=mot_de_passe_fort
|
||||
|
||||
MAIL_MAILER=smtp
|
||||
MAIL_HOST=smtp.example.com
|
||||
MAIL_PORT=587
|
||||
MAIL_USERNAME=...
|
||||
MAIL_PASSWORD=...
|
||||
MAIL_FROM_ADDRESS=mesreleves@example.com
|
||||
DB_PASSWORD=mot_de_passe_fort # mot de passe PostgreSQL
|
||||
DB_DATABASE=mesreleves # nom de la base (créée automatiquement)
|
||||
DB_USERNAME=mesreleves # utilisateur PostgreSQL
|
||||
```
|
||||
|
||||
**3. Relancer le script**
|
||||
> **`APP_KEY`** est généré automatiquement au premier démarrage — laissez-le vide.
|
||||
> **SMTP** peut être configuré après installation via **Administration → Paramètres du site** (le 2FA par e-mail s'active dès qu'un serveur SMTP est enregistré).
|
||||
|
||||
**4. Installation complète**
|
||||
|
||||
```bash
|
||||
./install.sh
|
||||
```
|
||||
|
||||
L'application est accessible sur le port 80 (ou la valeur de `APP_URL`).
|
||||
Le script :
|
||||
1. Construit l'image Docker (php-fpm 8.5, extensions PostgreSQL)
|
||||
2. Démarre les services `app`, `nginx` et `db`
|
||||
3. Attend que PostgreSQL soit prêt
|
||||
4. Exécute les migrations
|
||||
5. Crée le lien symbolique `storage/`
|
||||
6. Affiche la commande crontab à copier
|
||||
|
||||
**4. Activer les tâches planifiées** (optionnel — nécessaire pour la vérification automatique des mises à jour)
|
||||
L'application est accessible sur **http://votre-domaine.fr** (port 80).
|
||||
|
||||
Ajoutez à votre crontab (`crontab -e`) la ligne affichée à la fin du script d'installation :
|
||||
### Commandes courantes
|
||||
|
||||
```bash
|
||||
# Voir les logs en temps réel
|
||||
docker compose -f docker-compose.prod.yml logs -f app
|
||||
|
||||
# Ouvrir un shell dans le container
|
||||
docker compose -f docker-compose.prod.yml exec app sh
|
||||
|
||||
# Relancer après modification du .env
|
||||
docker compose -f docker-compose.prod.yml restart app
|
||||
```
|
||||
|
||||
### Tâches planifiées
|
||||
|
||||
Ajoutez à votre crontab (`crontab -e`) la ligne affichée à la fin de `./install.sh` :
|
||||
|
||||
```
|
||||
* * * * * cd /chemin/vers/mesreleves && docker compose -f docker-compose.prod.yml exec -T app php artisan schedule:run >> /dev/null 2>&1
|
||||
```
|
||||
|
||||
---
|
||||
### Mises à jour
|
||||
|
||||
## Mises à jour
|
||||
|
||||
### Vérifier la version installée et les mises à jour disponibles
|
||||
Vérifier la version disponible :
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.prod.yml exec app php artisan app:check-update
|
||||
```
|
||||
|
||||
### Appliquer une mise à jour
|
||||
Appliquer une mise à jour (sauvegarde BDD → téléchargement → migrations) :
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.prod.yml exec app php artisan app:update
|
||||
```
|
||||
|
||||
Le processus :
|
||||
1. Sauvegarde PostgreSQL dans `storage/app/backups/`
|
||||
2. Téléchargement de la nouvelle archive
|
||||
3. Synchronisation des fichiers (`.env` et `storage/` préservés)
|
||||
4. `composer install --no-dev`
|
||||
5. Migrations
|
||||
6. Rechargement gracieux de php-fpm
|
||||
7. L'application est disponible pendant toute l'opération (sauf la fenêtre de migration)
|
||||
|
||||
### Mise à jour automatique
|
||||
|
||||
Pour activer la mise à jour automatique lors de la vérification planifiée, ajoutez dans `.env` :
|
||||
|
||||
```env
|
||||
AUTO_UPDATE=true
|
||||
```
|
||||
|
||||
> Par défaut `AUTO_UPDATE=false` : la vérification quotidienne notifie uniquement dans le tableau de bord admin.
|
||||
|
||||
### Rollback
|
||||
|
||||
En cas de problème, lister les sauvegardes disponibles :
|
||||
Lister les sauvegardes et restaurer en cas de problème :
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.prod.yml exec app php artisan app:rollback --list
|
||||
```
|
||||
|
||||
Restaurer la base de données :
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.prod.yml exec app php artisan app:rollback
|
||||
```
|
||||
|
||||
@@ -140,38 +266,35 @@ docker compose -f docker-compose.prod.yml exec app php artisan app:rollback
|
||||
|
||||
### Prérequis
|
||||
|
||||
- PHP 8.2+, Composer, Node.js 20+, npm
|
||||
- PostgreSQL 16+ ou Docker
|
||||
- PHP 8.2+, Composer, Node.js 20+
|
||||
- MySQL 8+ / MariaDB 10.5+ ou PostgreSQL 16+
|
||||
|
||||
### Démarrage rapide
|
||||
|
||||
```bash
|
||||
# Cloner et installer les dépendances
|
||||
composer install
|
||||
npm install && npm run build
|
||||
|
||||
# Configuration
|
||||
cp .env.example .env
|
||||
php artisan key:generate
|
||||
|
||||
# Démarrer PostgreSQL et Redis (Docker)
|
||||
# Démarrer la base de données (Docker)
|
||||
docker compose up -d
|
||||
|
||||
# Base de données
|
||||
php artisan migrate
|
||||
php artisan migrate:fresh --seed # reset + données de test
|
||||
|
||||
# Serveur de développement
|
||||
php artisan serve # http://localhost:8000
|
||||
npm run dev # Vite en watch (CSS/JS)
|
||||
php artisan serve # http://localhost:8000
|
||||
npm run dev # Vite en watch (CSS/JS)
|
||||
```
|
||||
|
||||
Compte administrateur créé par le seeder :
|
||||
Comptes de test créés par le seeder :
|
||||
|
||||
| Champ | Valeur |
|
||||
|---|---|
|
||||
| E-mail | `admin@example.com` |
|
||||
| Mot de passe | `password` |
|
||||
| E-mail | Rôle | Mot de passe |
|
||||
|---|---|---|
|
||||
| `admin@mesreleves.local` | Administrateur | `password` |
|
||||
| `responsable@mesreleves.local` | Responsable de section | `password` |
|
||||
| `membre@mesreleves.local` | Membre | `password` |
|
||||
|
||||
### Commandes utiles
|
||||
|
||||
@@ -180,26 +303,18 @@ php artisan test # tous les tests
|
||||
php artisan test --filter=NomTest # un test précis
|
||||
./vendor/bin/pint # formatage PHP (Laravel Pint)
|
||||
./vendor/bin/phpstan analyse # analyse statique
|
||||
|
||||
php artisan app:check-update # vérifier les mises à jour
|
||||
php artisan app:update # appliquer une mise à jour
|
||||
php artisan app:rollback --list # lister les sauvegardes
|
||||
```
|
||||
|
||||
### Créer une release
|
||||
|
||||
```bash
|
||||
# Modifier VERSION (ex : 1.1.0)
|
||||
echo "1.1.0" > VERSION
|
||||
git add VERSION && git commit -m "bump version 1.1.0"
|
||||
|
||||
# Construire l'archive
|
||||
bin/build-release.sh
|
||||
# → mesreleves-1.1.0.tar.gz + mesreleves-1.1.0.tar.gz.sha256
|
||||
# → mesreleves-1.1.0.tar.gz
|
||||
|
||||
# Publier
|
||||
git tag v1.1.0 && git push origin v1.1.0
|
||||
# Créer une release sur Gitea et joindre les deux fichiers
|
||||
```
|
||||
|
||||
---
|
||||
@@ -210,26 +325,28 @@ git tag v1.1.0 && git push origin v1.1.0
|
||||
app/
|
||||
Models/ Eloquent : User, Source, Releve, Lieu, Section, Depot…
|
||||
Http/
|
||||
Controllers/ Un controller par entité (+ Admin/ pour la gestion)
|
||||
Middleware/ RoleMiddleware (admin, section_manager, member)
|
||||
Requests/ Form requests avec validation dynamique
|
||||
Policies/ Autorisation par modèle (Gates/Policies)
|
||||
Services/ GedcomExportService, DateConversionService, UpdateService
|
||||
Controllers/ Un controller par entité (+ Admin/ + Auth/)
|
||||
Middleware/ RoleMiddleware, CheckInstallation, EnsureUserIsActive
|
||||
Requests/ Form requests avec validation
|
||||
Policies/ Autorisation par modèle
|
||||
Services/ GedcomExportService, DateConversionService, UpdateService,
|
||||
SiteSettingsService
|
||||
Enums/ SourceStatus, UserRole, CalendarType, FieldType
|
||||
database/
|
||||
migrations/ 10 migrations (lieux, sections, sources, relevés…)
|
||||
migrations/ Schéma complet (lieux, sections, sources, relevés…)
|
||||
seeders/ Données de démonstration
|
||||
resources/views/
|
||||
layouts/ Navigation, app layout
|
||||
components/ lieu-picker (Alpine.js + AJAX)
|
||||
layouts/ Navigation (sélecteur de thème), app layout, guest layout
|
||||
components/ lieu-picker, user-picker (recherche modale), dropdown…
|
||||
setup/ Assistant d'installation en 5 étapes
|
||||
sources/ CRUD + workflow de statut
|
||||
releves/ Formulaire dynamique par type de source
|
||||
recherche/ Recherche plein texte + filtres
|
||||
admin/ Tableau de bord, utilisateurs, sections, dépôts…
|
||||
bin/
|
||||
build-release.sh Construction de l'archive de distribution
|
||||
admin/ Tableau de bord, utilisateurs, sections, dépôts, paramètres
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Licence
|
||||
|
||||
Usage interne — association de généalogie.
|
||||
|
||||
@@ -18,14 +18,16 @@ class SettingController extends Controller
|
||||
{
|
||||
public function index(UpdateService $updates): View
|
||||
{
|
||||
$updatesDisabled = SiteSettingsService::updatesDisabled();
|
||||
$installedVersion = $updates->getInstalledVersion();
|
||||
$latestRelease = $updates->fetchLatestRelease();
|
||||
$latestRelease = $updatesDisabled ? null : $updates->fetchLatestRelease();
|
||||
$updateAvailable = $latestRelease
|
||||
&& version_compare($latestRelease['version'], $installedVersion, '>');
|
||||
|
||||
return view('admin.parametres.index', [
|
||||
'logoUrl' => SiteSettingsService::logoUrl(),
|
||||
'registrationEnabled' => SiteSettingsService::registrationEnabled(),
|
||||
'updatesDisabled' => $updatesDisabled,
|
||||
'installedVersion' => $installedVersion,
|
||||
'latestRelease' => $latestRelease,
|
||||
'updateAvailable' => $updateAvailable,
|
||||
@@ -144,6 +146,17 @@ class SettingController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
// ── Mises à jour ─────────────────────────────────────────────────────────
|
||||
|
||||
public function updateUpdates(Request $request): RedirectResponse
|
||||
{
|
||||
SiteSettingsService::set('updates_disabled', $request->boolean('updates_disabled'));
|
||||
|
||||
return back()->with('success', $request->boolean('updates_disabled')
|
||||
? 'Vérification automatique des mises à jour désactivée.'
|
||||
: 'Vérification automatique des mises à jour activée.');
|
||||
}
|
||||
|
||||
// ── Paramètres généraux ───────────────────────────────────────────────────
|
||||
|
||||
public function updateSettings(Request $request): RedirectResponse
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Lieu;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class CarteController extends Controller
|
||||
{
|
||||
public function index(): View
|
||||
{
|
||||
return view('carte.index');
|
||||
}
|
||||
|
||||
public function data(Request $request): JsonResponse
|
||||
{
|
||||
$lieux = Lieu::whereNotNull('latitude')
|
||||
->whereNotNull('longitude')
|
||||
->whereHas('sources.releves')
|
||||
->with([
|
||||
'sources' => function ($q) {
|
||||
$q->withCount('releves')
|
||||
->select('id', 'nom', 'lieu_id', 'status', 'annee_debut', 'annee_fin')
|
||||
->orderBy('nom');
|
||||
},
|
||||
])
|
||||
->get()
|
||||
->map(function (Lieu $lieu) {
|
||||
$relevesTotal = $lieu->sources->sum('releves_count');
|
||||
return [
|
||||
'id' => $lieu->id,
|
||||
'nom' => $lieu->nom_long ?? $lieu->nom,
|
||||
'lat' => (float) $lieu->latitude,
|
||||
'lng' => (float) $lieu->longitude,
|
||||
'sources_count' => $lieu->sources->count(),
|
||||
'releves_count' => $relevesTotal,
|
||||
'sources' => $lieu->sources->map(fn ($s) => [
|
||||
'id' => $s->id,
|
||||
'nom' => $s->nom,
|
||||
'releves_count' => $s->releves_count,
|
||||
'status' => $s->status->label(),
|
||||
'status_value' => $s->status->value,
|
||||
'annees' => $s->annee_debut
|
||||
? ($s->annee_debut === $s->annee_fin || ! $s->annee_fin
|
||||
? (string) $s->annee_debut
|
||||
: "{$s->annee_debut}–{$s->annee_fin}")
|
||||
: null,
|
||||
]),
|
||||
];
|
||||
});
|
||||
|
||||
return response()->json($lieux);
|
||||
}
|
||||
}
|
||||
@@ -145,6 +145,12 @@ class SetupController extends Controller
|
||||
if (! $ok) $success = false;
|
||||
}
|
||||
|
||||
// 2b. Purge du cache de config pour forcer la lecture du nouveau .env
|
||||
// (un bootstrap/cache/config.php résiduel sinon l'emporte sur le .env réécrit)
|
||||
if ($success) {
|
||||
$this->artisanRun($artisan, 'config:clear');
|
||||
}
|
||||
|
||||
// 3. Migrations
|
||||
if ($success) {
|
||||
[$ok, $out] = $this->artisanRun($artisan, 'migrate --force');
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Models;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
|
||||
|
||||
class Lieu extends Model
|
||||
{
|
||||
@@ -32,6 +33,16 @@ class Lieu extends Model
|
||||
return $this->hasMany(Section::class);
|
||||
}
|
||||
|
||||
public function sources(): HasMany
|
||||
{
|
||||
return $this->hasMany(Source::class);
|
||||
}
|
||||
|
||||
public function releves(): HasManyThrough
|
||||
{
|
||||
return $this->hasManyThrough(Releve::class, Source::class);
|
||||
}
|
||||
|
||||
public function calculerNomLong(): string
|
||||
{
|
||||
$noms = [$this->nom];
|
||||
|
||||
@@ -87,4 +87,11 @@ class SiteSettingsService
|
||||
// Désactivées par défaut
|
||||
return (bool) self::get('registration_enabled', false);
|
||||
}
|
||||
|
||||
// ── Mises à jour ──────────────────────────────────────────────────────────
|
||||
|
||||
public static function updatesDisabled(): bool
|
||||
{
|
||||
return (bool) self::get('updates_disabled', false);
|
||||
}
|
||||
}
|
||||
|
||||
+16
-14
@@ -1,25 +1,27 @@
|
||||
<IfModule mod_rewrite.c>
|
||||
<IfModule mod_negotiation.c>
|
||||
Options -MultiViews -Indexes
|
||||
</IfModule>
|
||||
# ── Sécurité ──────────────────────────────────────────────────────────────────
|
||||
Options -Indexes -MultiViews
|
||||
|
||||
# ── En-têtes HTTP transmis à PHP ───────────────────────────────────────────────
|
||||
# Nécessaire pour que Laravel reçoive le token Authorization (API) et CSRF
|
||||
SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
|
||||
SetEnvIf X-XSRF-Token "(.*)" HTTP_X_XSRF_TOKEN=$1
|
||||
|
||||
# ── Routage vers le contrôleur frontal ────────────────────────────────────────
|
||||
# FallbackResource ne requiert pas mod_rewrite (disponible depuis Apache 2.2.16).
|
||||
# Tout chemin qui ne correspond pas à un fichier ou répertoire existant
|
||||
# est servi par index.php — comportement identique à la règle mod_rewrite ci-dessous.
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteEngine On
|
||||
|
||||
# Handle Authorization Header
|
||||
RewriteCond %{HTTP:Authorization} .
|
||||
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
|
||||
|
||||
# Handle X-XSRF-Token Header
|
||||
RewriteCond %{HTTP:x-xsrf-token} .
|
||||
RewriteRule .* - [E=HTTP_X_XSRF_TOKEN:%{HTTP:X-XSRF-Token}]
|
||||
|
||||
# Redirect Trailing Slashes If Not A Folder...
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteCond %{REQUEST_URI} (.+)/$
|
||||
RewriteRule ^ %1 [L,R=301]
|
||||
|
||||
# Send Requests To Front Controller...
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteRule ^ index.php [L]
|
||||
</IfModule>
|
||||
|
||||
<IfModule !mod_rewrite.c>
|
||||
FallbackResource /index.php
|
||||
</IfModule>
|
||||
|
||||
@@ -5,6 +5,25 @@ use Illuminate\Http\Request;
|
||||
|
||||
define('LARAVEL_START', microtime(true));
|
||||
|
||||
// Si aucun .env n'existe, en créer un minimal depuis .env.example pour que
|
||||
// l'assistant d'installation (/setup) puisse démarrer sans erreur de clé.
|
||||
// Le wizard remplace ce fichier avec les paramètres définitifs lors de l'installation.
|
||||
(function () {
|
||||
$envFile = __DIR__ . '/../.env';
|
||||
$example = __DIR__ . '/../.env.example';
|
||||
if (! file_exists($envFile) && file_exists($example)) {
|
||||
$content = file_get_contents($example);
|
||||
// Génère une clé temporaire pour permettre le boot de Laravel
|
||||
$content = preg_replace('/^APP_KEY=$/m',
|
||||
'APP_KEY=base64:' . base64_encode(random_bytes(32)), $content);
|
||||
// Pilotes sans dépendance BDD pendant l'installation
|
||||
$content = preg_replace('/^SESSION_DRIVER=.*/m', 'SESSION_DRIVER=file', $content);
|
||||
$content = preg_replace('/^CACHE_STORE=.*/m', 'CACHE_STORE=file', $content);
|
||||
$content = preg_replace('/^QUEUE_CONNECTION=.*/m','QUEUE_CONNECTION=sync',$content);
|
||||
file_put_contents($envFile, $content);
|
||||
}
|
||||
})();
|
||||
|
||||
// Determine if the application is in maintenance mode...
|
||||
if (file_exists($maintenance = __DIR__.'/../storage/framework/maintenance.php')) {
|
||||
require $maintenance;
|
||||
|
||||
@@ -0,0 +1,412 @@
|
||||
<?php
|
||||
/**
|
||||
* MesRelevés — Diagnostic de configuration serveur
|
||||
*
|
||||
* ATTENTION : ce fichier est réservé aux tests. Il ne doit JAMAIS
|
||||
* être déployé sur un environnement de production.
|
||||
* Supprimez-le dès que votre vérification est terminée.
|
||||
*/
|
||||
|
||||
// ── Collecte des informations ──────────────────────────────────────────────
|
||||
|
||||
$root = dirname(__DIR__); // répertoire racine du projet
|
||||
$phpVer = PHP_VERSION;
|
||||
$reqVer = '8.2.0';
|
||||
$phpOk = version_compare($phpVer, $reqVer, '>=');
|
||||
|
||||
// Extensions requises : [nom => optionnel?]
|
||||
$extensions = [
|
||||
'pdo' => false,
|
||||
'pdo_mysql' => false,
|
||||
'mbstring' => false,
|
||||
'openssl' => false,
|
||||
'tokenizer' => false,
|
||||
'xml' => false,
|
||||
'ctype' => false,
|
||||
'json' => false,
|
||||
'bcmath' => false,
|
||||
'fileinfo' => false,
|
||||
'zip' => false,
|
||||
'pdo_pgsql' => true, // optionnel : seulement si PostgreSQL
|
||||
'curl' => true, // optionnel : mises à jour automatiques
|
||||
'intl' => true, // optionnel : formatage avancé des dates
|
||||
];
|
||||
|
||||
// Répertoires à vérifier en écriture
|
||||
$dirs = [
|
||||
'storage/' => $root . '/storage',
|
||||
'storage/logs/' => $root . '/storage/logs',
|
||||
'storage/app/' => $root . '/storage/app',
|
||||
'storage/framework/' => $root . '/storage/framework',
|
||||
'bootstrap/cache/' => $root . '/bootstrap/cache',
|
||||
'Racine (écriture .env)' => $root,
|
||||
];
|
||||
|
||||
// Fichiers à vérifier
|
||||
$files = [
|
||||
'.env' => [$root . '/.env', false],
|
||||
'vendor/autoload.php' => [$root . '/vendor/autoload.php', false],
|
||||
'storage/installed' => [$root . '/storage/installed', true],
|
||||
'public/build/manifest.json' => [$root . '/public/build/manifest.json', false],
|
||||
];
|
||||
|
||||
// Configuration PHP recommandée : [directive => [valeur_min_bytes, label_min]]
|
||||
$iniChecks = [
|
||||
'memory_limit' => [128 * 1024 * 1024, '128 Mo'],
|
||||
'upload_max_filesize'=> [2 * 1024 * 1024, '2 Mo'],
|
||||
'post_max_size' => [8 * 1024 * 1024, '8 Mo'],
|
||||
'max_execution_time' => [30, '30 s'],
|
||||
];
|
||||
|
||||
function iniBytes(string $val): int {
|
||||
$val = trim($val);
|
||||
$last = strtolower($val[-1] ?? '');
|
||||
$n = (int) $val;
|
||||
return match ($last) {
|
||||
'g' => $n * 1024 * 1024 * 1024,
|
||||
'm' => $n * 1024 * 1024,
|
||||
'k' => $n * 1024,
|
||||
default => $n,
|
||||
};
|
||||
}
|
||||
|
||||
// Test de connexion BDD (si formulaire soumis)
|
||||
$dbResult = null;
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['db_host'])) {
|
||||
$driver = $_POST['db_driver'] ?? 'mysql';
|
||||
$host = $_POST['db_host'] ?? '';
|
||||
$port = (int) ($_POST['db_port'] ?? ($driver === 'pgsql' ? 5432 : 3306));
|
||||
$dbname = $_POST['db_name'] ?? '';
|
||||
$user = $_POST['db_user'] ?? '';
|
||||
$password = $_POST['db_password'] ?? '';
|
||||
try {
|
||||
$dsn = $driver === 'pgsql'
|
||||
? "pgsql:host={$host};port={$port};dbname={$dbname}"
|
||||
: "mysql:host={$host};port={$port};dbname={$dbname};charset=utf8mb4";
|
||||
$pdo = new PDO($dsn, $user, $password, [PDO::ATTR_TIMEOUT => 5]);
|
||||
$ver = $pdo->query('SELECT version()')->fetchColumn();
|
||||
$dbResult = ['ok' => true, 'msg' => "Connexion réussie — " . htmlspecialchars($ver)];
|
||||
} catch (Exception $e) {
|
||||
$dbResult = ['ok' => false, 'msg' => htmlspecialchars($e->getMessage())];
|
||||
}
|
||||
}
|
||||
|
||||
// ── Compteur global ────────────────────────────────────────────────────────
|
||||
$totalChecks = 0;
|
||||
$totalFailed = 0;
|
||||
|
||||
?><!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Diagnostic serveur — MesRelevés</title>
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
:root {
|
||||
--bg: #f3f4f6; --card: #fff; --border: #e5e7eb;
|
||||
--text: #111827; --muted: #6b7280;
|
||||
--ok: #16a34a; --ok-bg: #f0fdf4; --ok-border: #bbf7d0;
|
||||
--warn: #d97706; --warn-bg: #fffbeb; --warn-border: #fde68a;
|
||||
--err: #dc2626; --err-bg: #fef2f2; --err-border: #fecaca;
|
||||
--accent: #4f46e5;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--bg: #111827; --card: #1f2937; --border: #374151;
|
||||
--text: #f9fafb; --muted: #9ca3af;
|
||||
--ok: #4ade80; --ok-bg: #052e16; --ok-border: #166534;
|
||||
--warn: #fbbf24; --warn-bg: #1c1400; --warn-border: #92400e;
|
||||
--err: #f87171; --err-bg: #1a0000; --err-border: #991b1b;
|
||||
}
|
||||
}
|
||||
body { background: var(--bg); color: var(--text); font: 15px/1.6 system-ui, sans-serif; padding: 24px 16px 60px; }
|
||||
h1 { font-size: 22px; font-weight: 700; margin-bottom: 4px; }
|
||||
.warning-banner {
|
||||
background: #7f1d1d; color: #fef2f2; border: 2px solid #991b1b;
|
||||
border-radius: 8px; padding: 12px 16px; margin-bottom: 24px;
|
||||
font-weight: 600; font-size: 14px; display: flex; align-items: center; gap: 10px;
|
||||
}
|
||||
.warning-banner svg { flex-shrink: 0; }
|
||||
.container { max-width: 860px; margin: 0 auto; }
|
||||
.card { background: var(--card); border: 1px solid var(--border); border-radius: 10px; margin-bottom: 20px; overflow: hidden; }
|
||||
.card-header { padding: 14px 20px; border-bottom: 1px solid var(--border); font-weight: 700; font-size: 13px; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); display: flex; align-items: center; justify-content: space-between; }
|
||||
table { width: 100%; border-collapse: collapse; font-size: 14px; }
|
||||
td, th { padding: 9px 20px; text-align: left; border-bottom: 1px solid var(--border); }
|
||||
tr:last-child td { border-bottom: none; }
|
||||
th { font-size: 12px; font-weight: 600; color: var(--muted); text-transform: uppercase; background: var(--bg); }
|
||||
.badge { display: inline-flex; align-items: center; gap: 5px; padding: 3px 10px; border-radius: 9999px; font-size: 12px; font-weight: 600; white-space: nowrap; }
|
||||
.badge-ok { background: var(--ok-bg); color: var(--ok); border: 1px solid var(--ok-border); }
|
||||
.badge-warn { background: var(--warn-bg); color: var(--warn); border: 1px solid var(--warn-border); }
|
||||
.badge-err { background: var(--err-bg); color: var(--err); border: 1px solid var(--err-border); }
|
||||
.badge-info { background: var(--bg); color: var(--muted);border: 1px solid var(--border); }
|
||||
.mono { font-family: 'SF Mono', 'Fira Code', monospace; font-size: 13px; }
|
||||
.muted { color: var(--muted); font-size: 13px; }
|
||||
.summary { display: flex; gap: 16px; margin-bottom: 24px; flex-wrap: wrap; }
|
||||
.summary-item { background: var(--card); border: 1px solid var(--border); border-radius: 8px; padding: 14px 20px; flex: 1; min-width: 140px; }
|
||||
.summary-item .val { font-size: 28px; font-weight: 800; line-height: 1; }
|
||||
.summary-item .lbl { font-size: 12px; color: var(--muted); margin-top: 4px; }
|
||||
.val-ok { color: var(--ok); }
|
||||
.val-err { color: var(--err); }
|
||||
form { padding: 16px 20px; }
|
||||
.form-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; margin-bottom: 14px; }
|
||||
label { display: block; font-size: 13px; font-weight: 600; color: var(--muted); margin-bottom: 4px; }
|
||||
input, select { width: 100%; padding: 7px 10px; border: 1px solid var(--border); border-radius: 6px; background: var(--bg); color: var(--text); font-size: 14px; }
|
||||
button[type=submit] { background: var(--accent); color: #fff; border: none; padding: 8px 20px; border-radius: 6px; font-size: 14px; font-weight: 600; cursor: pointer; }
|
||||
button[type=submit]:hover { opacity: .9; }
|
||||
.db-result { margin-top: 12px; padding: 10px 14px; border-radius: 6px; font-size: 13px; font-weight: 600; }
|
||||
.db-ok { background: var(--ok-bg); color: var(--ok); border: 1px solid var(--ok-border); }
|
||||
.db-err { background: var(--err-bg); color: var(--err); border: 1px solid var(--err-border); }
|
||||
.server-info { padding: 14px 20px; font-size: 13px; display: grid; grid-template-columns: 200px 1fr; gap: 6px 16px; }
|
||||
.server-info dt { color: var(--muted); }
|
||||
.server-info dd { font-family: monospace; word-break: break-all; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
|
||||
<h1>Diagnostic serveur — MesRelevés</h1>
|
||||
<p class="muted" style="margin-bottom:20px">Version PHP <?= htmlspecialchars($phpVer) ?> · <?= htmlspecialchars($_SERVER['SERVER_SOFTWARE'] ?? 'Serveur inconnu') ?></p>
|
||||
|
||||
<div class="warning-banner">
|
||||
<svg width="20" height="20" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M12 9v2m0 4h.01M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/>
|
||||
</svg>
|
||||
FICHIER DE TEST UNIQUEMENT — À supprimer immédiatement après diagnostic. Ne jamais laisser en production.
|
||||
</div>
|
||||
|
||||
<?php
|
||||
// ── Comptage global (première passe) ──────────────────────────────────────
|
||||
$extFailed = 0; $extMandatoryFailed = 0;
|
||||
foreach ($extensions as $ext => $optional) {
|
||||
if (!extension_loaded($ext) && !$optional) $extMandatoryFailed++;
|
||||
}
|
||||
$dirFailed = 0;
|
||||
foreach ($dirs as $path) {
|
||||
if (!is_writable($path)) $dirFailed++;
|
||||
}
|
||||
$fileFailed = 0;
|
||||
foreach ($files as [$path, $optional]) {
|
||||
if (!file_exists($path) && !$optional) $fileFailed++;
|
||||
}
|
||||
$totalFailed = (!$phpOk ? 1 : 0) + $extMandatoryFailed + $dirFailed + $fileFailed;
|
||||
$totalOk = ($phpOk ? 1 : 0)
|
||||
+ count(array_filter(array_keys($extensions), fn($e) => extension_loaded($e) && !$extensions[$e]))
|
||||
+ count(array_filter(array_keys($dirs), fn($k) => is_writable($dirs[$k])))
|
||||
+ count(array_filter(array_keys($files), fn($k) => file_exists($files[$k][0])));
|
||||
?>
|
||||
|
||||
<div class="summary">
|
||||
<div class="summary-item">
|
||||
<div class="val <?= $totalFailed === 0 ? 'val-ok' : 'val-err' ?>"><?= $totalFailed === 0 ? '✓' : $totalFailed ?></div>
|
||||
<div class="lbl"><?= $totalFailed === 0 ? 'Tout est OK' : 'Problème(s) bloquant(s)' ?></div>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<div class="val"><?= PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION ?></div>
|
||||
<div class="lbl">Version PHP</div>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<div class="val"><?= count(array_filter(array_keys($extensions), fn($e) => extension_loaded($e))) ?>/<?= count($extensions) ?></div>
|
||||
<div class="lbl">Extensions chargées</div>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<div class="val"><?= ini_get('memory_limit') ?></div>
|
||||
<div class="lbl">memory_limit</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- ── Informations serveur ────────────────────────────────────────────── --}}
|
||||
<div class="card">
|
||||
<div class="card-header">Environnement</div>
|
||||
<dl class="server-info">
|
||||
<dt>Serveur</dt> <dd><?= htmlspecialchars($_SERVER['SERVER_SOFTWARE'] ?? '—') ?></dd>
|
||||
<dt>SAPI</dt> <dd><?= htmlspecialchars(php_sapi_name()) ?></dd>
|
||||
<dt>Document root</dt><dd><?= htmlspecialchars($_SERVER['DOCUMENT_ROOT'] ?? '—') ?></dd>
|
||||
<dt>Chemin du script</dt><dd><?= htmlspecialchars(__FILE__) ?></dd>
|
||||
<dt>Racine projet</dt><dd><?= htmlspecialchars($root) ?></dd>
|
||||
<dt>OS</dt> <dd><?= htmlspecialchars(PHP_OS_FAMILY) ?></dd>
|
||||
<dt>Fuseau horaire</dt><dd><?= htmlspecialchars(ini_get('date.timezone') ?: 'non défini') ?></dd>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
{{-- ── Version PHP ─────────────────────────────────────────────────────── --}}
|
||||
<div class="card">
|
||||
<div class="card-header">Version PHP</div>
|
||||
<table>
|
||||
<tr>
|
||||
<td><strong>PHP <?= htmlspecialchars($phpVer) ?></strong></td>
|
||||
<td class="muted">Minimum requis : <?= $reqVer ?></td>
|
||||
<td><span class="badge <?= $phpOk ? 'badge-ok' : 'badge-err' ?>"><?= $phpOk ? '✓ OK' : '✗ Insuffisant' ?></span></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{{-- ── Extensions PHP ──────────────────────────────────────────────────── --}}
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
Extensions PHP
|
||||
<span class="muted" style="font-weight:400;text-transform:none">
|
||||
<?= count(array_filter(array_keys($extensions), fn($e) => extension_loaded($e))) ?>/<?= count($extensions) ?> chargées
|
||||
</span>
|
||||
</div>
|
||||
<table>
|
||||
<thead><tr><th>Extension</th><th>Statut</th><th>Note</th></tr></thead>
|
||||
<tbody>
|
||||
<?php foreach ($extensions as $ext => $optional):
|
||||
$loaded = extension_loaded($ext); ?>
|
||||
<tr>
|
||||
<td class="mono"><?= htmlspecialchars($ext) ?></td>
|
||||
<td>
|
||||
<?php if ($loaded): ?>
|
||||
<span class="badge badge-ok">✓ Présente</span>
|
||||
<?php elseif ($optional): ?>
|
||||
<span class="badge badge-warn">⚠ Absente</span>
|
||||
<?php else: ?>
|
||||
<span class="badge badge-err">✗ Manquante</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td class="muted">
|
||||
<?php if ($optional && !$loaded): echo 'Optionnelle'; endif; ?>
|
||||
<?php if ($ext === 'pdo_pgsql' && !$loaded): echo '— nécessaire uniquement si PostgreSQL'; endif; ?>
|
||||
<?php if ($ext === 'curl' && !$loaded): echo '— nécessaire pour les mises à jour automatiques'; endif; ?>
|
||||
<?php if ($ext === 'intl' && !$loaded): echo '— recommandée (formatage dates/nombres)'; endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{{-- ── Configuration PHP ───────────────────────────────────────────────── --}}
|
||||
<div class="card">
|
||||
<div class="card-header">Configuration PHP (php.ini)</div>
|
||||
<table>
|
||||
<thead><tr><th>Directive</th><th>Valeur actuelle</th><th>Minimum recommandé</th><th>Statut</th></tr></thead>
|
||||
<tbody>
|
||||
<?php foreach ($iniChecks as $key => [$minBytes, $minLabel]):
|
||||
$raw = ini_get($key);
|
||||
$bytes = iniBytes($raw);
|
||||
$ok = $bytes >= $minBytes || $minBytes === 30 && ($bytes === 0 || $bytes >= $minBytes);
|
||||
// max_execution_time = 0 means unlimited
|
||||
if ($key === 'max_execution_time') $ok = ($bytes === 0 || $bytes >= $minBytes);
|
||||
?>
|
||||
<tr>
|
||||
<td class="mono"><?= htmlspecialchars($key) ?></td>
|
||||
<td class="mono"><?= htmlspecialchars($raw) ?></td>
|
||||
<td class="muted"><?= $minLabel ?></td>
|
||||
<td><span class="badge <?= $ok ? 'badge-ok' : 'badge-warn' ?>"><?= $ok ? '✓ OK' : '⚠ Faible' ?></span></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{{-- ── Répertoires ─────────────────────────────────────────────────────── --}}
|
||||
<div class="card">
|
||||
<div class="card-header">Répertoires accessibles en écriture</div>
|
||||
<table>
|
||||
<thead><tr><th>Répertoire</th><th>Chemin</th><th>Statut</th></tr></thead>
|
||||
<tbody>
|
||||
<?php foreach ($dirs as $label => $path):
|
||||
$exists = is_dir($path);
|
||||
$writable = $exists && is_writable($path);
|
||||
?>
|
||||
<tr>
|
||||
<td><strong><?= htmlspecialchars($label) ?></strong></td>
|
||||
<td class="mono muted"><?= htmlspecialchars(str_replace($root, '…', $path)) ?></td>
|
||||
<td>
|
||||
<?php if (!$exists): ?>
|
||||
<span class="badge badge-err">✗ Inexistant</span>
|
||||
<?php elseif ($writable): ?>
|
||||
<span class="badge badge-ok">✓ OK</span>
|
||||
<?php else: ?>
|
||||
<span class="badge badge-err">✗ Non accessible</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{{-- ── Fichiers clés ───────────────────────────────────────────────────── --}}
|
||||
<div class="card">
|
||||
<div class="card-header">Fichiers clés</div>
|
||||
<table>
|
||||
<thead><tr><th>Fichier</th><th>Présent</th><th>Note</th></tr></thead>
|
||||
<tbody>
|
||||
<?php foreach ($files as $label => [$path, $optional]):
|
||||
$exists = file_exists($path); ?>
|
||||
<tr>
|
||||
<td class="mono"><?= htmlspecialchars($label) ?></td>
|
||||
<td>
|
||||
<?php if ($exists): ?>
|
||||
<span class="badge badge-ok">✓ Présent</span>
|
||||
<?php elseif ($optional): ?>
|
||||
<span class="badge badge-info">— Absent</span>
|
||||
<?php else: ?>
|
||||
<span class="badge badge-err">✗ Manquant</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td class="muted">
|
||||
<?php if ($label === '.env' && !$exists): echo 'Créé automatiquement par l\'assistant /setup'; endif; ?>
|
||||
<?php if ($label === 'storage/installed'): echo $exists ? 'Application déjà installée' : 'Installation non encore effectuée → aller sur /setup'; endif; ?>
|
||||
<?php if ($label === 'vendor/autoload.php' && !$exists): echo 'Exécutez : composer install --no-dev'; endif; ?>
|
||||
<?php if ($label === 'public/build/manifest.json' && !$exists): echo 'Assets non compilés — npm run build requis'; endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{{-- ── Test de connexion BDD ───────────────────────────────────────────── --}}
|
||||
<div class="card">
|
||||
<div class="card-header">Test de connexion base de données</div>
|
||||
<form method="POST">
|
||||
<div class="form-grid">
|
||||
<div>
|
||||
<label>Driver</label>
|
||||
<select name="db_driver">
|
||||
<option value="mysql" <?= ($_POST['db_driver'] ?? 'mysql') === 'mysql' ? 'selected' : '' ?>>MySQL / MariaDB</option>
|
||||
<option value="pgsql" <?= ($_POST['db_driver'] ?? '') === 'pgsql' ? 'selected' : '' ?>>PostgreSQL</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label>Hôte</label>
|
||||
<input type="text" name="db_host" value="<?= htmlspecialchars($_POST['db_host'] ?? 'localhost') ?>" placeholder="localhost">
|
||||
</div>
|
||||
<div>
|
||||
<label>Port</label>
|
||||
<input type="number" name="db_port" value="<?= htmlspecialchars($_POST['db_port'] ?? '3306') ?>" placeholder="3306">
|
||||
</div>
|
||||
<div>
|
||||
<label>Base de données</label>
|
||||
<input type="text" name="db_name" value="<?= htmlspecialchars($_POST['db_name'] ?? '') ?>" placeholder="mesreleves">
|
||||
</div>
|
||||
<div>
|
||||
<label>Utilisateur</label>
|
||||
<input type="text" name="db_user" value="<?= htmlspecialchars($_POST['db_user'] ?? '') ?>" placeholder="mesreleves">
|
||||
</div>
|
||||
<div>
|
||||
<label>Mot de passe</label>
|
||||
<input type="password" name="db_password" placeholder="••••••••">
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit">Tester la connexion</button>
|
||||
<?php if ($dbResult !== null): ?>
|
||||
<div class="db-result <?= $dbResult['ok'] ? 'db-ok' : 'db-err' ?>">
|
||||
<?= $dbResult['ok'] ? '✓ ' : '✗ ' ?><?= $dbResult['msg'] ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<p class="muted" style="text-align:center;font-size:13px;margin-top:8px">
|
||||
Généré le <?= date('d/m/Y à H:i:s') ?> · MesRelevés <?= htmlspecialchars(trim(@file_get_contents($root . '/VERSION') ?: '')) ?>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,3 +1,32 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
[x-cloak] { display: none !important; }
|
||||
|
||||
@layer base {
|
||||
/*
|
||||
* Dark mode — champs de formulaire
|
||||
* Appliqué globalement pour couvrir tous les <input>, <select>, <textarea>
|
||||
* bruts qui n'utilisent pas le composant <x-text-input>.
|
||||
* La spécificité (.dark + élément) surpasse les resets de @tailwindcss/forms.
|
||||
*/
|
||||
.dark input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="submit"]):not([type="button"]):not([type="reset"]):not([type="range"]),
|
||||
.dark select,
|
||||
.dark textarea {
|
||||
background-color: theme('colors.gray.700');
|
||||
border-color: theme('colors.gray.600');
|
||||
color: theme('colors.gray.100');
|
||||
}
|
||||
|
||||
.dark input::placeholder,
|
||||
.dark textarea::placeholder {
|
||||
color: theme('colors.gray.400');
|
||||
}
|
||||
|
||||
/* Options du <select> natif (fond navigateur) */
|
||||
.dark select option {
|
||||
background-color: theme('colors.gray.700');
|
||||
color: theme('colors.gray.100');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="text-xl font-semibold text-gray-800">Tableau de bord — Administration</h2>
|
||||
<h2 class="text-xl font-semibold text-gray-800 dark:text-gray-200">Tableau de bord — Administration</h2>
|
||||
</x-slot>
|
||||
|
||||
<div class="py-8 max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 space-y-8">
|
||||
|
||||
{{-- Bandeau mise à jour disponible --}}
|
||||
@if($updateAvailable)
|
||||
<div class="bg-indigo-50 border border-indigo-300 rounded-xl p-4 flex items-center justify-between gap-4">
|
||||
<div class="bg-indigo-50 dark:bg-indigo-900/30 border border-indigo-300 rounded-xl p-4 flex items-center justify-between gap-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<svg class="w-5 h-5 text-indigo-500 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg class="w-5 h-5 text-indigo-500 dark:text-indigo-400 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12"/>
|
||||
</svg>
|
||||
<div>
|
||||
<p class="text-sm font-semibold text-indigo-800">
|
||||
<p class="text-sm font-semibold text-indigo-800 dark:text-indigo-200">
|
||||
Mise à jour disponible : v{{ $latestRelease['version'] }}
|
||||
<span class="ml-2 font-normal text-indigo-600">(installé : v{{ $installedVersion }})</span>
|
||||
</p>
|
||||
@@ -35,10 +35,10 @@
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
@php
|
||||
$statusCards = [
|
||||
['label' => 'À faire', 'key' => 'a_faire', 'color' => 'bg-gray-50 border-gray-200 text-gray-700'],
|
||||
['label' => 'À faire', 'key' => 'a_faire', 'color' => 'bg-gray-50 dark:bg-gray-700 border-gray-200 dark:border-gray-700 text-gray-700 dark:text-gray-300'],
|
||||
['label' => 'En cours', 'key' => 'en_cours', 'color' => 'bg-blue-50 border-blue-200 text-blue-700'],
|
||||
['label' => 'À valider', 'key' => 'a_valider', 'color' => 'bg-yellow-50 border-yellow-200 text-yellow-700'],
|
||||
['label' => 'Terminé', 'key' => 'termine', 'color' => 'bg-green-50 border-green-200 text-green-700'],
|
||||
['label' => 'À valider', 'key' => 'a_valider', 'color' => 'bg-yellow-50 border-yellow-200 dark:border-yellow-700 text-yellow-700'],
|
||||
['label' => 'Terminé', 'key' => 'termine', 'color' => 'bg-green-50 dark:bg-green-900/30 border-green-200 dark:border-green-700 text-green-700'],
|
||||
];
|
||||
@endphp
|
||||
@foreach($statusCards as $card)
|
||||
@@ -53,19 +53,19 @@
|
||||
|
||||
{{-- Ligne de métriques secondaires --}}
|
||||
<div class="grid grid-cols-3 gap-4">
|
||||
<div class="bg-white border border-gray-200 rounded-xl p-5 flex items-center gap-4">
|
||||
<div class="p-3 bg-indigo-100 rounded-lg">
|
||||
<div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl p-5 flex items-center gap-4">
|
||||
<div class="p-3 bg-indigo-100 dark:bg-indigo-900/50 rounded-lg">
|
||||
<svg class="w-6 h-6 text-indigo-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-2xl font-bold text-gray-900">{{ number_format($totalReleves) }}</p>
|
||||
<p class="text-sm text-gray-500">relevé{{ $totalReleves > 1 ? 's' : '' }} saisi{{ $totalReleves > 1 ? 's' : '' }}</p>
|
||||
<p class="text-2xl font-bold text-gray-900 dark:text-white">{{ number_format($totalReleves) }}</p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">relevé{{ $totalReleves > 1 ? 's' : '' }} saisi{{ $totalReleves > 1 ? 's' : '' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-white border border-gray-200 rounded-xl p-5 flex items-center gap-4">
|
||||
<div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl p-5 flex items-center gap-4">
|
||||
<div class="p-3 bg-purple-100 rounded-lg">
|
||||
<svg class="w-6 h-6 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
@@ -73,18 +73,18 @@
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-2xl font-bold text-gray-900">{{ $totalUsers }}</p>
|
||||
<p class="text-sm text-gray-500">utilisateur{{ $totalUsers > 1 ? 's' : '' }}</p>
|
||||
<p class="text-2xl font-bold text-gray-900 dark:text-white">{{ $totalUsers }}</p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">utilisateur{{ $totalUsers > 1 ? 's' : '' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-white border border-gray-200 rounded-xl p-5">
|
||||
<p class="text-xs font-medium text-gray-500 uppercase mb-3">Répartition des rôles</p>
|
||||
<div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl p-5">
|
||||
<p class="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase mb-3">Répartition des rôles</p>
|
||||
<div class="space-y-2">
|
||||
@foreach(\App\Enums\UserRole::cases() as $role)
|
||||
@php $count = (int)($usersByRole[$role->value] ?? 0); @endphp
|
||||
<div class="flex items-center justify-between text-sm">
|
||||
<span class="text-gray-600">{{ $role->label() }}</span>
|
||||
<span class="font-semibold text-gray-900">{{ $count }}</span>
|
||||
<span class="text-gray-600 dark:text-gray-400">{{ $role->label() }}</span>
|
||||
<span class="font-semibold text-gray-900 dark:text-white">{{ $count }}</span>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@@ -93,16 +93,16 @@
|
||||
|
||||
{{-- Activité mensuelle (6 derniers mois) --}}
|
||||
@if($activiteMensuelle->isNotEmpty())
|
||||
<div class="bg-white border border-gray-200 rounded-xl p-6">
|
||||
<h3 class="text-sm font-semibold text-gray-700 uppercase tracking-wide mb-4">Relevés saisis — 6 derniers mois</h3>
|
||||
<div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl p-6">
|
||||
<h3 class="text-sm font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wide mb-4">Relevés saisis — 6 derniers mois</h3>
|
||||
@php $maxReleves = $activiteMensuelle->max('total') ?: 1; @endphp
|
||||
<div class="flex items-end gap-3 h-24">
|
||||
@foreach($activiteMensuelle as $mois)
|
||||
@php $h = max(4, round(($mois->total / $maxReleves) * 96)); @endphp
|
||||
<div class="flex-1 flex flex-col items-center gap-1">
|
||||
<span class="text-xs text-gray-500">{{ $mois->total }}</span>
|
||||
<div class="w-full bg-indigo-500 rounded-t" style="height: {{ $h }}px" title="{{ $mois->mois }} : {{ $mois->total }}"></div>
|
||||
<span class="text-xs text-gray-400 whitespace-nowrap">{{ $mois->mois }}</span>
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400">{{ $mois->total }}</span>
|
||||
<div class="w-full bg-indigo-50 dark:bg-indigo-900/300 rounded-t" style="height: {{ $h }}px" title="{{ $mois->mois }} : {{ $mois->total }}"></div>
|
||||
<span class="text-xs text-gray-400 dark:text-gray-500 whitespace-nowrap">{{ $mois->mois }}</span>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@@ -118,8 +118,8 @@
|
||||
['label' => 'Types de lieux', 'route' => 'admin.lieu-types.index', 'icon' => 'M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z'],
|
||||
] as $link)
|
||||
<a href="{{ route($link['route']) }}"
|
||||
class="flex items-center gap-3 px-4 py-3 bg-white border border-gray-200 rounded-xl hover:border-indigo-300 hover:shadow-sm transition-all text-sm text-gray-700 font-medium">
|
||||
<svg class="w-5 h-5 text-gray-400 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
class="flex items-center gap-3 px-4 py-3 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl hover:border-indigo-300 hover:shadow-sm transition-all text-sm text-gray-700 dark:text-gray-300 font-medium">
|
||||
<svg class="w-5 h-5 text-gray-400 dark:text-gray-500 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="{{ $link['icon'] }}"/>
|
||||
</svg>
|
||||
{{ $link['label'] }}
|
||||
@@ -130,57 +130,57 @@
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
|
||||
{{-- Sources à valider --}}
|
||||
<div class="bg-white border border-gray-200 rounded-xl p-6">
|
||||
<div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl p-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="text-sm font-semibold text-gray-700 uppercase tracking-wide">En attente de validation</h3>
|
||||
<h3 class="text-sm font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wide">En attente de validation</h3>
|
||||
<a href="{{ route('sources.index', ['status' => 'a_valider']) }}"
|
||||
class="text-xs text-indigo-600 hover:underline">Voir tout</a>
|
||||
</div>
|
||||
@forelse($sourcesAValider as $source)
|
||||
<div class="flex items-center justify-between py-2 border-b border-gray-100 last:border-0">
|
||||
<div class="flex items-center justify-between py-2 border-b border-gray-100 dark:border-gray-700 last:border-0">
|
||||
<div>
|
||||
<a href="{{ route('sources.show', $source) }}"
|
||||
class="text-sm font-medium text-indigo-600 hover:underline">{{ $source->nom }}</a>
|
||||
<p class="text-xs text-gray-400">{{ $source->sourceType->nom }}</p>
|
||||
<p class="text-xs text-gray-400 dark:text-gray-500">{{ $source->sourceType->nom }}</p>
|
||||
</div>
|
||||
<span class="text-xs text-gray-400 whitespace-nowrap">
|
||||
<span class="text-xs text-gray-400 dark:text-gray-500 whitespace-nowrap">
|
||||
{{ $source->updated_at->diffForHumans() }}
|
||||
</span>
|
||||
</div>
|
||||
@empty
|
||||
<p class="text-sm text-gray-400 py-4 text-center">Aucune source en attente.</p>
|
||||
<p class="text-sm text-gray-400 dark:text-gray-500 py-4 text-center">Aucune source en attente.</p>
|
||||
@endforelse
|
||||
</div>
|
||||
|
||||
{{-- Relevés récents --}}
|
||||
<div class="bg-white border border-gray-200 rounded-xl p-6">
|
||||
<div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl p-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="text-sm font-semibold text-gray-700 uppercase tracking-wide">Derniers relevés saisis</h3>
|
||||
<h3 class="text-sm font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wide">Derniers relevés saisis</h3>
|
||||
<a href="{{ route('recherche') }}" class="text-xs text-indigo-600 hover:underline">Recherche</a>
|
||||
</div>
|
||||
@forelse($relevesRecents as $releve)
|
||||
<div class="flex items-center justify-between py-2 border-b border-gray-100 last:border-0">
|
||||
<div class="flex items-center justify-between py-2 border-b border-gray-100 dark:border-gray-700 last:border-0">
|
||||
<div class="min-w-0">
|
||||
<a href="{{ route('releves.show', $releve) }}"
|
||||
class="text-sm font-medium text-gray-900 hover:text-indigo-600 truncate block">
|
||||
class="text-sm font-medium text-gray-900 dark:text-white hover:text-indigo-600 truncate block">
|
||||
{{ $releve->nom ?? '—' }}
|
||||
@if($releve->prenom) {{ $releve->prenom }} @endif
|
||||
</a>
|
||||
<p class="text-xs text-gray-400">
|
||||
<p class="text-xs text-gray-400 dark:text-gray-500">
|
||||
{{ $releve->source->nom }} · {{ $releve->createur?->name ?? '?' }}
|
||||
</p>
|
||||
</div>
|
||||
<span class="text-xs text-gray-400 whitespace-nowrap ml-3">
|
||||
<span class="text-xs text-gray-400 dark:text-gray-500 whitespace-nowrap ml-3">
|
||||
{{ $releve->created_at->diffForHumans() }}
|
||||
</span>
|
||||
</div>
|
||||
@empty
|
||||
<p class="text-sm text-gray-400 py-4 text-center">Aucun relevé pour l'instant.</p>
|
||||
<p class="text-sm text-gray-400 dark:text-gray-500 py-4 text-center">Aucun relevé pour l'instant.</p>
|
||||
@endforelse
|
||||
</div>
|
||||
</div>
|
||||
{{-- Version --}}
|
||||
<div class="flex items-center justify-end gap-2 text-xs text-gray-400 pt-2">
|
||||
<div class="flex items-center justify-end gap-2 text-xs text-gray-400 dark:text-gray-500 pt-2">
|
||||
<span>MesRelevés v{{ $installedVersion }}</span>
|
||||
@if(! $updateAvailable)
|
||||
<span class="inline-flex items-center gap-1 text-green-600">
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
<div class="space-y-5">
|
||||
<div>
|
||||
<label for="nom" class="block text-sm font-medium text-gray-700">Nom <span class="text-red-500">*</span></label>
|
||||
<label for="nom" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Nom <span class="text-red-500">*</span></label>
|
||||
<input type="text" id="nom" name="nom" value="{{ old('nom', $depot?->nom) }}" required
|
||||
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
@error('nom') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
|
||||
</div>
|
||||
<div>
|
||||
<label for="description" class="block text-sm font-medium text-gray-700">Description</label>
|
||||
<label for="description" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Description</label>
|
||||
<textarea id="description" name="description" rows="3"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">{{ old('description', $depot?->description) }}</textarea>
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">{{ old('description', $depot?->description) }}</textarea>
|
||||
</div>
|
||||
<div>
|
||||
<label for="adresse_postale" class="block text-sm font-medium text-gray-700">Adresse postale</label>
|
||||
<label for="adresse_postale" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Adresse postale</label>
|
||||
<input type="text" id="adresse_postale" name="adresse_postale" value="{{ old('adresse_postale', $depot?->adresse_postale) }}"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
</div>
|
||||
<div>
|
||||
<label for="url" class="block text-sm font-medium text-gray-700">Site internet</label>
|
||||
<label for="url" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Site internet</label>
|
||||
<input type="url" id="url" name="url" value="{{ old('url', $depot?->url) }}"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
@error('url') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header"><h2 class="text-xl font-semibold text-gray-800">Nouveau dépôt</h2></x-slot>
|
||||
<x-slot name="header"><h2 class="text-xl font-semibold text-gray-800 dark:text-gray-200">Nouveau dépôt</h2></x-slot>
|
||||
<div class="py-8 max-w-2xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="bg-white shadow rounded-lg p-6">
|
||||
<div class="bg-white dark:bg-gray-800 shadow rounded-lg p-6">
|
||||
<form method="POST" action="{{ route('admin.depots.store') }}">
|
||||
@csrf
|
||||
@include('admin.depots._form', ['depot' => null])
|
||||
<div class="mt-6 flex gap-4">
|
||||
<button type="submit" class="px-5 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700">Créer</button>
|
||||
<a href="{{ route('admin.depots.index') }}" class="text-sm text-gray-500 self-center hover:text-gray-700">Annuler</a>
|
||||
<a href="{{ route('admin.depots.index') }}" class="text-sm text-gray-500 dark:text-gray-400 self-center hover:text-gray-700 dark:hover:text-gray-300">Annuler</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header"><h2 class="text-xl font-semibold text-gray-800">Modifier : {{ $depot->nom }}</h2></x-slot>
|
||||
<x-slot name="header"><h2 class="text-xl font-semibold text-gray-800 dark:text-gray-200">Modifier : {{ $depot->nom }}</h2></x-slot>
|
||||
<div class="py-8 max-w-2xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="bg-white shadow rounded-lg p-6">
|
||||
<div class="bg-white dark:bg-gray-800 shadow rounded-lg p-6">
|
||||
<form method="POST" action="{{ route('admin.depots.update', $depot) }}">
|
||||
@csrf @method('PUT')
|
||||
@include('admin.depots._form', ['depot' => $depot])
|
||||
<div class="mt-6 flex gap-4">
|
||||
<button type="submit" class="px-5 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700">Enregistrer</button>
|
||||
<a href="{{ route('admin.depots.show', $depot) }}" class="text-sm text-gray-500 self-center hover:text-gray-700">Annuler</a>
|
||||
<a href="{{ route('admin.depots.show', $depot) }}" class="text-sm text-gray-500 dark:text-gray-400 self-center hover:text-gray-700 dark:hover:text-gray-300">Annuler</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -1,37 +1,37 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="text-xl font-semibold text-gray-800">Dépôts d'archives</h2>
|
||||
<h2 class="text-xl font-semibold text-gray-800 dark:text-gray-200">Dépôts d'archives</h2>
|
||||
<a href="{{ route('admin.depots.create') }}" class="px-4 py-2 bg-indigo-600 text-white text-sm rounded-md hover:bg-indigo-700">+ Nouveau dépôt</a>
|
||||
</div>
|
||||
</x-slot>
|
||||
<div class="py-8 max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
@if(session('success')) <div class="mb-4 p-4 bg-green-50 border border-green-200 text-green-800 rounded-md">{{ session('success') }}</div> @endif
|
||||
@if(session('error')) <div class="mb-4 p-4 bg-red-50 border border-red-200 text-red-800 rounded-md">{{ session('error') }}</div> @endif
|
||||
<div class="bg-white shadow rounded-lg overflow-hidden">
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50">
|
||||
@if(session('success')) <div class="mb-4 p-4 bg-green-50 dark:bg-green-900/30 border border-green-200 dark:border-green-700 text-green-800 dark:text-green-200 rounded-md">{{ session('success') }}</div> @endif
|
||||
@if(session('error')) <div class="mb-4 p-4 bg-red-50 dark:bg-red-900/30 border border-red-200 dark:border-red-700 text-red-800 dark:text-red-200 rounded-md">{{ session('error') }}</div> @endif
|
||||
<div class="bg-white dark:bg-gray-800 shadow rounded-lg overflow-hidden">
|
||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<thead class="bg-gray-50 dark:bg-gray-700">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Nom</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Sources</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Site</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Nom</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Sources</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Site</th>
|
||||
<th class="px-6 py-3"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200">
|
||||
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
|
||||
@forelse($depots as $depot)
|
||||
<tr class="hover:bg-gray-50">
|
||||
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700">
|
||||
<td class="px-6 py-4 font-medium">
|
||||
<a href="{{ route('admin.depots.show', $depot) }}" class="text-indigo-600 hover:underline">{{ $depot->nom }}</a>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-500">{{ $depot->sources_count }}</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-500">
|
||||
<td class="px-6 py-4 text-sm text-gray-500 dark:text-gray-400">{{ $depot->sources_count }}</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-500 dark:text-gray-400">
|
||||
@if($depot->url) <a href="{{ $depot->url }}" target="_blank" class="text-indigo-600 hover:underline truncate max-w-xs block">{{ $depot->url }}</a>
|
||||
@else —
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-6 py-4 text-right text-sm space-x-3">
|
||||
<a href="{{ route('admin.depots.edit', $depot) }}" class="text-gray-600 hover:text-indigo-600">Modifier</a>
|
||||
<a href="{{ route('admin.depots.edit', $depot) }}" class="text-gray-600 dark:text-gray-400 hover:text-indigo-600">Modifier</a>
|
||||
<form method="POST" action="{{ route('admin.depots.destroy', $depot) }}" class="inline"
|
||||
x-data @submit.prevent="if(confirm('Supprimer ce dépôt ?')) $el.submit()">
|
||||
@csrf @method('DELETE')
|
||||
@@ -40,7 +40,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr><td colspan="4" class="px-6 py-10 text-center text-gray-400">Aucun dépôt.</td></tr>
|
||||
<tr><td colspan="4" class="px-6 py-10 text-center text-gray-400 dark:text-gray-500">Aucun dépôt.</td></tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="text-xl font-semibold text-gray-800">{{ $depot->nom }}</h2>
|
||||
<h2 class="text-xl font-semibold text-gray-800 dark:text-gray-200">{{ $depot->nom }}</h2>
|
||||
<a href="{{ route('admin.depots.edit', $depot) }}" class="px-4 py-2 bg-indigo-600 text-white text-sm rounded-md hover:bg-indigo-700">Modifier</a>
|
||||
</div>
|
||||
</x-slot>
|
||||
<div class="py-8 max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 space-y-6">
|
||||
<div class="bg-white shadow rounded-lg divide-y divide-gray-100 text-sm">
|
||||
<div class="bg-white dark:bg-gray-800 shadow rounded-lg divide-y divide-gray-100 dark:divide-gray-700 text-sm">
|
||||
@if($depot->description)
|
||||
<div class="px-6 py-4 grid grid-cols-3 gap-4"><dt class="font-medium text-gray-500">Description</dt><dd class="col-span-2">{{ $depot->description }}</dd></div>
|
||||
<div class="px-6 py-4 grid grid-cols-3 gap-4"><dt class="font-medium text-gray-500 dark:text-gray-400">Description</dt><dd class="col-span-2">{{ $depot->description }}</dd></div>
|
||||
@endif
|
||||
@if($depot->adresse_postale)
|
||||
<div class="px-6 py-4 grid grid-cols-3 gap-4"><dt class="font-medium text-gray-500">Adresse</dt><dd class="col-span-2">{{ $depot->adresse_postale }}</dd></div>
|
||||
<div class="px-6 py-4 grid grid-cols-3 gap-4"><dt class="font-medium text-gray-500 dark:text-gray-400">Adresse</dt><dd class="col-span-2">{{ $depot->adresse_postale }}</dd></div>
|
||||
@endif
|
||||
@if($depot->url)
|
||||
<div class="px-6 py-4 grid grid-cols-3 gap-4"><dt class="font-medium text-gray-500">Site</dt><dd class="col-span-2"><a href="{{ $depot->url }}" target="_blank" class="text-indigo-600 hover:underline">{{ $depot->url }}</a></dd></div>
|
||||
<div class="px-6 py-4 grid grid-cols-3 gap-4"><dt class="font-medium text-gray-500 dark:text-gray-400">Site</dt><dd class="col-span-2"><a href="{{ $depot->url }}" target="_blank" class="text-indigo-600 hover:underline">{{ $depot->url }}</a></dd></div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@if($depot->sources->isNotEmpty())
|
||||
<div class="bg-white shadow rounded-lg overflow-hidden">
|
||||
<div class="px-6 py-4 border-b font-medium text-gray-900">Sources ({{ $depot->sources->count() }})</div>
|
||||
<ul class="divide-y divide-gray-100">
|
||||
<div class="bg-white dark:bg-gray-800 shadow rounded-lg overflow-hidden">
|
||||
<div class="px-6 py-4 border-b font-medium text-gray-900 dark:text-white">Sources ({{ $depot->sources->count() }})</div>
|
||||
<ul class="divide-y divide-gray-100 dark:divide-gray-700">
|
||||
@foreach($depot->sources as $source)
|
||||
<li class="px-6 py-3 text-sm text-gray-700">{{ $source->nom }}</li>
|
||||
<li class="px-6 py-3 text-sm text-gray-700 dark:text-gray-300">{{ $source->nom }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<div class="space-y-5">
|
||||
<div>
|
||||
<label for="nom" class="block text-sm font-medium text-gray-700">Nom <span class="text-red-500">*</span></label>
|
||||
<label for="nom" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Nom <span class="text-red-500">*</span></label>
|
||||
<input type="text" id="nom" name="nom" value="{{ old('nom', $lieuType?->nom) }}" required
|
||||
placeholder="ex : Pays, Région, Département, Ville…"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 @error('nom') border-red-500 @enderror">
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 @error('nom') border-red-500 @enderror">
|
||||
@error('nom') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="ordre" class="block text-sm font-medium text-gray-700">Ordre d'affichage <span class="text-red-500">*</span></label>
|
||||
<label for="ordre" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Ordre d'affichage <span class="text-red-500">*</span></label>
|
||||
<input type="number" id="ordre" name="ordre" value="{{ old('ordre', $lieuType?->ordre ?? 0) }}"
|
||||
min="0" max="999" required
|
||||
class="mt-1 block w-32 rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
<p class="mt-1 text-xs text-gray-400">Les valeurs les plus basses apparaissent en premier (ex : Pays=0, Région=10, Département=20, Ville=30…)</p>
|
||||
class="mt-1 block w-32 rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
<p class="mt-1 text-xs text-gray-400 dark:text-gray-500">Les valeurs les plus basses apparaissent en premier (ex : Pays=0, Région=10, Département=20, Ville=30…)</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header"><h2 class="text-xl font-semibold text-gray-800">Nouveau type de lieu</h2></x-slot>
|
||||
<x-slot name="header"><h2 class="text-xl font-semibold text-gray-800 dark:text-gray-200">Nouveau type de lieu</h2></x-slot>
|
||||
<div class="py-8 max-w-lg mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="bg-white shadow rounded-lg p-6">
|
||||
<div class="bg-white dark:bg-gray-800 shadow rounded-lg p-6">
|
||||
<form method="POST" action="{{ route('admin.lieu-types.store') }}">
|
||||
@csrf
|
||||
@include('admin.lieu-types._form', ['lieuType' => null])
|
||||
<div class="mt-6 flex gap-4">
|
||||
<button type="submit" class="px-5 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700">Créer</button>
|
||||
<a href="{{ route('admin.lieu-types.index') }}" class="text-sm text-gray-500 self-center hover:text-gray-700">Annuler</a>
|
||||
<a href="{{ route('admin.lieu-types.index') }}" class="text-sm text-gray-500 dark:text-gray-400 self-center hover:text-gray-700 dark:hover:text-gray-300">Annuler</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header"><h2 class="text-xl font-semibold text-gray-800">Modifier : {{ $lieuType->nom }}</h2></x-slot>
|
||||
<x-slot name="header"><h2 class="text-xl font-semibold text-gray-800 dark:text-gray-200">Modifier : {{ $lieuType->nom }}</h2></x-slot>
|
||||
<div class="py-8 max-w-lg mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="bg-white shadow rounded-lg p-6">
|
||||
<div class="bg-white dark:bg-gray-800 shadow rounded-lg p-6">
|
||||
<form method="POST" action="{{ route('admin.lieu-types.update', $lieuType) }}">
|
||||
@csrf @method('PUT')
|
||||
@include('admin.lieu-types._form', ['lieuType' => $lieuType])
|
||||
<div class="mt-6 flex gap-4">
|
||||
<button type="submit" class="px-5 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700">Enregistrer</button>
|
||||
<a href="{{ route('admin.lieu-types.index') }}" class="text-sm text-gray-500 self-center hover:text-gray-700">Annuler</a>
|
||||
<a href="{{ route('admin.lieu-types.index') }}" class="text-sm text-gray-500 dark:text-gray-400 self-center hover:text-gray-700 dark:hover:text-gray-300">Annuler</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="text-xl font-semibold text-gray-800">Types de lieux</h2>
|
||||
<h2 class="text-xl font-semibold text-gray-800 dark:text-gray-200">Types de lieux</h2>
|
||||
<a href="{{ route('admin.lieu-types.create') }}"
|
||||
class="px-4 py-2 bg-indigo-600 text-white text-sm rounded-md hover:bg-indigo-700">+ Nouveau type</a>
|
||||
</div>
|
||||
@@ -10,31 +10,31 @@
|
||||
<div class="py-8 max-w-3xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
@foreach(['success','error'] as $flash)
|
||||
@if(session($flash))
|
||||
<div class="mb-4 p-4 rounded-md {{ $flash === 'success' ? 'bg-green-50 border border-green-200 text-green-800' : 'bg-red-50 border border-red-200 text-red-800' }}">
|
||||
<div class="mb-4 p-4 rounded-md {{ $flash === 'success' ? 'bg-green-50 dark:bg-green-900/30 border border-green-200 dark:border-green-700 text-green-800 dark:text-green-200' : 'bg-red-50 dark:bg-red-900/30 border border-red-200 dark:border-red-700 text-red-800 dark:text-red-200' }}">
|
||||
{{ session($flash) }}
|
||||
</div>
|
||||
@endif
|
||||
@endforeach
|
||||
|
||||
<div class="bg-white shadow rounded-lg overflow-hidden">
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50">
|
||||
<div class="bg-white dark:bg-gray-800 shadow rounded-lg overflow-hidden">
|
||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<thead class="bg-gray-50 dark:bg-gray-700">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Ordre</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Nom</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Lieux</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Ordre</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Nom</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Lieux</th>
|
||||
<th class="px-6 py-3"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200">
|
||||
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
|
||||
@forelse($lieuTypes as $lt)
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="px-6 py-4 text-sm text-gray-400 w-16">{{ $lt->ordre }}</td>
|
||||
<td class="px-6 py-4 font-medium text-gray-900">{{ $lt->nom }}</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-500">{{ $lt->lieux_count }}</td>
|
||||
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700">
|
||||
<td class="px-6 py-4 text-sm text-gray-400 dark:text-gray-500 w-16">{{ $lt->ordre }}</td>
|
||||
<td class="px-6 py-4 font-medium text-gray-900 dark:text-white">{{ $lt->nom }}</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-500 dark:text-gray-400">{{ $lt->lieux_count }}</td>
|
||||
<td class="px-6 py-4 text-right text-sm space-x-3">
|
||||
<a href="{{ route('admin.lieu-types.edit', $lt) }}"
|
||||
class="text-gray-600 hover:text-indigo-600">Modifier</a>
|
||||
class="text-gray-600 dark:text-gray-400 hover:text-indigo-600">Modifier</a>
|
||||
<form method="POST" action="{{ route('admin.lieu-types.destroy', $lt) }}" class="inline"
|
||||
x-data @submit.prevent="if(confirm('Supprimer ce type ?')) $el.submit()">
|
||||
@csrf @method('DELETE')
|
||||
@@ -44,7 +44,7 @@
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="4" class="px-6 py-10 text-center text-gray-400">
|
||||
<td colspan="4" class="px-6 py-10 text-center text-gray-400 dark:text-gray-500">
|
||||
Aucun type de lieu défini.
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -1,23 +1,76 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="text-xl font-semibold text-gray-800">Paramètres du site</h2>
|
||||
<h2 class="text-xl font-semibold text-gray-800 dark:text-gray-200">Paramètres du site</h2>
|
||||
</x-slot>
|
||||
|
||||
<div class="py-8 max-w-2xl mx-auto px-4 sm:px-6 lg:px-8 space-y-6">
|
||||
|
||||
@if(session('success'))
|
||||
<div class="p-4 bg-green-50 border border-green-200 text-green-800 rounded-md">{{ session('success') }}</div>
|
||||
<div class="p-4 bg-green-50 dark:bg-green-900/30 border border-green-200 dark:border-green-700 text-green-800 dark:text-green-200 rounded-md">{{ session('success') }}</div>
|
||||
@endif
|
||||
|
||||
{{-- Paramètres généraux (titre + inscriptions) --}}
|
||||
<div class="bg-white dark:bg-gray-800 shadow rounded-lg p-6 space-y-6">
|
||||
<h3 class="text-sm font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wide">Paramètres généraux</h3>
|
||||
|
||||
<form method="POST" action="{{ route('admin.parametres.update') }}" class="space-y-5">
|
||||
@csrf
|
||||
|
||||
{{-- Titre du site --}}
|
||||
<div>
|
||||
<label for="site_name" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
Titre du site
|
||||
</label>
|
||||
<input type="text" id="site_name" name="site_name"
|
||||
value="{{ old('site_name', \App\Services\SiteSettingsService::get('site_name')) }}"
|
||||
placeholder="{{ config('app.name', 'MesRelevés') }}"
|
||||
maxlength="100"
|
||||
class="block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm text-sm
|
||||
focus:border-indigo-500 focus:ring-indigo-500">
|
||||
<p class="mt-1 text-xs text-gray-400 dark:text-gray-500">
|
||||
Affiché dans la navigation, les e-mails et les exports.
|
||||
Laisser vide pour utiliser la valeur par défaut
|
||||
(« {{ config('app.name', 'MesRelevés') }} »).
|
||||
</p>
|
||||
@error('site_name')
|
||||
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
{{-- Inscriptions --}}
|
||||
<div class="pt-4 border-t border-gray-100 dark:border-gray-700">
|
||||
<p class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Inscription publique des comptes</p>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 mb-3">
|
||||
Autorise ou non les visiteurs à créer un compte via la page d'inscription.
|
||||
Quand désactivée, seul un administrateur peut créer des comptes.
|
||||
</p>
|
||||
<label class="flex items-center gap-3 cursor-pointer">
|
||||
<input type="hidden" name="registration_enabled" value="0">
|
||||
<input type="checkbox" name="registration_enabled" value="1"
|
||||
{{ $registrationEnabled ? 'checked' : '' }}
|
||||
class="w-4 h-4 text-indigo-600 border-gray-300 dark:border-gray-600 rounded focus:ring-indigo-500">
|
||||
<span class="text-sm text-gray-700 dark:text-gray-300">Autoriser l'inscription de nouveaux comptes</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button type="submit"
|
||||
class="px-5 py-2 bg-indigo-600 text-white text-sm font-medium rounded-md hover:bg-indigo-700">
|
||||
Enregistrer
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{{-- Logo --}}
|
||||
<div class="bg-white shadow rounded-lg p-6 space-y-5">
|
||||
<h3 class="text-sm font-semibold text-gray-700 uppercase tracking-wide">Logo du site</h3>
|
||||
<div class="bg-white dark:bg-gray-800 shadow rounded-lg p-6 space-y-5">
|
||||
<h3 class="text-sm font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wide">Logo du site</h3>
|
||||
|
||||
@if($logoUrl)
|
||||
<div class="flex items-center gap-6">
|
||||
<img src="{{ $logoUrl }}" alt="Logo actuel" class="h-20 w-auto object-contain rounded border border-gray-200 p-2">
|
||||
<img src="{{ $logoUrl }}" alt="Logo actuel" class="h-20 w-auto object-contain rounded border border-gray-200 dark:border-gray-700 p-2">
|
||||
<div>
|
||||
<p class="text-sm text-gray-600 mb-2">Logo actuel</p>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-2">Logo actuel</p>
|
||||
<form method="POST" action="{{ route('admin.parametres.logo.delete') }}"
|
||||
x-data @submit.prevent="if(confirm('Supprimer le logo ?')) $el.submit()">
|
||||
@csrf @method('DELETE')
|
||||
@@ -26,19 +79,19 @@
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
<p class="text-sm text-gray-400">Aucun logo configuré — le nom de l'application est affiché.</p>
|
||||
<p class="text-sm text-gray-400 dark:text-gray-500">Aucun logo configuré — le nom de l'application est affiché.</p>
|
||||
@endif
|
||||
|
||||
<form method="POST" action="{{ route('admin.parametres.logo.update') }}"
|
||||
enctype="multipart/form-data" class="space-y-4">
|
||||
@csrf
|
||||
<div>
|
||||
<label for="logo" class="block text-sm font-medium text-gray-700 mb-1">
|
||||
<label for="logo" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
{{ $logoUrl ? 'Remplacer le logo' : 'Téléverser un logo' }}
|
||||
</label>
|
||||
<input type="file" id="logo" name="logo" accept="image/*"
|
||||
class="block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-md file:border-0 file:text-sm file:font-medium file:bg-indigo-50 file:text-indigo-700 hover:file:bg-indigo-100">
|
||||
<p class="mt-1 text-xs text-gray-400">PNG, JPG, SVG ou WebP · max 2 Mo · format recommandé : carré ou paysage, fond transparent</p>
|
||||
class="block w-full text-sm text-gray-500 dark:text-gray-400 file:mr-4 file:py-2 file:px-4 file:rounded-md file:border-0 file:text-sm file:font-medium file:bg-indigo-50 file:text-indigo-700 hover:file:bg-indigo-100">
|
||||
<p class="mt-1 text-xs text-gray-400 dark:text-gray-500">PNG, JPG, SVG ou WebP · max 2 Mo · format recommandé : carré ou paysage, fond transparent</p>
|
||||
@error('logo') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
|
||||
</div>
|
||||
<button type="submit"
|
||||
@@ -50,7 +103,7 @@
|
||||
|
||||
{{-- SMTP --}}
|
||||
@php $smtp = \App\Services\SiteSettingsService::smtpConfig(); @endphp
|
||||
<div class="bg-white shadow rounded-lg p-6 space-y-5"
|
||||
<div class="bg-white dark:bg-gray-800 shadow rounded-lg p-6 space-y-5"
|
||||
x-data="{
|
||||
host: '{{ $smtp['host'] ?? '' }}',
|
||||
port: '{{ $smtp['port'] ?? 587 }}',
|
||||
@@ -98,13 +151,13 @@
|
||||
|
||||
<div class="flex items-start justify-between">
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold text-gray-700 uppercase tracking-wide">Serveur SMTP</h3>
|
||||
<p class="text-xs text-gray-400 mt-0.5">
|
||||
<h3 class="text-sm font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wide">Serveur SMTP</h3>
|
||||
<p class="text-xs text-gray-400 dark:text-gray-500 mt-0.5">
|
||||
Quand configuré, la connexion nécessitera un code PIN envoyé par e-mail (2FA).
|
||||
</p>
|
||||
</div>
|
||||
@if(\App\Services\SiteSettingsService::smtpConfigured())
|
||||
<span class="inline-flex items-center gap-1.5 text-xs text-green-700 bg-green-50 border border-green-200 px-2.5 py-1 rounded-full">
|
||||
<span class="inline-flex items-center gap-1.5 text-xs text-green-700 bg-green-50 dark:bg-green-900/30 border border-green-200 dark:border-green-700 px-2.5 py-1 rounded-full">
|
||||
<svg class="w-3 h-3 fill-current" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
@@ -118,24 +171,24 @@
|
||||
|
||||
<div class="grid grid-cols-3 gap-4">
|
||||
<div class="col-span-2">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Hôte SMTP</label>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Hôte SMTP</label>
|
||||
<input type="text" name="smtp_host" x-model="host"
|
||||
placeholder="smtp.exemple.fr"
|
||||
class="w-full rounded-md border-gray-300 shadow-sm text-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
class="w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm text-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
@error('smtp_host') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Port</label>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Port</label>
|
||||
<input type="number" name="smtp_port" x-model="port"
|
||||
class="w-full rounded-md border-gray-300 shadow-sm text-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
class="w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm text-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
@error('smtp_port') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Chiffrement</label>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Chiffrement</label>
|
||||
<select name="smtp_encryption" x-model="encryption"
|
||||
class="w-full rounded-md border-gray-300 shadow-sm text-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
class="w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm text-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
<option value="tls">TLS / STARTTLS (port 587 recommandé)</option>
|
||||
<option value="ssl">SSL (port 465)</option>
|
||||
<option value="">Aucun (déconseillé)</option>
|
||||
@@ -144,39 +197,39 @@
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Identifiant</label>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Identifiant</label>
|
||||
<input type="text" name="smtp_username" x-model="username"
|
||||
autocomplete="off"
|
||||
class="w-full rounded-md border-gray-300 shadow-sm text-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
class="w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm text-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Mot de passe</label>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Mot de passe</label>
|
||||
<input type="password" name="smtp_password" x-model="password"
|
||||
autocomplete="new-password"
|
||||
class="w-full rounded-md border-gray-300 shadow-sm text-sm focus:border-indigo-500 focus:ring-indigo-500"
|
||||
class="w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm text-sm focus:border-indigo-500 focus:ring-indigo-500"
|
||||
placeholder="{{ \App\Services\SiteSettingsService::smtpConfigured() ? '(inchangé si vide)' : '' }}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4 pt-2 border-t border-gray-100">
|
||||
<div class="grid grid-cols-2 gap-4 pt-2 border-t border-gray-100 dark:border-gray-700">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Adresse d'expéditeur</label>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Adresse d'expéditeur</label>
|
||||
<input type="email" name="smtp_from_address" x-model="fromAddress"
|
||||
placeholder="noreply@exemple.fr"
|
||||
class="w-full rounded-md border-gray-300 shadow-sm text-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
class="w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm text-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
@error('smtp_from_address') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Nom d'expéditeur</label>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Nom d'expéditeur</label>
|
||||
<input type="text" name="smtp_from_name" x-model="fromName"
|
||||
class="w-full rounded-md border-gray-300 shadow-sm text-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
class="w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm text-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
@error('smtp_from_name') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Résultat test --}}
|
||||
<div x-show="tested" x-cloak class="p-3 rounded-lg text-sm flex items-start gap-2"
|
||||
:class="testOk ? 'bg-green-50 border border-green-200 text-green-800' : 'bg-red-50 border border-red-200 text-red-800'">
|
||||
:class="testOk ? 'bg-green-50 dark:bg-green-900/30 border border-green-200 dark:border-green-700 text-green-800 dark:text-green-200' : 'bg-red-50 dark:bg-red-900/30 border border-red-200 dark:border-red-700 text-red-800 dark:text-red-200'">
|
||||
<span x-text="testOk ? '✓' : '✗'" class="font-bold shrink-0"></span>
|
||||
<span x-text="testMsg" class="break-all"></span>
|
||||
</div>
|
||||
@@ -210,26 +263,26 @@
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{{-- Version --}}
|
||||
<div class="bg-white shadow rounded-lg p-6 space-y-4">
|
||||
<h3 class="text-sm font-semibold text-gray-700 uppercase tracking-wide">Version du logiciel</h3>
|
||||
{{-- Version du logiciel --}}
|
||||
<div class="bg-white dark:bg-gray-800 shadow rounded-lg p-6 space-y-4">
|
||||
<h3 class="text-sm font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wide">Version du logiciel</h3>
|
||||
|
||||
@if($updateAvailable)
|
||||
<div class="flex items-start gap-3 p-4 bg-indigo-50 border border-indigo-200 rounded-lg">
|
||||
<svg class="w-5 h-5 text-indigo-500 shrink-0 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<div class="flex items-start gap-3 p-4 bg-indigo-50 dark:bg-indigo-900/30 border border-indigo-200 dark:border-indigo-700 rounded-lg">
|
||||
<svg class="w-5 h-5 text-indigo-500 dark:text-indigo-400 shrink-0 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12"/>
|
||||
</svg>
|
||||
<div>
|
||||
<p class="text-sm font-semibold text-indigo-800">
|
||||
<p class="text-sm font-semibold text-indigo-800 dark:text-indigo-200">
|
||||
Mise à jour disponible : v{{ $latestRelease['version'] }}
|
||||
</p>
|
||||
@if($latestRelease['published_at'])
|
||||
<p class="text-xs text-indigo-500 mt-0.5">
|
||||
<p class="text-xs text-indigo-500 dark:text-indigo-400 mt-0.5">
|
||||
Publié {{ \Carbon\Carbon::parse($latestRelease['published_at'])->diffForHumans() }}
|
||||
</p>
|
||||
@endif
|
||||
<p class="text-xs text-indigo-600 mt-2 font-mono bg-indigo-100 inline-block px-2 py-1 rounded">
|
||||
<p class="text-xs text-indigo-600 mt-2 font-mono bg-indigo-100 dark:bg-indigo-900/50 inline-block px-2 py-1 rounded">
|
||||
php artisan app:update
|
||||
</p>
|
||||
</div>
|
||||
@@ -238,16 +291,16 @@
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm text-gray-700 font-medium">MesRelevés v{{ $installedVersion }}</p>
|
||||
<p class="text-sm text-gray-700 dark:text-gray-300 font-medium">MesRelevés v{{ $installedVersion }}</p>
|
||||
@php $installedAt = storage_path('installed'); @endphp
|
||||
@if(file_exists($installedAt))
|
||||
<p class="text-xs text-gray-400 mt-0.5">
|
||||
<p class="text-xs text-gray-400 dark:text-gray-500 mt-0.5">
|
||||
Installé le {{ \Carbon\Carbon::createFromTimestamp(filemtime($installedAt))->isoFormat('LL') }}
|
||||
</p>
|
||||
@endif
|
||||
</div>
|
||||
@if(! $updateAvailable)
|
||||
<span class="inline-flex items-center gap-1.5 text-xs text-green-700 bg-green-50 border border-green-200 px-3 py-1.5 rounded-full">
|
||||
@if(! $updateAvailable && ! $updatesDisabled)
|
||||
<span class="inline-flex items-center gap-1.5 text-xs text-green-700 bg-green-50 dark:bg-green-900/30 border border-green-200 dark:border-green-700 px-3 py-1.5 rounded-full">
|
||||
<svg class="w-3.5 h-3.5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
@@ -255,59 +308,41 @@
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- Option désactiver les mises à jour --}}
|
||||
<div class="pt-4 border-t border-gray-100 dark:border-gray-700">
|
||||
<form method="POST" action="{{ route('admin.parametres.updates') }}">
|
||||
@csrf
|
||||
<div class="flex items-start gap-3">
|
||||
<div class="flex items-center h-5 mt-0.5">
|
||||
<input type="hidden" name="updates_disabled" value="0">
|
||||
<input type="checkbox" id="updates_disabled" name="updates_disabled" value="1"
|
||||
{{ $updatesDisabled ? 'checked' : '' }}
|
||||
onchange="this.form.submit()"
|
||||
class="w-4 h-4 text-amber-600 border-gray-300 dark:border-gray-600 rounded focus:ring-amber-500">
|
||||
</div>
|
||||
<div>
|
||||
<label for="updates_disabled" class="text-sm font-medium text-gray-700 dark:text-gray-300 cursor-pointer">
|
||||
Désactiver la vérification automatique des mises à jour
|
||||
</label>
|
||||
<p class="text-xs text-gray-400 dark:text-gray-500 mt-0.5">
|
||||
Quand coché, le site ne contacte plus le serveur distant pour vérifier l'existence
|
||||
d'une nouvelle version. Utile en environnement sans accès Internet ou en production
|
||||
isolée.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@if($updatesDisabled)
|
||||
<p class="mt-2 ml-7 text-xs text-amber-600 flex items-center gap-1">
|
||||
<svg class="w-3.5 h-3.5 shrink-0" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
Vérification des mises à jour désactivée — les nouvelles versions ne seront pas signalées.
|
||||
</p>
|
||||
@endif
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Titre du site + Inscriptions (formulaire commun) --}}
|
||||
<div class="bg-white shadow rounded-lg p-6 space-y-6">
|
||||
<h3 class="text-sm font-semibold text-gray-700 uppercase tracking-wide">Paramètres généraux</h3>
|
||||
|
||||
<form method="POST" action="{{ route('admin.parametres.update') }}" class="space-y-5">
|
||||
@csrf
|
||||
|
||||
{{-- Titre du site --}}
|
||||
<div>
|
||||
<label for="site_name" class="block text-sm font-medium text-gray-700 mb-1">
|
||||
Titre du site
|
||||
</label>
|
||||
<input type="text" id="site_name" name="site_name"
|
||||
value="{{ old('site_name', \App\Services\SiteSettingsService::get('site_name')) }}"
|
||||
placeholder="{{ config('app.name', 'MesRelevés') }}"
|
||||
maxlength="100"
|
||||
class="block w-full rounded-md border-gray-300 shadow-sm text-sm
|
||||
focus:border-indigo-500 focus:ring-indigo-500">
|
||||
<p class="mt-1 text-xs text-gray-400">
|
||||
Affiché dans la navigation, les e-mails et les exports.
|
||||
Laisser vide pour utiliser la valeur par défaut
|
||||
(« {{ config('app.name', 'MesRelevés') }} »).
|
||||
</p>
|
||||
@error('site_name')
|
||||
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
{{-- Inscriptions --}}
|
||||
<div class="pt-4 border-t border-gray-100">
|
||||
<p class="text-sm font-medium text-gray-700 mb-2">Inscription publique des comptes</p>
|
||||
<p class="text-xs text-gray-500 mb-3">
|
||||
Autorise ou non les visiteurs à créer un compte via la page d'inscription.
|
||||
Quand désactivée, seul un administrateur peut créer des comptes.
|
||||
</p>
|
||||
<label class="flex items-center gap-3 cursor-pointer">
|
||||
<input type="hidden" name="registration_enabled" value="0">
|
||||
<input type="checkbox" name="registration_enabled" value="1"
|
||||
{{ $registrationEnabled ? 'checked' : '' }}
|
||||
class="w-4 h-4 text-indigo-600 border-gray-300 rounded focus:ring-indigo-500">
|
||||
<span class="text-sm text-gray-700">Autoriser l'inscription de nouveaux comptes</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button type="submit"
|
||||
class="px-5 py-2 bg-indigo-600 text-white text-sm font-medium rounded-md hover:bg-indigo-700">
|
||||
Enregistrer
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</x-app-layout>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<div class="space-y-5">
|
||||
<div>
|
||||
<label for="nom" class="block text-sm font-medium text-gray-700">Nom <span class="text-red-500">*</span></label>
|
||||
<label for="nom" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Nom <span class="text-red-500">*</span></label>
|
||||
<input type="text" id="nom" name="nom" value="{{ old('nom', $section?->nom) }}" required
|
||||
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 @error('nom') border-red-500 @enderror">
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 @error('nom') border-red-500 @enderror">
|
||||
@error('nom') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
|
||||
</div>
|
||||
|
||||
@@ -16,22 +16,22 @@
|
||||
/>
|
||||
|
||||
<div>
|
||||
<label for="adresse" class="block text-sm font-medium text-gray-700">Adresse</label>
|
||||
<label for="adresse" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Adresse</label>
|
||||
<input type="text" id="adresse" name="adresse" value="{{ old('adresse', $section?->adresse) }}"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="email_contact" class="block text-sm font-medium text-gray-700">Email de contact</label>
|
||||
<label for="email_contact" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Email de contact</label>
|
||||
<input type="email" id="email_contact" name="email_contact" value="{{ old('email_contact', $section?->email_contact) }}"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 @error('email_contact') border-red-500 @enderror">
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 @error('email_contact') border-red-500 @enderror">
|
||||
@error('email_contact') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="url" class="block text-sm font-medium text-gray-700">Site internet</label>
|
||||
<label for="url" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Site internet</label>
|
||||
<input type="url" id="url" name="url" value="{{ old('url', $section?->url) }}"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 @error('url') border-red-500 @enderror">
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 @error('url') border-red-500 @enderror">
|
||||
@error('url') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header"><h2 class="text-xl font-semibold text-gray-800">Nouvelle section</h2></x-slot>
|
||||
<x-slot name="header"><h2 class="text-xl font-semibold text-gray-800 dark:text-gray-200">Nouvelle section</h2></x-slot>
|
||||
<div class="py-8 max-w-2xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="bg-white shadow rounded-lg p-6">
|
||||
<div class="bg-white dark:bg-gray-800 shadow rounded-lg p-6">
|
||||
<form method="POST" action="{{ route('admin.sections.store') }}">
|
||||
@csrf
|
||||
@include('admin.sections._form', ['section' => null])
|
||||
<div class="mt-6 flex gap-4">
|
||||
<button type="submit" class="px-5 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700">Créer</button>
|
||||
<a href="{{ route('admin.sections.index') }}" class="text-sm text-gray-500 hover:text-gray-700 self-center">Annuler</a>
|
||||
<a href="{{ route('admin.sections.index') }}" class="text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 self-center">Annuler</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header"><h2 class="text-xl font-semibold text-gray-800">Modifier : {{ $section->nom }}</h2></x-slot>
|
||||
<x-slot name="header"><h2 class="text-xl font-semibold text-gray-800 dark:text-gray-200">Modifier : {{ $section->nom }}</h2></x-slot>
|
||||
<div class="py-8 max-w-2xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="bg-white shadow rounded-lg p-6">
|
||||
<div class="bg-white dark:bg-gray-800 shadow rounded-lg p-6">
|
||||
<form method="POST" action="{{ route('admin.sections.update', $section) }}">
|
||||
@csrf @method('PUT')
|
||||
@include('admin.sections._form', ['section' => $section])
|
||||
<div class="mt-6 flex gap-4">
|
||||
<button type="submit" class="px-5 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700">Enregistrer</button>
|
||||
<a href="{{ route('admin.sections.show', $section) }}" class="text-sm text-gray-500 hover:text-gray-700 self-center">Annuler</a>
|
||||
<a href="{{ route('admin.sections.show', $section) }}" class="text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 self-center">Annuler</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="text-xl font-semibold text-gray-800">Sections locales</h2>
|
||||
<h2 class="text-xl font-semibold text-gray-800 dark:text-gray-200">Sections locales</h2>
|
||||
<a href="{{ route('admin.sections.create') }}"
|
||||
class="px-4 py-2 bg-indigo-600 text-white text-sm rounded-md hover:bg-indigo-700">+ Nouvelle section</a>
|
||||
</div>
|
||||
@@ -9,29 +9,29 @@
|
||||
|
||||
<div class="py-8 max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
@if(session('success'))
|
||||
<div class="mb-4 p-4 bg-green-50 border border-green-200 text-green-800 rounded-md">{{ session('success') }}</div>
|
||||
<div class="mb-4 p-4 bg-green-50 dark:bg-green-900/30 border border-green-200 dark:border-green-700 text-green-800 dark:text-green-200 rounded-md">{{ session('success') }}</div>
|
||||
@endif
|
||||
|
||||
<div class="bg-white shadow rounded-lg overflow-hidden">
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50">
|
||||
<div class="bg-white dark:bg-gray-800 shadow rounded-lg overflow-hidden">
|
||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<thead class="bg-gray-50 dark:bg-gray-700">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Nom</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Lieu</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Contact</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Nom</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Lieu</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Contact</th>
|
||||
<th class="px-6 py-3"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200">
|
||||
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
|
||||
@forelse($sections as $section)
|
||||
<tr class="hover:bg-gray-50">
|
||||
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700">
|
||||
<td class="px-6 py-4 font-medium">
|
||||
<a href="{{ route('admin.sections.show', $section) }}" class="text-indigo-600 hover:underline">{{ $section->nom }}</a>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-500">{{ $section->lieu?->nom ?? '—' }}</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-500">{{ $section->email_contact ?? '—' }}</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-500 dark:text-gray-400">{{ $section->lieu?->nom ?? '—' }}</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-500 dark:text-gray-400">{{ $section->email_contact ?? '—' }}</td>
|
||||
<td class="px-6 py-4 text-right text-sm space-x-3">
|
||||
<a href="{{ route('admin.sections.edit', $section) }}" class="text-gray-600 hover:text-indigo-600">Modifier</a>
|
||||
<a href="{{ route('admin.sections.edit', $section) }}" class="text-gray-600 dark:text-gray-400 hover:text-indigo-600">Modifier</a>
|
||||
<form method="POST" action="{{ route('admin.sections.destroy', $section) }}" class="inline"
|
||||
x-data @submit.prevent="if(confirm('Supprimer cette section ?')) $el.submit()">
|
||||
@csrf @method('DELETE')
|
||||
@@ -40,7 +40,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr><td colspan="4" class="px-6 py-10 text-center text-gray-400">Aucune section.</td></tr>
|
||||
<tr><td colspan="4" class="px-6 py-10 text-center text-gray-400 dark:text-gray-500">Aucune section.</td></tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="text-xl font-semibold text-gray-800">{{ $section->nom }}</h2>
|
||||
<h2 class="text-xl font-semibold text-gray-800 dark:text-gray-200">{{ $section->nom }}</h2>
|
||||
<a href="{{ route('admin.sections.edit', $section) }}"
|
||||
class="px-4 py-2 bg-indigo-600 text-white text-sm rounded-md hover:bg-indigo-700">Modifier</a>
|
||||
</div>
|
||||
@@ -10,42 +10,42 @@
|
||||
<div class="py-8 max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 space-y-6">
|
||||
@foreach(['success','error'] as $flash)
|
||||
@if(session($flash))
|
||||
<div class="p-4 rounded-md {{ $flash === 'success' ? 'bg-green-50 border border-green-200 text-green-800' : 'bg-red-50 border border-red-200 text-red-800' }}">
|
||||
<div class="p-4 rounded-md {{ $flash === 'success' ? 'bg-green-50 dark:bg-green-900/30 border border-green-200 dark:border-green-700 text-green-800 dark:text-green-200' : 'bg-red-50 dark:bg-red-900/30 border border-red-200 dark:border-red-700 text-red-800 dark:text-red-200' }}">
|
||||
{{ session($flash) }}
|
||||
</div>
|
||||
@endif
|
||||
@endforeach
|
||||
|
||||
{{-- Fiche --}}
|
||||
<div class="bg-white shadow rounded-lg divide-y divide-gray-100 text-sm">
|
||||
<div class="bg-white dark:bg-gray-800 shadow rounded-lg divide-y divide-gray-100 dark:divide-gray-700 text-sm">
|
||||
@foreach([['Lieu', $section->lieu?->nom_long ?? '—'], ['Adresse', $section->adresse ?? '—'], ['Email', $section->email_contact ?? '—'], ['Site', $section->url ?? '—']] as [$label, $val])
|
||||
<div class="px-6 py-4 grid grid-cols-3 gap-4">
|
||||
<dt class="font-medium text-gray-500">{{ $label }}</dt>
|
||||
<dd class="col-span-2 text-gray-900">{{ $val }}</dd>
|
||||
<dt class="font-medium text-gray-500 dark:text-gray-400">{{ $label }}</dt>
|
||||
<dd class="col-span-2 text-gray-900 dark:text-white">{{ $val }}</dd>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
{{-- Membres --}}
|
||||
<div class="bg-white shadow rounded-lg overflow-hidden">
|
||||
<div class="px-6 py-4 border-b border-gray-200 font-medium text-gray-900">
|
||||
<div class="bg-white dark:bg-gray-800 shadow rounded-lg overflow-hidden">
|
||||
<div class="px-6 py-4 border-b border-gray-200 dark:border-gray-700 font-medium text-gray-900 dark:text-white">
|
||||
Membres ({{ $section->membres->count() }})
|
||||
</div>
|
||||
@if($section->membres->isNotEmpty())
|
||||
<table class="min-w-full divide-y divide-gray-200 text-sm">
|
||||
<thead class="bg-gray-50">
|
||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700 text-sm">
|
||||
<thead class="bg-gray-50 dark:bg-gray-700">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Nom</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Email</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Rôle dans la section</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Nom</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Email</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Rôle dans la section</th>
|
||||
<th class="px-6 py-3"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200">
|
||||
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
|
||||
@foreach($section->membres as $membre)
|
||||
<tr>
|
||||
<td class="px-6 py-3">{{ $membre->name }}</td>
|
||||
<td class="px-6 py-3 text-gray-500">{{ $membre->email }}</td>
|
||||
<td class="px-6 py-3 text-gray-500 dark:text-gray-400">{{ $membre->email }}</td>
|
||||
<td class="px-6 py-3">
|
||||
{{ $membre->pivot->role_in_section === 'section_manager' ? 'Responsable' : 'Membre' }}
|
||||
</td>
|
||||
@@ -63,25 +63,25 @@
|
||||
@endif
|
||||
|
||||
{{-- Ajouter un membre --}}
|
||||
<div class="px-6 py-4 bg-gray-50 border-t border-gray-200">
|
||||
<form method="POST" action="{{ route('admin.sections.membres.add', $section) }}" class="flex gap-3 items-end">
|
||||
<div class="px-6 py-4 bg-gray-50 dark:bg-gray-700 border-t border-gray-200 dark:border-gray-700">
|
||||
<form method="POST" action="{{ route('admin.sections.membres.add', $section) }}"
|
||||
x-data="{ canSubmit: false }"
|
||||
@submit.prevent="if ($el.querySelector('[name=user_id]').value) $el.submit()">
|
||||
@csrf
|
||||
<div class="flex-1">
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">Utilisateur</label>
|
||||
<select name="user_id" class="block w-full rounded-md border-gray-300 shadow-sm text-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
@foreach($users as $user)
|
||||
<option value="{{ $user->id }}">{{ $user->name }} ({{ $user->email }})</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<div class="flex flex-col sm:flex-row gap-3 items-stretch sm:items-end">
|
||||
<div class="flex-1">
|
||||
<label class="block text-xs font-medium text-gray-600 dark:text-gray-400 mb-1">Utilisateur</label>
|
||||
<x-user-picker :users="$users" placeholder="Rechercher un utilisateur…" required />
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600 dark:text-gray-400 mb-1">Rôle</label>
|
||||
<select name="role_in_section" class="block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100 shadow-sm text-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
<option value="member">Membre</option>
|
||||
<option value="section_manager">Responsable</option>
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit" class="px-4 py-2 bg-indigo-600 text-white text-sm rounded-md hover:bg-indigo-700 shrink-0">Ajouter</button>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">Rôle</label>
|
||||
<select name="role_in_section" class="block w-full rounded-md border-gray-300 shadow-sm text-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
<option value="member">Membre</option>
|
||||
<option value="section_manager">Responsable</option>
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit" class="px-4 py-2 bg-indigo-600 text-white text-sm rounded-md hover:bg-indigo-700">Ajouter</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header"><h2 class="text-xl font-semibold text-gray-800">Nouveau type de source</h2></x-slot>
|
||||
<x-slot name="header"><h2 class="text-xl font-semibold text-gray-800 dark:text-gray-200">Nouveau type de source</h2></x-slot>
|
||||
<div class="py-8 max-w-2xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="bg-white shadow rounded-lg p-6">
|
||||
<div class="bg-white dark:bg-gray-800 shadow rounded-lg p-6">
|
||||
<form method="POST" action="{{ route('admin.source-types.store') }}">
|
||||
@csrf
|
||||
<div class="space-y-5">
|
||||
<div>
|
||||
<label for="nom" class="block text-sm font-medium text-gray-700">Nom <span class="text-red-500">*</span></label>
|
||||
<label for="nom" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Nom <span class="text-red-500">*</span></label>
|
||||
<input type="text" id="nom" name="nom" value="{{ old('nom') }}" required
|
||||
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
@error('nom') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
|
||||
</div>
|
||||
<div>
|
||||
<label for="description" class="block text-sm font-medium text-gray-700">Description</label>
|
||||
<label for="description" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Description</label>
|
||||
<textarea id="description" name="description" rows="3"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">{{ old('description') }}</textarea>
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">{{ old('description') }}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-6 flex gap-4">
|
||||
<button type="submit" class="px-5 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700">Créer et définir les champs</button>
|
||||
<a href="{{ route('admin.source-types.index') }}" class="text-sm text-gray-500 self-center hover:text-gray-700">Annuler</a>
|
||||
<a href="{{ route('admin.source-types.index') }}" class="text-sm text-gray-500 dark:text-gray-400 self-center hover:text-gray-700 dark:hover:text-gray-300">Annuler</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header"><h2 class="text-xl font-semibold text-gray-800">Modifier : {{ $sourceType->nom }}</h2></x-slot>
|
||||
<x-slot name="header"><h2 class="text-xl font-semibold text-gray-800 dark:text-gray-200">Modifier : {{ $sourceType->nom }}</h2></x-slot>
|
||||
<div class="py-8 max-w-2xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="bg-white shadow rounded-lg p-6">
|
||||
<div class="bg-white dark:bg-gray-800 shadow rounded-lg p-6">
|
||||
<form method="POST" action="{{ route('admin.source-types.update', $sourceType) }}">
|
||||
@csrf @method('PUT')
|
||||
<div class="space-y-5">
|
||||
<div>
|
||||
<label for="nom" class="block text-sm font-medium text-gray-700">Nom <span class="text-red-500">*</span></label>
|
||||
<label for="nom" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Nom <span class="text-red-500">*</span></label>
|
||||
<input type="text" id="nom" name="nom" value="{{ old('nom', $sourceType->nom) }}" required
|
||||
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
</div>
|
||||
<div>
|
||||
<label for="description" class="block text-sm font-medium text-gray-700">Description</label>
|
||||
<label for="description" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Description</label>
|
||||
<textarea id="description" name="description" rows="3"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">{{ old('description', $sourceType->description) }}</textarea>
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">{{ old('description', $sourceType->description) }}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-6 flex gap-4">
|
||||
<button type="submit" class="px-5 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700">Enregistrer</button>
|
||||
<a href="{{ route('admin.source-types.show', $sourceType) }}" class="text-sm text-gray-500 self-center hover:text-gray-700">Annuler</a>
|
||||
<a href="{{ route('admin.source-types.show', $sourceType) }}" class="text-sm text-gray-500 dark:text-gray-400 self-center hover:text-gray-700 dark:hover:text-gray-300">Annuler</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="text-xl font-semibold text-gray-800">Types de sources</h2>
|
||||
<h2 class="text-xl font-semibold text-gray-800 dark:text-gray-200">Types de sources</h2>
|
||||
<a href="{{ route('admin.source-types.create') }}" class="px-4 py-2 bg-indigo-600 text-white text-sm rounded-md hover:bg-indigo-700">+ Nouveau type</a>
|
||||
</div>
|
||||
</x-slot>
|
||||
<div class="py-8 max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
@if(session('success')) <div class="mb-4 p-4 bg-green-50 border border-green-200 text-green-800 rounded-md">{{ session('success') }}</div> @endif
|
||||
@if(session('error')) <div class="mb-4 p-4 bg-red-50 border border-red-200 text-red-800 rounded-md">{{ session('error') }}</div> @endif
|
||||
<div class="bg-white shadow rounded-lg overflow-hidden">
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50">
|
||||
@if(session('success')) <div class="mb-4 p-4 bg-green-50 dark:bg-green-900/30 border border-green-200 dark:border-green-700 text-green-800 dark:text-green-200 rounded-md">{{ session('success') }}</div> @endif
|
||||
@if(session('error')) <div class="mb-4 p-4 bg-red-50 dark:bg-red-900/30 border border-red-200 dark:border-red-700 text-red-800 dark:text-red-200 rounded-md">{{ session('error') }}</div> @endif
|
||||
<div class="bg-white dark:bg-gray-800 shadow rounded-lg overflow-hidden">
|
||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<thead class="bg-gray-50 dark:bg-gray-700">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Nom</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Champs</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Sources liées</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Nom</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Champs</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Sources liées</th>
|
||||
<th class="px-6 py-3"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200">
|
||||
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
|
||||
@forelse($sourceTypes as $st)
|
||||
<tr class="hover:bg-gray-50">
|
||||
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700">
|
||||
<td class="px-6 py-4 font-medium">
|
||||
<a href="{{ route('admin.source-types.show', $st) }}" class="text-indigo-600 hover:underline">{{ $st->nom }}</a>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-500">{{ $st->fields_count ?? '—' }}</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-500">{{ $st->sources_count }}</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-500 dark:text-gray-400">{{ $st->fields_count ?? '—' }}</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-500 dark:text-gray-400">{{ $st->sources_count }}</td>
|
||||
<td class="px-6 py-4 text-right text-sm space-x-3">
|
||||
<a href="{{ route('admin.source-types.edit', $st) }}" class="text-gray-600 hover:text-indigo-600">Modifier</a>
|
||||
<a href="{{ route('admin.source-types.edit', $st) }}" class="text-gray-600 dark:text-gray-400 hover:text-indigo-600">Modifier</a>
|
||||
<form method="POST" action="{{ route('admin.source-types.destroy', $st) }}" class="inline"
|
||||
x-data @submit.prevent="if(confirm('Supprimer ce type ?')) $el.submit()">
|
||||
@csrf @method('DELETE')
|
||||
@@ -36,7 +36,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr><td colspan="4" class="px-6 py-10 text-center text-gray-400">Aucun type de source.</td></tr>
|
||||
<tr><td colspan="4" class="px-6 py-10 text-center text-gray-400 dark:text-gray-500">Aucun type de source.</td></tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="text-xl font-semibold text-gray-800">{{ $sourceType->nom }}</h2>
|
||||
<h2 class="text-xl font-semibold text-gray-800 dark:text-gray-200">{{ $sourceType->nom }}</h2>
|
||||
<a href="{{ route('admin.source-types.edit', $sourceType) }}" class="px-4 py-2 bg-indigo-600 text-white text-sm rounded-md hover:bg-indigo-700">Modifier</a>
|
||||
</div>
|
||||
</x-slot>
|
||||
@@ -9,21 +9,21 @@
|
||||
<div class="py-8 max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 space-y-6">
|
||||
@foreach(['success','error'] as $flash)
|
||||
@if(session($flash))
|
||||
<div class="p-4 rounded-md {{ $flash === 'success' ? 'bg-green-50 border border-green-200 text-green-800' : 'bg-red-50 border border-red-200 text-red-800' }}">
|
||||
<div class="p-4 rounded-md {{ $flash === 'success' ? 'bg-green-50 dark:bg-green-900/30 border border-green-200 dark:border-green-700 text-green-800 dark:text-green-200' : 'bg-red-50 dark:bg-red-900/30 border border-red-200 dark:border-red-700 text-red-800 dark:text-red-200' }}">
|
||||
{{ session($flash) }}
|
||||
</div>
|
||||
@endif
|
||||
@endforeach
|
||||
|
||||
@if($sourceType->description)
|
||||
<div class="bg-white shadow rounded-lg px-6 py-4 text-sm text-gray-700">{{ $sourceType->description }}</div>
|
||||
<div class="bg-white dark:bg-gray-800 shadow rounded-lg px-6 py-4 text-sm text-gray-700 dark:text-gray-300">{{ $sourceType->description }}</div>
|
||||
@endif
|
||||
|
||||
{{-- Champs existants --}}
|
||||
<div class="bg-white shadow rounded-lg overflow-hidden">
|
||||
<div class="px-6 py-4 border-b border-gray-200 flex items-center justify-between">
|
||||
<h3 class="font-medium text-gray-900">Champs ({{ $sourceType->fields->count() }})</h3>
|
||||
<span class="text-xs text-gray-400">Glisser-déposer pour réordonner</span>
|
||||
<div class="bg-white dark:bg-gray-800 shadow rounded-lg overflow-hidden">
|
||||
<div class="px-6 py-4 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between">
|
||||
<h3 class="font-medium text-gray-900 dark:text-white">Champs ({{ $sourceType->fields->count() }})</h3>
|
||||
<span class="text-xs text-gray-400 dark:text-gray-500">Glisser-déposer pour réordonner</span>
|
||||
</div>
|
||||
|
||||
@if($sourceType->fields->isNotEmpty())
|
||||
@@ -31,21 +31,21 @@
|
||||
@csrf
|
||||
</form>
|
||||
|
||||
<ul id="fields-list" class="divide-y divide-gray-100">
|
||||
<ul id="fields-list" class="divide-y divide-gray-100 dark:divide-gray-700">
|
||||
@foreach($sourceType->fields as $field)
|
||||
<li class="px-6 py-3 flex items-center gap-4" data-id="{{ $field->id }}">
|
||||
<span class="cursor-move text-gray-300 hover:text-gray-500 select-none">⠿</span>
|
||||
<span class="cursor-move text-gray-300 dark:text-gray-600 hover:text-gray-500 select-none">⠿</span>
|
||||
<div class="flex-1 grid grid-cols-4 gap-3 text-sm">
|
||||
<span class="font-mono text-indigo-700">{{ $field->name }}</span>
|
||||
<span class="text-gray-700">{{ $field->label }}</span>
|
||||
<span class="text-gray-500">{{ $field->type->value }}</span>
|
||||
<span class="text-gray-400">{{ $field->required ? 'Obligatoire' : 'Optionnel' }}</span>
|
||||
<span class="text-gray-700 dark:text-gray-300">{{ $field->label }}</span>
|
||||
<span class="text-gray-500 dark:text-gray-400">{{ $field->type->value }}</span>
|
||||
<span class="text-gray-400 dark:text-gray-500">{{ $field->required ? 'Obligatoire' : 'Optionnel' }}</span>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<button type="button"
|
||||
x-data="{ open: false }"
|
||||
@click="open = !open"
|
||||
class="text-xs text-gray-500 hover:text-indigo-600">Modifier</button>
|
||||
class="text-xs text-gray-500 dark:text-gray-400 hover:text-indigo-600">Modifier</button>
|
||||
<form method="POST" action="{{ route('admin.source-types.fields.destroy', [$sourceType, $field]) }}"
|
||||
x-data @submit.prevent="if(confirm('Supprimer ce champ ?')) $el.submit()">
|
||||
@csrf @method('DELETE')
|
||||
@@ -56,34 +56,34 @@
|
||||
@endforeach
|
||||
</ul>
|
||||
@else
|
||||
<p class="px-6 py-8 text-center text-gray-400 text-sm">Aucun champ défini. Ajoutez-en ci-dessous.</p>
|
||||
<p class="px-6 py-8 text-center text-gray-400 dark:text-gray-500 text-sm">Aucun champ défini. Ajoutez-en ci-dessous.</p>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- Ajouter un champ --}}
|
||||
<div class="bg-white shadow rounded-lg p-6">
|
||||
<h3 class="font-medium text-gray-900 mb-4">Ajouter un champ</h3>
|
||||
<div class="bg-white dark:bg-gray-800 shadow rounded-lg p-6">
|
||||
<h3 class="font-medium text-gray-900 dark:text-white mb-4">Ajouter un champ</h3>
|
||||
<form method="POST" action="{{ route('admin.source-types.fields.store', $sourceType) }}">
|
||||
@csrf
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">Nom technique <span class="text-red-500">*</span></label>
|
||||
<label class="block text-xs font-medium text-gray-600 dark:text-gray-400 mb-1">Nom technique <span class="text-red-500">*</span></label>
|
||||
<input type="text" name="name" value="{{ old('name') }}" required
|
||||
placeholder="ex: nom_pere"
|
||||
class="block w-full rounded-md border-gray-300 shadow-sm text-sm focus:border-indigo-500 focus:ring-indigo-500 @error('name') border-red-500 @enderror">
|
||||
class="block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm text-sm focus:border-indigo-500 focus:ring-indigo-500 @error('name') border-red-500 @enderror">
|
||||
@error('name') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">Libellé affiché <span class="text-red-500">*</span></label>
|
||||
<label class="block text-xs font-medium text-gray-600 dark:text-gray-400 mb-1">Libellé affiché <span class="text-red-500">*</span></label>
|
||||
<input type="text" name="label" value="{{ old('label') }}" required
|
||||
placeholder="ex: Nom du père"
|
||||
class="block w-full rounded-md border-gray-300 shadow-sm text-sm focus:border-indigo-500 focus:ring-indigo-500 @error('label') border-red-500 @enderror">
|
||||
class="block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm text-sm focus:border-indigo-500 focus:ring-indigo-500 @error('label') border-red-500 @enderror">
|
||||
@error('label') <p class="mt-1 text-xs text-red-600">{{ $message }}</p> @enderror
|
||||
</div>
|
||||
<div x-data="{ type: '{{ old('type', 'text') }}' }">
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">Type <span class="text-red-500">*</span></label>
|
||||
<label class="block text-xs font-medium text-gray-600 dark:text-gray-400 mb-1">Type <span class="text-red-500">*</span></label>
|
||||
<select name="type" x-model="type"
|
||||
class="block w-full rounded-md border-gray-300 shadow-sm text-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
class="block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm text-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
@foreach(\App\Enums\FieldType::cases() as $ft)
|
||||
<option value="{{ $ft->value }}" {{ old('type') === $ft->value ? 'selected' : '' }}>{{ $ft->value }}</option>
|
||||
@endforeach
|
||||
@@ -91,15 +91,15 @@
|
||||
|
||||
{{-- Options pour type=select --}}
|
||||
<div x-show="type === 'select'" x-cloak class="mt-3">
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">Options (une par ligne)</label>
|
||||
<label class="block text-xs font-medium text-gray-600 dark:text-gray-400 mb-1">Options (une par ligne)</label>
|
||||
<textarea name="options_raw" rows="3" placeholder="Option A Option B Option C"
|
||||
class="block w-full rounded-md border-gray-300 shadow-sm text-sm focus:border-indigo-500 focus:ring-indigo-500">{{ old('options_raw') }}</textarea>
|
||||
class="block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm text-sm focus:border-indigo-500 focus:ring-indigo-500">{{ old('options_raw') }}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-end">
|
||||
<label class="flex items-center gap-2 text-sm text-gray-700">
|
||||
<label class="flex items-center gap-2 text-sm text-gray-700 dark:text-gray-300">
|
||||
<input type="checkbox" name="required" value="1" {{ old('required') ? 'checked' : '' }}
|
||||
class="rounded border-gray-300 text-indigo-600 focus:ring-indigo-500">
|
||||
class="rounded border-gray-300 dark:border-gray-600 text-indigo-600 focus:ring-indigo-500">
|
||||
Champ obligatoire
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@@ -2,48 +2,48 @@
|
||||
<x-slot name="header">
|
||||
<div class="flex items-center gap-3">
|
||||
<a href="{{ route('admin.utilisateurs.index') }}" class="text-sm text-indigo-600 hover:underline">← Utilisateurs</a>
|
||||
<span class="text-gray-400">/</span>
|
||||
<h2 class="text-xl font-semibold text-gray-800">{{ $user->name }}</h2>
|
||||
<span class="text-gray-400 dark:text-gray-500">/</span>
|
||||
<h2 class="text-xl font-semibold text-gray-800 dark:text-gray-200">{{ $user->name }}</h2>
|
||||
</div>
|
||||
</x-slot>
|
||||
|
||||
<div class="py-8 max-w-2xl mx-auto px-4 sm:px-6 lg:px-8 space-y-6">
|
||||
|
||||
@if(session('success'))
|
||||
<div class="p-4 bg-green-50 border border-green-200 text-green-800 rounded-md">{{ session('success') }}</div>
|
||||
<div class="p-4 bg-green-50 dark:bg-green-900/30 border border-green-200 dark:border-green-700 text-green-800 dark:text-green-200 rounded-md">{{ session('success') }}</div>
|
||||
@endif
|
||||
@if(session('error'))
|
||||
<div class="p-4 bg-red-50 border border-red-200 text-red-800 rounded-md">{{ session('error') }}</div>
|
||||
<div class="p-4 bg-red-50 dark:bg-red-900/30 border border-red-200 dark:border-red-700 text-red-800 dark:text-red-200 rounded-md">{{ session('error') }}</div>
|
||||
@endif
|
||||
|
||||
{{-- Informations --}}
|
||||
<div class="bg-white shadow rounded-lg p-6 space-y-3">
|
||||
<h3 class="text-sm font-semibold text-gray-700 uppercase tracking-wide">Informations</h3>
|
||||
<div class="bg-white dark:bg-gray-800 shadow rounded-lg p-6 space-y-3">
|
||||
<h3 class="text-sm font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wide">Informations</h3>
|
||||
<dl class="grid grid-cols-2 gap-x-6 gap-y-3 text-sm">
|
||||
<dt class="text-gray-500">Nom</dt>
|
||||
<dd class="text-gray-900 font-medium">{{ $user->name }}</dd>
|
||||
<dt class="text-gray-500">E-mail</dt>
|
||||
<dd class="text-gray-900">{{ $user->email }}</dd>
|
||||
<dt class="text-gray-500">Inscrit le</dt>
|
||||
<dd class="text-gray-900">{{ $user->created_at->format('d/m/Y') }}</dd>
|
||||
<dt class="text-gray-500">Sections</dt>
|
||||
<dd class="text-gray-900">
|
||||
<dt class="text-gray-500 dark:text-gray-400">Nom</dt>
|
||||
<dd class="text-gray-900 dark:text-white font-medium">{{ $user->name }}</dd>
|
||||
<dt class="text-gray-500 dark:text-gray-400">E-mail</dt>
|
||||
<dd class="text-gray-900 dark:text-white">{{ $user->email }}</dd>
|
||||
<dt class="text-gray-500 dark:text-gray-400">Inscrit le</dt>
|
||||
<dd class="text-gray-900 dark:text-white">{{ $user->created_at->format('d/m/Y') }}</dd>
|
||||
<dt class="text-gray-500 dark:text-gray-400">Sections</dt>
|
||||
<dd class="text-gray-900 dark:text-white">
|
||||
@if($user->sections->isNotEmpty())
|
||||
{{ $user->sections->pluck('nom')->join(', ') }}
|
||||
@else
|
||||
—
|
||||
@endif
|
||||
</dd>
|
||||
<dt class="text-gray-500">Sources assignées</dt>
|
||||
<dd class="text-gray-900">{{ $user->sourcesAssignees->count() }}</dd>
|
||||
<dt class="text-gray-500 dark:text-gray-400">Sources assignées</dt>
|
||||
<dd class="text-gray-900 dark:text-white">{{ $user->sourcesAssignees->count() }}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
{{-- Statut actif / inactif --}}
|
||||
<div class="bg-white shadow rounded-lg p-6 flex items-center justify-between">
|
||||
<div class="bg-white dark:bg-gray-800 shadow rounded-lg p-6 flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-900">Statut du compte</p>
|
||||
<p class="text-sm text-gray-500 mt-0.5">
|
||||
<p class="text-sm font-medium text-gray-900 dark:text-white">Statut du compte</p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 mt-0.5">
|
||||
@if($user->is_active)
|
||||
Le compte est <span class="text-green-600 font-medium">actif</span> — l'utilisateur peut se connecter et être assigné à des sources.
|
||||
@else
|
||||
@@ -59,8 +59,8 @@
|
||||
<button type="submit"
|
||||
class="px-4 py-2 text-sm font-medium rounded-md
|
||||
{{ $user->is_active
|
||||
? 'bg-red-50 text-red-700 border border-red-200 hover:bg-red-100'
|
||||
: 'bg-green-50 text-green-700 border border-green-200 hover:bg-green-100' }}">
|
||||
? 'bg-red-50 dark:bg-red-900/30 text-red-700 border border-red-200 dark:border-red-700 hover:bg-red-100'
|
||||
: 'bg-green-50 dark:bg-green-900/30 text-green-700 border border-green-200 dark:border-green-700 hover:bg-green-100' }}">
|
||||
{{ $user->is_active ? 'Désactiver le compte' : 'Activer le compte' }}
|
||||
</button>
|
||||
</form>
|
||||
@@ -68,20 +68,20 @@
|
||||
</div>
|
||||
|
||||
{{-- Modifier le rôle --}}
|
||||
<div class="bg-white shadow rounded-lg p-6">
|
||||
<h3 class="text-sm font-semibold text-gray-700 uppercase tracking-wide mb-4">Rôle</h3>
|
||||
<div class="bg-white dark:bg-gray-800 shadow rounded-lg p-6">
|
||||
<h3 class="text-sm font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wide mb-4">Rôle</h3>
|
||||
<form method="POST" action="{{ route('admin.utilisateurs.update', $user) }}">
|
||||
@csrf @method('PUT')
|
||||
<div class="space-y-3">
|
||||
@foreach(\App\Enums\UserRole::cases() as $role)
|
||||
<label class="flex items-start gap-3 p-3 border rounded-lg cursor-pointer hover:bg-gray-50
|
||||
{{ $user->role === $role ? 'border-indigo-400 bg-indigo-50' : 'border-gray-200' }}">
|
||||
<label class="flex items-start gap-3 p-3 border rounded-lg cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-700
|
||||
{{ $user->role === $role ? 'border-indigo-400 bg-indigo-50 dark:bg-indigo-900/30' : 'border-gray-200 dark:border-gray-700' }}">
|
||||
<input type="radio" name="role" value="{{ $role->value }}"
|
||||
{{ $user->role === $role ? 'checked' : '' }}
|
||||
class="mt-0.5 text-indigo-600">
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-900">{{ $role->label() }}</p>
|
||||
<p class="text-xs text-gray-500 mt-0.5">
|
||||
<p class="text-sm font-medium text-gray-900 dark:text-white">{{ $role->label() }}</p>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 mt-0.5">
|
||||
@if($role === \App\Enums\UserRole::Admin)
|
||||
Accès complet : gestion des utilisateurs, sections, dépôts, types de sources et statistiques.
|
||||
@elseif($role === \App\Enums\UserRole::SectionManager)
|
||||
@@ -100,7 +100,7 @@
|
||||
Enregistrer
|
||||
</button>
|
||||
<a href="{{ route('admin.utilisateurs.index') }}"
|
||||
class="text-sm text-gray-500 self-center hover:text-gray-700">
|
||||
class="text-sm text-gray-500 dark:text-gray-400 self-center hover:text-gray-700 dark:hover:text-gray-300">
|
||||
Annuler
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="text-xl font-semibold text-gray-800">Importer des utilisateurs</h2>
|
||||
<h2 class="text-xl font-semibold text-gray-800 dark:text-gray-200">Importer des utilisateurs</h2>
|
||||
<a href="{{ route('admin.utilisateurs.index') }}"
|
||||
class="text-sm text-indigo-600 hover:underline">← Retour à la liste</a>
|
||||
</div>
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
{{-- Résultats d'import --}}
|
||||
@if(isset($results))
|
||||
<div class="bg-white shadow rounded-lg p-6 space-y-4">
|
||||
<div class="bg-white dark:bg-gray-800 shadow rounded-lg p-6 space-y-4">
|
||||
<div class="flex items-center gap-4">
|
||||
@if($created > 0)
|
||||
<span class="text-green-700 font-semibold text-sm">
|
||||
@@ -26,31 +26,31 @@
|
||||
</div>
|
||||
|
||||
@if($created > 0)
|
||||
<div class="p-3 bg-amber-50 border border-amber-200 rounded-lg text-xs text-amber-800">
|
||||
<div class="p-3 bg-amber-50 dark:bg-amber-900/30 border border-amber-200 rounded-lg text-xs text-amber-800">
|
||||
Les mots de passe temporaires ci-dessous sont affichés <strong>une seule fois</strong>.
|
||||
Notez-les et communiquez-les aux utilisateurs concernés. Ils pourront les changer via leur profil.
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full text-sm divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50">
|
||||
<table class="min-w-full text-sm divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<thead class="bg-gray-50 dark:bg-gray-700">
|
||||
<tr>
|
||||
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">Ligne</th>
|
||||
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">Nom</th>
|
||||
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">E-mail</th>
|
||||
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">Rôle</th>
|
||||
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">Mot de passe temporaire</th>
|
||||
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">Statut</th>
|
||||
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Ligne</th>
|
||||
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Nom</th>
|
||||
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">E-mail</th>
|
||||
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Rôle</th>
|
||||
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Mot de passe temporaire</th>
|
||||
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Statut</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 bg-white">
|
||||
<tbody class="divide-y divide-gray-200 dark:divide-gray-700 bg-white dark:bg-gray-800">
|
||||
@foreach($results as $r)
|
||||
<tr class="{{ $r['ok'] ? '' : 'bg-red-50' }}">
|
||||
<td class="px-4 py-2 text-gray-400">{{ $r['line'] }}</td>
|
||||
<td class="px-4 py-2 font-medium text-gray-900">{{ $r['name'] }}</td>
|
||||
<td class="px-4 py-2 text-gray-600">{{ $r['email'] }}</td>
|
||||
<td class="px-4 py-2 text-gray-500">{{ $r['role'] ?? '' }}</td>
|
||||
<tr class="{{ $r['ok'] ? '' : 'bg-red-50 dark:bg-red-900/30' }}">
|
||||
<td class="px-4 py-2 text-gray-400 dark:text-gray-500">{{ $r['line'] }}</td>
|
||||
<td class="px-4 py-2 font-medium text-gray-900 dark:text-white">{{ $r['name'] }}</td>
|
||||
<td class="px-4 py-2 text-gray-600 dark:text-gray-400">{{ $r['email'] }}</td>
|
||||
<td class="px-4 py-2 text-gray-500 dark:text-gray-400">{{ $r['role'] ?? '' }}</td>
|
||||
<td class="px-4 py-2 font-mono text-indigo-700">
|
||||
{{ $r['ok'] ? $r['password'] : '' }}
|
||||
</td>
|
||||
@@ -77,26 +77,26 @@
|
||||
@endif
|
||||
|
||||
{{-- Formulaire d'import --}}
|
||||
<div class="bg-white shadow rounded-lg p-6 space-y-5">
|
||||
<h3 class="text-sm font-semibold text-gray-700 uppercase tracking-wide">Importer un fichier CSV</h3>
|
||||
<div class="bg-white dark:bg-gray-800 shadow rounded-lg p-6 space-y-5">
|
||||
<h3 class="text-sm font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wide">Importer un fichier CSV</h3>
|
||||
|
||||
@if($errors->any())
|
||||
<div class="p-4 bg-red-50 border border-red-200 text-red-700 text-sm rounded-md space-y-1">
|
||||
<div class="p-4 bg-red-50 dark:bg-red-900/30 border border-red-200 dark:border-red-700 text-red-700 text-sm rounded-md space-y-1">
|
||||
@foreach($errors->all() as $e)<p>{{ $e }}</p>@endforeach
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="p-4 bg-blue-50 border border-blue-200 rounded-lg text-sm text-blue-800 space-y-1">
|
||||
<div class="p-4 bg-blue-50 border border-blue-200 rounded-lg text-sm text-blue-800 dark:text-blue-200 space-y-1">
|
||||
<p class="font-semibold">Format attendu du fichier CSV :</p>
|
||||
<ul class="list-disc list-inside text-xs space-y-0.5">
|
||||
<li>Séparateur : <code class="bg-blue-100 px-1 rounded">;</code> ou <code class="bg-blue-100 px-1 rounded">,</code></li>
|
||||
<li>Séparateur : <code class="bg-blue-100 dark:bg-blue-900/50 px-1 rounded">;</code> ou <code class="bg-blue-100 dark:bg-blue-900/50 px-1 rounded">,</code></li>
|
||||
<li>Encodage : UTF-8 (avec ou sans BOM)</li>
|
||||
<li>Colonnes obligatoires : <code class="bg-blue-100 px-1 rounded">name</code>, <code class="bg-blue-100 px-1 rounded">email</code>, <code class="bg-blue-100 px-1 rounded">role</code></li>
|
||||
<li>Colonne optionnelle : <code class="bg-blue-100 px-1 rounded">is_active</code> (1 ou 0, défaut : 1)</li>
|
||||
<li>Valeurs acceptées pour <code class="bg-blue-100 px-1 rounded">role</code> :
|
||||
<code class="bg-blue-100 px-1 rounded">member</code>,
|
||||
<code class="bg-blue-100 px-1 rounded">section_manager</code>,
|
||||
<code class="bg-blue-100 px-1 rounded">admin</code>
|
||||
<li>Colonnes obligatoires : <code class="bg-blue-100 dark:bg-blue-900/50 px-1 rounded">name</code>, <code class="bg-blue-100 dark:bg-blue-900/50 px-1 rounded">email</code>, <code class="bg-blue-100 dark:bg-blue-900/50 px-1 rounded">role</code></li>
|
||||
<li>Colonne optionnelle : <code class="bg-blue-100 dark:bg-blue-900/50 px-1 rounded">is_active</code> (1 ou 0, défaut : 1)</li>
|
||||
<li>Valeurs acceptées pour <code class="bg-blue-100 dark:bg-blue-900/50 px-1 rounded">role</code> :
|
||||
<code class="bg-blue-100 dark:bg-blue-900/50 px-1 rounded">member</code>,
|
||||
<code class="bg-blue-100 dark:bg-blue-900/50 px-1 rounded">section_manager</code>,
|
||||
<code class="bg-blue-100 dark:bg-blue-900/50 px-1 rounded">admin</code>
|
||||
</li>
|
||||
<li>Un mot de passe temporaire aléatoire sera généré pour chaque compte importé.</li>
|
||||
</ul>
|
||||
@@ -110,15 +110,15 @@
|
||||
enctype="multipart/form-data" class="space-y-4">
|
||||
@csrf
|
||||
<div>
|
||||
<label for="file" class="block text-sm font-medium text-gray-700 mb-1">
|
||||
<label for="file" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
Fichier CSV <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<input type="file" id="file" name="file" accept=".csv,.txt" required
|
||||
class="block w-full text-sm text-gray-500
|
||||
class="block w-full text-sm text-gray-500 dark:text-gray-400
|
||||
file:mr-4 file:py-2 file:px-4 file:rounded-md file:border-0
|
||||
file:text-sm file:font-medium file:bg-indigo-50 file:text-indigo-700
|
||||
hover:file:bg-indigo-100">
|
||||
<p class="mt-1 text-xs text-gray-400">Taille maximale : 2 Mo.</p>
|
||||
<p class="mt-1 text-xs text-gray-400 dark:text-gray-500">Taille maximale : 2 Mo.</p>
|
||||
@error('file') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="text-xl font-semibold text-gray-800">Gestion des utilisateurs</h2>
|
||||
<h2 class="text-xl font-semibold text-gray-800 dark:text-gray-200">Gestion des utilisateurs</h2>
|
||||
<div class="flex items-center gap-3">
|
||||
<a href="{{ route('admin.utilisateurs.import') }}"
|
||||
class="flex items-center gap-1.5 px-4 py-2 border border-gray-300 text-sm text-gray-700 rounded-md hover:bg-gray-50 transition-colors">
|
||||
class="flex items-center gap-1.5 px-4 py-2 border border-gray-300 dark:border-gray-600 text-sm text-gray-700 dark:text-gray-300 rounded-md hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"/>
|
||||
@@ -12,7 +12,7 @@
|
||||
Importer CSV
|
||||
</a>
|
||||
<a href="{{ route('admin.utilisateurs.export', request()->only(['role', 'status', 'q'])) }}"
|
||||
class="flex items-center gap-1.5 px-4 py-2 border border-gray-300 text-sm text-gray-700 rounded-md hover:bg-gray-50 transition-colors">
|
||||
class="flex items-center gap-1.5 px-4 py-2 border border-gray-300 dark:border-gray-600 text-sm text-gray-700 dark:text-gray-300 rounded-md hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4 4m0 0l4 4m-4-4h12"/>
|
||||
@@ -26,33 +26,33 @@
|
||||
<div class="py-8 max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 space-y-6">
|
||||
|
||||
@if(session('success'))
|
||||
<div class="p-4 bg-green-50 border border-green-200 text-green-800 text-sm rounded-md">
|
||||
<div class="p-4 bg-green-50 dark:bg-green-900/30 border border-green-200 dark:border-green-700 text-green-800 dark:text-green-200 text-sm rounded-md">
|
||||
{{ session('success') }}
|
||||
</div>
|
||||
@endif
|
||||
@if(session('error'))
|
||||
<div class="p-4 bg-red-50 border border-red-200 text-red-800 text-sm rounded-md">
|
||||
<div class="p-4 bg-red-50 dark:bg-red-900/30 border border-red-200 dark:border-red-700 text-red-800 dark:text-red-200 text-sm rounded-md">
|
||||
{{ session('error') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Filtres --}}
|
||||
@php $hasFilters = request()->anyFilled(['role', 'q', 'status']); @endphp
|
||||
<div class="bg-white shadow rounded-lg p-5">
|
||||
<div class="bg-white dark:bg-gray-800 shadow rounded-lg p-5">
|
||||
<form method="GET" action="{{ route('admin.utilisateurs.index') }}"
|
||||
class="flex flex-wrap items-end gap-4">
|
||||
<div class="flex-1 min-w-[180px]">
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">Recherche</label>
|
||||
<label class="block text-xs font-medium text-gray-600 dark:text-gray-400 mb-1">Recherche</label>
|
||||
<input type="text" name="q" value="{{ request('q') }}"
|
||||
placeholder="Nom ou e-mail…"
|
||||
class="block w-full rounded-md border-gray-300 shadow-sm text-sm
|
||||
class="block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm text-sm
|
||||
focus:border-indigo-500 focus:ring-indigo-500">
|
||||
</div>
|
||||
|
||||
<div class="w-44">
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">Rôle</label>
|
||||
<label class="block text-xs font-medium text-gray-600 dark:text-gray-400 mb-1">Rôle</label>
|
||||
<select name="role"
|
||||
class="block w-full rounded-md border-gray-300 shadow-sm text-sm
|
||||
class="block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm text-sm
|
||||
focus:border-indigo-500 focus:ring-indigo-500">
|
||||
<option value="">— Tous —</option>
|
||||
@foreach(\App\Enums\UserRole::cases() as $r)
|
||||
@@ -64,9 +64,9 @@
|
||||
</div>
|
||||
|
||||
<div class="w-40">
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">Statut</label>
|
||||
<label class="block text-xs font-medium text-gray-600 dark:text-gray-400 mb-1">Statut</label>
|
||||
<select name="status"
|
||||
class="block w-full rounded-md border-gray-300 shadow-sm text-sm
|
||||
class="block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm text-sm
|
||||
focus:border-indigo-500 focus:ring-indigo-500">
|
||||
<option value="">— Tous —</option>
|
||||
<option value="active" {{ request('status') === 'active' ? 'selected' : '' }}>Actifs</option>
|
||||
@@ -81,7 +81,7 @@
|
||||
</button>
|
||||
@if($hasFilters)
|
||||
<a href="{{ route('admin.utilisateurs.index') }}"
|
||||
class="px-4 py-2 border border-gray-300 text-gray-600 text-sm rounded-md hover:bg-gray-50">
|
||||
class="px-4 py-2 border border-gray-300 dark:border-gray-600 text-gray-600 dark:text-gray-400 text-sm rounded-md hover:bg-gray-50 dark:hover:bg-gray-700">
|
||||
Effacer
|
||||
</a>
|
||||
@endif
|
||||
@@ -90,52 +90,52 @@
|
||||
</div>
|
||||
|
||||
{{-- Tableau --}}
|
||||
<div class="bg-white shadow rounded-lg overflow-hidden">
|
||||
<table class="min-w-full divide-y divide-gray-200 text-sm">
|
||||
<thead class="bg-gray-50">
|
||||
<div class="bg-white dark:bg-gray-800 shadow rounded-lg overflow-hidden">
|
||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700 text-sm">
|
||||
<thead class="bg-gray-50 dark:bg-gray-700">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Nom</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">E-mail</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Rôle</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Sections</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Sources</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Inscrit le</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Nom</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">E-mail</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Rôle</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Sections</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Sources</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Inscrit le</th>
|
||||
<th class="px-6 py-3"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200">
|
||||
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
|
||||
@forelse($users as $user)
|
||||
@php
|
||||
$roleColors = [
|
||||
'admin' => 'bg-red-100 text-red-700',
|
||||
'section_manager' => 'bg-blue-100 text-blue-700',
|
||||
'member' => 'bg-gray-100 text-gray-600',
|
||||
'admin' => 'bg-red-100 dark:bg-red-900/50 text-red-700',
|
||||
'section_manager' => 'bg-blue-100 dark:bg-blue-900/50 text-blue-700',
|
||||
'member' => 'bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400',
|
||||
];
|
||||
$color = $roleColors[$user->role->value] ?? 'bg-gray-100 text-gray-600';
|
||||
$color = $roleColors[$user->role->value] ?? 'bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400';
|
||||
@endphp
|
||||
<tr class="hover:bg-gray-50 {{ ! $user->is_active ? 'opacity-60' : '' }}">
|
||||
<td class="px-6 py-4 font-medium text-gray-900">
|
||||
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700 {{ ! $user->is_active ? 'opacity-60' : '' }}">
|
||||
<td class="px-6 py-4 font-medium text-gray-900 dark:text-white">
|
||||
{{ $user->name }}
|
||||
@if($user->id === auth()->id())
|
||||
<span class="ml-1 text-xs text-gray-400">(vous)</span>
|
||||
<span class="ml-1 text-xs text-gray-400 dark:text-gray-500">(vous)</span>
|
||||
@endif
|
||||
@if(! $user->is_active)
|
||||
<span class="ml-2 inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-red-100 text-red-600">
|
||||
<span class="ml-2 inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-red-100 dark:bg-red-900/50 text-red-600">
|
||||
Inactif
|
||||
</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-6 py-4 text-gray-500">{{ $user->email }}</td>
|
||||
<td class="px-6 py-4 text-gray-500 dark:text-gray-400">{{ $user->email }}</td>
|
||||
<td class="px-6 py-4">
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium {{ $color }}">
|
||||
{{ $user->role->label() }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-gray-500">
|
||||
<td class="px-6 py-4 text-gray-500 dark:text-gray-400">
|
||||
{{ $user->sections->isNotEmpty() ? $user->sections->pluck('nom')->join(', ') : '—' }}
|
||||
</td>
|
||||
<td class="px-6 py-4 text-gray-500">{{ $user->sources_assignees_count ?: '—' }}</td>
|
||||
<td class="px-6 py-4 text-gray-500 whitespace-nowrap">
|
||||
<td class="px-6 py-4 text-gray-500 dark:text-gray-400">{{ $user->sources_assignees_count ?: '—' }}</td>
|
||||
<td class="px-6 py-4 text-gray-500 dark:text-gray-400 whitespace-nowrap">
|
||||
{{ $user->created_at->format('d/m/Y') }}
|
||||
</td>
|
||||
<td class="px-6 py-4 text-right space-x-3">
|
||||
@@ -157,7 +157,7 @@
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="7" class="px-6 py-10 text-center text-gray-400">
|
||||
<td colspan="7" class="px-6 py-10 text-center text-gray-400 dark:text-gray-500">
|
||||
Aucun utilisateur trouvé.
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<x-guest-layout>
|
||||
<div class="mb-4 text-sm text-gray-600">
|
||||
<div class="mb-4 text-sm text-gray-600 dark:text-gray-400">
|
||||
{{ __('This is a secure area of the application. Please confirm your password before continuing.') }}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<x-guest-layout>
|
||||
<div class="mb-4 text-sm text-gray-600">
|
||||
<div class="mb-4 text-sm text-gray-600 dark:text-gray-400">
|
||||
{{ __('Forgot your password? No problem. Just let us know your email address and we will email you a password reset link that will allow you to choose a new one.') }}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -27,14 +27,14 @@
|
||||
<!-- Remember Me -->
|
||||
<div class="block mt-4">
|
||||
<label for="remember_me" class="inline-flex items-center">
|
||||
<input id="remember_me" type="checkbox" class="rounded border-gray-300 text-indigo-600 shadow-sm focus:ring-indigo-500" name="remember">
|
||||
<span class="ms-2 text-sm text-gray-600">{{ __('Remember me') }}</span>
|
||||
<input id="remember_me" type="checkbox" class="rounded border-gray-300 dark:border-gray-600 text-indigo-600 shadow-sm focus:ring-indigo-500" name="remember">
|
||||
<span class="ms-2 text-sm text-gray-600 dark:text-gray-400">{{ __('Remember me') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end mt-4">
|
||||
@if (Route::has('password.request'))
|
||||
<a class="underline text-sm text-gray-600 hover:text-gray-900 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" href="{{ route('password.request') }}">
|
||||
<a class="underline text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" href="{{ route('password.request') }}">
|
||||
{{ __('Forgot your password?') }}
|
||||
</a>
|
||||
@endif
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end mt-4">
|
||||
<a class="underline text-sm text-gray-600 hover:text-gray-900 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" href="{{ route('login') }}">
|
||||
<a class="underline text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" href="{{ route('login') }}">
|
||||
{{ __('Already registered?') }}
|
||||
</a>
|
||||
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
<x-guest-layout>
|
||||
<div class="mb-6 text-center">
|
||||
<div class="inline-flex items-center justify-center w-14 h-14 rounded-full bg-indigo-100 mb-3">
|
||||
<div class="inline-flex items-center justify-center w-14 h-14 rounded-full bg-indigo-100 dark:bg-indigo-900/50 mb-3">
|
||||
<svg class="w-7 h-7 text-indigo-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h2 class="text-lg font-semibold text-gray-900">Vérification en deux étapes</h2>
|
||||
<p class="text-sm text-gray-500 mt-1">
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">Vérification en deux étapes</h2>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1">
|
||||
Un code à 6 chiffres a été envoyé à<br>
|
||||
<span class="font-medium text-gray-700">{{ $maskedEmail }}</span>
|
||||
<span class="font-medium text-gray-700 dark:text-gray-300">{{ $maskedEmail }}</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@if(session('resent'))
|
||||
<div class="mb-4 p-3 bg-green-50 border border-green-200 text-green-800 text-sm rounded-md text-center">
|
||||
<div class="mb-4 p-3 bg-green-50 dark:bg-green-900/30 border border-green-200 dark:border-green-700 text-green-800 dark:text-green-200 text-sm rounded-md text-center">
|
||||
Un nouveau code a été envoyé.
|
||||
</div>
|
||||
@endif
|
||||
@@ -28,7 +28,7 @@
|
||||
inputmode="numeric" pattern="[0-9]{6}" maxlength="6" autocomplete="one-time-code"
|
||||
autofocus required
|
||||
class="mt-1 block w-full text-center text-3xl tracking-[.5em] font-mono
|
||||
border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-md shadow-sm"
|
||||
border-gray-300 dark:border-gray-600 focus:border-indigo-500 focus:ring-indigo-500 rounded-md shadow-sm"
|
||||
placeholder="——————">
|
||||
<x-input-error :messages="$errors->get('pin')" class="mt-2"/>
|
||||
</div>
|
||||
@@ -45,7 +45,7 @@
|
||||
Renvoyer le code
|
||||
</button>
|
||||
</form>
|
||||
<a href="{{ route('login') }}" class="text-sm text-gray-400 hover:text-gray-600">
|
||||
<a href="{{ route('login') }}" class="text-sm text-gray-400 dark:text-gray-500 hover:text-gray-600">
|
||||
← Retour à la connexion
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<x-guest-layout>
|
||||
<div class="mb-4 text-sm text-gray-600">
|
||||
<div class="mb-4 text-sm text-gray-600 dark:text-gray-400">
|
||||
{{ __('Thanks for signing up! Before getting started, could you verify your email address by clicking on the link we just emailed to you? If you didn\'t receive the email, we will gladly send you another.') }}
|
||||
</div>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<form method="POST" action="{{ route('logout') }}">
|
||||
@csrf
|
||||
|
||||
<button type="submit" class="underline text-sm text-gray-600 hover:text-gray-900 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
|
||||
<button type="submit" class="underline text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
|
||||
{{ __('Log Out') }}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="text-xl font-semibold text-gray-800 dark:text-gray-200">Carte des relevés</h2>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400" id="carte-stats"></p>
|
||||
</div>
|
||||
</x-slot>
|
||||
|
||||
@push('head')
|
||||
{{-- Leaflet CSS --}}
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
|
||||
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin="">
|
||||
<style>
|
||||
#carte-map {
|
||||
height: calc(100vh - 120px);
|
||||
min-height: 400px;
|
||||
}
|
||||
/* Popup dark mode */
|
||||
.dark .leaflet-popup-content-wrapper,
|
||||
.dark .leaflet-popup-tip {
|
||||
background-color: #1f2937;
|
||||
color: #f3f4f6;
|
||||
}
|
||||
.dark .leaflet-popup-content-wrapper {
|
||||
border: 1px solid #374151;
|
||||
}
|
||||
/* Contrôles dark mode */
|
||||
.dark .leaflet-control-zoom a,
|
||||
.dark .leaflet-control-attribution {
|
||||
background-color: #1f2937;
|
||||
color: #d1d5db;
|
||||
border-color: #374151;
|
||||
}
|
||||
.dark .leaflet-control-zoom a:hover {
|
||||
background-color: #374151;
|
||||
}
|
||||
/* Tuiles légèrement assombries en dark mode */
|
||||
.dark .leaflet-tile {
|
||||
filter: brightness(0.7) invert(1) contrast(3) hue-rotate(200deg) saturate(0.3) brightness(0.7);
|
||||
}
|
||||
</style>
|
||||
@endpush
|
||||
|
||||
{{-- La carte occupe toute la hauteur disponible --}}
|
||||
<div id="carte-map" class="w-full"></div>
|
||||
|
||||
@push('head')
|
||||
{{-- Leaflet JS (chargé en fin de head pour ne pas bloquer le rendu) --}}
|
||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
|
||||
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV/XN2GqM8=" crossorigin=""
|
||||
defer></script>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
// ── Initialisation de la carte ──────────────────────────────────────
|
||||
const map = L.map('carte-map', {
|
||||
center: [46.5, 2.2], // centre de la France
|
||||
zoom: 6,
|
||||
zoomControl: true,
|
||||
});
|
||||
|
||||
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
maxZoom: 19,
|
||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>',
|
||||
}).addTo(map);
|
||||
|
||||
// ── Icône personnalisée ────────────────────────────────────────────
|
||||
function makeIcon(count) {
|
||||
const size = count > 10 ? 40 : count > 3 ? 34 : 28;
|
||||
const color = count > 10 ? '#4f46e5' : count > 3 ? '#6366f1' : '#818cf8';
|
||||
return L.divIcon({
|
||||
className: '',
|
||||
html: `<div style="
|
||||
width:${size}px; height:${size}px;
|
||||
background:${color};
|
||||
border:3px solid white;
|
||||
border-radius:50%;
|
||||
box-shadow:0 2px 6px rgba(0,0,0,.35);
|
||||
display:flex; align-items:center; justify-content:center;
|
||||
font-size:${size > 30 ? 13 : 11}px;
|
||||
font-weight:700; color:white; font-family:sans-serif;
|
||||
">${count}</div>`,
|
||||
iconSize: [size, size],
|
||||
iconAnchor: [size / 2, size / 2],
|
||||
popupAnchor: [0, -(size / 2 + 4)],
|
||||
});
|
||||
}
|
||||
|
||||
// ── Statut → badge couleur ─────────────────────────────────────────
|
||||
const statusColors = {
|
||||
'a_faire': 'background:#f3f4f6;color:#374151',
|
||||
'en_cours': 'background:#dbeafe;color:#1d4ed8',
|
||||
'a_valider': 'background:#fef9c3;color:#92400e',
|
||||
'termine': 'background:#d1fae5;color:#065f46',
|
||||
};
|
||||
|
||||
// ── Chargement des données ─────────────────────────────────────────
|
||||
fetch('{{ route('carte.data') }}')
|
||||
.then(r => r.json())
|
||||
.then(lieux => {
|
||||
if (lieux.length === 0) {
|
||||
document.getElementById('carte-stats').textContent =
|
||||
'Aucun lieu géolocalisé avec des relevés.';
|
||||
return;
|
||||
}
|
||||
|
||||
const totalReleves = lieux.reduce((s, l) => s + l.releves_count, 0);
|
||||
document.getElementById('carte-stats').textContent =
|
||||
`${lieux.length} lieu${lieux.length > 1 ? 'x' : ''} · ${totalReleves.toLocaleString('fr-FR')} relevé${totalReleves > 1 ? 's' : ''}`;
|
||||
|
||||
const bounds = [];
|
||||
|
||||
lieux.forEach(lieu => {
|
||||
bounds.push([lieu.lat, lieu.lng]);
|
||||
|
||||
// ── Contenu du popup ───────────────────────────────────
|
||||
const sourcesHtml = lieu.sources.map(s => {
|
||||
const style = statusColors[s.status_value] || statusColors['a_faire'];
|
||||
const annees = s.annees ? ` <span style="color:#6b7280;font-size:11px">(${s.annees})</span>` : '';
|
||||
return `<div style="display:flex;align-items:center;justify-content:space-between;gap:8px;padding:3px 0;border-bottom:1px solid #f3f4f6">
|
||||
<div style="min-width:0">
|
||||
<a href="/sources/${s.id}" style="color:#4f46e5;font-size:13px;font-weight:500;text-decoration:none"
|
||||
onmouseover="this.style.textDecoration='underline'"
|
||||
onmouseout="this.style.textDecoration='none'">${s.nom}</a>${annees}
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:6px;flex-shrink:0">
|
||||
<span style="font-size:11px;${style};padding:1px 6px;border-radius:9999px;white-space:nowrap">${s.status}</span>
|
||||
<span style="font-size:12px;color:#6b7280;white-space:nowrap">${s.releves_count.toLocaleString('fr-FR')} rel.</span>
|
||||
</div>
|
||||
</div>`;
|
||||
}).join('');
|
||||
|
||||
const popup = `
|
||||
<div style="min-width:260px;max-width:320px;font-family:sans-serif">
|
||||
<div style="font-size:15px;font-weight:700;margin-bottom:6px;padding-bottom:6px;border-bottom:2px solid #e5e7eb">
|
||||
📍 ${lieu.nom}
|
||||
</div>
|
||||
<div style="font-size:12px;color:#6b7280;margin-bottom:8px">
|
||||
${lieu.sources_count} source${lieu.sources_count > 1 ? 's' : ''} ·
|
||||
<strong>${lieu.releves_count.toLocaleString('fr-FR')}</strong> relevé${lieu.releves_count > 1 ? 's' : ''}
|
||||
</div>
|
||||
<div>${sourcesHtml}</div>
|
||||
<div style="margin-top:8px;text-align:right">
|
||||
<a href="/recherche?lieu_id=${lieu.id}"
|
||||
style="font-size:12px;color:#4f46e5;text-decoration:none"
|
||||
onmouseover="this.style.textDecoration='underline'"
|
||||
onmouseout="this.style.textDecoration='none'">
|
||||
Rechercher dans ces relevés →
|
||||
</a>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
L.marker([lieu.lat, lieu.lng], { icon: makeIcon(lieu.sources_count) })
|
||||
.addTo(map)
|
||||
.bindPopup(popup, { maxWidth: 340 });
|
||||
});
|
||||
|
||||
// Ajuster la vue sur tous les marqueurs
|
||||
if (bounds.length > 0) {
|
||||
map.fitBounds(bounds, { padding: [40, 40], maxZoom: 12 });
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
document.getElementById('carte-stats').textContent = 'Erreur lors du chargement des données.';
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
|
||||
</x-app-layout>
|
||||
@@ -1 +1 @@
|
||||
<a {{ $attributes->merge(['class' => 'block w-full px-4 py-2 text-start text-sm leading-5 text-gray-700 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 transition duration-150 ease-in-out']) }}>{{ $slot }}</a>
|
||||
<a {{ $attributes->merge(['class' => 'block w-full px-4 py-2 text-start text-sm leading-5 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 focus:outline-none focus:bg-gray-100 dark:focus:bg-gray-700 transition duration-150 ease-in-out']) }}>{{ $slot }}</a>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@props(['align' => 'right', 'width' => '48', 'contentClasses' => 'py-1 bg-white'])
|
||||
@props(['align' => 'right', 'width' => '48', 'contentClasses' => 'py-1 bg-white dark:bg-gray-800'])
|
||||
|
||||
@php
|
||||
$alignmentClasses = match ($align) {
|
||||
@@ -28,7 +28,7 @@ $width = match ($width) {
|
||||
class="absolute z-50 mt-2 {{ $width }} rounded-md shadow-lg {{ $alignmentClasses }}"
|
||||
style="display: none;"
|
||||
@click="open = false">
|
||||
<div class="rounded-md ring-1 ring-black ring-opacity-5 {{ $contentClasses }}">
|
||||
<div class="rounded-md ring-1 ring-black ring-opacity-5 dark:ring-gray-700 {{ $contentClasses }}">
|
||||
{{ $content }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
@props(['value'])
|
||||
|
||||
<label {{ $attributes->merge(['class' => 'block font-medium text-sm text-gray-700']) }}>
|
||||
<label {{ $attributes->merge(['class' => 'block font-medium text-sm text-gray-700 dark:text-gray-300']) }}>
|
||||
{{ $value ?? $slot }}
|
||||
</label>
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
}"
|
||||
@keydown.escape.window="open = false"
|
||||
>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
{{ $label }}
|
||||
@if($required) <span class="text-red-500">*</span> @endif
|
||||
</label>
|
||||
@@ -82,12 +82,17 @@
|
||||
<button
|
||||
type="button"
|
||||
@click="openModal()"
|
||||
class="flex-1 text-left px-3 py-2 border border-gray-300 rounded-md bg-white text-sm shadow-sm
|
||||
hover:border-indigo-400 focus:outline-none focus:ring-2 focus:ring-indigo-500 transition-colors
|
||||
class="flex-1 text-left px-3 py-2 border border-gray-300 dark:border-gray-600
|
||||
rounded-md bg-white dark:bg-gray-700 text-sm shadow-sm
|
||||
hover:border-indigo-400 dark:hover:border-indigo-500
|
||||
focus:outline-none focus:ring-2 focus:ring-indigo-500 transition-colors
|
||||
@error($name) border-red-500 @enderror"
|
||||
>
|
||||
<span x-show="selected.id" class="text-gray-900" x-text="selected.name"></span>
|
||||
<span x-show="!selected.id" class="text-gray-400">{{ $placeholder }}</span>
|
||||
<span x-show="selected.id"
|
||||
class="text-gray-900 dark:text-gray-100"
|
||||
x-text="selected.name"></span>
|
||||
<span x-show="!selected.id"
|
||||
class="text-gray-400 dark:text-gray-500">{{ $placeholder }}</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
@@ -95,7 +100,7 @@
|
||||
x-show="selected.id"
|
||||
@click="clear()"
|
||||
title="Effacer"
|
||||
class="px-2 py-2 text-gray-400 hover:text-red-500 transition-colors"
|
||||
class="px-2 py-2 text-gray-400 dark:text-gray-500 hover:text-red-500 dark:hover:text-red-400 transition-colors"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
||||
@@ -104,7 +109,7 @@
|
||||
</div>
|
||||
|
||||
@error($name)
|
||||
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
|
||||
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
|
||||
{{-- Modale de recherche --}}
|
||||
@@ -115,13 +120,15 @@
|
||||
@click.self="open = false"
|
||||
>
|
||||
{{-- Fond semi-transparent --}}
|
||||
<div class="absolute inset-0 bg-black/40" @click="open = false"></div>
|
||||
<div class="absolute inset-0 bg-black/40 dark:bg-black/60" @click="open = false"></div>
|
||||
|
||||
{{-- Panneau --}}
|
||||
<div class="relative bg-white rounded-xl shadow-2xl w-full max-w-lg z-10 overflow-hidden">
|
||||
{{-- En-tête --}}
|
||||
<div class="px-4 py-3 border-b border-gray-100 flex items-center gap-3">
|
||||
<svg class="w-5 h-5 text-gray-400 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<div class="relative bg-white dark:bg-gray-800 rounded-xl shadow-2xl dark:shadow-gray-900/50
|
||||
w-full max-w-lg z-10 overflow-hidden">
|
||||
|
||||
{{-- En-tête avec champ de recherche --}}
|
||||
<div class="px-4 py-3 border-b border-gray-100 dark:border-gray-700 flex items-center gap-3">
|
||||
<svg class="w-5 h-5 text-gray-400 dark:text-gray-500 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M21 21l-4.35-4.35M17 11A6 6 0 1 1 5 11a6 6 0 0 1 12 0z"/>
|
||||
</svg>
|
||||
@@ -131,10 +138,13 @@
|
||||
x-model="search"
|
||||
@input="onSearchInput()"
|
||||
placeholder="Nom, code INSEE…"
|
||||
class="flex-1 text-sm outline-none placeholder-gray-400"
|
||||
class="flex-1 text-sm outline-none bg-transparent
|
||||
text-gray-900 dark:text-gray-100
|
||||
placeholder-gray-400 dark:placeholder-gray-500
|
||||
focus:ring-0 border-none p-0"
|
||||
>
|
||||
<button type="button" @click="open = false"
|
||||
class="text-gray-400 hover:text-gray-600">
|
||||
class="text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
||||
</svg>
|
||||
@@ -144,39 +154,44 @@
|
||||
{{-- Résultats --}}
|
||||
<div class="max-h-80 overflow-y-auto">
|
||||
{{-- Chargement --}}
|
||||
<div x-show="loading" class="px-4 py-6 text-center text-sm text-gray-400">
|
||||
<div x-show="loading"
|
||||
class="px-4 py-6 text-center text-sm text-gray-400 dark:text-gray-500">
|
||||
Recherche…
|
||||
</div>
|
||||
|
||||
{{-- Aucun résultat --}}
|
||||
<div x-show="!loading && search.length > 0 && results.length === 0"
|
||||
class="px-4 py-6 text-center text-sm text-gray-400">
|
||||
class="px-4 py-6 text-center text-sm text-gray-400 dark:text-gray-500">
|
||||
Aucun lieu trouvé pour « <span x-text="search"></span> »
|
||||
</div>
|
||||
|
||||
{{-- Invite initiale --}}
|
||||
<div x-show="!loading && search.length === 0"
|
||||
class="px-4 py-6 text-center text-sm text-gray-400">
|
||||
class="px-4 py-6 text-center text-sm text-gray-400 dark:text-gray-500">
|
||||
Saisissez au moins une lettre pour rechercher
|
||||
</div>
|
||||
|
||||
{{-- Liste --}}
|
||||
{{-- Liste des résultats --}}
|
||||
<ul x-show="results.length > 0">
|
||||
<template x-for="lieu in results" :key="lieu.id">
|
||||
<li>
|
||||
<button
|
||||
type="button"
|
||||
@click="select(lieu)"
|
||||
class="w-full text-left px-4 py-3 hover:bg-indigo-50 flex items-center justify-between gap-3 transition-colors"
|
||||
class="w-full text-left px-4 py-3 flex items-center justify-between gap-3 transition-colors
|
||||
hover:bg-indigo-50 dark:hover:bg-indigo-900/30"
|
||||
>
|
||||
<div>
|
||||
<span class="text-sm font-medium text-gray-900" x-text="lieu.nom_long"></span>
|
||||
<span class="text-sm font-medium text-gray-900 dark:text-gray-100"
|
||||
x-text="lieu.nom_long"></span>
|
||||
<span x-show="lieu.code"
|
||||
class="ml-2 text-xs text-gray-400"
|
||||
class="ml-2 text-xs text-gray-400 dark:text-gray-500"
|
||||
x-text="lieu.code"></span>
|
||||
</div>
|
||||
<span x-show="lieu.type"
|
||||
class="shrink-0 text-xs px-2 py-0.5 bg-gray-100 text-gray-500 rounded-full"
|
||||
class="shrink-0 text-xs px-2 py-0.5 rounded-full
|
||||
bg-gray-100 dark:bg-gray-700
|
||||
text-gray-500 dark:text-gray-400"
|
||||
x-text="lieu.type"></span>
|
||||
</button>
|
||||
</li>
|
||||
@@ -186,9 +201,9 @@
|
||||
|
||||
@can('create', App\Models\Lieu::class)
|
||||
{{-- Pied : créer un nouveau lieu --}}
|
||||
<div class="border-t border-gray-100 px-4 py-2.5">
|
||||
<div class="border-t border-gray-100 dark:border-gray-700 px-4 py-2.5">
|
||||
<a href="{{ route('lieux.create') }}" target="_blank"
|
||||
class="text-xs text-indigo-600 hover:underline">
|
||||
class="text-xs text-indigo-600 dark:text-indigo-400 hover:underline">
|
||||
+ Créer un nouveau lieu
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -18,10 +18,8 @@ $maxWidth = [
|
||||
x-data="{
|
||||
show: @js($show),
|
||||
focusables() {
|
||||
// All focusable element types...
|
||||
let selector = 'a, button, input:not([type=\'hidden\']), textarea, select, details, [tabindex]:not([tabindex=\'-1\'])'
|
||||
return [...$el.querySelectorAll(selector)]
|
||||
// All non-disabled elements...
|
||||
.filter(el => ! el.hasAttribute('disabled'))
|
||||
},
|
||||
firstFocusable() { return this.focusables()[0] },
|
||||
@@ -60,12 +58,12 @@ $maxWidth = [
|
||||
x-transition:leave-start="opacity-100"
|
||||
x-transition:leave-end="opacity-0"
|
||||
>
|
||||
<div class="absolute inset-0 bg-gray-500 opacity-75"></div>
|
||||
<div class="absolute inset-0 bg-gray-500 dark:bg-gray-900 opacity-75"></div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
x-show="show"
|
||||
class="mb-6 bg-white rounded-lg overflow-hidden shadow-xl transform transition-all sm:w-full {{ $maxWidth }} sm:mx-auto"
|
||||
class="mb-6 bg-white dark:bg-gray-800 rounded-lg overflow-hidden shadow-xl dark:shadow-gray-900/50 transform transition-all sm:w-full {{ $maxWidth }} sm:mx-auto"
|
||||
x-transition:enter="ease-out duration-300"
|
||||
x-transition:enter-start="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100"
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
@php
|
||||
$classes = ($active ?? false)
|
||||
? 'inline-flex items-center px-1 pt-1 border-b-2 border-indigo-400 text-sm font-medium leading-5 text-gray-900 focus:outline-none focus:border-indigo-700 transition duration-150 ease-in-out'
|
||||
: 'inline-flex items-center px-1 pt-1 border-b-2 border-transparent text-sm font-medium leading-5 text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:outline-none focus:text-gray-700 focus:border-gray-300 transition duration-150 ease-in-out';
|
||||
? 'inline-flex items-center px-1 pt-1 border-b-2 border-indigo-400 text-sm font-medium leading-5 text-gray-900 dark:text-white focus:outline-none focus:border-indigo-700 transition duration-150 ease-in-out'
|
||||
: 'inline-flex items-center px-1 pt-1 border-b-2 border-transparent text-sm font-medium leading-5 text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 hover:border-gray-300 dark:hover:border-gray-600 focus:outline-none focus:text-gray-700 dark:focus:text-gray-200 focus:border-gray-300 dark:focus:border-gray-600 transition duration-150 ease-in-out';
|
||||
@endphp
|
||||
|
||||
<a {{ $attributes->merge(['class' => $classes]) }}>
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<button {{ $attributes->merge(['type' => 'submit', 'class' => 'inline-flex items-center px-4 py-2 bg-gray-800 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-gray-700 focus:bg-gray-700 active:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 transition ease-in-out duration-150']) }}>
|
||||
<button {{ $attributes->merge(['type' => 'submit', 'class' => 'inline-flex items-center px-4 py-2 bg-gray-800 dark:bg-gray-200 border border-transparent rounded-md font-semibold text-xs text-white dark:text-gray-800 uppercase tracking-widest hover:bg-gray-700 dark:hover:bg-gray-300 focus:bg-gray-700 active:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800 transition ease-in-out duration-150']) }}>
|
||||
{{ $slot }}
|
||||
</button>
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
@php
|
||||
$classes = ($active ?? false)
|
||||
? 'block w-full ps-3 pe-4 py-2 border-l-4 border-indigo-400 text-start text-base font-medium text-indigo-700 bg-indigo-50 focus:outline-none focus:text-indigo-800 focus:bg-indigo-100 focus:border-indigo-700 transition duration-150 ease-in-out'
|
||||
: 'block w-full ps-3 pe-4 py-2 border-l-4 border-transparent text-start text-base font-medium text-gray-600 hover:text-gray-800 hover:bg-gray-50 hover:border-gray-300 focus:outline-none focus:text-gray-800 focus:bg-gray-50 focus:border-gray-300 transition duration-150 ease-in-out';
|
||||
? 'block w-full ps-3 pe-4 py-2 border-l-4 border-indigo-400 text-start text-base font-medium text-indigo-700 dark:text-indigo-300 bg-indigo-50 dark:bg-indigo-900/30 focus:outline-none focus:text-indigo-800 focus:bg-indigo-100 focus:border-indigo-700 transition duration-150 ease-in-out'
|
||||
: 'block w-full ps-3 pe-4 py-2 border-l-4 border-transparent text-start text-base font-medium text-gray-600 dark:text-gray-400 hover:text-gray-800 dark:hover:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-700 hover:border-gray-300 dark:hover:border-gray-600 focus:outline-none focus:text-gray-800 focus:bg-gray-50 focus:border-gray-300 transition duration-150 ease-in-out';
|
||||
@endphp
|
||||
|
||||
<a {{ $attributes->merge(['class' => $classes]) }}>
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<button {{ $attributes->merge(['type' => 'button', 'class' => 'inline-flex items-center px-4 py-2 bg-white border border-gray-300 rounded-md font-semibold text-xs text-gray-700 uppercase tracking-widest shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 disabled:opacity-25 transition ease-in-out duration-150']) }}>
|
||||
<button {{ $attributes->merge(['type' => 'button', 'class' => 'inline-flex items-center px-4 py-2 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md font-semibold text-xs text-gray-700 dark:text-gray-300 uppercase tracking-widest shadow-sm hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800 disabled:opacity-25 transition ease-in-out duration-150']) }}>
|
||||
{{ $slot }}
|
||||
</button>
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
@props(['disabled' => false])
|
||||
|
||||
<input @disabled($disabled) {{ $attributes->merge(['class' => 'border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-md shadow-sm']) }}>
|
||||
<input @disabled($disabled) {{ $attributes->merge(['class' => 'border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100 dark:placeholder-gray-400 focus:border-indigo-500 dark:focus:border-indigo-400 focus:ring-indigo-500 dark:focus:ring-indigo-400 rounded-md shadow-sm']) }}>
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
{{--
|
||||
Sélecteur d'utilisateur avec recherche (fenêtre modale).
|
||||
Props :
|
||||
name — nom du champ hidden (défaut : user_id)
|
||||
users — collection/array d'objets avec {id, name, email}
|
||||
placeholder — texte affiché avant sélection
|
||||
required — ajoute l'attribut required sur le champ hidden (pour validation formulaire)
|
||||
--}}
|
||||
@props([
|
||||
'name' => 'user_id',
|
||||
'users' => [],
|
||||
'placeholder' => 'Sélectionner un utilisateur…',
|
||||
'required' => false,
|
||||
])
|
||||
|
||||
@php
|
||||
$usersJson = collect($users)->map(fn ($u) => [
|
||||
'id' => $u->id,
|
||||
'name' => $u->name,
|
||||
'email' => $u->email,
|
||||
])->values()->toJson();
|
||||
@endphp
|
||||
|
||||
<div x-data="{
|
||||
open: false,
|
||||
search: '',
|
||||
selectedId: null,
|
||||
selectedLabel: '',
|
||||
users: {{ $usersJson }},
|
||||
get filtered() {
|
||||
if (! this.search.trim()) return this.users;
|
||||
const q = this.search.toLowerCase();
|
||||
return this.users.filter(u =>
|
||||
u.name.toLowerCase().includes(q) || u.email.toLowerCase().includes(q)
|
||||
);
|
||||
},
|
||||
select(u) {
|
||||
this.selectedId = u.id;
|
||||
this.selectedLabel = u.name + ' (' + u.email + ')';
|
||||
this.open = false;
|
||||
this.search = '';
|
||||
},
|
||||
clear() {
|
||||
this.selectedId = null;
|
||||
this.selectedLabel = '';
|
||||
}
|
||||
}" @keydown.escape.window="open = false">
|
||||
|
||||
<input type="hidden" name="{{ $name }}" :value="selectedId" {{ $required ? 'required' : '' }}>
|
||||
|
||||
{{-- Bouton déclencheur --}}
|
||||
<button type="button" @click="open = true"
|
||||
class="w-full flex items-center justify-between gap-2 px-3 py-2
|
||||
bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600
|
||||
rounded-md shadow-sm text-sm text-left
|
||||
hover:bg-gray-50 dark:hover:bg-gray-600 transition-colors
|
||||
focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-1 dark:focus:ring-offset-gray-800">
|
||||
<span :class="selectedId ? 'text-gray-900 dark:text-gray-100' : 'text-gray-400 dark:text-gray-500'"
|
||||
x-text="selectedLabel || '{{ $placeholder }}'"></span>
|
||||
<svg class="w-4 h-4 text-gray-400 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{{-- Fenêtre modale --}}
|
||||
<div x-show="open" x-cloak
|
||||
class="fixed inset-0 z-50 flex items-start justify-center pt-16 sm:pt-24 px-4"
|
||||
x-transition:enter="transition ease-out duration-150"
|
||||
x-transition:enter-start="opacity-0"
|
||||
x-transition:enter-end="opacity-100"
|
||||
x-transition:leave="transition ease-in duration-100"
|
||||
x-transition:leave-start="opacity-100"
|
||||
x-transition:leave-end="opacity-0">
|
||||
|
||||
{{-- Fond semi-transparent --}}
|
||||
<div class="absolute inset-0 bg-gray-900/50 dark:bg-gray-950/70" @click="open = false"></div>
|
||||
|
||||
{{-- Panneau --}}
|
||||
<div class="relative w-full max-w-md bg-white dark:bg-gray-800 rounded-xl shadow-xl dark:shadow-gray-900/50 overflow-hidden"
|
||||
@click.stop
|
||||
x-transition:enter="transition ease-out duration-150"
|
||||
x-transition:enter-start="opacity-0 -translate-y-2"
|
||||
x-transition:enter-end="opacity-100 translate-y-0">
|
||||
|
||||
{{-- Champ de recherche --}}
|
||||
<div class="flex items-center gap-2 px-4 py-3 border-b border-gray-200 dark:border-gray-700">
|
||||
<svg class="w-4 h-4 text-gray-400 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
|
||||
</svg>
|
||||
<input type="text" x-model="search" x-ref="searchInput"
|
||||
x-init="$watch('open', v => v && $nextTick(() => $refs.searchInput && $refs.searchInput.focus()))"
|
||||
placeholder="Rechercher par nom ou email…"
|
||||
class="flex-1 bg-transparent border-none outline-none p-0 text-sm
|
||||
text-gray-900 dark:text-gray-100
|
||||
placeholder-gray-400 dark:placeholder-gray-500
|
||||
focus:ring-0">
|
||||
<button type="button" @click="open = false"
|
||||
class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{{-- Liste des utilisateurs --}}
|
||||
<div class="overflow-y-auto" style="max-height: 18rem;">
|
||||
<template x-if="filtered.length === 0">
|
||||
<p class="px-4 py-8 text-center text-sm text-gray-400 dark:text-gray-500">
|
||||
Aucun résultat pour « <span x-text="search"></span> »
|
||||
</p>
|
||||
</template>
|
||||
<template x-for="u in filtered" :key="u.id">
|
||||
<button type="button" @click="select(u)"
|
||||
class="w-full flex items-center gap-3 px-4 py-2.5 text-left transition-colors
|
||||
hover:bg-gray-50 dark:hover:bg-gray-700"
|
||||
:class="selectedId === u.id
|
||||
? 'bg-indigo-50 dark:bg-indigo-900/30'
|
||||
: ''">
|
||||
{{-- Avatar initiale --}}
|
||||
<div class="w-8 h-8 rounded-full bg-indigo-100 dark:bg-indigo-900/50
|
||||
flex items-center justify-center shrink-0">
|
||||
<span class="text-xs font-semibold text-indigo-700 dark:text-indigo-300"
|
||||
x-text="u.name.charAt(0).toUpperCase()"></span>
|
||||
</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="text-sm font-medium text-gray-900 dark:text-gray-100 truncate"
|
||||
x-text="u.name"></p>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 truncate"
|
||||
x-text="u.email"></p>
|
||||
</div>
|
||||
<template x-if="selectedId === u.id">
|
||||
<svg class="w-4 h-4 text-indigo-600 dark:text-indigo-400 shrink-0"
|
||||
fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd"
|
||||
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
|
||||
clip-rule="evenodd"/>
|
||||
</svg>
|
||||
</template>
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
{{-- Pied : compteur --}}
|
||||
<div class="px-4 py-2 border-t border-gray-100 dark:border-gray-700
|
||||
text-xs text-gray-400 dark:text-gray-500 flex items-center justify-between">
|
||||
<span>
|
||||
<span x-text="filtered.length"></span> /
|
||||
<span x-text="users.length"></span> utilisateur<span x-show="users.length !== 1">s</span>
|
||||
</span>
|
||||
<button x-show="selectedId" type="button" @click="clear()"
|
||||
class="text-red-500 hover:text-red-700 dark:hover:text-red-400 transition-colors">
|
||||
Effacer la sélection
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,6 +1,6 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="text-xl font-semibold text-gray-800">Tableau de bord</h2>
|
||||
<h2 class="text-xl font-semibold text-gray-800 dark:text-gray-200">Tableau de bord</h2>
|
||||
</x-slot>
|
||||
|
||||
<div class="py-8 max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 space-y-8">
|
||||
@@ -9,9 +9,9 @@
|
||||
|
||||
{{-- Bloc admin : lien vers le dashboard admin --}}
|
||||
@if($user->isAdmin())
|
||||
<div class="bg-indigo-50 border border-indigo-200 rounded-xl p-5 flex items-center justify-between">
|
||||
<div class="bg-indigo-50 dark:bg-indigo-900/30 border border-indigo-200 dark:border-indigo-700 rounded-xl p-5 flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm font-semibold text-indigo-800">Accès administrateur</p>
|
||||
<p class="text-sm font-semibold text-indigo-800 dark:text-indigo-200">Accès administrateur</p>
|
||||
<p class="text-xs text-indigo-600 mt-0.5">Statistiques globales, gestion des utilisateurs et des sections.</p>
|
||||
</div>
|
||||
<a href="{{ route('admin.dashboard') }}"
|
||||
@@ -26,10 +26,10 @@
|
||||
@foreach($sectionsStats as $stat)
|
||||
@php
|
||||
$statusColors = [
|
||||
'a_faire' => ['bg' => 'bg-gray-100', 'text' => 'text-gray-700'],
|
||||
'en_cours' => ['bg' => 'bg-blue-100', 'text' => 'text-blue-700'],
|
||||
'a_valider' => ['bg' => 'bg-yellow-100', 'text' => 'text-yellow-700'],
|
||||
'termine' => ['bg' => 'bg-green-100', 'text' => 'text-green-700'],
|
||||
'a_faire' => ['bg' => 'bg-gray-100 dark:bg-gray-700', 'text' => 'text-gray-700 dark:text-gray-300'],
|
||||
'en_cours' => ['bg' => 'bg-blue-100 dark:bg-blue-900/50', 'text' => 'text-blue-700'],
|
||||
'a_valider' => ['bg' => 'bg-yellow-100 dark:bg-yellow-900/50', 'text' => 'text-yellow-700'],
|
||||
'termine' => ['bg' => 'bg-green-100 dark:bg-green-900/50', 'text' => 'text-green-700'],
|
||||
];
|
||||
$statusLabels = [
|
||||
'a_faire' => 'À faire',
|
||||
@@ -39,17 +39,17 @@
|
||||
];
|
||||
@endphp
|
||||
<div class="space-y-4">
|
||||
<h3 class="text-base font-semibold text-gray-800 flex items-center gap-2">
|
||||
<h3 class="text-base font-semibold text-gray-800 dark:text-gray-200 flex items-center gap-2">
|
||||
<span>Section — {{ $stat['section']->nom }}</span>
|
||||
@if($user->isManagerOfSection($stat['section']))
|
||||
<span class="text-xs px-2 py-0.5 bg-blue-100 text-blue-700 rounded-full">Responsable</span>
|
||||
<span class="text-xs px-2 py-0.5 bg-blue-100 dark:bg-blue-900/50 text-blue-700 rounded-full">Responsable</span>
|
||||
@endif
|
||||
</h3>
|
||||
|
||||
{{-- Compteurs par statut --}}
|
||||
<div class="grid grid-cols-2 sm:grid-cols-4 gap-3">
|
||||
@foreach($stat['by_status'] as $statusVal => $count)
|
||||
@php $c = $statusColors[$statusVal] ?? ['bg' => 'bg-gray-100', 'text' => 'text-gray-700']; @endphp
|
||||
@php $c = $statusColors[$statusVal] ?? ['bg' => 'bg-gray-100 dark:bg-gray-700', 'text' => 'text-gray-700 dark:text-gray-300']; @endphp
|
||||
<a href="{{ route('sources.index', ['status' => $statusVal]) }}"
|
||||
class="rounded-xl border p-4 flex flex-col gap-1 hover:shadow-md transition-shadow
|
||||
{{ $c['bg'] }} border-transparent">
|
||||
@@ -62,38 +62,38 @@
|
||||
|
||||
{{-- Métriques globales section --}}
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="bg-white border border-gray-200 rounded-xl px-5 py-4 flex items-center gap-4">
|
||||
<div class="p-2 bg-indigo-100 rounded-lg">
|
||||
<div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl px-5 py-4 flex items-center gap-4">
|
||||
<div class="p-2 bg-indigo-100 dark:bg-indigo-900/50 rounded-lg">
|
||||
<svg class="w-5 h-5 text-indigo-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 8h14M5 8a2 2 0 110-4h14a2 2 0 110 4M5 8v10a2 2 0 002 2h10a2 2 0 002-2V8"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xl font-bold text-gray-900">{{ $stat['total_sources'] }}</p>
|
||||
<p class="text-xs text-gray-500">source{{ $stat['total_sources'] > 1 ? 's' : '' }} au total</p>
|
||||
<p class="text-xl font-bold text-gray-900 dark:text-white">{{ $stat['total_sources'] }}</p>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">source{{ $stat['total_sources'] > 1 ? 's' : '' }} au total</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-white border border-gray-200 rounded-xl px-5 py-4 flex items-center gap-4">
|
||||
<div class="p-2 bg-green-100 rounded-lg">
|
||||
<div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl px-5 py-4 flex items-center gap-4">
|
||||
<div class="p-2 bg-green-100 dark:bg-green-900/50 rounded-lg">
|
||||
<svg class="w-5 h-5 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xl font-bold text-gray-900">{{ number_format($stat['total_releves']) }}</p>
|
||||
<p class="text-xs text-gray-500">relevé{{ $stat['total_releves'] > 1 ? 's' : '' }} saisi{{ $stat['total_releves'] > 1 ? 's' : '' }}</p>
|
||||
<p class="text-xl font-bold text-gray-900 dark:text-white">{{ number_format($stat['total_releves']) }}</p>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">relevé{{ $stat['total_releves'] > 1 ? 's' : '' }} saisi{{ $stat['total_releves'] > 1 ? 's' : '' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Sources récentes de la section --}}
|
||||
@if($stat['sources_recentes']->isNotEmpty())
|
||||
<div class="bg-white border border-gray-200 rounded-xl p-5">
|
||||
<p class="text-xs font-semibold text-gray-500 uppercase mb-3">Sources récentes</p>
|
||||
<div class="divide-y divide-gray-100">
|
||||
<div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl p-5">
|
||||
<p class="text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase mb-3">Sources récentes</p>
|
||||
<div class="divide-y divide-gray-100 dark:divide-gray-700">
|
||||
@foreach($stat['sources_recentes'] as $src)
|
||||
@php
|
||||
$sc = $statusColors[$src->status->value] ?? ['bg' => 'bg-gray-100', 'text' => 'text-gray-600'];
|
||||
$sc = $statusColors[$src->status->value] ?? ['bg' => 'bg-gray-100 dark:bg-gray-700', 'text' => 'text-gray-600 dark:text-gray-400'];
|
||||
@endphp
|
||||
<div class="flex items-center justify-between py-2.5">
|
||||
<div class="min-w-0">
|
||||
@@ -101,7 +101,7 @@
|
||||
class="text-sm font-medium text-indigo-600 hover:underline truncate block">
|
||||
{{ $src->nom }}
|
||||
</a>
|
||||
<p class="text-xs text-gray-400">
|
||||
<p class="text-xs text-gray-400 dark:text-gray-500">
|
||||
{{ $src->sourceType->nom }} · {{ $src->releves_count }} relevé{{ $src->releves_count > 1 ? 's' : '' }}
|
||||
</p>
|
||||
</div>
|
||||
@@ -119,45 +119,45 @@
|
||||
|
||||
{{-- ── Mes sources assignées ────────────────────────────────────────── --}}
|
||||
@if($mesSources->isNotEmpty())
|
||||
<div class="bg-white border border-gray-200 rounded-xl p-6">
|
||||
<div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl p-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="text-sm font-semibold text-gray-700 uppercase tracking-wide">Mes sources assignées</h3>
|
||||
<h3 class="text-sm font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wide">Mes sources assignées</h3>
|
||||
<a href="{{ route('sources.index') }}" class="text-xs text-indigo-600 hover:underline">Voir toutes</a>
|
||||
</div>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full text-sm divide-y divide-gray-100">
|
||||
<table class="min-w-full text-sm divide-y divide-gray-100 dark:divide-gray-700">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="pb-2 text-left text-xs font-medium text-gray-500 uppercase">Source</th>
|
||||
<th class="pb-2 text-left text-xs font-medium text-gray-500 uppercase">Type</th>
|
||||
<th class="pb-2 text-left text-xs font-medium text-gray-500 uppercase">Statut</th>
|
||||
<th class="pb-2 text-left text-xs font-medium text-gray-500 uppercase">Relevés</th>
|
||||
<th class="pb-2 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Source</th>
|
||||
<th class="pb-2 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Type</th>
|
||||
<th class="pb-2 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Statut</th>
|
||||
<th class="pb-2 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Relevés</th>
|
||||
<th class="pb-2"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-50">
|
||||
<tbody class="divide-y divide-gray-50 dark:divide-gray-700">
|
||||
@foreach($mesSources as $source)
|
||||
@php
|
||||
$colors = [
|
||||
'a_faire' => 'bg-gray-100 text-gray-600',
|
||||
'en_cours' => 'bg-blue-100 text-blue-700',
|
||||
'a_valider' => 'bg-yellow-100 text-yellow-700',
|
||||
'termine' => 'bg-green-100 text-green-700',
|
||||
'a_faire' => 'bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400',
|
||||
'en_cours' => 'bg-blue-100 dark:bg-blue-900/50 text-blue-700',
|
||||
'a_valider' => 'bg-yellow-100 dark:bg-yellow-900/50 text-yellow-700',
|
||||
'termine' => 'bg-green-100 dark:bg-green-900/50 text-green-700',
|
||||
];
|
||||
$c = $colors[$source->status->value] ?? 'bg-gray-100 text-gray-600';
|
||||
$c = $colors[$source->status->value] ?? 'bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400';
|
||||
@endphp
|
||||
<tr class="hover:bg-gray-50">
|
||||
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700">
|
||||
<td class="py-2.5 pr-4">
|
||||
<a href="{{ route('sources.show', $source) }}"
|
||||
class="font-medium text-indigo-600 hover:underline">{{ $source->nom }}</a>
|
||||
</td>
|
||||
<td class="py-2.5 pr-4 text-gray-500">{{ $source->sourceType->nom }}</td>
|
||||
<td class="py-2.5 pr-4 text-gray-500 dark:text-gray-400">{{ $source->sourceType->nom }}</td>
|
||||
<td class="py-2.5 pr-4">
|
||||
<span class="inline-flex px-2 py-0.5 rounded-full text-xs font-medium {{ $c }}">
|
||||
{{ $source->status->label() }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="py-2.5 pr-4 text-gray-500">{{ $source->releves_count }}</td>
|
||||
<td class="py-2.5 pr-4 text-gray-500 dark:text-gray-400">{{ $source->releves_count }}</td>
|
||||
<td class="py-2.5 text-right">
|
||||
<a href="{{ route('sources.releves.index', $source) }}"
|
||||
class="text-xs text-indigo-600 hover:underline">Saisir →</a>
|
||||
@@ -169,8 +169,8 @@
|
||||
</div>
|
||||
</div>
|
||||
@elseif(! $sectionsStats || $sectionsStats->isEmpty())
|
||||
<div class="bg-white border border-gray-200 rounded-xl p-10 text-center text-gray-400">
|
||||
<svg class="mx-auto w-10 h-10 mb-3 text-gray-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl p-10 text-center text-gray-400 dark:text-gray-500">
|
||||
<svg class="mx-auto w-10 h-10 mb-3 text-gray-300 dark:text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"
|
||||
d="M5 8h14M5 8a2 2 0 110-4h14a2 2 0 110 4M5 8v10a2 2 0 002 2h10a2 2 0 002-2V8"/>
|
||||
</svg>
|
||||
@@ -183,22 +183,22 @@
|
||||
|
||||
{{-- ── Mes derniers relevés ─────────────────────────────────────────── --}}
|
||||
@if($mesReleves->isNotEmpty())
|
||||
<div class="bg-white border border-gray-200 rounded-xl p-6">
|
||||
<div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl p-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="text-sm font-semibold text-gray-700 uppercase tracking-wide">Mes derniers relevés</h3>
|
||||
<h3 class="text-sm font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wide">Mes derniers relevés</h3>
|
||||
<a href="{{ route('recherche') }}" class="text-xs text-indigo-600 hover:underline">Recherche</a>
|
||||
</div>
|
||||
<div class="space-y-0 divide-y divide-gray-100">
|
||||
<div class="space-y-0 divide-y divide-gray-100 dark:divide-gray-700">
|
||||
@foreach($mesReleves as $releve)
|
||||
<div class="flex items-center justify-between py-2.5">
|
||||
<div class="min-w-0">
|
||||
<a href="{{ route('releves.show', $releve) }}"
|
||||
class="text-sm font-medium text-gray-900 hover:text-indigo-600">
|
||||
class="text-sm font-medium text-gray-900 dark:text-white hover:text-indigo-600">
|
||||
{{ $releve->nom ?? '—' }}{{ $releve->prenom ? ', ' . $releve->prenom : '' }}
|
||||
</a>
|
||||
<p class="text-xs text-gray-400">{{ $releve->source->nom }}</p>
|
||||
<p class="text-xs text-gray-400 dark:text-gray-500">{{ $releve->source->nom }}</p>
|
||||
</div>
|
||||
<span class="text-xs text-gray-400 whitespace-nowrap ml-4">
|
||||
<span class="text-xs text-gray-400 dark:text-gray-500 whitespace-nowrap ml-4">
|
||||
{{ $releve->created_at->diffForHumans() }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}" class="">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
@@ -11,20 +11,35 @@
|
||||
<link rel="icon" href="{{ $siteLogoUrl }}">
|
||||
@endif
|
||||
|
||||
<!-- Dark mode init — must run before first paint to avoid flash -->
|
||||
<script>
|
||||
(function () {
|
||||
var t = localStorage.getItem('colorTheme') || 'auto';
|
||||
var dark = t === 'dark' || (t === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches);
|
||||
document.documentElement.classList.toggle('dark', dark);
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function (e) {
|
||||
if ((localStorage.getItem('colorTheme') || 'auto') === 'auto') {
|
||||
document.documentElement.classList.toggle('dark', e.matches);
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="preconnect" href="https://fonts.bunny.net">
|
||||
<link href="https://fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet" />
|
||||
|
||||
<!-- Scripts -->
|
||||
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
||||
@stack('head')
|
||||
</head>
|
||||
<body class="font-sans antialiased">
|
||||
<div class="min-h-screen bg-gray-100">
|
||||
<body class="font-sans antialiased bg-gray-100 dark:bg-gray-900 text-gray-900 dark:text-gray-100">
|
||||
<div class="min-h-screen">
|
||||
@include('layouts.navigation')
|
||||
|
||||
<!-- Page Heading -->
|
||||
@isset($header)
|
||||
<header class="bg-white shadow">
|
||||
<header class="bg-white dark:bg-gray-800 shadow dark:shadow-gray-900/50">
|
||||
<div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
|
||||
{{ $header }}
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}" class="">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
@@ -11,6 +11,20 @@
|
||||
<link rel="icon" href="{{ $siteLogoUrl }}">
|
||||
@endif
|
||||
|
||||
<!-- Dark mode init -->
|
||||
<script>
|
||||
(function () {
|
||||
var t = localStorage.getItem('colorTheme') || 'auto';
|
||||
var dark = t === 'dark' || (t === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches);
|
||||
document.documentElement.classList.toggle('dark', dark);
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function (e) {
|
||||
if ((localStorage.getItem('colorTheme') || 'auto') === 'auto') {
|
||||
document.documentElement.classList.toggle('dark', e.matches);
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="preconnect" href="https://fonts.bunny.net">
|
||||
<link href="https://fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet" />
|
||||
@@ -18,19 +32,19 @@
|
||||
<!-- Scripts -->
|
||||
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
||||
</head>
|
||||
<body class="font-sans text-gray-900 antialiased">
|
||||
<div class="min-h-screen flex flex-col sm:justify-center items-center pt-6 sm:pt-0 bg-gray-100">
|
||||
<body class="font-sans text-gray-900 dark:text-gray-100 antialiased">
|
||||
<div class="min-h-screen flex flex-col sm:justify-center items-center pt-6 sm:pt-0 bg-gray-100 dark:bg-gray-900">
|
||||
<div class="mb-2">
|
||||
<a href="/">
|
||||
@if($siteLogoUrl)
|
||||
<img src="{{ $siteLogoUrl }}" alt="{{ config('app.name') }}" class="h-16 w-auto object-contain">
|
||||
@else
|
||||
<x-application-logo class="w-16 h-16 fill-current text-gray-400" />
|
||||
<x-application-logo class="w-16 h-16 fill-current text-gray-400 dark:text-gray-500" />
|
||||
@endif
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="w-full sm:max-w-md mt-6 px-6 py-4 bg-white shadow-md overflow-hidden sm:rounded-lg">
|
||||
<div class="w-full sm:max-w-md mt-6 px-6 py-4 bg-white dark:bg-gray-800 shadow-md dark:shadow-gray-900/50 overflow-hidden sm:rounded-lg">
|
||||
{{ $slot }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<nav x-data="{ open: false }" class="bg-white border-b border-gray-100">
|
||||
<nav x-data="{ open: false }" class="bg-white dark:bg-gray-800 border-b border-gray-100 dark:border-gray-700">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex justify-between h-16">
|
||||
<div class="flex">
|
||||
<!-- Logo — max-h contraint sur l'image directement, sans chaîne h-full -->
|
||||
<!-- Logo -->
|
||||
<div class="shrink-0 flex items-center">
|
||||
<a href="{{ route('dashboard') }}" class="flex items-center">
|
||||
@if($siteLogoUrl)
|
||||
@@ -10,7 +10,7 @@
|
||||
class="block w-auto object-contain"
|
||||
style="max-height: 40px; max-width: 200px;">
|
||||
@else
|
||||
<span class="font-semibold text-gray-800 text-lg">{{ config('app.name') }}</span>
|
||||
<span class="font-semibold text-gray-800 dark:text-gray-200 text-lg">{{ config('app.name') }}</span>
|
||||
@endif
|
||||
</a>
|
||||
</div>
|
||||
@@ -33,13 +33,16 @@
|
||||
Recherche
|
||||
</x-nav-link>
|
||||
|
||||
<x-nav-link :href="route('carte')" :active="request()->routeIs('carte*')">
|
||||
Carte
|
||||
</x-nav-link>
|
||||
|
||||
@if(auth()->user()->isSectionManager())
|
||||
<!-- Menu Administration — utilise le composant x-dropdown (même positionnement que le menu utilisateur) -->
|
||||
<div class="hidden sm:flex sm:items-center">
|
||||
<x-dropdown align="left" width="w-56">
|
||||
<x-slot name="trigger">
|
||||
<button class="inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium leading-5 transition duration-150 ease-in-out
|
||||
{{ request()->routeIs('admin.*') ? 'border-indigo-400 text-gray-900' : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300' }}">
|
||||
{{ request()->routeIs('admin.*') ? 'border-indigo-400 text-gray-900 dark:text-white' : 'border-transparent text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 hover:border-gray-300 dark:hover:border-gray-600' }}">
|
||||
Administration
|
||||
<svg class="ms-1 h-4 w-4 fill-current" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd"/>
|
||||
@@ -70,7 +73,7 @@
|
||||
<x-dropdown-link :href="route('admin.lieu-types.index')">
|
||||
Types de lieux
|
||||
</x-dropdown-link>
|
||||
<div class="border-t border-gray-100 my-1"></div>
|
||||
<div class="border-t border-gray-100 dark:border-gray-700 my-1"></div>
|
||||
<x-dropdown-link :href="route('admin.parametres')">
|
||||
Paramètres du site
|
||||
</x-dropdown-link>
|
||||
@@ -82,11 +85,53 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Cloche notifications -->
|
||||
<div class="hidden sm:flex sm:items-center sm:ms-4">
|
||||
<!-- Right side: theme toggle + notifications + user menu -->
|
||||
<div class="hidden sm:flex sm:items-center sm:gap-1">
|
||||
|
||||
<!-- Sélecteur de thème -->
|
||||
<div x-data="{
|
||||
theme: localStorage.getItem('colorTheme') || 'auto',
|
||||
apply(t) {
|
||||
this.theme = t;
|
||||
localStorage.setItem('colorTheme', t);
|
||||
var dark = t === 'dark' || (t === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches);
|
||||
document.documentElement.classList.toggle('dark', dark);
|
||||
}
|
||||
}" class="flex items-center">
|
||||
<div class="flex items-center bg-gray-100 dark:bg-gray-700 rounded-full p-0.5 gap-0.5">
|
||||
<!-- Clair -->
|
||||
<button @click="apply('light')"
|
||||
:class="theme === 'light' ? 'bg-white dark:bg-gray-600 shadow text-gray-800 dark:text-gray-100' : 'text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-300'"
|
||||
class="p-1.5 rounded-full transition-all" title="Mode clair">
|
||||
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364-6.364l-.707.707M6.343 17.657l-.707.707M17.657 17.657l-.707-.707M6.343 6.343l-.707-.707M12 7a5 5 0 100 10A5 5 0 0012 7z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<!-- Automatique -->
|
||||
<button @click="apply('auto')"
|
||||
:class="theme === 'auto' ? 'bg-white dark:bg-gray-600 shadow text-gray-800 dark:text-gray-100' : 'text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-300'"
|
||||
class="p-1.5 rounded-full transition-all" title="Automatique (système)">
|
||||
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<!-- Sombre -->
|
||||
<button @click="apply('dark')"
|
||||
:class="theme === 'dark' ? 'bg-white dark:bg-gray-600 shadow text-gray-800 dark:text-gray-100' : 'text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-300'"
|
||||
class="p-1.5 rounded-full transition-all" title="Mode sombre">
|
||||
<svg class="w-3.5 h-3.5" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Notifications -->
|
||||
@php $unreadCount = auth()->user()->unreadNotifications->count(); @endphp
|
||||
<a href="{{ route('notifications.index') }}"
|
||||
class="relative p-2 text-gray-500 hover:text-indigo-600 transition-colors"
|
||||
class="relative p-2 text-gray-500 dark:text-gray-400 hover:text-indigo-600 dark:hover:text-indigo-400 transition-colors"
|
||||
title="Notifications">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
@@ -98,13 +143,11 @@
|
||||
</span>
|
||||
@endif
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Menu utilisateur (Profil / Déconnexion) -->
|
||||
<div class="hidden sm:flex sm:items-center sm:ms-2">
|
||||
<!-- Menu utilisateur -->
|
||||
<x-dropdown align="right" width="48">
|
||||
<x-slot name="trigger">
|
||||
<button class="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-gray-500 bg-white hover:text-gray-700 focus:outline-none transition ease-in-out duration-150">
|
||||
<button class="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-gray-500 dark:text-gray-400 bg-white dark:bg-gray-800 hover:text-gray-700 dark:hover:text-gray-200 focus:outline-none transition ease-in-out duration-150">
|
||||
<div>{{ Auth::user()->name }}</div>
|
||||
<div class="ms-1">
|
||||
<svg class="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
|
||||
@@ -115,7 +158,7 @@
|
||||
</x-slot>
|
||||
|
||||
<x-slot name="content">
|
||||
<div class="px-4 py-2 text-xs text-gray-400 border-b border-gray-100">
|
||||
<div class="px-4 py-2 text-xs text-gray-400 dark:text-gray-500 border-b border-gray-100 dark:border-gray-700">
|
||||
{{ Auth::user()->role->label() }}
|
||||
</div>
|
||||
<x-dropdown-link :href="route('profile.edit')">
|
||||
@@ -134,7 +177,7 @@
|
||||
|
||||
<!-- Hamburger (mobile) -->
|
||||
<div class="-me-2 flex items-center sm:hidden">
|
||||
<button @click="open = ! open" class="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none transition duration-150 ease-in-out">
|
||||
<button @click="open = ! open" class="inline-flex items-center justify-center p-2 rounded-md text-gray-400 dark:text-gray-500 hover:text-gray-500 dark:hover:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 focus:outline-none transition duration-150 ease-in-out">
|
||||
<svg class="h-6 w-6" stroke="currentColor" fill="none" viewBox="0 0 24 24">
|
||||
<path :class="{'hidden': open, 'inline-flex': ! open }" class="inline-flex" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
|
||||
<path :class="{'hidden': ! open, 'inline-flex': open }" class="hidden" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
@@ -159,6 +202,9 @@
|
||||
<x-responsive-nav-link :href="route('recherche')" :active="request()->routeIs('recherche')">
|
||||
Recherche
|
||||
</x-responsive-nav-link>
|
||||
<x-responsive-nav-link :href="route('carte')" :active="request()->routeIs('carte*')">
|
||||
Carte
|
||||
</x-responsive-nav-link>
|
||||
@if(auth()->user()->isSectionManager())
|
||||
@if(auth()->user()->isAdmin())
|
||||
<x-responsive-nav-link :href="route('admin.dashboard')" :active="request()->routeIs('admin.dashboard')">
|
||||
@@ -189,12 +235,52 @@
|
||||
</div>
|
||||
|
||||
<!-- Options utilisateur (mobile) -->
|
||||
<div class="pt-4 pb-1 border-t border-gray-200">
|
||||
<div class="pt-4 pb-1 border-t border-gray-200 dark:border-gray-700">
|
||||
<div class="px-4">
|
||||
<div class="font-medium text-base text-gray-800">{{ Auth::user()->name }}</div>
|
||||
<div class="font-medium text-sm text-gray-500">{{ Auth::user()->email }}</div>
|
||||
<div class="text-xs text-gray-400">{{ Auth::user()->role->label() }}</div>
|
||||
<div class="font-medium text-base text-gray-800 dark:text-gray-200">{{ Auth::user()->name }}</div>
|
||||
<div class="font-medium text-sm text-gray-500 dark:text-gray-400">{{ Auth::user()->email }}</div>
|
||||
<div class="text-xs text-gray-400 dark:text-gray-500">{{ Auth::user()->role->label() }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Sélecteur de thème mobile -->
|
||||
<div x-data="{
|
||||
theme: localStorage.getItem('colorTheme') || 'auto',
|
||||
apply(t) {
|
||||
this.theme = t;
|
||||
localStorage.setItem('colorTheme', t);
|
||||
var dark = t === 'dark' || (t === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches);
|
||||
document.documentElement.classList.toggle('dark', dark);
|
||||
}
|
||||
}" class="px-4 pt-3 pb-1">
|
||||
<p class="text-xs text-gray-400 dark:text-gray-500 mb-2">Apparence</p>
|
||||
<div class="flex gap-2">
|
||||
<button @click="apply('light')"
|
||||
:class="theme === 'light' ? 'bg-indigo-50 dark:bg-indigo-900/50 text-indigo-700 dark:text-indigo-300 border-indigo-300 dark:border-indigo-600' : 'border-gray-200 dark:border-gray-600 text-gray-600 dark:text-gray-400'"
|
||||
class="flex-1 flex items-center justify-center gap-1.5 py-1.5 text-xs border rounded-md transition-colors">
|
||||
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364-6.364l-.707.707M6.343 17.657l-.707.707M17.657 17.657l-.707-.707M6.343 6.343l-.707-.707M12 7a5 5 0 100 10A5 5 0 0012 7z"/>
|
||||
</svg>
|
||||
Clair
|
||||
</button>
|
||||
<button @click="apply('auto')"
|
||||
:class="theme === 'auto' ? 'bg-indigo-50 dark:bg-indigo-900/50 text-indigo-700 dark:text-indigo-300 border-indigo-300 dark:border-indigo-600' : 'border-gray-200 dark:border-gray-600 text-gray-600 dark:text-gray-400'"
|
||||
class="flex-1 flex items-center justify-center gap-1.5 py-1.5 text-xs border rounded-md transition-colors">
|
||||
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/>
|
||||
</svg>
|
||||
Auto
|
||||
</button>
|
||||
<button @click="apply('dark')"
|
||||
:class="theme === 'dark' ? 'bg-indigo-50 dark:bg-indigo-900/50 text-indigo-700 dark:text-indigo-300 border-indigo-300 dark:border-indigo-600' : 'border-gray-200 dark:border-gray-600 text-gray-600 dark:text-gray-400'"
|
||||
class="flex-1 flex items-center justify-center gap-1.5 py-1.5 text-xs border rounded-md transition-colors">
|
||||
<svg class="w-3.5 h-3.5" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z"/>
|
||||
</svg>
|
||||
Sombre
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 space-y-1">
|
||||
<x-responsive-nav-link :href="route('profile.edit')">Mon profil</x-responsive-nav-link>
|
||||
<form method="POST" action="{{ route('logout') }}">
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
<div class="space-y-5">
|
||||
{{-- Nom --}}
|
||||
<div>
|
||||
<label for="nom" class="block text-sm font-medium text-gray-700">Nom <span class="text-red-500">*</span></label>
|
||||
<label for="nom" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Nom <span class="text-red-500">*</span></label>
|
||||
<input type="text" id="nom" name="nom"
|
||||
value="{{ old('nom', $lieu?->nom) }}"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 @error('nom') border-red-500 @enderror"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 @error('nom') border-red-500 @enderror"
|
||||
required>
|
||||
@error('nom') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
|
||||
</div>
|
||||
|
||||
{{-- Type de lieu --}}
|
||||
<div>
|
||||
<label for="lieu_type_id" class="block text-sm font-medium text-gray-700">Type <span class="text-red-500">*</span></label>
|
||||
<label for="lieu_type_id" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Type <span class="text-red-500">*</span></label>
|
||||
<select id="lieu_type_id" name="lieu_type_id" required
|
||||
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 @error('lieu_type_id') border-red-500 @enderror">
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 @error('lieu_type_id') border-red-500 @enderror">
|
||||
<option value="">— Choisir un type —</option>
|
||||
@foreach($lieuTypes as $lt)
|
||||
<option value="{{ $lt->id }}" {{ old('lieu_type_id', $lieu?->lieu_type_id) == $lt->id ? 'selected' : '' }}>
|
||||
@@ -32,10 +32,10 @@
|
||||
|
||||
{{-- Code --}}
|
||||
<div>
|
||||
<label for="code" class="block text-sm font-medium text-gray-700">Code (INSEE, postal…)</label>
|
||||
<label for="code" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Code (INSEE, postal…)</label>
|
||||
<input type="text" id="code" name="code"
|
||||
value="{{ old('code', $lieu?->code) }}"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-indigo-500 focus:ring-indigo-500"
|
||||
maxlength="20">
|
||||
</div>
|
||||
|
||||
@@ -53,18 +53,18 @@
|
||||
{{-- Coordonnées --}}
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label for="latitude" class="block text-sm font-medium text-gray-700">Latitude</label>
|
||||
<label for="latitude" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Latitude</label>
|
||||
<input type="number" id="latitude" name="latitude" step="0.0000001"
|
||||
value="{{ old('latitude', $lieu?->latitude) }}"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-indigo-500 focus:ring-indigo-500"
|
||||
placeholder="48.8566">
|
||||
@error('latitude') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
|
||||
</div>
|
||||
<div>
|
||||
<label for="longitude" class="block text-sm font-medium text-gray-700">Longitude</label>
|
||||
<label for="longitude" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Longitude</label>
|
||||
<input type="number" id="longitude" name="longitude" step="0.0000001"
|
||||
value="{{ old('longitude', $lieu?->longitude) }}"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-indigo-500 focus:ring-indigo-500"
|
||||
placeholder="2.3522">
|
||||
@error('longitude') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
|
||||
</div>
|
||||
@@ -72,8 +72,8 @@
|
||||
|
||||
{{-- Note --}}
|
||||
<div>
|
||||
<label for="note" class="block text-sm font-medium text-gray-700">Note</label>
|
||||
<label for="note" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Note</label>
|
||||
<textarea id="note" name="note" rows="3"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">{{ old('note', $lieu?->note) }}</textarea>
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">{{ old('note', $lieu?->note) }}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="text-xl font-semibold text-gray-800">Nouveau lieu</h2>
|
||||
<h2 class="text-xl font-semibold text-gray-800 dark:text-gray-200">Nouveau lieu</h2>
|
||||
</x-slot>
|
||||
|
||||
<div class="py-8 max-w-2xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="bg-white shadow rounded-lg p-6">
|
||||
<div class="bg-white dark:bg-gray-800 shadow rounded-lg p-6">
|
||||
<form method="POST" action="{{ route('lieux.store') }}">
|
||||
@csrf
|
||||
@include('lieux._form', ['lieu' => null])
|
||||
@@ -14,7 +14,7 @@
|
||||
class="px-5 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700">
|
||||
Créer
|
||||
</button>
|
||||
<a href="{{ route('lieux.index') }}" class="text-sm text-gray-500 hover:text-gray-700">Annuler</a>
|
||||
<a href="{{ route('lieux.index') }}" class="text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300">Annuler</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="text-xl font-semibold text-gray-800">Modifier : {{ $lieu->nom }}</h2>
|
||||
<h2 class="text-xl font-semibold text-gray-800 dark:text-gray-200">Modifier : {{ $lieu->nom }}</h2>
|
||||
</x-slot>
|
||||
|
||||
<div class="py-8 max-w-2xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="bg-white shadow rounded-lg p-6">
|
||||
<div class="bg-white dark:bg-gray-800 shadow rounded-lg p-6">
|
||||
<form method="POST" action="{{ route('lieux.update', $lieu) }}">
|
||||
@csrf @method('PUT')
|
||||
@include('lieux._form', ['lieu' => $lieu])
|
||||
@@ -14,7 +14,7 @@
|
||||
class="px-5 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700">
|
||||
Enregistrer
|
||||
</button>
|
||||
<a href="{{ route('lieux.show', $lieu) }}" class="text-sm text-gray-500 hover:text-gray-700">Annuler</a>
|
||||
<a href="{{ route('lieux.show', $lieu) }}" class="text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300">Annuler</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="text-xl font-semibold text-gray-800">Lieux</h2>
|
||||
<h2 class="text-xl font-semibold text-gray-800 dark:text-gray-200">Lieux</h2>
|
||||
@can('create', App\Models\Lieu::class)
|
||||
<a href="{{ route('lieux.create') }}"
|
||||
class="px-4 py-2 bg-indigo-600 text-white text-sm rounded-md hover:bg-indigo-700">
|
||||
@@ -14,29 +14,29 @@
|
||||
<div class="py-8 max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 space-y-6">
|
||||
|
||||
@if(session('success'))
|
||||
<div class="p-4 bg-green-50 border border-green-200 text-green-800 rounded-md">{{ session('success') }}</div>
|
||||
<div class="p-4 bg-green-50 dark:bg-green-900/30 border border-green-200 dark:border-green-700 text-green-800 dark:text-green-200 rounded-md">{{ session('success') }}</div>
|
||||
@endif
|
||||
@if(session('error'))
|
||||
<div class="p-4 bg-red-50 border border-red-200 text-red-800 rounded-md">{{ session('error') }}</div>
|
||||
<div class="p-4 bg-red-50 dark:bg-red-900/30 border border-red-200 dark:border-red-700 text-red-800 dark:text-red-200 rounded-md">{{ session('error') }}</div>
|
||||
@endif
|
||||
|
||||
{{-- Filtres --}}
|
||||
@php $hasFilters = request()->anyFilled(['lieu_type_id', 'q', 'lieu_id']); @endphp
|
||||
<div class="bg-white shadow rounded-lg p-5">
|
||||
<div class="bg-white dark:bg-gray-800 shadow rounded-lg p-5">
|
||||
<form method="GET" action="{{ route('lieux.index') }}">
|
||||
<div class="flex flex-wrap items-end gap-4">
|
||||
|
||||
<div class="flex-1 min-w-[200px]">
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">Recherche</label>
|
||||
<label class="block text-xs font-medium text-gray-600 dark:text-gray-400 mb-1">Recherche</label>
|
||||
<input type="text" name="q" value="{{ request('q') }}"
|
||||
placeholder="Nom, code INSEE…"
|
||||
class="block w-full rounded-md border-gray-300 shadow-sm text-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
class="block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm text-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
</div>
|
||||
|
||||
<div class="w-52">
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">Type de lieu</label>
|
||||
<label class="block text-xs font-medium text-gray-600 dark:text-gray-400 mb-1">Type de lieu</label>
|
||||
<select name="lieu_type_id"
|
||||
class="block w-full rounded-md border-gray-300 shadow-sm text-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
class="block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm text-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
<option value="">— Tous les types —</option>
|
||||
@foreach($lieuTypes as $lt)
|
||||
<option value="{{ $lt->id }}" {{ request('lieu_type_id') == $lt->id ? 'selected' : '' }}>
|
||||
@@ -53,10 +53,10 @@
|
||||
</button>
|
||||
@if($hasFilters)
|
||||
<a href="{{ route('lieux.index') }}"
|
||||
class="px-4 py-2 border border-gray-300 text-gray-600 text-sm rounded-md hover:bg-gray-50">
|
||||
class="px-4 py-2 border border-gray-300 dark:border-gray-600 text-gray-600 dark:text-gray-400 text-sm rounded-md hover:bg-gray-50 dark:hover:bg-gray-700">
|
||||
Effacer
|
||||
</a>
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs bg-indigo-100 text-indigo-700">
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs bg-indigo-100 dark:bg-indigo-900/50 text-indigo-700">
|
||||
filtres actifs
|
||||
</span>
|
||||
@endif
|
||||
@@ -77,29 +77,29 @@
|
||||
</div>
|
||||
|
||||
{{-- Tableau --}}
|
||||
<div class="bg-white shadow rounded-lg overflow-hidden">
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50">
|
||||
<div class="bg-white dark:bg-gray-800 shadow rounded-lg overflow-hidden">
|
||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<thead class="bg-gray-50 dark:bg-gray-700">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Lieu</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Type</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Code</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Parent</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Coordonnées</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Lieu</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Type</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Code</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Parent</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Coordonnées</th>
|
||||
<th class="px-6 py-3"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200">
|
||||
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
|
||||
@forelse($lieux as $lieu)
|
||||
<tr class="hover:bg-gray-50">
|
||||
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700">
|
||||
<td class="px-6 py-4">
|
||||
<a href="{{ route('lieux.show', $lieu) }}" class="font-medium text-indigo-600 hover:underline">
|
||||
{{ $lieu->nom }}
|
||||
</a>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-500">{{ $lieu->lieuType?->nom ?? '—' }}</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-500">{{ $lieu->code ?? '—' }}</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-500">
|
||||
<td class="px-6 py-4 text-sm text-gray-500 dark:text-gray-400">{{ $lieu->lieuType?->nom ?? '—' }}</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-500 dark:text-gray-400">{{ $lieu->code ?? '—' }}</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-500 dark:text-gray-400">
|
||||
@if($lieu->parent)
|
||||
<a href="{{ route('lieux.show', $lieu->parent) }}" class="text-indigo-600 hover:underline">
|
||||
{{ $lieu->parent->nom }}
|
||||
@@ -108,7 +108,7 @@
|
||||
—
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-500">
|
||||
<td class="px-6 py-4 text-sm text-gray-500 dark:text-gray-400">
|
||||
@if($lieu->latitude && $lieu->longitude)
|
||||
{{ number_format($lieu->latitude, 4) }}, {{ number_format($lieu->longitude, 4) }}
|
||||
@else
|
||||
@@ -117,7 +117,7 @@
|
||||
</td>
|
||||
<td class="px-6 py-4 text-right text-sm space-x-3">
|
||||
@can('update', $lieu)
|
||||
<a href="{{ route('lieux.edit', $lieu) }}" class="text-gray-600 hover:text-indigo-600">Modifier</a>
|
||||
<a href="{{ route('lieux.edit', $lieu) }}" class="text-gray-600 dark:text-gray-400 hover:text-indigo-600">Modifier</a>
|
||||
@endcan
|
||||
@can('delete', $lieu)
|
||||
<form method="POST" action="{{ route('lieux.destroy', $lieu) }}" class="inline"
|
||||
@@ -131,7 +131,7 @@
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="6" class="px-6 py-10 text-center text-gray-400">
|
||||
<td colspan="6" class="px-6 py-10 text-center text-gray-400 dark:text-gray-500">
|
||||
@if($hasFilters) Aucun lieu ne correspond aux filtres.
|
||||
@else Aucun lieu enregistré. @endif
|
||||
</td>
|
||||
@@ -141,7 +141,7 @@
|
||||
</table>
|
||||
|
||||
@if($lieux->hasPages())
|
||||
<div class="px-6 py-4 border-t border-gray-200">
|
||||
<div class="px-6 py-4 border-t border-gray-200 dark:border-gray-700">
|
||||
{{ $lieux->links() }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="text-xl font-semibold text-gray-800">{{ $lieu->nom }}</h2>
|
||||
<h2 class="text-xl font-semibold text-gray-800 dark:text-gray-200">{{ $lieu->nom }}</h2>
|
||||
<div class="flex gap-3">
|
||||
@can('update', $lieu)
|
||||
<a href="{{ route('lieux.edit', $lieu) }}"
|
||||
@@ -26,67 +26,67 @@
|
||||
<div class="py-8 max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 space-y-6">
|
||||
|
||||
@if(session('success'))
|
||||
<div class="p-4 bg-green-50 border border-green-200 text-green-800 rounded-md">
|
||||
<div class="p-4 bg-green-50 dark:bg-green-900/30 border border-green-200 dark:border-green-700 text-green-800 dark:text-green-200 rounded-md">
|
||||
{{ session('success') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Fiche --}}
|
||||
<div class="bg-white shadow rounded-lg divide-y divide-gray-100">
|
||||
<div class="bg-white dark:bg-gray-800 shadow rounded-lg divide-y divide-gray-100 dark:divide-gray-700">
|
||||
<div class="px-6 py-4 grid grid-cols-3 gap-4 text-sm">
|
||||
<dt class="font-medium text-gray-500">Nom complet</dt>
|
||||
<dd class="col-span-2 text-gray-900">{{ $lieu->nom_long ?? $lieu->nom }}</dd>
|
||||
<dt class="font-medium text-gray-500 dark:text-gray-400">Nom complet</dt>
|
||||
<dd class="col-span-2 text-gray-900 dark:text-white">{{ $lieu->nom_long ?? $lieu->nom }}</dd>
|
||||
</div>
|
||||
<div class="px-6 py-4 grid grid-cols-3 gap-4 text-sm">
|
||||
<dt class="font-medium text-gray-500">Type</dt>
|
||||
<dd class="col-span-2 text-gray-900">{{ $lieu->lieuType?->nom ?? '—' }}</dd>
|
||||
<dt class="font-medium text-gray-500 dark:text-gray-400">Type</dt>
|
||||
<dd class="col-span-2 text-gray-900 dark:text-white">{{ $lieu->lieuType?->nom ?? '—' }}</dd>
|
||||
</div>
|
||||
@if($lieu->code)
|
||||
<div class="px-6 py-4 grid grid-cols-3 gap-4 text-sm">
|
||||
<dt class="font-medium text-gray-500">Code</dt>
|
||||
<dd class="col-span-2 text-gray-900">{{ $lieu->code }}</dd>
|
||||
<dt class="font-medium text-gray-500 dark:text-gray-400">Code</dt>
|
||||
<dd class="col-span-2 text-gray-900 dark:text-white">{{ $lieu->code }}</dd>
|
||||
</div>
|
||||
@endif
|
||||
<div class="px-6 py-4 grid grid-cols-3 gap-4 text-sm">
|
||||
<dt class="font-medium text-gray-500">Lieu parent</dt>
|
||||
<dt class="font-medium text-gray-500 dark:text-gray-400">Lieu parent</dt>
|
||||
<dd class="col-span-2">
|
||||
@if($lieu->parent)
|
||||
<a href="{{ route('lieux.show', $lieu->parent) }}" class="text-indigo-600 hover:underline">
|
||||
{{ $lieu->parent->nom_long ?? $lieu->parent->nom }}
|
||||
</a>
|
||||
@else
|
||||
<span class="text-gray-400">Lieu racine</span>
|
||||
<span class="text-gray-400 dark:text-gray-500">Lieu racine</span>
|
||||
@endif
|
||||
</dd>
|
||||
</div>
|
||||
@if($lieu->latitude && $lieu->longitude)
|
||||
<div class="px-6 py-4 grid grid-cols-3 gap-4 text-sm">
|
||||
<dt class="font-medium text-gray-500">Coordonnées</dt>
|
||||
<dd class="col-span-2 text-gray-900">{{ $lieu->latitude }}, {{ $lieu->longitude }}</dd>
|
||||
<dt class="font-medium text-gray-500 dark:text-gray-400">Coordonnées</dt>
|
||||
<dd class="col-span-2 text-gray-900 dark:text-white">{{ $lieu->latitude }}, {{ $lieu->longitude }}</dd>
|
||||
</div>
|
||||
@endif
|
||||
@if($lieu->note)
|
||||
<div class="px-6 py-4 grid grid-cols-3 gap-4 text-sm">
|
||||
<dt class="font-medium text-gray-500">Note</dt>
|
||||
<dd class="col-span-2 text-gray-900 whitespace-pre-line">{{ $lieu->note }}</dd>
|
||||
<dt class="font-medium text-gray-500 dark:text-gray-400">Note</dt>
|
||||
<dd class="col-span-2 text-gray-900 dark:text-white whitespace-pre-line">{{ $lieu->note }}</dd>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- Enfants --}}
|
||||
@if($lieu->enfants->isNotEmpty())
|
||||
<div class="bg-white shadow rounded-lg overflow-hidden">
|
||||
<div class="px-6 py-4 border-b border-gray-200">
|
||||
<h3 class="font-medium text-gray-900">Subdivisions ({{ $lieu->enfants->count() }})</h3>
|
||||
<div class="bg-white dark:bg-gray-800 shadow rounded-lg overflow-hidden">
|
||||
<div class="px-6 py-4 border-b border-gray-200 dark:border-gray-700">
|
||||
<h3 class="font-medium text-gray-900 dark:text-white">Subdivisions ({{ $lieu->enfants->count() }})</h3>
|
||||
</div>
|
||||
<ul class="divide-y divide-gray-100">
|
||||
<ul class="divide-y divide-gray-100 dark:divide-gray-700">
|
||||
@foreach($lieu->enfants->sortBy('nom') as $enfant)
|
||||
<li class="px-6 py-3">
|
||||
<a href="{{ route('lieux.show', $enfant) }}" class="text-indigo-600 hover:underline">
|
||||
{{ $enfant->nom }}
|
||||
</a>
|
||||
@if($enfant->code)
|
||||
<span class="ml-2 text-xs text-gray-400">({{ $enfant->code }})</span>
|
||||
<span class="ml-2 text-xs text-gray-400 dark:text-gray-500">({{ $enfant->code }})</span>
|
||||
@endif
|
||||
</li>
|
||||
@endforeach
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="text-xl font-semibold text-gray-800">Notifications</h2>
|
||||
<h2 class="text-xl font-semibold text-gray-800 dark:text-gray-200">Notifications</h2>
|
||||
@if(auth()->user()->unreadNotifications->isNotEmpty())
|
||||
<form method="POST" action="{{ route('notifications.read-all') }}">
|
||||
@csrf
|
||||
@@ -16,12 +16,12 @@
|
||||
|
||||
<div class="py-8 max-w-3xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
@if(session('success'))
|
||||
<div class="mb-4 p-4 bg-green-50 border border-green-200 text-green-800 rounded-md">
|
||||
<div class="mb-4 p-4 bg-green-50 dark:bg-green-900/30 border border-green-200 dark:border-green-700 text-green-800 dark:text-green-200 rounded-md">
|
||||
{{ session('success') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="bg-white shadow rounded-lg divide-y divide-gray-100">
|
||||
<div class="bg-white dark:bg-gray-800 shadow rounded-lg divide-y divide-gray-100 dark:divide-gray-700">
|
||||
@forelse($notifications as $notification)
|
||||
@php
|
||||
$data = $notification->data;
|
||||
@@ -32,13 +32,13 @@
|
||||
{{-- Icône --}}
|
||||
<div class="shrink-0 mt-0.5">
|
||||
@if($isRejet)
|
||||
<span class="inline-flex items-center justify-center w-8 h-8 rounded-full bg-red-100 text-red-600">
|
||||
<span class="inline-flex items-center justify-center w-8 h-8 rounded-full bg-red-100 dark:bg-red-900/50 text-red-600">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
</svg>
|
||||
</span>
|
||||
@else
|
||||
<span class="inline-flex items-center justify-center w-8 h-8 rounded-full bg-yellow-100 text-yellow-600">
|
||||
<span class="inline-flex items-center justify-center w-8 h-8 rounded-full bg-yellow-100 dark:bg-yellow-900/50 text-yellow-600">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
</svg>
|
||||
@@ -48,7 +48,7 @@
|
||||
|
||||
{{-- Contenu --}}
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-sm text-gray-900">
|
||||
<p class="text-sm text-gray-900 dark:text-white">
|
||||
@if($isRejet)
|
||||
<strong>{{ $data['rejete_par'] }}</strong> a renvoyé la source
|
||||
<strong>{{ $data['source_nom'] }}</strong> en cours de saisie.
|
||||
@@ -57,10 +57,10 @@
|
||||
<strong>{{ $data['source_nom'] }}</strong> pour validation.
|
||||
@endif
|
||||
</p>
|
||||
<div class="mt-1 flex items-center gap-3 text-xs text-gray-400">
|
||||
<div class="mt-1 flex items-center gap-3 text-xs text-gray-400 dark:text-gray-500">
|
||||
<span>{{ $notification->created_at->diffForHumans() }}</span>
|
||||
@if(!$isRead)
|
||||
<span class="inline-block w-2 h-2 rounded-full bg-indigo-500"></span>
|
||||
<span class="inline-block w-2 h-2 rounded-full bg-indigo-50 dark:bg-indigo-900/300"></span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@@ -74,14 +74,14 @@
|
||||
@if(!$isRead)
|
||||
<form method="POST" action="{{ route('notifications.read', $notification->id) }}">
|
||||
@csrf
|
||||
<button type="submit" class="text-xs text-gray-400 hover:text-gray-600" title="Marquer comme lu">✓</button>
|
||||
<button type="submit" class="text-xs text-gray-400 dark:text-gray-500 hover:text-gray-600" title="Marquer comme lu">✓</button>
|
||||
</form>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@empty
|
||||
<div class="px-6 py-16 text-center text-gray-400">
|
||||
<svg class="mx-auto w-10 h-10 mb-3 text-gray-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<div class="px-6 py-16 text-center text-gray-400 dark:text-gray-500">
|
||||
<svg class="mx-auto w-10 h-10 mb-3 text-gray-300 dark:text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9"/>
|
||||
</svg>
|
||||
<p>Aucune notification</p>
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
|
||||
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
|
||||
Mon profil
|
||||
</h2>
|
||||
</x-slot>
|
||||
|
||||
<div class="py-12">
|
||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8 space-y-6">
|
||||
<div class="p-4 sm:p-8 bg-white shadow sm:rounded-lg">
|
||||
<div class="p-4 sm:p-8 bg-white dark:bg-gray-800 shadow sm:rounded-lg">
|
||||
<div class="max-w-xl">
|
||||
@include('profile.partials.update-profile-information-form')
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-4 sm:p-8 bg-white shadow sm:rounded-lg">
|
||||
<div class="p-4 sm:p-8 bg-white dark:bg-gray-800 shadow sm:rounded-lg">
|
||||
<div class="max-w-xl">
|
||||
@include('profile.partials.update-password-form')
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-4 sm:p-8 bg-white shadow sm:rounded-lg">
|
||||
<div class="p-4 sm:p-8 bg-white dark:bg-gray-800 shadow sm:rounded-lg">
|
||||
<div class="max-w-xl">
|
||||
@include('profile.partials.delete-user-form')
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<section class="space-y-6">
|
||||
<header>
|
||||
<h2 class="text-lg font-medium text-gray-900">Supprimer le compte</h2>
|
||||
<p class="mt-1 text-sm text-gray-600">
|
||||
<h2 class="text-lg font-medium text-gray-900 dark:text-white">Supprimer le compte</h2>
|
||||
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
|
||||
Une fois votre compte supprimé, toutes ses données seront définitivement effacées.
|
||||
Téléchargez toute information que vous souhaitez conserver avant de procéder.
|
||||
</p>
|
||||
@@ -17,11 +17,11 @@
|
||||
@csrf
|
||||
@method('delete')
|
||||
|
||||
<h2 class="text-lg font-medium text-gray-900">
|
||||
<h2 class="text-lg font-medium text-gray-900 dark:text-white">
|
||||
Êtes-vous sûr de vouloir supprimer votre compte ?
|
||||
</h2>
|
||||
|
||||
<p class="mt-1 text-sm text-gray-600">
|
||||
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
|
||||
Cette action est irréversible. Toutes vos données seront définitivement supprimées.
|
||||
Saisissez votre mot de passe pour confirmer.
|
||||
</p>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<section>
|
||||
<header>
|
||||
<h2 class="text-lg font-medium text-gray-900">Changer le mot de passe</h2>
|
||||
<p class="mt-1 text-sm text-gray-600">
|
||||
<h2 class="text-lg font-medium text-gray-900 dark:text-white">Changer le mot de passe</h2>
|
||||
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
|
||||
Utilisez un mot de passe long et aléatoire pour sécuriser votre compte.
|
||||
</p>
|
||||
</header>
|
||||
@@ -37,7 +37,7 @@
|
||||
@if (session('status') === 'password-updated')
|
||||
<p x-data="{ show: true }" x-show="show" x-transition
|
||||
x-init="setTimeout(() => show = false, 2000)"
|
||||
class="text-sm text-gray-600">
|
||||
class="text-sm text-gray-600 dark:text-gray-400">
|
||||
Enregistré.
|
||||
</p>
|
||||
@endif
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<section>
|
||||
<header>
|
||||
<h2 class="text-lg font-medium text-gray-900">Informations du profil</h2>
|
||||
<p class="mt-1 text-sm text-gray-600">
|
||||
<h2 class="text-lg font-medium text-gray-900 dark:text-white">Informations du profil</h2>
|
||||
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
|
||||
Mettez à jour votre nom et votre adresse e-mail.
|
||||
</p>
|
||||
</header>
|
||||
@@ -29,10 +29,10 @@
|
||||
|
||||
@if ($user instanceof \Illuminate\Contracts\Auth\MustVerifyEmail && ! $user->hasVerifiedEmail())
|
||||
<div>
|
||||
<p class="text-sm mt-2 text-gray-800">
|
||||
<p class="text-sm mt-2 text-gray-800 dark:text-gray-200">
|
||||
Votre adresse e-mail n'est pas vérifiée.
|
||||
<button form="send-verification"
|
||||
class="underline text-sm text-gray-600 hover:text-gray-900 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
|
||||
class="underline text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
|
||||
Cliquez ici pour renvoyer l'e-mail de vérification.
|
||||
</button>
|
||||
</p>
|
||||
@@ -51,7 +51,7 @@
|
||||
@if (session('status') === 'profile-updated')
|
||||
<p x-data="{ show: true }" x-show="show" x-transition
|
||||
x-init="setTimeout(() => show = false, 2000)"
|
||||
class="text-sm text-gray-600">
|
||||
class="text-sm text-gray-600 dark:text-gray-400">
|
||||
Enregistré.
|
||||
</p>
|
||||
@endif
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="text-xl font-semibold text-gray-800">Recherche dans les relevés</h2>
|
||||
<h2 class="text-xl font-semibold text-gray-800 dark:text-gray-200">Recherche dans les relevés</h2>
|
||||
</x-slot>
|
||||
|
||||
<div class="py-8 max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 space-y-6">
|
||||
|
||||
{{-- Formulaire de recherche --}}
|
||||
<div class="bg-white shadow rounded-lg p-6">
|
||||
<div class="bg-white dark:bg-gray-800 shadow rounded-lg p-6">
|
||||
<form method="GET" action="{{ route('recherche') }}" class="space-y-4">
|
||||
|
||||
{{-- Barre principale --}}
|
||||
<div class="flex gap-3">
|
||||
<div class="flex-1 relative">
|
||||
<div class="absolute inset-y-0 left-3 flex items-center pointer-events-none">
|
||||
<svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg class="w-5 h-5 text-gray-400 dark:text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M21 21l-4.35-4.35M17 11A6 6 0 1 1 5 11a6 6 0 0 1 12 0z"/>
|
||||
</svg>
|
||||
@@ -21,7 +21,7 @@
|
||||
<input type="text" name="q" value="{{ request('q') }}"
|
||||
placeholder="Nom, prénom, lieu, note…"
|
||||
autofocus
|
||||
class="block w-full pl-10 pr-4 py-2.5 border border-gray-300 rounded-md shadow-sm
|
||||
class="block w-full pl-10 pr-4 py-2.5 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm
|
||||
text-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
</div>
|
||||
<button type="submit"
|
||||
@@ -30,7 +30,7 @@
|
||||
</button>
|
||||
@if(request()->anyFilled(['q', 'source_type_id', 'annee_debut', 'annee_fin']))
|
||||
<a href="{{ route('recherche') }}"
|
||||
class="px-4 py-2.5 border border-gray-300 text-gray-600 text-sm rounded-md hover:bg-gray-50">
|
||||
class="px-4 py-2.5 border border-gray-300 dark:border-gray-600 text-gray-600 dark:text-gray-400 text-sm rounded-md hover:bg-gray-50 dark:hover:bg-gray-700">
|
||||
Effacer
|
||||
</a>
|
||||
@endif
|
||||
@@ -45,7 +45,7 @@
|
||||
class="text-sm text-indigo-600 hover:underline flex items-center gap-1">
|
||||
<span x-text="open ? '▲ Masquer les filtres' : '▼ Filtres avancés'"></span>
|
||||
@if($hasAdvanced)
|
||||
<span class="ml-1 inline-flex items-center px-1.5 py-0.5 rounded-full text-xs bg-indigo-100 text-indigo-700">
|
||||
<span class="ml-1 inline-flex items-center px-1.5 py-0.5 rounded-full text-xs bg-indigo-100 dark:bg-indigo-900/50 text-indigo-700">
|
||||
actifs
|
||||
</span>
|
||||
@endif
|
||||
@@ -54,9 +54,9 @@
|
||||
<div x-show="open" x-cloak class="mt-4 space-y-4">
|
||||
<div class="grid grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">Type de source</label>
|
||||
<label class="block text-xs font-medium text-gray-600 dark:text-gray-400 mb-1">Type de source</label>
|
||||
<select name="source_type_id"
|
||||
class="block w-full rounded-md border-gray-300 shadow-sm text-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
class="block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm text-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
<option value="">— Tous les types —</option>
|
||||
@foreach($sourceTypes as $st)
|
||||
<option value="{{ $st->id }}" {{ request('source_type_id') == $st->id ? 'selected' : '' }}>
|
||||
@@ -66,16 +66,16 @@
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">Année de début</label>
|
||||
<label class="block text-xs font-medium text-gray-600 dark:text-gray-400 mb-1">Année de début</label>
|
||||
<input type="number" name="annee_debut" value="{{ request('annee_debut') }}"
|
||||
min="1000" max="2100" placeholder="ex : 1820"
|
||||
class="block w-full rounded-md border-gray-300 shadow-sm text-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
class="block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm text-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">Année de fin</label>
|
||||
<label class="block text-xs font-medium text-gray-600 dark:text-gray-400 mb-1">Année de fin</label>
|
||||
<input type="number" name="annee_fin" value="{{ request('annee_fin') }}"
|
||||
min="1000" max="2100" placeholder="ex : 1830"
|
||||
class="block w-full rounded-md border-gray-300 shadow-sm text-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
class="block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm text-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
placeholder="— Tous les lieux —"
|
||||
/>
|
||||
@if($lieuSelectionne)
|
||||
<p class="mt-1 text-xs text-gray-400">
|
||||
<p class="mt-1 text-xs text-gray-400 dark:text-gray-500">
|
||||
Inclut toutes les subdivisions de {{ $lieuSelectionne->nom_long ?? $lieuSelectionne->nom }}.
|
||||
</p>
|
||||
@endif
|
||||
@@ -102,7 +102,7 @@
|
||||
{{-- Résultats --}}
|
||||
@if($resultats !== null)
|
||||
<div>
|
||||
<p class="text-sm text-gray-500 mb-3">
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 mb-3">
|
||||
@if($total === 0)
|
||||
Aucun relevé trouvé.
|
||||
@else
|
||||
@@ -117,20 +117,20 @@
|
||||
</p>
|
||||
|
||||
@if($resultats->isNotEmpty())
|
||||
<div class="bg-white shadow rounded-lg overflow-hidden">
|
||||
<div class="bg-white dark:bg-gray-800 shadow rounded-lg overflow-hidden">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200 text-sm">
|
||||
<thead class="bg-gray-50">
|
||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700 text-sm">
|
||||
<thead class="bg-gray-50 dark:bg-gray-700">
|
||||
<tr>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Nom</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Prénom</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Date</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Source</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Type</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Nom</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Prénom</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Date</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Source</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Type</th>
|
||||
<th class="px-4 py-3"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200">
|
||||
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
|
||||
@foreach($resultats as $releve)
|
||||
@php
|
||||
$data = $releve->data;
|
||||
@@ -139,32 +139,32 @@
|
||||
? ($dateEvt['valeur'] ?? '—') . ($dateEvt['calendrier'] !== 'gregorien' ? ' (' . $dateEvt['calendrier'] . ')' : '')
|
||||
: ($releve->date_evenement ?? '—');
|
||||
@endphp
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="px-4 py-3 font-medium text-gray-900">
|
||||
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700">
|
||||
<td class="px-4 py-3 font-medium text-gray-900 dark:text-white">
|
||||
@if(request('q') && $releve->nom)
|
||||
{!! preg_replace('/(' . preg_quote(request('q'), '/') . ')/i', '<mark class="bg-yellow-100 rounded px-0.5">$1</mark>', e($releve->nom)) !!}
|
||||
{!! preg_replace('/(' . preg_quote(request('q'), '/') . ')/i', '<mark class="bg-yellow-100 dark:bg-yellow-900/50 rounded px-0.5">$1</mark>', e($releve->nom)) !!}
|
||||
@else
|
||||
{{ $releve->nom ?? '—' }}
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-4 py-3 text-gray-700">
|
||||
<td class="px-4 py-3 text-gray-700 dark:text-gray-300">
|
||||
@if(request('q') && $releve->prenom)
|
||||
{!! preg_replace('/(' . preg_quote(request('q'), '/') . ')/i', '<mark class="bg-yellow-100 rounded px-0.5">$1</mark>', e($releve->prenom)) !!}
|
||||
{!! preg_replace('/(' . preg_quote(request('q'), '/') . ')/i', '<mark class="bg-yellow-100 dark:bg-yellow-900/50 rounded px-0.5">$1</mark>', e($releve->prenom)) !!}
|
||||
@else
|
||||
{{ $releve->prenom ?? '—' }}
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-4 py-3 text-gray-600 whitespace-nowrap">
|
||||
<td class="px-4 py-3 text-gray-600 dark:text-gray-400 whitespace-nowrap">
|
||||
{{ $dateAffichee }}
|
||||
</td>
|
||||
<td class="px-4 py-3 text-gray-600">
|
||||
<td class="px-4 py-3 text-gray-600 dark:text-gray-400">
|
||||
<a href="{{ route('sources.show', $releve->source) }}"
|
||||
class="hover:text-indigo-600 hover:underline">
|
||||
{{ $releve->source->nom }}
|
||||
</a>
|
||||
</td>
|
||||
<td class="px-4 py-3">
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-100 text-gray-600">
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400">
|
||||
{{ $releve->source->sourceType->nom }}
|
||||
</span>
|
||||
</td>
|
||||
@@ -181,7 +181,7 @@
|
||||
</div>
|
||||
|
||||
@if($resultats->hasPages())
|
||||
<div class="px-6 py-4 border-t border-gray-200">
|
||||
<div class="px-6 py-4 border-t border-gray-200 dark:border-gray-700">
|
||||
{{ $resultats->links() }}
|
||||
</div>
|
||||
@endif
|
||||
@@ -190,8 +190,8 @@
|
||||
</div>
|
||||
@else
|
||||
{{-- État initial --}}
|
||||
<div class="text-center py-16 text-gray-400">
|
||||
<svg class="mx-auto w-12 h-12 mb-4 text-gray-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<div class="text-center py-16 text-gray-400 dark:text-gray-500">
|
||||
<svg class="mx-auto w-12 h-12 mb-4 text-gray-300 dark:text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"
|
||||
d="M21 21l-4.35-4.35M17 11A6 6 0 1 1 5 11a6 6 0 0 1 12 0z"/>
|
||||
</svg>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
@endphp
|
||||
|
||||
<div class="space-y-1">
|
||||
<label for="{{ $inputId }}" class="block text-sm font-medium text-gray-700">
|
||||
<label for="{{ $inputId }}" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
{{ $field->label }}
|
||||
@if($field->required) <span class="text-red-500">*</span> @endif
|
||||
</label>
|
||||
@@ -21,20 +21,20 @@
|
||||
<input type="text" id="{{ $inputId }}" name="{{ $name }}"
|
||||
value="{{ $oldValue }}"
|
||||
{{ $field->required ? 'required' : '' }}
|
||||
class="block w-full rounded-md border-gray-300 shadow-sm text-sm focus:border-indigo-500 focus:ring-indigo-500 @error("data.{$field->name}") border-red-500 @enderror">
|
||||
class="block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm text-sm focus:border-indigo-500 focus:ring-indigo-500 @error("data.{$field->name}") border-red-500 @enderror">
|
||||
@break
|
||||
|
||||
@case(FieldType::Textarea)
|
||||
<textarea id="{{ $inputId }}" name="{{ $name }}" rows="3"
|
||||
{{ $field->required ? 'required' : '' }}
|
||||
class="block w-full rounded-md border-gray-300 shadow-sm text-sm focus:border-indigo-500 focus:ring-indigo-500 @error("data.{$field->name}") border-red-500 @enderror">{{ $oldValue }}</textarea>
|
||||
class="block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm text-sm focus:border-indigo-500 focus:ring-indigo-500 @error("data.{$field->name}") border-red-500 @enderror">{{ $oldValue }}</textarea>
|
||||
@break
|
||||
|
||||
@case(FieldType::Number)
|
||||
<input type="number" id="{{ $inputId }}" name="{{ $name }}"
|
||||
value="{{ $oldValue }}" step="any"
|
||||
{{ $field->required ? 'required' : '' }}
|
||||
class="block w-full rounded-md border-gray-300 shadow-sm text-sm focus:border-indigo-500 focus:ring-indigo-500 @error("data.{$field->name}") border-red-500 @enderror">
|
||||
class="block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm text-sm focus:border-indigo-500 focus:ring-indigo-500 @error("data.{$field->name}") border-red-500 @enderror">
|
||||
@break
|
||||
|
||||
@case(FieldType::Boolean)
|
||||
@@ -43,15 +43,15 @@
|
||||
<input type="hidden" name="{{ $name }}" value="0">
|
||||
<input type="checkbox" id="{{ $inputId }}" name="{{ $name }}" value="1"
|
||||
{{ $checked ? 'checked' : '' }}
|
||||
class="rounded border-gray-300 text-indigo-600 focus:ring-indigo-500">
|
||||
<span class="text-sm text-gray-600">{{ $field->label }}</span>
|
||||
class="rounded border-gray-300 dark:border-gray-600 text-indigo-600 focus:ring-indigo-500">
|
||||
<span class="text-sm text-gray-600 dark:text-gray-400">{{ $field->label }}</span>
|
||||
</div>
|
||||
@break
|
||||
|
||||
@case(FieldType::Select)
|
||||
<select id="{{ $inputId }}" name="{{ $name }}"
|
||||
{{ $field->required ? 'required' : '' }}
|
||||
class="block w-full rounded-md border-gray-300 shadow-sm text-sm focus:border-indigo-500 focus:ring-indigo-500 @error("data.{$field->name}") border-red-500 @enderror">
|
||||
class="block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm text-sm focus:border-indigo-500 focus:ring-indigo-500 @error("data.{$field->name}") border-red-500 @enderror">
|
||||
@if(!$field->required) <option value="">— Choisir —</option> @endif
|
||||
@foreach($field->options ?? [] as $opt)
|
||||
<option value="{{ $opt }}" {{ $oldValue === $opt ? 'selected' : '' }}>{{ $opt }}</option>
|
||||
@@ -67,7 +67,7 @@
|
||||
<div x-data="{ cal: '{{ $dateCal }}' }" class="flex gap-2">
|
||||
{{-- Sélecteur de calendrier --}}
|
||||
<select name="{{ $name }}[calendrier]" x-model="cal"
|
||||
class="w-40 rounded-md border-gray-300 shadow-sm text-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
class="w-40 rounded-md border-gray-300 dark:border-gray-600 shadow-sm text-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
<option value="gregorien">Grégorien</option>
|
||||
<option value="julien">Julien</option>
|
||||
<option value="republicain">Républicain</option>
|
||||
@@ -78,14 +78,14 @@
|
||||
type="date" name="{{ $name }}[valeur]"
|
||||
value="{{ $dateCal !== 'republicain' ? $dateVal : '' }}"
|
||||
{{ $field->required ? 'required' : '' }}
|
||||
class="flex-1 rounded-md border-gray-300 shadow-sm text-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
class="flex-1 rounded-md border-gray-300 dark:border-gray-600 shadow-sm text-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
|
||||
{{-- Date républicaine : saisie texte libre (ex: "15 Vendémiaire An III") --}}
|
||||
<input x-show="cal === 'republicain'" x-cloak
|
||||
type="text" name="{{ $name }}[valeur]"
|
||||
value="{{ $dateCal === 'republicain' ? $dateVal : '' }}"
|
||||
placeholder="ex : 15 Vendémiaire An III"
|
||||
class="flex-1 rounded-md border-gray-300 shadow-sm text-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
class="flex-1 rounded-md border-gray-300 dark:border-gray-600 shadow-sm text-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
</div>
|
||||
@error("data.{$field->name}.valeur")
|
||||
<p class="text-sm text-red-600">{{ $message }}</p>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
@endphp
|
||||
@include('releves._field', ['field' => $field, 'value' => $rawValue])
|
||||
@empty
|
||||
<p class="text-sm text-gray-400 italic">
|
||||
<p class="text-sm text-gray-400 dark:text-gray-500 italic">
|
||||
Ce type de source n'a aucun champ défini.
|
||||
<a href="{{ route('admin.source-types.show', $source->sourceType) }}" class="text-indigo-600 hover:underline">Configurer les champs →</a>
|
||||
</p>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<div>
|
||||
<h2 class="text-xl font-semibold text-gray-800">Nouveau relevé</h2>
|
||||
<p class="text-sm text-gray-500 mt-0.5">
|
||||
<h2 class="text-xl font-semibold text-gray-800 dark:text-gray-200">Nouveau relevé</h2>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 mt-0.5">
|
||||
Source : <a href="{{ route('sources.show', $source) }}" class="text-indigo-600 hover:underline">{{ $source->nom }}</a>
|
||||
· Type : {{ $source->sourceType->nom }}
|
||||
</p>
|
||||
@@ -10,17 +10,17 @@
|
||||
</x-slot>
|
||||
|
||||
<div class="py-8 max-w-3xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="bg-white shadow rounded-lg p-6">
|
||||
<div class="bg-white dark:bg-gray-800 shadow rounded-lg p-6">
|
||||
<form method="POST" action="{{ route('sources.releves.store', $source) }}">
|
||||
@csrf
|
||||
@include('releves._form', ['releve' => null])
|
||||
<div class="mt-8 pt-6 border-t border-gray-200 flex items-center gap-4">
|
||||
<div class="mt-8 pt-6 border-t border-gray-200 dark:border-gray-700 flex items-center gap-4">
|
||||
<button type="submit"
|
||||
class="px-5 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700">
|
||||
Enregistrer le relevé
|
||||
</button>
|
||||
<a href="{{ route('sources.releves.index', $source) }}"
|
||||
class="text-sm text-gray-500 hover:text-gray-700">Annuler</a>
|
||||
class="text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300">Annuler</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<div>
|
||||
<h2 class="text-xl font-semibold text-gray-800">Modifier le relevé #{{ $releve->id }}</h2>
|
||||
<p class="text-sm text-gray-500 mt-0.5">
|
||||
<h2 class="text-xl font-semibold text-gray-800 dark:text-gray-200">Modifier le relevé #{{ $releve->id }}</h2>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 mt-0.5">
|
||||
Source : <a href="{{ route('sources.show', $source) }}" class="text-indigo-600 hover:underline">{{ $source->nom }}</a>
|
||||
</p>
|
||||
</div>
|
||||
</x-slot>
|
||||
|
||||
<div class="py-8 max-w-3xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="bg-white shadow rounded-lg p-6">
|
||||
<div class="bg-white dark:bg-gray-800 shadow rounded-lg p-6">
|
||||
<form method="POST" action="{{ route('releves.update', $releve) }}">
|
||||
@csrf @method('PUT')
|
||||
@include('releves._form', ['releve' => $releve])
|
||||
<div class="mt-8 pt-6 border-t border-gray-200 flex items-center gap-4">
|
||||
<div class="mt-8 pt-6 border-t border-gray-200 dark:border-gray-700 flex items-center gap-4">
|
||||
<button type="submit"
|
||||
class="px-5 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700">
|
||||
Enregistrer
|
||||
</button>
|
||||
<a href="{{ route('releves.show', $releve) }}"
|
||||
class="text-sm text-gray-500 hover:text-gray-700">Annuler</a>
|
||||
class="text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300">Annuler</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
<x-slot name="header">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 class="text-xl font-semibold text-gray-800">Relevés — {{ $source->nom }}</h2>
|
||||
<p class="text-sm text-gray-500 mt-0.5">
|
||||
<h2 class="text-xl font-semibold text-gray-800 dark:text-gray-200">Relevés — {{ $source->nom }}</h2>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 mt-0.5">
|
||||
Type : {{ $source->sourceType->nom }}
|
||||
@if($source->cote) · Cote : {{ $source->cote }} @endif
|
||||
</p>
|
||||
@@ -11,7 +11,7 @@
|
||||
<div class="flex items-center gap-3">
|
||||
<a href="{{ route('sources.show', $source) }}" class="text-sm text-indigo-600 hover:underline">← Source</a>
|
||||
<a href="{{ route('export.source', $source) }}"
|
||||
class="px-4 py-2 border border-gray-300 text-gray-700 text-sm rounded-md hover:bg-gray-50"
|
||||
class="px-4 py-2 border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 text-sm rounded-md hover:bg-gray-50 dark:hover:bg-gray-700"
|
||||
title="Télécharger au format GEDCOM 5.5.1">
|
||||
↓ GEDCOM
|
||||
</a>
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
<div class="py-8 max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
@if(session('success'))
|
||||
<div class="mb-4 p-4 bg-green-50 border border-green-200 text-green-800 rounded-md">{{ session('success') }}</div>
|
||||
<div class="mb-4 p-4 bg-green-50 dark:bg-green-900/30 border border-green-200 dark:border-green-700 text-green-800 dark:text-green-200 rounded-md">{{ session('success') }}</div>
|
||||
@endif
|
||||
|
||||
@php
|
||||
@@ -35,31 +35,31 @@
|
||||
$colonnes = $source->sourceType->fields->take(5);
|
||||
@endphp
|
||||
|
||||
<div class="bg-white shadow rounded-lg overflow-hidden">
|
||||
<div class="bg-white dark:bg-gray-800 shadow rounded-lg overflow-hidden">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200 text-sm">
|
||||
<thead class="bg-gray-50">
|
||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700 text-sm">
|
||||
<thead class="bg-gray-50 dark:bg-gray-700">
|
||||
<tr>
|
||||
@foreach($colonnes as $col)
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase whitespace-nowrap">
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase whitespace-nowrap">
|
||||
{{ $col->label }}
|
||||
</th>
|
||||
@endforeach
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Saisi par</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Date</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Saisi par</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Date</th>
|
||||
<th class="px-4 py-3"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200">
|
||||
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
|
||||
@forelse($releves as $releve)
|
||||
<tr class="hover:bg-gray-50">
|
||||
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700">
|
||||
@foreach($colonnes as $col)
|
||||
<td class="px-4 py-3 text-gray-700">
|
||||
<td class="px-4 py-3 text-gray-700 dark:text-gray-300">
|
||||
@php $val = $releve->data[$col->name] ?? null; @endphp
|
||||
@if(is_array($val))
|
||||
{{ $val['valeur'] ?? '' }}
|
||||
@if(!empty($val['calendrier']) && $val['calendrier'] !== 'gregorien')
|
||||
<span class="text-xs text-gray-400">({{ $val['calendrier'] }})</span>
|
||||
<span class="text-xs text-gray-400 dark:text-gray-500">({{ $val['calendrier'] }})</span>
|
||||
@endif
|
||||
@elseif(is_bool($val))
|
||||
{{ $val ? 'Oui' : 'Non' }}
|
||||
@@ -68,12 +68,12 @@
|
||||
@endif
|
||||
</td>
|
||||
@endforeach
|
||||
<td class="px-4 py-3 text-gray-500">{{ $releve->createur?->name ?? '—' }}</td>
|
||||
<td class="px-4 py-3 text-gray-500 whitespace-nowrap">{{ $releve->created_at->format('d/m/Y') }}</td>
|
||||
<td class="px-4 py-3 text-gray-500 dark:text-gray-400">{{ $releve->createur?->name ?? '—' }}</td>
|
||||
<td class="px-4 py-3 text-gray-500 dark:text-gray-400 whitespace-nowrap">{{ $releve->created_at->format('d/m/Y') }}</td>
|
||||
<td class="px-4 py-3 text-right whitespace-nowrap space-x-3">
|
||||
<a href="{{ route('releves.show', $releve) }}" class="text-indigo-600 hover:underline">Voir</a>
|
||||
@can('update', $releve)
|
||||
<a href="{{ route('releves.edit', $releve) }}" class="text-gray-600 hover:text-indigo-600">Modifier</a>
|
||||
<a href="{{ route('releves.edit', $releve) }}" class="text-gray-600 dark:text-gray-400 hover:text-indigo-600">Modifier</a>
|
||||
@endcan
|
||||
@can('delete', $releve)
|
||||
<form method="POST" action="{{ route('releves.destroy', $releve) }}" class="inline"
|
||||
@@ -87,7 +87,7 @@
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="{{ $colonnes->count() + 3 }}"
|
||||
class="px-6 py-10 text-center text-gray-400">
|
||||
class="px-6 py-10 text-center text-gray-400 dark:text-gray-500">
|
||||
Aucun relevé pour cette source.
|
||||
</td>
|
||||
</tr>
|
||||
@@ -98,10 +98,10 @@
|
||||
|
||||
{{-- Navigation curseur (keyset pagination) --}}
|
||||
@if($releves->hasPages())
|
||||
<div class="px-6 py-4 border-t border-gray-200 flex items-center justify-between text-sm">
|
||||
<div class="px-6 py-4 border-t border-gray-200 dark:border-gray-700 flex items-center justify-between text-sm">
|
||||
<div>
|
||||
@if($releves->onFirstPage())
|
||||
<span class="text-gray-400">← Précédent</span>
|
||||
<span class="text-gray-400 dark:text-gray-500">← Précédent</span>
|
||||
@else
|
||||
<a href="{{ $releves->previousPageUrl() }}" class="text-indigo-600 hover:underline">← Précédent</a>
|
||||
@endif
|
||||
@@ -110,7 +110,7 @@
|
||||
@if($releves->hasMorePages())
|
||||
<a href="{{ $releves->nextPageUrl() }}" class="text-indigo-600 hover:underline">Suivant →</a>
|
||||
@else
|
||||
<span class="text-gray-400">Suivant →</span>
|
||||
<span class="text-gray-400 dark:text-gray-500">Suivant →</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
<x-slot name="header">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 class="text-xl font-semibold text-gray-800">Relevé #{{ $releve->id }}</h2>
|
||||
<p class="text-sm text-gray-500 mt-0.5">
|
||||
<h2 class="text-xl font-semibold text-gray-800 dark:text-gray-200">Relevé #{{ $releve->id }}</h2>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 mt-0.5">
|
||||
Source : <a href="{{ route('sources.show', $source) }}" class="text-indigo-600 hover:underline">{{ $source->nom }}</a>
|
||||
</p>
|
||||
</div>
|
||||
@@ -30,25 +30,25 @@
|
||||
|
||||
<div class="py-8 max-w-3xl mx-auto px-4 sm:px-6 lg:px-8 space-y-6">
|
||||
@if(session('success'))
|
||||
<div class="p-4 bg-green-50 border border-green-200 text-green-800 rounded-md">{{ session('success') }}</div>
|
||||
<div class="p-4 bg-green-50 dark:bg-green-900/30 border border-green-200 dark:border-green-700 text-green-800 dark:text-green-200 rounded-md">{{ session('success') }}</div>
|
||||
@endif
|
||||
|
||||
{{-- Champs du relevé --}}
|
||||
<div class="bg-white shadow rounded-lg divide-y divide-gray-100">
|
||||
<div class="bg-white dark:bg-gray-800 shadow rounded-lg divide-y divide-gray-100 dark:divide-gray-700">
|
||||
@foreach($source->sourceType->fields as $field)
|
||||
@php $val = $releve->data[$field->name] ?? null; @endphp
|
||||
<div class="px-6 py-4 grid grid-cols-3 gap-4 text-sm">
|
||||
<dt class="font-medium text-gray-500">{{ $field->label }}</dt>
|
||||
<dd class="col-span-2 text-gray-900">
|
||||
<dt class="font-medium text-gray-500 dark:text-gray-400">{{ $field->label }}</dt>
|
||||
<dd class="col-span-2 text-gray-900 dark:text-white">
|
||||
@if($val === null || $val === '')
|
||||
<span class="text-gray-400">—</span>
|
||||
<span class="text-gray-400 dark:text-gray-500">—</span>
|
||||
@elseif(is_array($val))
|
||||
{{ $val['valeur'] ?? '—' }}
|
||||
@if(!empty($val['calendrier']) && $val['calendrier'] !== 'gregorien')
|
||||
<span class="ml-1 text-xs text-gray-400 capitalize">({{ $val['calendrier'] }})</span>
|
||||
<span class="ml-1 text-xs text-gray-400 dark:text-gray-500 capitalize">({{ $val['calendrier'] }})</span>
|
||||
@endif
|
||||
@elseif(is_bool($val))
|
||||
<span class="{{ $val ? 'text-green-700' : 'text-gray-400' }}">
|
||||
<span class="{{ $val ? 'text-green-700' : 'text-gray-400 dark:text-gray-500' }}">
|
||||
{{ $val ? 'Oui' : 'Non' }}
|
||||
</span>
|
||||
@else
|
||||
@@ -60,7 +60,7 @@
|
||||
</div>
|
||||
|
||||
{{-- Méta-données de saisie --}}
|
||||
<div class="bg-gray-50 rounded-lg px-6 py-4 text-xs text-gray-500 space-y-1">
|
||||
<div class="bg-gray-50 dark:bg-gray-700 rounded-lg px-6 py-4 text-xs text-gray-500 dark:text-gray-400 space-y-1">
|
||||
<p>Saisi par <strong>{{ $releve->createur?->name ?? '?' }}</strong> le {{ $releve->created_at->format('d/m/Y à H:i') }}</p>
|
||||
@if($releve->updated_at != $releve->created_at)
|
||||
<p>Modifié par <strong>{{ $releve->modificateur?->name ?? '?' }}</strong> le {{ $releve->updated_at->format('d/m/Y à H:i') }}</p>
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
<div class="space-y-5">
|
||||
<div>
|
||||
<label for="nom" class="block text-sm font-medium text-gray-700">Nom <span class="text-red-500">*</span></label>
|
||||
<label for="nom" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Nom <span class="text-red-500">*</span></label>
|
||||
<input type="text" id="nom" name="nom" value="{{ old('nom', $source?->nom) }}" required
|
||||
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 @error('nom') border-red-500 @enderror">
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 @error('nom') border-red-500 @enderror">
|
||||
@error('nom') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="description" class="block text-sm font-medium text-gray-700">Description</label>
|
||||
<label for="description" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Description</label>
|
||||
<textarea id="description" name="description" rows="3"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">{{ old('description', $source?->description) }}</textarea>
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">{{ old('description', $source?->description) }}</textarea>
|
||||
</div>
|
||||
|
||||
{{-- Section propriétaire --}}
|
||||
@if($sections->isNotEmpty())
|
||||
<div>
|
||||
<label for="section_id" class="block text-sm font-medium text-gray-700">Section</label>
|
||||
<label for="section_id" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Section</label>
|
||||
<select id="section_id" name="section_id"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 text-sm">
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 text-sm">
|
||||
<option value="">— Aucune (globale) —</option>
|
||||
@foreach($sections as $sec)
|
||||
<option value="{{ $sec->id }}" {{ old('section_id', $source?->section_id) == $sec->id ? 'selected' : '' }}>
|
||||
@@ -31,9 +31,9 @@
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label for="source_type_id" class="block text-sm font-medium text-gray-700">Type de source <span class="text-red-500">*</span></label>
|
||||
<label for="source_type_id" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Type de source <span class="text-red-500">*</span></label>
|
||||
<select id="source_type_id" name="source_type_id" required
|
||||
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 @error('source_type_id') border-red-500 @enderror">
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 @error('source_type_id') border-red-500 @enderror">
|
||||
<option value="">— Choisir —</option>
|
||||
@foreach($sourceTypes as $st)
|
||||
<option value="{{ $st->id }}" {{ old('source_type_id', $source?->source_type_id) == $st->id ? 'selected' : '' }}>
|
||||
@@ -45,9 +45,9 @@
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="depot_id" class="block text-sm font-medium text-gray-700">Dépôt d'archives</label>
|
||||
<label for="depot_id" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Dépôt d'archives</label>
|
||||
<select id="depot_id" name="depot_id"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
<option value="">— Aucun —</option>
|
||||
@foreach($depots as $depot)
|
||||
<option value="{{ $depot->id }}" {{ old('depot_id', $source?->depot_id) == $depot->id ? 'selected' : '' }}>
|
||||
@@ -83,33 +83,33 @@
|
||||
{{-- Période couverte --}}
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label for="annee_debut" class="block text-sm font-medium text-gray-700">Année de début</label>
|
||||
<label for="annee_debut" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Année de début</label>
|
||||
<input type="number" id="annee_debut" name="annee_debut"
|
||||
value="{{ old('annee_debut', $source?->annee_debut) }}"
|
||||
min="1000" max="2100" placeholder="ex : 1820"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 @error('annee_debut') border-red-500 @enderror">
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 @error('annee_debut') border-red-500 @enderror">
|
||||
@error('annee_debut') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
|
||||
</div>
|
||||
<div>
|
||||
<label for="annee_fin" class="block text-sm font-medium text-gray-700">Année de fin</label>
|
||||
<label for="annee_fin" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Année de fin</label>
|
||||
<input type="number" id="annee_fin" name="annee_fin"
|
||||
value="{{ old('annee_fin', $source?->annee_fin) }}"
|
||||
min="1000" max="2100" placeholder="ex : 1870"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 @error('annee_fin') border-red-500 @enderror">
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 @error('annee_fin') border-red-500 @enderror">
|
||||
@error('annee_fin') <p class="mt-1 text-sm text-red-600">{{ $message }}</p> @enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label for="cote" class="block text-sm font-medium text-gray-700">Cote</label>
|
||||
<label for="cote" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Cote</label>
|
||||
<input type="text" id="cote" name="cote" value="{{ old('cote', $source?->cote) }}"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
</div>
|
||||
<div>
|
||||
<label for="auteur" class="block text-sm font-medium text-gray-700">Auteur</label>
|
||||
<label for="auteur" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Auteur</label>
|
||||
<input type="text" id="auteur" name="auteur" value="{{ old('auteur', $source?->auteur) }}"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header"><h2 class="text-xl font-semibold text-gray-800">Nouvelle source</h2></x-slot>
|
||||
<x-slot name="header"><h2 class="text-xl font-semibold text-gray-800 dark:text-gray-200">Nouvelle source</h2></x-slot>
|
||||
<div class="py-8 max-w-2xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="bg-white shadow rounded-lg p-6">
|
||||
<div class="bg-white dark:bg-gray-800 shadow rounded-lg p-6">
|
||||
<form method="POST" action="{{ route('sources.store') }}">
|
||||
@csrf
|
||||
@include('sources._form', ['source' => null])
|
||||
<div class="mt-6 flex gap-4">
|
||||
<button type="submit" class="px-5 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700">Créer</button>
|
||||
<a href="{{ route('sources.index') }}" class="text-sm text-gray-500 self-center hover:text-gray-700">Annuler</a>
|
||||
<a href="{{ route('sources.index') }}" class="text-sm text-gray-500 dark:text-gray-400 self-center hover:text-gray-700 dark:hover:text-gray-300">Annuler</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header"><h2 class="text-xl font-semibold text-gray-800">Modifier : {{ $source->nom }}</h2></x-slot>
|
||||
<x-slot name="header"><h2 class="text-xl font-semibold text-gray-800 dark:text-gray-200">Modifier : {{ $source->nom }}</h2></x-slot>
|
||||
<div class="py-8 max-w-2xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="bg-white shadow rounded-lg p-6">
|
||||
<div class="bg-white dark:bg-gray-800 shadow rounded-lg p-6">
|
||||
<form method="POST" action="{{ route('sources.update', $source) }}">
|
||||
@csrf @method('PUT')
|
||||
@include('sources._form', ['source' => $source])
|
||||
<div class="mt-6 flex gap-4">
|
||||
<button type="submit" class="px-5 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700">Enregistrer</button>
|
||||
<a href="{{ route('sources.show', $source) }}" class="text-sm text-gray-500 self-center hover:text-gray-700">Annuler</a>
|
||||
<a href="{{ route('sources.show', $source) }}" class="text-sm text-gray-500 dark:text-gray-400 self-center hover:text-gray-700 dark:hover:text-gray-300">Annuler</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="text-xl font-semibold text-gray-800">Sources</h2>
|
||||
<h2 class="text-xl font-semibold text-gray-800 dark:text-gray-200">Sources</h2>
|
||||
@can('create', App\Models\Source::class)
|
||||
<a href="{{ route('sources.create') }}"
|
||||
class="px-4 py-2 bg-indigo-600 text-white text-sm rounded-md hover:bg-indigo-700">+ Nouvelle source</a>
|
||||
@@ -11,22 +11,22 @@
|
||||
|
||||
<div class="py-8 max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 space-y-6">
|
||||
@if(session('success'))
|
||||
<div class="p-4 bg-green-50 border border-green-200 text-green-800 rounded-md">{{ session('success') }}</div>
|
||||
<div class="p-4 bg-green-50 dark:bg-green-900/30 border border-green-200 dark:border-green-700 text-green-800 dark:text-green-200 rounded-md">{{ session('success') }}</div>
|
||||
@endif
|
||||
|
||||
{{-- Filtres --}}
|
||||
@php
|
||||
$hasFilters = request()->anyFilled(['status', 'source_type_id', 'lieu_id', 'annee_debut', 'annee_fin']);
|
||||
@endphp
|
||||
<div class="bg-white shadow rounded-lg p-5">
|
||||
<div class="bg-white dark:bg-gray-800 shadow rounded-lg p-5">
|
||||
<form method="GET" action="{{ route('sources.index') }}">
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
|
||||
{{-- Statut --}}
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">Statut</label>
|
||||
<label class="block text-xs font-medium text-gray-600 dark:text-gray-400 mb-1">Statut</label>
|
||||
<select name="status"
|
||||
class="block w-full rounded-md border-gray-300 shadow-sm text-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
class="block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm text-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
<option value="">— Tous —</option>
|
||||
@foreach(\App\Enums\SourceStatus::cases() as $s)
|
||||
<option value="{{ $s->value }}" {{ request('status') === $s->value ? 'selected' : '' }}>
|
||||
@@ -38,9 +38,9 @@
|
||||
|
||||
{{-- Type de source --}}
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">Type de source</label>
|
||||
<label class="block text-xs font-medium text-gray-600 dark:text-gray-400 mb-1">Type de source</label>
|
||||
<select name="source_type_id"
|
||||
class="block w-full rounded-md border-gray-300 shadow-sm text-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
class="block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm text-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
<option value="">— Tous —</option>
|
||||
@foreach($sourceTypes as $st)
|
||||
<option value="{{ $st->id }}" {{ request('source_type_id') == $st->id ? 'selected' : '' }}>
|
||||
@@ -52,18 +52,18 @@
|
||||
|
||||
{{-- Année de début --}}
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">Période — de</label>
|
||||
<label class="block text-xs font-medium text-gray-600 dark:text-gray-400 mb-1">Période — de</label>
|
||||
<input type="number" name="annee_debut" value="{{ request('annee_debut') }}"
|
||||
min="1000" max="2100" placeholder="ex : 1820"
|
||||
class="block w-full rounded-md border-gray-300 shadow-sm text-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
class="block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm text-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
</div>
|
||||
|
||||
{{-- Année de fin --}}
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">Période — à</label>
|
||||
<label class="block text-xs font-medium text-gray-600 dark:text-gray-400 mb-1">Période — à</label>
|
||||
<input type="number" name="annee_fin" value="{{ request('annee_fin') }}"
|
||||
min="1000" max="2100" placeholder="ex : 1870"
|
||||
class="block w-full rounded-md border-gray-300 shadow-sm text-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
class="block w-full rounded-md border-gray-300 dark:border-gray-600 shadow-sm text-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -85,10 +85,10 @@
|
||||
</button>
|
||||
@if($hasFilters)
|
||||
<a href="{{ route('sources.index') }}"
|
||||
class="px-4 py-2 border border-gray-300 text-gray-600 text-sm rounded-md hover:bg-gray-50">
|
||||
class="px-4 py-2 border border-gray-300 dark:border-gray-600 text-gray-600 dark:text-gray-400 text-sm rounded-md hover:bg-gray-50 dark:hover:bg-gray-700">
|
||||
Effacer les filtres
|
||||
</a>
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs bg-indigo-100 text-indigo-700">
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs bg-indigo-100 dark:bg-indigo-900/50 text-indigo-700">
|
||||
filtres actifs
|
||||
</span>
|
||||
@endif
|
||||
@@ -97,30 +97,30 @@
|
||||
</div>
|
||||
|
||||
{{-- Tableau --}}
|
||||
<div class="bg-white shadow rounded-lg overflow-hidden">
|
||||
<table class="min-w-full divide-y divide-gray-200 text-sm">
|
||||
<thead class="bg-gray-50">
|
||||
<div class="bg-white dark:bg-gray-800 shadow rounded-lg overflow-hidden">
|
||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700 text-sm">
|
||||
<thead class="bg-gray-50 dark:bg-gray-700">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Nom</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Type</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Statut</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Lieu</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Période</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Relevés</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Dépôt</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Nom</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Type</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Statut</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Lieu</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Période</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Relevés</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Dépôt</th>
|
||||
<th class="px-6 py-3"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200">
|
||||
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
|
||||
@forelse($sources as $source)
|
||||
@php
|
||||
$statusColors = [
|
||||
'a_faire' => 'bg-gray-100 text-gray-600',
|
||||
'en_cours' => 'bg-blue-100 text-blue-700',
|
||||
'a_valider' => 'bg-yellow-100 text-yellow-700',
|
||||
'termine' => 'bg-green-100 text-green-700',
|
||||
'a_faire' => 'bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400',
|
||||
'en_cours' => 'bg-blue-100 dark:bg-blue-900/50 text-blue-700',
|
||||
'a_valider' => 'bg-yellow-100 dark:bg-yellow-900/50 text-yellow-700',
|
||||
'termine' => 'bg-green-100 dark:bg-green-900/50 text-green-700',
|
||||
];
|
||||
$color = $statusColors[$source->status->value] ?? 'bg-gray-100 text-gray-600';
|
||||
$color = $statusColors[$source->status->value] ?? 'bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400';
|
||||
$periode = match(true) {
|
||||
$source->annee_debut && $source->annee_fin => $source->annee_debut . ' – ' . $source->annee_fin,
|
||||
(bool)$source->annee_debut => 'depuis ' . $source->annee_debut,
|
||||
@@ -128,32 +128,32 @@
|
||||
default => '—',
|
||||
};
|
||||
@endphp
|
||||
<tr class="hover:bg-gray-50">
|
||||
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700">
|
||||
<td class="px-6 py-4 font-medium">
|
||||
<a href="{{ route('sources.show', $source) }}" class="text-indigo-600 hover:underline">{{ $source->nom }}</a>
|
||||
@if($source->cote) <span class="ml-2 text-xs text-gray-400">{{ $source->cote }}</span> @endif
|
||||
@if($source->cote) <span class="ml-2 text-xs text-gray-400 dark:text-gray-500">{{ $source->cote }}</span> @endif
|
||||
</td>
|
||||
<td class="px-6 py-4 text-gray-500">{{ $source->sourceType->nom }}</td>
|
||||
<td class="px-6 py-4 text-gray-500 dark:text-gray-400">{{ $source->sourceType->nom }}</td>
|
||||
<td class="px-6 py-4">
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium {{ $color }}">
|
||||
{{ $source->status->label() }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-gray-500 max-w-[180px] truncate" title="{{ $source->lieu?->nom_long ?? $source->lieu?->nom }}">
|
||||
<td class="px-6 py-4 text-gray-500 dark:text-gray-400 max-w-[180px] truncate" title="{{ $source->lieu?->nom_long ?? $source->lieu?->nom }}">
|
||||
{{ $source->lieu?->nom ?? '—' }}
|
||||
</td>
|
||||
<td class="px-6 py-4 text-gray-500 whitespace-nowrap">{{ $periode }}</td>
|
||||
<td class="px-6 py-4 text-gray-500">{{ $source->releves_count }}</td>
|
||||
<td class="px-6 py-4 text-gray-500">{{ $source->depot?->nom ?? '—' }}</td>
|
||||
<td class="px-6 py-4 text-gray-500 dark:text-gray-400 whitespace-nowrap">{{ $periode }}</td>
|
||||
<td class="px-6 py-4 text-gray-500 dark:text-gray-400">{{ $source->releves_count }}</td>
|
||||
<td class="px-6 py-4 text-gray-500 dark:text-gray-400">{{ $source->depot?->nom ?? '—' }}</td>
|
||||
<td class="px-6 py-4 text-right text-sm space-x-3">
|
||||
@can('update', $source)
|
||||
<a href="{{ route('sources.edit', $source) }}" class="text-gray-600 hover:text-indigo-600">Modifier</a>
|
||||
<a href="{{ route('sources.edit', $source) }}" class="text-gray-600 dark:text-gray-400 hover:text-indigo-600">Modifier</a>
|
||||
@endcan
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="8" class="px-6 py-10 text-center text-gray-400">
|
||||
<td colspan="8" class="px-6 py-10 text-center text-gray-400 dark:text-gray-500">
|
||||
@if($hasFilters) Aucune source ne correspond aux filtres.
|
||||
@else Aucune source disponible. @endif
|
||||
</td>
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
<x-slot name="header">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 class="text-xl font-semibold text-gray-800">{{ $source->nom }}</h2>
|
||||
<h2 class="text-xl font-semibold text-gray-800 dark:text-gray-200">{{ $source->nom }}</h2>
|
||||
@if($source->cote)
|
||||
<p class="text-sm text-gray-500 mt-0.5">Cote : {{ $source->cote }}</p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 mt-0.5">Cote : {{ $source->cote }}</p>
|
||||
@endif
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
@can('update', $source)
|
||||
<a href="{{ route('sources.edit', $source) }}"
|
||||
class="px-4 py-2 bg-white border border-gray-300 text-gray-700 text-sm rounded-md hover:bg-gray-50">Modifier</a>
|
||||
class="px-4 py-2 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 text-sm rounded-md hover:bg-gray-50 dark:hover:bg-gray-700">Modifier</a>
|
||||
@endcan
|
||||
@can('delete', $source)
|
||||
<form method="POST" action="{{ route('sources.destroy', $source) }}"
|
||||
@@ -26,7 +26,7 @@
|
||||
<div class="py-8 max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 space-y-6">
|
||||
@foreach(['success','error'] as $flash)
|
||||
@if(session($flash))
|
||||
<div class="p-4 rounded-md {{ $flash === 'success' ? 'bg-green-50 border border-green-200 text-green-800' : 'bg-red-50 border border-red-200 text-red-800' }}">
|
||||
<div class="p-4 rounded-md {{ $flash === 'success' ? 'bg-green-50 dark:bg-green-900/30 border border-green-200 dark:border-green-700 text-green-800 dark:text-green-200' : 'bg-red-50 dark:bg-red-900/30 border border-red-200 dark:border-red-700 text-red-800 dark:text-red-200' }}">
|
||||
{{ session($flash) }}
|
||||
</div>
|
||||
@endif
|
||||
@@ -34,33 +34,33 @@
|
||||
|
||||
<div class="grid grid-cols-3 gap-6">
|
||||
{{-- Fiche source --}}
|
||||
<div class="col-span-2 bg-white shadow rounded-lg divide-y divide-gray-100 text-sm">
|
||||
<div class="col-span-2 bg-white dark:bg-gray-800 shadow rounded-lg divide-y divide-gray-100 dark:divide-gray-700 text-sm">
|
||||
@foreach([
|
||||
['Type', $source->sourceType->nom],
|
||||
['Dépôt', $source->depot?->nom ?? '—'],
|
||||
['Auteur', $source->auteur ?? '—'],
|
||||
] as [$label, $val])
|
||||
<div class="px-6 py-4 grid grid-cols-3 gap-4">
|
||||
<dt class="font-medium text-gray-500">{{ $label }}</dt>
|
||||
<dd class="col-span-2 text-gray-900">{{ $val }}</dd>
|
||||
<dt class="font-medium text-gray-500 dark:text-gray-400">{{ $label }}</dt>
|
||||
<dd class="col-span-2 text-gray-900 dark:text-white">{{ $val }}</dd>
|
||||
</div>
|
||||
@endforeach
|
||||
@if($source->description)
|
||||
<div class="px-6 py-4 grid grid-cols-3 gap-4">
|
||||
<dt class="font-medium text-gray-500">Description</dt>
|
||||
<dd class="col-span-2 text-gray-900 whitespace-pre-line">{{ $source->description }}</dd>
|
||||
<dt class="font-medium text-gray-500 dark:text-gray-400">Description</dt>
|
||||
<dd class="col-span-2 text-gray-900 dark:text-white whitespace-pre-line">{{ $source->description }}</dd>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- Statut + transitions --}}
|
||||
<div class="bg-white shadow rounded-lg p-6 space-y-4">
|
||||
<div class="bg-white dark:bg-gray-800 shadow rounded-lg p-6 space-y-4">
|
||||
@php
|
||||
$statusColors = ['a_faire'=>'gray','en_cours'=>'blue','a_valider'=>'yellow','termine'=>'green'];
|
||||
$color = $statusColors[$source->status->value] ?? 'gray';
|
||||
@endphp
|
||||
<div>
|
||||
<p class="text-xs font-medium text-gray-500 uppercase mb-2">Statut</p>
|
||||
<p class="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase mb-2">Statut</p>
|
||||
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-{{ $color }}-100 text-{{ $color }}-700">
|
||||
{{ $source->status->label() }}
|
||||
</span>
|
||||
@@ -70,14 +70,14 @@
|
||||
@php $transitions = $source->status->transitions(); @endphp
|
||||
@if(count($transitions))
|
||||
<div class="space-y-2">
|
||||
<p class="text-xs font-medium text-gray-500 uppercase">Changer le statut</p>
|
||||
<p class="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Changer le statut</p>
|
||||
@foreach($transitions as $next)
|
||||
@if($source->canTransitionTo($next, auth()->user()))
|
||||
<form method="POST" action="{{ route('sources.transition', $source) }}">
|
||||
@csrf
|
||||
<input type="hidden" name="status" value="{{ $next->value }}">
|
||||
<button type="submit"
|
||||
class="w-full text-left px-3 py-2 text-sm border border-gray-200 rounded-md hover:bg-gray-50 hover:border-indigo-300 transition-colors">
|
||||
class="w-full text-left px-3 py-2 text-sm border border-gray-200 dark:border-gray-700 rounded-md hover:bg-gray-50 dark:hover:bg-gray-700 hover:border-indigo-300 transition-colors">
|
||||
→ {{ $next->label() }}
|
||||
</button>
|
||||
</form>
|
||||
@@ -91,17 +91,17 @@
|
||||
|
||||
{{-- Membres assignés --}}
|
||||
@can('assignMembre', $source)
|
||||
<div class="bg-white shadow rounded-lg overflow-hidden">
|
||||
<div class="px-6 py-4 border-b border-gray-200 font-medium text-gray-900">
|
||||
<div class="bg-white dark:bg-gray-800 shadow rounded-lg overflow-hidden">
|
||||
<div class="px-6 py-4 border-b border-gray-200 dark:border-gray-700 font-medium text-gray-900 dark:text-white">
|
||||
Membres assignés ({{ $source->membres->count() }})
|
||||
</div>
|
||||
@if($source->membres->isNotEmpty())
|
||||
<table class="min-w-full divide-y divide-gray-200 text-sm">
|
||||
<tbody class="divide-y divide-gray-200">
|
||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700 text-sm">
|
||||
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
|
||||
@foreach($source->membres as $membre)
|
||||
<tr>
|
||||
<td class="px-6 py-3">{{ $membre->name }}</td>
|
||||
<td class="px-6 py-3 text-gray-500">{{ $membre->email }}</td>
|
||||
<td class="px-6 py-3 text-gray-500 dark:text-gray-400">{{ $membre->email }}</td>
|
||||
<td class="px-6 py-3 text-right">
|
||||
<form method="POST" action="{{ route('sources.membres.remove', [$source, $membre]) }}"
|
||||
x-data @submit.prevent="if(confirm('Retirer ce membre ?')) $el.submit()">
|
||||
@@ -114,27 +114,26 @@
|
||||
</tbody>
|
||||
</table>
|
||||
@endif
|
||||
<div class="px-6 py-4 bg-gray-50 border-t border-gray-200">
|
||||
<form method="POST" action="{{ route('sources.membres.add', $source) }}" class="flex gap-3 items-end">
|
||||
<div class="px-6 py-4 bg-gray-50 dark:bg-gray-700 border-t border-gray-200 dark:border-gray-700">
|
||||
<form method="POST" action="{{ route('sources.membres.add', $source) }}"
|
||||
@submit.prevent="if ($el.querySelector('[name=user_id]').value) $el.submit()">
|
||||
@csrf
|
||||
<div class="flex-1">
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">Ajouter un membre</label>
|
||||
<select name="user_id" class="block w-full rounded-md border-gray-300 shadow-sm text-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
@foreach($availableUsers as $u)
|
||||
<option value="{{ $u->id }}">{{ $u->name }} ({{ $u->email }})</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<div class="flex flex-col sm:flex-row gap-3 items-stretch sm:items-end">
|
||||
<div class="flex-1">
|
||||
<label class="block text-xs font-medium text-gray-600 dark:text-gray-400 mb-1">Ajouter un membre</label>
|
||||
<x-user-picker :users="$availableUsers" placeholder="Rechercher un membre à assigner…" required />
|
||||
</div>
|
||||
<button type="submit" class="px-4 py-2 bg-indigo-600 text-white text-sm rounded-md hover:bg-indigo-700 shrink-0">Assigner</button>
|
||||
</div>
|
||||
<button type="submit" class="px-4 py-2 bg-indigo-600 text-white text-sm rounded-md hover:bg-indigo-700">Assigner</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@endcan
|
||||
|
||||
{{-- Liste des relevés (aperçu) --}}
|
||||
<div class="bg-white shadow rounded-lg overflow-hidden">
|
||||
<div class="px-6 py-4 border-b border-gray-200 flex items-center justify-between">
|
||||
<h3 class="font-medium text-gray-900">Relevés ({{ $source->releves->count() }})</h3>
|
||||
<div class="bg-white dark:bg-gray-800 shadow rounded-lg overflow-hidden">
|
||||
<div class="px-6 py-4 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between">
|
||||
<h3 class="font-medium text-gray-900 dark:text-white">Relevés ({{ $source->releves->count() }})</h3>
|
||||
@can('create', [App\Models\Releve::class, $source])
|
||||
<a href="{{ route('sources.releves.create', $source) }}"
|
||||
class="px-3 py-1.5 bg-indigo-600 text-white text-xs rounded-md hover:bg-indigo-700">
|
||||
@@ -143,9 +142,9 @@
|
||||
@endcan
|
||||
</div>
|
||||
@if($source->releves->isEmpty())
|
||||
<p class="px-6 py-8 text-center text-gray-400 text-sm">Aucun relevé pour cette source.</p>
|
||||
<p class="px-6 py-8 text-center text-gray-400 dark:text-gray-500 text-sm">Aucun relevé pour cette source.</p>
|
||||
@else
|
||||
<p class="px-6 py-4 text-sm text-gray-500">
|
||||
<p class="px-6 py-4 text-sm text-gray-500 dark:text-gray-400">
|
||||
<a href="{{ route('sources.releves.index', $source) }}" class="text-indigo-600 hover:underline">
|
||||
Voir les {{ $source->releves->count() }} relevé(s) →
|
||||
</a>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<link href="https://fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet"/>
|
||||
@endif
|
||||
</head>
|
||||
<body class="font-sans antialiased bg-gray-50 min-h-screen flex flex-col items-center justify-center px-4">
|
||||
<body class="font-sans antialiased bg-gray-50 dark:bg-gray-700 min-h-screen flex flex-col items-center justify-center px-4">
|
||||
|
||||
<div class="text-center max-w-md w-full">
|
||||
|
||||
@@ -23,10 +23,10 @@
|
||||
<img src="{{ $siteLogoUrl }}" alt="{{ config('app.name') }}"
|
||||
class="h-28 w-auto object-contain mx-auto mb-8">
|
||||
@else
|
||||
<h1 class="text-3xl font-bold text-gray-900 mb-4">{{ config('app.name') }}</h1>
|
||||
<h1 class="text-3xl font-bold text-gray-900 dark:text-white mb-4">{{ config('app.name') }}</h1>
|
||||
@endif
|
||||
|
||||
<p class="text-gray-500 text-sm mb-10">
|
||||
<p class="text-gray-500 dark:text-gray-400 text-sm mb-10">
|
||||
Application de relevés généalogiques
|
||||
</p>
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
</a>
|
||||
@if($registrationEnabled && Route::has('register'))
|
||||
<a href="{{ route('register') }}"
|
||||
class="px-8 py-3 border border-gray-300 text-gray-700 font-medium rounded-lg hover:bg-gray-100 transition-colors">
|
||||
class="px-8 py-3 border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 font-medium rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors">
|
||||
Créer un compte
|
||||
</a>
|
||||
@endif
|
||||
@@ -51,7 +51,7 @@
|
||||
@endauth
|
||||
</div>
|
||||
|
||||
<footer class="absolute bottom-6 text-xs text-gray-300">
|
||||
<footer class="absolute bottom-6 text-xs text-gray-300 dark:text-gray-600">
|
||||
{{ config('app.name') }}
|
||||
</footer>
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ Route::middleware(['auth', 'role:admin'])->prefix('admin')->name('admin.')->grou
|
||||
Route::post('parametres/smtp', [SettingController::class, 'updateSmtp'])->name('parametres.smtp.update');
|
||||
Route::delete('parametres/smtp', [SettingController::class, 'deleteSmtp'])->name('parametres.smtp.delete');
|
||||
Route::post('parametres/smtp/test', [SettingController::class, 'testSmtp'])->name('parametres.smtp.test');
|
||||
Route::post('parametres/updates', [SettingController::class, 'updateUpdates'])->name('parametres.updates');
|
||||
|
||||
// Routes spécifiques avant la resource pour éviter les conflits de paramètre
|
||||
Route::get('utilisateurs/export', [UserController::class, 'export'])->name('utilisateurs.export');
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Controllers\CarteController;
|
||||
use App\Http\Controllers\DashboardController;
|
||||
use App\Http\Controllers\ExportController;
|
||||
use App\Http\Controllers\LieuController;
|
||||
@@ -57,6 +58,8 @@ Route::middleware('auth')->group(function () {
|
||||
->parameters(['releves' => 'releve']);
|
||||
|
||||
Route::get('recherche', [RechercheController::class, 'index'])->name('recherche');
|
||||
Route::get('carte', [CarteController::class, 'index'])->name('carte');
|
||||
Route::get('carte/data', [CarteController::class, 'data'])->name('carte.data');
|
||||
Route::get('export/source/{source}', [ExportController::class, 'source'])->name('export.source');
|
||||
Route::get('export/recherche', [ExportController::class, 'recherche'])->name('export.recherche');
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import forms from '@tailwindcss/forms';
|
||||
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
darkMode: 'class',
|
||||
content: [
|
||||
'./vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php',
|
||||
'./storage/framework/views/*.php',
|
||||
|
||||
Reference in New Issue
Block a user