{"id":"cmv63zst5002bpf2ob883zupv","slug":"dashboard-admin-web","judul":"Dashboard Admin Web","deskripsi":null,"parentId":"cmv05qyqx000htm4dwawyj1ll","urut":10,"kategori":"web","tag":"[]","status":"draft","versi":null,"konsep":"## Dashboard Admin Web — ZX Parenting\n\n**URL:** https://zxparenting.zomet.my.id/admin  \n**Stack:** Next.js 15 + Prisma + PostgreSQL  \n**Repo:** `djvpri/zx_parenting` (private)  \n**Deploy:** Coolify VPS 103.93.129.94, container `pd59y3gb3k6nkqjqvm873uim`\n\n### Akses\n- Login: email/password atau Google OAuth\n- Role: ADMIN (dipromosikan manual via DB)\n- Non-admin → redirect `/login?tab=admin`\n- Session: JWT cookie (`sesiAdmin()`)\n\n### Halaman Admin\n\n| Route | Fungsi | File |\n|-------|--------|------|\n| `/admin` | Dashboard overview: summary cards (anak, ortu, tugas, device online) | `admin/page.tsx` |\n| `/admin/anak` | CRUD anak (list, tambah, edit, hapus) | `admin/anak/page.tsx` |\n| `/admin/tugas` | CRUD tugas + assign ke anak | `admin/tugas/page.tsx` |\n| `/admin/insights` | AI insights per anak (Gemini/rule-based) | `admin/insights/page.tsx` |\n| `/admin/error-log` | Error log dari app anak + ortu | `admin/error-log/page.tsx` |\n\n### Error Log (v3.6.0+)\n\n#### Admin Page: `/admin/error-log`\n- Server component, query `ActivityLog` where `tipe='error'`\n- Summary cards: Total, 24h, App Anak count, App Ortu count\n- Table: Waktu, Sumber (Anak/Ortu + nama), Screen, Message, Usia (red <24h, amber older)\n- Mockup: `/mockup-error-log.html` (live)\n\n#### Machine-Readable API: `GET /api/error-log`\n- **Auth:** `?token=<CRON_TOKEN>` (sama pola `/api/cron/jadwal-ai`)\n- **Params:**\n  - `limit` — max rows (default 50, max 500)\n  - `since` — filter age: `24h`, `7d`, `30d`, `all` (default `24h`)\n  - `platform` — filter source: `anak`, `ortu`, `all` (default `all`)\n- **Response:**\n```json\n{\n  \"summary\": { \"total\": 0, \"last24h\": 0, \"anakCount\": 0, \"ortuCount\": 0 },\n  \"errors\": [\n    {\n      \"id\": \"cmv...\",\n      \"waktu\": \"2026-10-13T04:24:48.304Z\",\n      \"ageMs\": 3600000,\n      \"sumber\": \"anak|ortu\",\n      \"nama\": \"Budi\",\n      \"screen\": \"LayarKuisAI\",\n      \"message\": \"kuisStart fail\",\n      \"extra\": { \"code\": 500 }\n    }\n  ]\n}\n```\n- **AI agent usage:**\n```bash\ncurl \"https://zxparenting.zomet.my.id/api/error-log?token=zx-cron-2026&since=24h&platform=anak\"\n```\n\n#### Endpoint Error Report (push dari app)\n\n| App | Endpoint | Auth | Body |\n|-----|----------|------|------|\n| Anak | `POST /api/agent` | `x-device-token` header | `{\"aksi\":\"lapor-error\",\"detail\":{\"screen\":\"...\",\"message\":\"...\"}}` |\n| Ortu | `POST /api/ortu/lapor-error` | `Authorization: Bearer <jwt>` | `{\"screen\":\"...\",\"message\":\"...\",\"extra\":{}}` |\n\n#### Schema: `ActivityLog`\n```prisma\nmodel ActivityLog {\n  id        String   @id @default(cuid())\n  anakId    String?  // nullable sejak v3.6.0 (error ortu punya no anakId)\n  userId    String?  // NEW v3.6.0 (FK → User.id, nullable)\n  tipe      String   // 'error' | 'activity' | 'lapor-error' (rewrite → 'error')\n  detail    Json?\n  createdAt DateTime @default(now())\n  anak      Anak?    @relation(fields: [anakId], references: [id])\n  user      User?    @relation(fields: [userId], references: [id])\n}\n```\n\n### Sidebar Navigation\n\n```\nDashboard\n├── Anak\n├── Tugas\n├── Insights\n└── Error Log ← NEW v3.6.0\n```\n\n### API Routes (admin)\n\n| Endpoint | Method | Auth | Fungsi |\n|----------|--------|------|--------|\n| `/api/admin/metrics` | GET | `sesiAdmin()` | Summary stats dashboard |\n| `/api/error-log` | GET | `?token=<CRON_TOKEN>` | Machine-readable error JSON |\n\n### Commits\n- `0f53b98` — schema + `/api/ortu/lapor-error` + `/admin/error-log` page\n- `2fab773` — `/api/error-log` machine-readable JSON endpoint\n- Mockup: `mockup-error-log.html` (committed to repo)","createdAt":"2026-10-13T03:16:08.729Z","updatedAt":"2026-10-13T06:06:57.140Z","bagian":[],"children":[],"parent":{"slug":"zx-parenting","judul":"ZX Parenting"}}