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

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

駐車場シェアリング事業「Parky」の運営をすべてカバーする統合管理ツール。 32の画面・100を超えるAPI関数・高度なゲーミフィケーション/カスタマイズシステムを持ち、 Supabase (PostgreSQL + Auth + Realtime + Cloudflare Workers) の上に React + Vite + TypeScript で構築されています。

A unified backoffice that covers every operational aspect of the Parky parking-sharing service. It contains 32 screens, 100+ API helpers, and a rich gamification / customization system, built on React + Vite + TypeScript on top of Supabase (PostgreSQL + Auth + Realtime + Cloudflare Workers).

このドキュメントの読み方 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 7
状態管理StateAuthContext, CodeProvider, PageTitleProvider (React Context)
UIBootstrap 5.3 + Lucide Icons
バックエンドBackendCloudflare Workers BFF (/v1/*) + Supabase (Auth / Realtime / PostgreSQL via Hyperdrive)
DBPostgreSQL 17 + PostGIS
認証AuthSupabase Auth (JWT)
ストレージStorageCloudflare R2 (画像・アセットimages / assets)
地図MapsMapbox Geocoding + GL JS

ディレクトリ構造 Directory layout

parky/web/portal/admin/
├── src/
│   ├── App.tsx                 ルート定義 (全32ページ)Route table (all 32 pages)
│   ├── main.tsx                エントリーポイントEntry point
│   ├── auth/
│   │   ├── AuthContext.tsx     認証状態とログイン/アウトAuth state + login/out
│   │   └── RequireAuth.tsx     保護ルートProtected routes
│   ├── lib/
│   │   ├── api.ts              APIヘルパー (100+)API helpers (100+)
│   │   ├── supabase.ts         Supabase client
│   │   ├── codes.tsx           マスターコードMaster codes
│   │   └── PageTitleContext.tsx
│   ├── components/
│   │   ├── layout/             AppLayout, Header, Sidebar
│   │   ├── VirtualTable.tsx    仮想スクロールテーブルVirtualized table
│   │   ├── ListFilterBar.tsx   共通フィルタバーUniversal filter bar
│   │   ├── ParkingMap.tsx      Mapbox visualizer
│   │   ├── ActivityLogFeed.tsx Realtime feed
│   │   ├── FileUpload.tsx      Cloudflare R2 upload
│   │   ├── MarkdownEditor.tsx  TinyMCE editor
│   │   └── MasterDetailLayout.tsx
│   ├── hooks/
│   │   ├── usePaginatedList.ts ページング + 無限スクロールPagination + infinite scroll
│   │   └── useTableSort.ts
│   └── pages/                  32ページ (全て lazy ロード)32 pages (all lazy loaded)
├── API_SPECIFICATION.md
└── README.md

最初のログインフロー 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.