{"id":50954533,"url":"https://github.com/gabrielnvian/demo-ops-dashboard","last_synced_at":"2026-06-18T05:01:18.350Z","repository":{"id":352262365,"uuid":"1214499869","full_name":"gabrielnvian/demo-ops-dashboard","owner":"gabrielnvian","description":"SMB Operations Dashboard — full-stack Next.js 14 + Prisma + PostgreSQL internal tool demo","archived":false,"fork":false,"pushed_at":"2026-04-25T21:56:06.000Z","size":224,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-25T23:26:43.425Z","etag":null,"topics":["admin-dashboard","internal-tools","nextjs","nextjs14","postgres","prisma","react","smb","typescript"],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gabrielnvian.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-18T16:58:53.000Z","updated_at":"2026-04-25T21:56:10.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/gabrielnvian/demo-ops-dashboard","commit_stats":null,"previous_names":["gabrielnvian/demo-ops-dashboard"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/gabrielnvian/demo-ops-dashboard","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gabrielnvian%2Fdemo-ops-dashboard","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gabrielnvian%2Fdemo-ops-dashboard/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gabrielnvian%2Fdemo-ops-dashboard/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gabrielnvian%2Fdemo-ops-dashboard/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gabrielnvian","download_url":"https://codeload.github.com/gabrielnvian/demo-ops-dashboard/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gabrielnvian%2Fdemo-ops-dashboard/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34476728,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-18T02:00:06.871Z","response_time":128,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["admin-dashboard","internal-tools","nextjs","nextjs14","postgres","prisma","react","smb","typescript"],"created_at":"2026-06-18T05:01:16.699Z","updated_at":"2026-06-18T05:01:18.342Z","avatar_url":"https://github.com/gabrielnvian.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Ops Dashboard\n\nMessy spreadsheet to a real admin panel in two weeks. Portfolio demo for\ncustom internal-tool work: a job-orders tracker for a small service business.\n\n## Case study\n\nA walkthrough of how the two weeks break down, the schema choices, the CSV\nimport hero feature, and what a real client engagement would add or skip:\n[docs/case-study.md](docs/case-study.md).\n\n## What you get\n\n- **A deployed admin panel.** Auth, database, and CI/CD included. Not a Figma\n  mock, not a localhost toy.\n- **Docs and handoff.** Schema diagrams, README, and a walkthrough so your\n  team owns the code after day fourteen.\n- **Two weeks from scope call to shipped.** Fixed-price, milestone-based.\n\nTypical scope is $3,500. Range $5k-$15k depending on integrations.\n\n## Who this is for\n\n- Ops teams living in a shared spreadsheet that keeps breaking.\n- Startups outgrowing Retool or Airtable and wanting to own the code.\n- Small businesses one real tool away from unblocking operations, not ready\n  for a full engineering hire.\n\n## About\n\nGabriel Vian builds and ships full-stack internal tools for SMBs and\nstartups. Design, database, API, deploy, and handoff, from one person.\n\nContact: gvian07@gmail.com\n\n---\n\n## For developers\n\nThis section is the part that names the stack.\n\n### Stack\n\n| Layer | Choice |\n|-------|--------|\n| Framework | Next.js 14 (App Router) + TypeScript |\n| Database | PostgreSQL via Prisma ORM |\n| UI | Tailwind CSS + shadcn/ui primitives |\n| Auth | NextAuth.js (credentials + JWT, scaffolded) |\n| Deploy | systemd unit on the portfolio host; Railway-ready |\n\n### Schema\n\nOne core model (`JobOrder`) plus the NextAuth tables (`User`, `Account`,\n`Session`). See `prisma/schema.prisma`.\n\n`JobOrder` fields: id, title, customer, status (enum: PENDING /\nIN_PROGRESS / COMPLETED / CANCELLED), notes, dueAt, createdAt, updatedAt.\n\n### What's wired\n\n- `/` server-rendered landing with live counts from Postgres.\n- `/dashboard` searchable, status-filterable table against a real\n  PostgreSQL instance via Prisma. Seed rows are SSR\u0026apos;d from Postgres;\n  per-visitor uploads and manual entries are hydrated from the browser's\n  localStorage on mount.\n- `/upload` drag-and-drop CSV import. The server parses and maps columns,\n  the browser saves the mapped rows to localStorage.\n- NextAuth configured but disabled for the public demo; the `/dashboard`\n  route would gate behind a session in production.\n\n### Public demo storage\n\nUploaded rows and rows created via the `+ New order` button live only in\nyour browser's localStorage. They do not leave your machine. The seed\nrows you see are SSR'd from a shared Postgres instance. In production,\nthe upload path writes to the same Postgres the seed reads from, behind\na signed-in session. Running the write path client-side for the public\ndemo removes the need for per-visitor session scoping, TTL cron sweeps,\nand cross-visitor content filtering, because there is no shared write\nsurface.\n\n### Local dev\n\n```bash\ncp .env.example .env.local\n# Fill DATABASE_URL and NEXTAUTH_SECRET\n\npnpm install\npnpm db:push   # sync schema to local Postgres\npnpm dev       # http://localhost:3000\n```\n\n### Tests\n\nVitest powers the unit and route-handler tests. CI runs `pnpm test` on every\npush, so a red test blocks merge alongside a failing build.\n\n```bash\npnpm test          # one-shot run\npnpm test:watch    # re-run on file changes\n```\n\n### Accessibility\n\nThe site targets WCAG 2.1 AA. Concretely:\n\n- `\u003chtml lang=\"en\"\u003e` set, `\u003cmain id=\"main-content\"\u003e` on every route, a\n  skip-to-main-content link revealed on keyboard focus.\n- Semantic landmarks: `\u003cnav aria-label=\"Primary\"\u003e`, `\u003cmain\u003e`, `\u003cfooter\u003e`,\n  headings in order (h1 -\u003e h2 -\u003e h3) with no skips.\n- Every interactive control is a real `\u003cbutton\u003e` or `\u003ca\u003e` with a visible\n  focus ring. The CSV dropzone is a button, not a div-with-onclick, so it\n  is reachable by keyboard.\n- Form inputs (dashboard filters, new-order form) have explicit\n  `\u003clabel htmlFor\u003e` associations. The hidden file input uses `aria-label`.\n- Icon-only and ambiguous-text buttons (`+ New order`, `Reset demo data`,\n  back links) carry descriptive `aria-label`s.\n- Status messages (CSV import flash, toast, inline error) use\n  `role=\"status\" aria-live=\"polite\"` or `role=\"alert\"` so screen readers\n  announce them.\n- Body copy runs slate-700 / slate-800 on white to clear the 4.5:1 AA\n  contrast ratio; slate-500 is reserved for decorative separators and is\n  marked `aria-hidden`.\n\nA source-level canary test in `app/__tests__/a11y-canaries.test.ts`\nguards the fixes above against regression.\n\n### Deploy (Railway path)\n\n1. Push to GitHub.\n2. New Railway project, \"Deploy from GitHub repo.\"\n3. Add the Postgres plugin; Railway injects `DATABASE_URL`.\n4. Add `NEXTAUTH_URL` (Railway domain) and `NEXTAUTH_SECRET`.\n5. Start command: `pnpm build \u0026\u0026 pnpm start`.\n6. First deploy: run `pnpm db:migrate` from the Railway shell.\n\n### License\n\nMIT.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgabrielnvian%2Fdemo-ops-dashboard","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgabrielnvian%2Fdemo-ops-dashboard","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgabrielnvian%2Fdemo-ops-dashboard/lists"}