注目機能の深堀り Highlighted features — deep dives
他のカテゴリで触れた機能のうち、設計・実装が独特でドキュメント化の価値が高いものをピックアップしました。
A selection of features whose design or implementation is interesting enough to warrant its own deep dive.
1. 料金ルール評価エンジンFee calculation engine
駐車場ごとに複雑な料金ルール (時間帯・曜日・キャップ・繰り返し) を設定でき、 入庫/出庫時刻からクライアント側で料金を計算します。管理者は詳細画面の計算機で即座に検証可能です。
Each lot can define complex pricing (time windows, day-of-week, caps, repeating caps). The portal evaluates it on the client from entry / exit timestamps, so admins can sanity-check via the live calculator.
ルール構造Rule shape
interface ParkingRule {
rule_id: number;
category: 'unit' | 'cap';
when: {
days: string[]; // ['mon','tue',..,'all','holiday']
time: [string, string] | null // ['09:00','18:00'] or null for 24h
};
charge: {
tiers?: { per_minutes: number; price: number }[]; // unit pricing
cap_type?: 'duration';
duration_hours?: number;
price?: number;
repeat?: boolean;
scope?: string | null;
};
}
計算の流れEvaluation flow
flowchart TD
A["Entry / Exit time"] --> B["Iterate minute by minute"]
B --> C{"Match any unit rule
for day and time?"}
C -- yes --> D["Add tier price"]
C -- no --> E["No charge this minute"]
D --> F{"Cap rule triggered?
duration hours exceeded"}
E --> F
F -- yes --> G["Replace with cap price
restart window if repeat"]
F -- no --> H["Continue"]
G --> H
H --> I{"More minutes?"}
I -- yes --> B
I -- no --> J["Return total"]
2. バッジ条件エンジンBadge condition engine
バッジの獲得条件は、アクティビティログ (user_activity_logs.metadata) の JSONB に対する
条件式配列として定義されます。条件は PostgreSQL 関数 evaluate_badge_conditions() で
AND 結合で評価され、一致したイベント数がバッジの進捗となります。
Each badge holds an array of conditions that are evaluated against the JSONB metadata of
user_activity_logs by the evaluate_badge_conditions() Postgres function.
All conditions are AND-combined; each matching event increments progress.
[
{ "field": "metadata.parking_lot_id", "operator": "eq", "value": "uuid" },
{ "field": "metadata.count", "operator": "gte", "value": "10" }
]
サポート演算子Supported operators
eq · neq · contains · gt · gte · lt · lte · in
バックフィルBackfill
条件を変更した場合、既存ユーザーの進捗を再計算する必要があります。単体バッジ (backfill_badge_progress) と全バッジ (backfill_all_badge_progress) の 2 種類の RPC が用意されています。
When conditions change, existing progress must be recomputed. Two RPCs cover this: backfill_badge_progress (single) and backfill_all_badge_progress (all).
DELETE FROM user_badge_progress WHERE badge_id = ?;
INSERT INTO user_badge_progress (user_id, badge_id, count)
SELECT user_id, ?, COUNT(*)
FROM user_activity_logs
WHERE activity_type = ?
AND evaluate_badge_conditions(metadata, conditions)
GROUP BY user_id;
3. カスタマイズ (着せ替え)Customization / theming
flowchart LR Theme[customization_themes] --> Item[customization_theme_items] Item --> Part[customization_theme_parts] Part --> PinA[(pin_asset)] Part --> IconA[(icon_asset)] Part --> LoadA[(loading_asset)] Part --> Color((color)) Gift[theme_gifts] --> UT[user_themes] Theme --> Gift
テーマは複数のパーツを束ねる入れ物。管理者は gift_theme_to_user() RPC でユーザーにテーマを配布でき、
ユーザー側の所有履歴は user_themes に記録されます。
A theme is a container that pulls several parts together. Admins gift a theme via gift_theme_to_user(),
with ownership recorded in user_themes.
4. 統合タスク (Admin Tasks)Unified admin task inbox
異質なタスク源 (サポートチケット / 誤情報報告 / オーナー申請 / 駐車場新規登録) を、
同一の admin_tasks テーブルで管理することで、1 画面の統合インボックスを実現しています。
Heterogeneous task sources (support / error reports / owner apps / new lot registrations) are all recorded in a
single admin_tasks table so the portal can surface a unified inbox in one screen.
admin_tasks (
id, task_kind, ref_id, assignee_id,
urgency, due_at, memo, ...
)
task_kind = 'support' → support_tickets.id
task_kind = 'misinformation_report' → error_reports.id
task_kind = 'owner_application' → owner_applications.id
task_kind = 'parking_new_registration'→ parking_lots.id
5. Realtime 購読Realtime subscriptions
Supabase Realtime を使い、2 つのチャネルで UI をライブ更新しています。
Two Realtime channels drive live UI updates.
| チャネルChannel | テーブル / イベントTable / Event | 用途Use |
|---|---|---|
live-user-activity |
user_activity_logs / INSERT |
アクティビティフィードに即時反映Streams into the activity feed instantly |
admin-notifications-sidebar |
admin_notifications / * |
サイドバーの通知バッジを自動更新Auto-refreshes the sidebar notification badge |
6. 大規模リストの最適化Large-list performance
- 仮想スクロール:
@tanstack/react-virtualをラップしたVirtualTable。数千行でも 60fps。 - Virtual scrolling:
VirtualTablewraps@tanstack/react-virtualto keep thousands of rows at 60fps. - 無限スクロール + ページング:
usePaginatedListhook が Supabase のcount: 'exact'と合わせて正確な総数を返す。 - Infinite scroll with paging: the
usePaginatedListhook combines Supabase'scount: 'exact'for accurate totals. - コード分割: 32ページすべてを
React.lazy()で動的読み込み。 - Code splitting: all 32 pages are dynamically imported via
React.lazy().
7. マスターコードシステムMaster code system
codes テーブルには user_status, vehicle_type 等のカテゴリ別ラベル定義が入っており、
CodeProvider が起動時に一括ロードしてアプリ全体で利用します。UI のドロップダウン・ラベルは DB 駆動です。
The codes table holds labels by category (user_status, vehicle_type, etc.).
The CodeProvider loads them once at boot, so every dropdown and label in the UI is DB-driven.
code.label('user_status', 'active') // → '利用中'
code.options('vehicle_type') // → [{code, label}, ...]
8. 外部 API 統合External API integrations
| API | 用途Use |
|---|---|
| Mapbox Geocoding | 住所 ⇄ 座標変換、候補サジェストAddress ⇄ coordinate, suggestions |
| Mapbox GL JS | 駐車場一覧のマップビジュアライザー (lazy ロード)Lazy-loaded map visualizer for the lot list |
| Cloudflare R2 | 画像・アセット保存。Cloudflare Workers が署名 URL を発行し、クライアントが PUT。Asset storage via Edge-Function-issued signed URLs and client-side PUT. |