パーキー管理者ポータル Parky Admin Portal

バックオフィスの全体像 Backoffice at a glance

駐車場シェアリング事業「Parky」の運営をすべてカバーする統合管理ツール。 47 画面 (+ Marketing Portal へ移管 2 画面)・/v1/admin/* の 50+ ルート群・高度なゲーミフィケーション/カスタマイズシステムを持ち、 React 19 + Vite + TypeScript SPA を Cloudflare Pages に配信、データは Cloudflare Workers BFF (Hono + Hyperdrive) 経由で Supabase (PostgreSQL + PostGIS + Auth + Realtime) を叩く構成です。

A unified backoffice that covers every operational aspect of the Parky parking-sharing service. 47 screens (plus 2 redirected to the Marketing Portal), the 50+ /v1/admin/* routes, and a rich gamification / customization system, built as a React 19 + Vite + TypeScript SPA on Cloudflare Pages. CRUD goes through a Cloudflare Workers BFF (Hono + Hyperdrive) into Supabase (PostgreSQL + PostGIS + Auth + Realtime).

このドキュメントの読み方 How to use this documentation

ポータルの位置づけ Where this portal sits

flowchart LR
  subgraph Users["利用者 / Consumers"]
    EndUser["エンドユーザー
End users"] Owner["駐車場オーナー
Lot owners"] end subgraph Clients["クライアント / Clients"] Mobile["モバイルアプリ
Mobile app"] OwnerPortal["オーナーポータル
Owner portal"] AdminPortal["管理者ポータル
Admin portal
(this doc)"] end subgraph Backend["バックエンド / Backend (Supabase)"] PG[(PostgreSQL
PostGIS)] Auth[Auth] RT[Realtime] Edge[Cloudflare Workers] end S3[(Cloudflare R2
assets)] Map[Mapbox] EndUser --> Mobile Owner --> OwnerPortal AdminPortal -- manages --> Mobile AdminPortal -- manages --> OwnerPortal Mobile --> Auth OwnerPortal --> Auth AdminPortal --> Auth Auth --> PG Mobile --> PG OwnerPortal --> PG AdminPortal --> PG AdminPortal --> RT AdminPortal --> Edge Edge --> PG AdminPortal --> S3 AdminPortal --> Map

対象ユーザー Who uses the portal

ロールRole 役割Responsibility
システム管理者 (Super Admin)Super Admin 全機能への無制限アクセス。管理者アカウントやロール定義も行う。 Unrestricted access. Manages admin accounts and role definitions.
オペレーションマネージャーOps Manager 駐車場・ユーザー・売上を日々監視。サポート対応も担当。 Day-to-day monitoring of lots, users, and revenue; handles support escalations.
コンテンツマネージャーContent Manager 記事・広告・ゲーミフィケーション・着せ替えの運営。 Runs articles, ads, gamification, and customization themes.
サポートエージェントSupport Agent サポートチケットと誤情報報告への一次対応。 Front-line handling of support tickets and misinformation reports.

技術スタック Technology stack

レイヤLayer 採用技術Technology
フレームワークFrameworkReact 19 + Vite
言語LanguageTypeScript
ルーティングRoutingReact Router DOM v7(全ページ React.lazy
状態管理StateAuthContext · CodeProvider · PermissionProvider · PageTitleProvider · I18nProvider · PortalApiProvider + TanStack Query v5
UIBootstrap 5 + 独自グラスモーフィズム CSS + Lucide React Icons
バックエンドBackendCloudflare Workers BFF (/v1/admin/*, Hono) + Supabase (Auth / Realtime / PostgreSQL via Hyperdrive)
DBPostgreSQL 17 + PostGIS(5 schema: public / admin / marketing / analytics / extensions
認証AuthSupabase Auth (Email + PW, JWT) — RBAC は admin schema の roles + role_permissions
ストレージStorageCloudflare R2(presigned PUT、配信は cdn.parky.co.jp
地図MapsMapbox GL JS + mapbox-gl-draw
通知NotificationsFCM v1 API via Cloudflare Workers Queue (parky-fcm-dispatch)。Supabase Edge Functions は不使用
監視Observability@sentry/react + PostHog + Cloudflare 構造化ログ

ディレクトリ構造 Directory layout

parky/web/portal/admin/
├── CLAUDE.md                   admin ポータル局所ルールAdmin portal local rules
└── src/
    ├── App.tsx                 ルート定義 (47 ページ + 2 marketing redirect)Route table (47 pages + 2 marketing redirects)
    ├── main.tsx                エントリーポイントEntry point
    ├── auth/
    │   ├── AuthContext.tsx     セッション + アイドルタイムアウト + admin role 検証Session + idle timeout + admin role re-verify
    │   ├── RequireAuth.tsx     マウント時に /v1/admin/admins/me 再検証Re-verifies via /v1/admin/admins/me on mount
    │   └── useAuth.ts
    ├── lib/
    │   ├── bff.ts              @parky/bff-client インスタンス@parky/bff-client instance
    │   ├── api/                BFF call wrapper(コンポーネント直 fetch 禁止)BFF call wrappers (no direct fetch)
    │   ├── queries/            TanStack Query keysTanStack Query keys
    │   ├── supabase.ts         Auth + Realtime のみ(anon key)Auth + Realtime only (anon key)
    │   ├── codes.tsx           マスターコード(useCode)Master codes (useCode)
    │   └── PageTitleContext.tsx
    ├── components/
    │   ├── layout/             AppLayout / Header / Sidebar / sidebarMenuData / SidebarNotificationsAppLayout / Header / Sidebar / sidebarMenuData / SidebarNotifications
    │   ├── ListFilterBar.tsx   共通フィルタバーUniversal filter bar
    │   ├── ParkingMap.tsx      Mapbox GL JS visualizer
    │   ├── ParkingShapeEditor.tsx mapbox-gl-draw shape editor
    │   ├── ActivityLogFeed.tsx Realtime feed
    │   ├── FileUpload.tsx      Cloudflare R2 presigned PUT
    │   ├── MarkdownEditor.tsx  marked + DOMPurify
    │   ├── ConfirmProvider.tsx + ToastProvider.tsx
    │   ├── ErrorBoundary.tsx + Forbidden.tsx
    │   └── MasterDetailLayout.tsx
    ├── hooks/
    │   ├── PermissionProvider.tsx RBAC ロード + usePermissionRBAC load + usePermission
    │   └── …
    └── pages/                  47 ページ (全て React.lazy ロード)47 pages (all React.lazy)

最初のログインフロー First login flow

sequenceDiagram
  participant U as Admin
  participant FE as Admin Portal (React)
  participant SB as Supabase Auth
  participant PG as PostgreSQL
  U->>FE: email + password
  FE->>SB: signInWithPassword()
  SB-->>FE: JWT session
  FE->>PG: SELECT admins, roles, role_permissions (RLS)
  PG-->>FE: current admin + permission keys
  FE-->>U: Dashboard with permitted nav items
ポイントKey point: 全ページが React.lazy() で遅延ロードされるため、初回バンドルは小さく、ナビゲーション時にページ単位でコードが取得されます。 Every page is behind React.lazy(), so the initial bundle stays small and code is fetched per-page on navigation.