尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證Staff scheduling app: live rule validation, one-click fixes, OR-Tools month generation and plain-language requests. Demo with fictional data.
翻譯準備中
取自儲存庫 README
正在載入完整 README。
檢視完整 README ↗Self-hosted web app for the monthly staff schedules of a small hotel (about 25–30 employees in six sections): month and week views, scheduling rules and validation, an hour bank, one-click repairs, full-month generation with OR-Tools, natural-language constraints and three visitor roles (manager, employee, read-only).
Stack: Next.js 16 (App Router) · TypeScript · Tailwind CSS 4 + shadcn/ui · PostgreSQL 18 · Drizzle ORM · Better Auth · pg-boss · any OpenAI-compatible LLM · Python FastAPI + OR-Tools CP-SAT · Vitest · Biome · Docker Compose + Caddy.
apps/web Next.js app (UI, Server Actions, jobs, domain logic)
apps/web/src/lib/domain Pure TypeScript: durations, day codes, hour bank, validator, repairs
services/solver OR-Tools CP-SAT schedule generator (FastAPI)
docs/seed-data.json Fictional demo data (single source of truth for the seed)
infra Production compose file, Caddyfile, backup script
infra/windows Native Windows Server install, update and backup scripts
Every person, hotel, place, employee number, birthday, leave period and schedule
in docs/seed-data.json is fictional. The demo hotel is "Demo Alpine Lodge", an
invented mountain hotel in an invented French Alps village; public holidays are
generated for France and the demo months are November 2026 to January 2027.
The November 2026 reference schedule is produced by
scripts/generate-demo-schedule.ts: it builds a schedule that respects the rules,
then adds a handful of deliberate mistakes (a birthday worked, a late shift followed
by an early one, a rest day worked, a couple without a shared rest day, a
short-staffed restaurant, two colleagues off the same weekend, a shift outside an
employee's skills) so the validator and the "Fix" button have something to show.
The seed also holds five example natural-language constraints (approved, awaiting review and rejected) with their parsed operations, so the Constraints page is not empty; every visitor sees them and managers can approve or reject them.
There are no accounts: the start page creates an anonymous session (Better Auth anonymous plugin) with the role the visitor picks.
Every night at 03:00 the demo data is restored and the guests are removed. An hourly job removes guests older than 24 hours, and when the current month has no published schedule it empties the data and imports the seed again with rolling demo months (the current month and the next two), each with a freshly generated published schedule; the current month contains the deliberate mistakes.
The start page also presents the custom-build offer, a contact form and a
"Schedule a meeting" dialog. The visitor picks a weekday, then a free 30-minute
slot (Paris time, starts 09:00–14:30 and 17:00–18:30); the slots busy in the
developer's Google Calendar are hidden (free/busy API through a service account,
GOOGLE_*). Messages, and calendar invitations (.ics) from the visitor's
address, are emailed to CONTACT_EMAIL through SMTP (SMTP_*; printed to the
console when SMTP_HOST is empty), with a honeypot field and a per-address rate
limit.
Quotas (DEMO_* variables) limit generations per hour (one at a time on the whole
site) and LLM calls per visitor and per day.
Prerequisites: Node.js 24+, pnpm (npm i -g pnpm), Docker Desktop.
cp .env.example .env
Edit .env: set BETTER_AUTH_SECRET (openssl rand -base64 32) and, to parse natural-language constraints, LLM_API_KEY / LLM_BASE_URL /
LLM_MODEL (OpenAI, Groq or any OpenAI-compatible API with structured outputs).
docker compose up -d --build
pnpm install
pnpm db:migrate
pnpm db:seed
pnpm dev
Open http://localhost:3000 and pick a role on the start page.
| Command | Description |
|---|---|
pnpm dev | Dev server on :3000 (also starts the pg-boss worker) |
pnpm check | Typecheck + Biome lint + Vitest |
pnpm format | Biome format and safe fixes |
pnpm db:generate | New SQL migration from the Drizzle schema (apps/web/drizzle/) |
pnpm db:migrate | Apply migrations |
pnpm db:seed | Idempotent import of docs/seed-data.json, then a validator report for the reference month |
pnpm db:generate-demo | Regenerate the demo reference schedule inside docs/seed-data.json |
pnpm db:reset | Drop every table of the local database (refuses any non-local host) |
pnpm db:studio | Drizzle Studio |
The seed only inserts rows that are missing (matched on natural keys), so it never
overwrites changes made in the app. To start from scratch:
pnpm db:reset && pnpm db:migrate && pnpm db:seed.
A day is either a shift (codes A–E, per section) or one of these codes:
| Code | Label | Kind | Due day | Credits time |
|---|---|---|---|---|
| OF | Day off (weekly rest) | rest | no | no |
| PH | Public holiday | absence | yes | yes |
| AL | Annual leave | absence | yes | yes |
| SL | Sick leave | absence | yes | yes |
| BD | Birthday leave | absence | yes | yes |
| TL | Time off in lieu | absence | yes | no |
| TR | Training | absence | yes | yes |
| OL | Other paid leave | absence | yes | yes |
| UL | Unpaid leave | absence | no | no |
Requirements: Docker Engine with the compose plugin, a DNS record pointing
APP_DOMAIN to the server, ports 80 and 443 open.
git clone <repo> /opt/shift-smith && cd /opt/shift-smith
cp .env.example .env
In .env, set at least APP_DOMAIN (e.g. planning.example.com),
POSTGRES_PASSWORD, BETTER_AUTH_SECRET, the LLM_* variables, APP_TIMEZONE and HOTEL_NAME. DATABASE_URL and
BETTER_AUTH_URL are derived by the compose file.
docker compose -f infra/docker-compose.prod.yml --env-file .env up -d --build
docker compose -f infra/docker-compose.prod.yml --env-file .env exec web node scripts/seed.mjs
postgres is not published on the host; data lives in the pgdata volume.web applies pending migrations at every start, then serves Next.js
(standalone build) and runs the background job worker.caddy obtains and renews the HTTPS certificate for APP_DOMAIN automatically.Update: git pull && docker compose -f infra/docker-compose.prod.yml --env-file .env up -d --build.
See infra/windows/README.md: PostgreSQL, Node.js and the Python solver run as Windows services behind an existing reverse proxy.
infra/backup.sh writes a gzipped pg_dump to ./backups and deletes dumps older
than KEEP_DAYS (14 by default). Install it as a daily cron job:
chmod +x infra/backup.sh
( crontab -l; echo "15 3 * * * /opt/shift-smith/infra/backup.sh >> /var/log/planner-backup.log 2>&1" ) | crontab -
Copy backups/ off the server regularly (e.g. rclone or rsync). Backups are
ignored by git and must never be committed.
Restore a dump (the dump contains DROP … IF EXISTS, so it replaces the current data):
docker compose -f infra/docker-compose.prod.yml --env-file .env stop web
gunzip -c backups/planner-YYYYMMDD-HHMMSS.sql.gz \
| docker compose -f infra/docker-compose.prod.yml --env-file .env exec -T postgres psql -U planner -d planner
docker compose -f infra/docker-compose.prod.yml --env-file .env start web
requireUser() / requireManager()), and every mutation is written
to audit_log.lib/domain/validator.ts checks every hard rule (rest time across
midnight, consecutive days, two rest days per ISO week, monthly weekend off,
birthday, couples, approved leave, eligibility, unavailability, staffing) and
computes hours per employee and ISO week.monthly_close entry per employee
with a complete month.source = constraint).