TreeMap
A free, open-source disk-space visualizer. Squarified treemap, duplicate hunting, disk-full forecasting, and cleanup that only ever moves files to the Trash.
翻譯準備中
尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證Planning de gardes hospitalier : édition en temps réel, bourse aux gardes, compteurs.
翻譯準備中
取自儲存庫 README
正在載入完整 README。 以下為已儲存的摘錄。
Application web progressive (PWA) permettant à une équipe de médecins d'un hôpital de synchroniser leur planning de gardes en dehors de l'outil métier, à partir d'un modèle simple de créneaux mensuels. Reprend les bonnes pratiques de la famille d'applications miss-*/mister-* (React 19 + Vite + TypeScript strict, Tailwind 4, vite-plugin-pwa, CSP en défense en profondeur, Supabase avec RLS), en version autonome (aucune dépendance à un registre npm privé).檢視完整 README ↗
Application web progressive (PWA) permettant à une équipe de médecins d'un hôpital de synchroniser leur planning de gardes en dehors de l'outil métier, à partir d'un modèle simple de créneaux mensuels.
Reprend les bonnes pratiques de la famille d'applications
miss-*/mister-*(React 19 + Vite + TypeScript strict, Tailwind 4,vite-plugin-pwa, CSP en défense en profondeur, Supabase avec RLS). Les briques partagées viennent du paquet@mister-guiiug/dev-pwa-config, publié sur GitHub Packages.
Vue mensuelle groupée par numéro de semaine ISO, avec liste (mobile) ou grille 7 colonnes (desktop, bascule mémorisée).
Créneaux cliniques configurables à occupant unique : les types de garde ne sont plus codés en dur, un admin les crée, édite, réordonne et désactive (libellé, heures, « clinique », « requis le week-end », « nuit », horaires du flux .ics, couleur) — voir docs/spec-creneaux-configurables.md. Configuration par défaut :
| Créneau | Libellé | Heures | Week-end | Nuit |
|---|---|---|---|---|
S1J | S1 Jour | 10 h | oui | non |
S1N | S1 Nuit | 15 h | oui | oui |
S2J | S2 Jour | 8 h | non | non |
1 médecin par créneau et par jour ; les créneaux non pourvus sont signalés « à couvrir ». Exception week-end / jours fériés (fériés FR calculés) : seuls les créneaux marqués « requis le week-end » le sont.
Heures Non Cliniques (HNC, ex-« S3 ») : plusieurs médecins par jour, chacun saisit son propre nombre d'heures, n'importe quel jour, hors couverture « à couvrir ». Comptées dans le temps total et dans un compteur HNC dédié.
Congés annuels et formations (heures/jour pour les formations), notes de jour, vœux / indisponibilités (dispo / indispo par jour).
Édition partagée temps réel (Supabase Realtime) : tout médecin approuvé affecte n'importe qui, avec alertes (repos de sécurité après une nuit, conflit garde/absence, cumul). Confirmation avant chaque suppression.
Affectation sur plusieurs semaines : depuis le dialogue d'affectation, répéter le même médecin sur le même créneau 1 à 4 semaines consécutives (mêmes jours de semaine).
Copier le mois précédent : reprend ses gardes dans le mois affiché, alignées sur le jour de semaine ; récapitulatif (gardes à créer, ignorées et pourquoi) avant toute écriture, et aucun créneau déjà attribué n'est écrasé.
Bourse aux gardes : proposer une de ses gardes (ciblée ou ouverte à tous) depuis la bourse (« Proposer une garde ») ou le planning ; accepter réaffecte la garde ; tri par date, « dans N j » et historique — page Échanges.
Mon planning (/mon-planning) : agenda personnel du mois (mes gardes,
absences, HNC) en liste, avec compteurs perso et abonnement calendrier.
Compteurs du médecin connecté (bascule mois / quadrimestre) : vendredis / samedis / dimanches, heures de week-end, HNC, heures totales, congés, formation. Deux vues en carrousel : pastilles ou mini-calendrier du mois.
Verrouillage de mois (admin) pour figer un planning validé.
/profil) : nom, couleur, thème clair/sombre, langue
(français / anglais), abonnement calendrier, partage du lien de l'app,
notifications push (opt-in), version + « forcer la mise à jour »,
déconnexion./compteurs : compteurs de toute l'équipe (bascule Tableau /
Équité) par mois / quadrimestre (4 mois) / année, colonnes triables dans
la vue Tableau, export CSV, Excel (.xlsx) et PDF.assign_shift_if_unchanged,
clear_shift_if_unchanged) : si un collègue a pris le créneau entre-temps,
c'est la base qui tranche, l'écriture est refusée, nommément notifiée et
conservée dans le panneau (« Réessayer » / « Abandonner »). Rien n'est écrasé,
rien n'est perdu en silence. La file appartient à l'appareil : la
déconnexion la jette, après confirmation.Le dépôt est public et la clé anon Supabase est présente dans le bundle
(c'est prévu : elle est publique). Toute la sécurité repose donc sur les
policies RLS côté serveur et sur une barrière d'approbation :
approved = false) et ne voit
rien du planning tant qu'un administrateur ne l'a pas approuvé ; il peut
supprimer lui-même sa demande, et un admin peut l'approuver ou la
rejeter (suppression de la fiche + du compte auth) ;app_config, jamais exposé au client) : écran « Compte en
attente » → « J'ai un code d'administrateur ». Le code ne fonctionne que tant
qu'aucun admin n'existe ;SECURITY DEFINER.Connexion : mot de passe (principal), avec deux options opt-in par médecin
depuis le profil — double authentification TOTP (code à 6 chiffres + codes de
secours) et connexion par empreinte / Face ID / Windows Hello (passkeys
WebAuthn natives de Supabase Auth, beta). La double authentification est vérifiée
par l'application à la connexion ; la base de données ne l'exige pas encore. Les
passkeys exigent une activation côté dashboard Supabase (RP ID + origine) : voir
docs/deploiement.md.
Ne committez jamais la clé service_role ni un token sbp_… (Management
API). Le fichier .env est ignoré par git.
Confidentialité (RGPD) : une politique de confidentialité est accessible
avant connexion (page de login) et depuis le profil — c'est un modèle dont les
mentions légales [À compléter] (responsable du traitement, base légale, durées,
contact) doivent être renseignées avant mise en service. Chaque médecin peut
exporter ses données (droit d'accès / portabilité, art. 15/20) en JSON depuis
son profil. Effacement (droit à l'oubli, art. 17) : un compte en attente se
supprime lui-même ; un compte approuvé peut s'anonymiser depuis son profil
(ou via un admin) — l'identité (nom, e-mail, connexion) est effacée tandis que les
gardes passées restent au planning sous une identité anonyme (intégrité préservée).
export NODE_AUTH_TOKEN=<jeton> # jeton GitHub (droit read:packages), exigé par .npmrc
npm install
cp .env.example .env # puis renseignez URL + clé anon (Supabase → Settings → API)
npm run dev # http://localhost:5173
Les icônes PWA sont versionnées dans public/icons/. Pour les régénérer
(optionnel), installez sharp puis lancez le script :
npm i -D sharp && npm run icons.
Scripts utiles : npm run build, npm run preview, npm run test,
npm run lint, npm run type-check.
Le schéma versionné est découpé en migrations dans
supabase/migrations/, appliquées dans l'ordre
(0001 → 0029). Pour une instance existante, le déploiement (Edge Functions +
migrations ≥ 0014) est automatisé par la CI — workflow
.github/workflows/supabase.yml, déclenché par
tout changement de supabase/migrations/, supabase/functions/ ou
supabase/config.toml sur main. Détails, secrets requis et procédure manuelle
de repli : docs/deploiement.md.
⚠️ Les migrations
0001→0013ne sont jamais appliquées à la base hébergée par la CI (l'une d'elles supprime des données) : sur une base neuve, il faut les passer à la main dans l'éditeur SQL, sanssupabase db push. La CI en vérifie ensuite la présence et refuse de continuer s'il en manque une. Le workflow « Supabase tests » les rejoue toutes, mais sur une pile jetable.
| Migration | Contenu |
|---|---|
0001_init | tables doctors / shifts / app_config, RLS, barrière d'approbation, RPC de base |
0002_admin_update_doctor | édition profil (soi / admin) |
0003_leaves | congés annuels & formations |
0004_calendar_feed · 0005_improvements | flux .ics (token), tokens par médecin, notes de jour, verrou de mois, réglages |
0006_notifications_backups | notifications (triggers) + sauvegarde/restauration + pg_cron |
0007_swaps_wishes | échanges de gardes + vœux |
0008_self_service | suppression de sa demande en attente |
0009_hnc | Heures Non Cliniques (table hnc_hours, migration des ex-S3) |
0010_admin_reject | rejet admin d'une demande en attente |
0011_shift_history | historique des changements par créneau |
0012_notif_improvements | améliorations des notifications |
0013_push_subscriptions | abonnements Web Push |
0014_calendar_rate_limit | rate-limit de l'Edge Function calendrier (table edge_rate_limit + RPC edge_rate_limit_hit) |
0015_calendar_token_privacy | confidentialité du token calendrier (privilège colonne : calendar_token illisible par les autres médecins) |
0016_extend_month_lock | verrou de mois étendu aux HNC / notes / vœux (triggers assert_month_unlocked) |
0017_audit_log | journal d'audit admin (table audit_log + triggers sur doctors / locked_months) |
0018_calendar_token_hash | tokens calendrier hashés au repos (SHA-256 ; plus de token en clair en base ; lien montré une seule fois) |
Après 0001, renseignez le code de bootstrap :
update public.app_config set bootstrap_code = 'VOTRE-CODE-SECRET' where id = 1;
Un flux iCalendar récapitule tout le planning (gardes + congés + formations),
servi par l'Edge Function Supabase supabase/functions/calendar :
https://<ref>.supabase.co/functions/v1/calendar?token=SECRET # toute l'équipe
https://<ref>.supabase.co/functions/v1/calendar?token=TOKEN&scope=me # ses propres gardes
L'accès est protégé par un token secret propre à chaque médecin (stocké
haché, cf. migration 0018) ; la fonction lit les données via la clé
service_role et est déployée avec verify_jwt = false (les agendas ne peuvent
pas envoyer de JWT). Dans l'app, la carte Calendrier du profil (et le bouton
de « Mon planning ») affiche l'URL d'abonnement (équipe ou personnelle) avec
liens webcal / Google Agenda / téléchargement, et permet de révoquer le
token en le régénérant (rotate_calendar_token()).
Déploiement de la fonction : automatisé par la CI (workflow
supabase.yml, verify_jwt déclaré dans
supabase/config.toml) ; en manuel :
supabase functions deploy calendar --no-verify-jwt.
Lien d'équipe partagé (facultatif et hérité : le lien de chaque médecin donne déjà le flux de l'équipe) : n'en stocker que l'empreinte.
update public.app_config
set calendar_token_hash = encode(extensions.digest('SECRET', 'sha256'), 'hex')
where id = 1;
La fonction accepte encore un calendar_token en clair, mais il resterait
lisible dans une copie de la base.
Tokens hashés au repos (migration 0018) : la base ne stocke plus que le
SHA-256 des tokens (calendar_token_hash) — un dump ne révèle aucun lien
utilisable. La fonction hache le token reçu et compare au hash (repli sur le clair
le temps de la transition). Conséquence : un lien n'est montré qu'une fois, à
la génération/régénération (il n'est plus ré-affichable). ⚠️ Déployer la fonction
avant d'appliquer 0018 (qui efface le clair).
Rate-limiting (défense en profondeur — le flux est public et lit via
service_role) : la fonction borne le débit par IP avant toute requête en base
(migration 0014, RPC atomique edge_rate_limit_hit). Défauts : 60 requêtes par
60 s, largement au-dessus du rafraîchissement des agendas ; réglables via les
variables d'environnement de la fonction CALENDAR_RATE_MAX / CALENDAR_RATE_WINDOW.
Au-delà : réponse 429 avec Retry-After. En cas d'échec de la RPC, on laisse
passer (fail-open) pour ne jamais casser un abonnement légitime.
Pourquoi l'écriture du .ics n'est pas celle du socle. Le module ./ical de
@mister-guiiug/dev-pwa-config (3.24.0) est promu de quatre réécritures RFC 5545,
dont celle-ci, et corrige deux défauts de la nôtre : DTSTAMP recalculé à chaque
événement (au lieu d'un seul par fichier, qui est la date de FABRICATION) et
lignes non pliées à 75 octets. Il n'est pourtant pas importé, pour des raisons
d'empaquetage et non de couverture :
.ics est engendré uniquement par cette Edge Function (Deno). Côté
navigateur, src/backend/calendar.ts ne construit
que l'URL d'abonnement, et le bouton « Télécharger le .ics » pointe sur ce même
flux : il n'y a rien à migrer là ;401 sans en-tête ; absent de npmjs). La CI déploie avec --use-api :
c'est l'infrastructure Supabase qui résout les spécifieurs npm:, sans nos
identifiants. Le .npmrc par fonction que Supabase prévoit pour un registre
privé demanderait le jeton en clair dans un dépôt public — celui de la
racine s'appuie sur ${NODE_AUTH_TOKEN}, qui n'existe pas sur le constructeur
distant ;supabase/functions/** poussée sur main déclenche
supabase.yml, donc un redéploiement et
les migrations : le coût d'un simple changement d'import n'est pas local.Une échappatoire existe — le dépôt du socle est public, donc ical.js et son
vcard.js sont servis en application/javascript par un CDN GitHub, importables
tels quels par Deno —, mais elle troque le registre contre un tiers dans la chaîne
d'approvisionnement d'un point d'entrée public qui lit via service_role, pour
deux corrections de forme. Elle n'est pas prise.
La couverture, elle, est vérifiée propriété par propriété — METHOD,
X-WR-CALNAME, X-WR-TIMEZONE, REFRESH-INTERVAL + X-PUBLISHED-TTL,
CATEGORIES, TRANSP, journées entières à DTEND exclusif et créneaux de nuit
franchissant minuit —, dans le même ordre de lignes. Le jour où le paquet est
joignable depuis le constructeur Supabase, la bascule est mécanique.
Le workflow .github/workflows/deploy.yml
construit et publie sur GitHub Pages avec base = /mister-doc/. Renseignez au
préalable, dans Settings → Secrets and variables → Actions → Variables :
VITE_SUPABASE_URLVITE_SUPABASE_ANON_KEYVITE_VAPID_PUBLIC_KEYpuis activez Settings → Pages → Source : GitHub Actions. Les trois variables
sont exigées : le déploiement s'arrête si l'une manque. VITE_SENTRY_DSN et
VITE_POSTHOG_KEY sont facultatives : absentes, rien n'est chargé ni envoyé.
VITE_POSTHOG_KEY ne doit pas être posée tant que la politique de
confidentialité porte [À compléter] (cf. src/App.tsx).
src/
lib/ env (validation JS) · client Supabase · dates (semaine ISO) ·
créneaux + compteurs · congés · HNC · validation/alertes · équité ·
répétition + copie de mois · tons de chips · couleurs
backend/ types · doctors · planning (shifts) · leaves · hnc · notes · wishes ·
swaps · shiftTypes · notifications · push · reminders · history ·
audit · gdpr · mfa · passkey · backup · locks · settings · calendar ·
syncQueue + planningSync (file d'écritures hors ligne)
auth/ contexte + porte d'authentification + page de connexion
i18n/ catalogue FR/EN typé + fournisseur de traduction
features/ planning/ (liste + grille, compteurs, dialogues affect./congé/note/HNC) ·
swaps/ · admin/ (panel, compteurs équipe, créneaux, sauvegarde) ·
profile/ · pending/ · sync/ (état de la file hors ligne) ·
legal/ (politique de confidentialité)
components/ en-tête · barre d'onglets basse · cloche notifications ·
calendrier · profil · spinner
Les exports PDF et Excel passent par les modules pdf et xlsx du socle, et
l'invite d'installation est son composant PwaInstallPrompt, posé sur le
planning.
Tests : Vitest dans src/ (compteurs, semaine ISO, créneaux actifs, congés,
alertes de validation, répétition hebdomadaire, copie de mois, file hors ligne,
MFA, exports, mentions légales), Playwright dans e2e/tests/ (connexion, MFA,
planning, verrou de mois, admin, file hors ligne, accessibilité) et pgTAP pour
la barrière d'approbation, les droits des fonctions security definer et deux
invariants de structure : aucune table sans RLS, et la liste relue des fonctions
qu'anon peut exécuter (supabase/tests/, workflow « Supabase tests »).
Les briques d'interface partagées viennent de @mister-guiiug/dev-pwa-config
(Button, TextField, EmptyState, Skeleton, ConfirmDialog, Toast,
BottomNav, Sheet, useTheme/ThemeProvider), habillées par
components.css et les quinze jetons --dwc-* câblés dans src/index.css.
Le thème est écrit une seule fois, par le ThemeProvider de main.tsx.
Pourquoi Badge reste local. Les deux composants n'ont pas le même axe, et
ce n'est pas une question de nommage :
brand,
success, warning, danger, info, muted : ce que la pastille veut
dire ;teal / indigo distinguent une
garde de jour d'une garde de nuit, violet / amber un congé
d'une formation, sky les HNC. Cinq catégories de même niveau, dont
aucune n'est une réussite, un avertissement ou un danger.Les replier sur six intentions sémantiques ferait perdre la distinction (tout
deviendrait muted) ou mentirait sur le sens (un congé n'est pas un danger).
S'ajoutait que size="xs", utilisé par les pastilles de la carte des créneaux,
n'avait pas d'équivalent. Le socle a ouvert depuis (02/09/2026) un axe de
taille (xs, sm, md) : cet argument est tombé, seul celui des tons demeure.
La carte des tons (src/lib/tones.ts) sert d'ailleurs aussi à Counters et
SwapStatusBadge, qui ne sont pas des Badge.
Décision réexaminée contre le socle 3.24.0 (2026-08-30) : inchangée. La taille existe désormais au socle ; la décision redeviendra discutable le jour où il ouvrira son axe de tons, pas avant.
0019_anonymize_doctor | effacement RGPD des comptes approuvés par anonymisation (RPC anonymize_doctor, self-service ou admin) |
0020_admin_reset_mfa | récupération 2FA : un admin réinitialise la double authentification d'un médecin (RPC admin_reset_mfa) |
0021_mfa_recovery_codes | codes de secours 2FA self-service (table mfa_recovery_codes hashés + RPC generate_/use_mfa_recovery_code) |
0022_shift_types | créneaux configurables (table shift_types, CHECK → FK, RPC admin d'écriture, (dés)activation, ordre et suppression) |
0023_shift_reminders | rappels de garde quotidiens (enqueue_shift_reminders + job pg_cron, RPC admin_send_reminders) |
0024_weekly_digest | récapitulatif hebdomadaire (enqueue_weekly_digest + job pg_cron, RPC admin_send_weekly_digest) |
0025_copy_previous_month | copie de mois (RPC assign_shifts_bulk) + notifications d'affectation groupées à l'INSERT |
0026_weekly_digest_idempotence | clé d'idempotence du récapitulatif ancrée sur le lundi de la semaine couverte (plus de doublon au rattrapage manuel) |
0027_shift_compare_and_set | écritures de garde conditionnelles pour le rejeu hors ligne (RPC assign_shift_if_unchanged / clear_shift_if_unchanged) |
0028_keep_alive | table keep_alive, lisible par anon seul, que le workflow « Supabase keep-alive » interroge |
0029_droits_fonctions | droits des fonctions : quatre fonctions internes fermées à anon et authenticated, garde d'anonymize_doctor refermée |