注目機能の深堀り Highlighted features — deep dives
他のカテゴリで触れた機能のうち、設計・実装が独特でドキュメント化の価値が高いものをピックアップしました。
A selection of features whose design or implementation is interesting enough to warrant its own deep dive.
0. 2026-04-24 更新ハイライト — 時間窓 / 祝日 / 日付オーバーライド2026-04-24 update — window types, holidays, date overrides
駐車場の営業/入庫/出庫を別管理できるよう、parking_lot_hours に window_type (business / entry / exit / after_hours_exit) を追加。
祝日マスター jp_holidays (2026・2027 年 34 件 seed) と、特定日だけルールを上書きできる parking_lot_date_overrides (花火大会 / 元旦特別営業など) を新設しました。
Added window_type to parking_lot_hours (business / entry / exit / after_hours_exit), introduced the
jp_holidays master (34 rows for 2026 / 2027), and added parking_lot_date_overrides for per-day exceptions (fireworks nights, New-Year specials, …).
判定 RPC の優先順Resolution order in the RPCs
is_parking_lot_window_active(lot_id, at, window_type) が以下の優先度で解決します(is_parking_lot_open_now は window_type='business' ラッパに差し替え済):
is_parking_lot_window_active(lot_id, at, window_type) resolves in this order (is_parking_lot_open_now is now a thin business wrapper):
- 日付オーバーライド (
parking_lot_date_overrides.hours->window_type) - Date override (
parking_lot_date_overrides.hours->window_type) - 祝日行 (
is_jp_holiday(date)=trueかつday_type='holiday') - Holiday row (
is_jp_holiday(date)=trueandday_type='holiday') - 曜日行 (
day_of_week一致) - Day-of-week row (
day_of_weekmatch) - day_type (
weekday/saturday/sunday/holiday_eve) - day_type (
weekday/saturday/sunday/holiday_eve) allフォールバックallfallback
追加 RPC: can_enter_parking_lot_now / can_exit_parking_lot_now / is_jp_holiday(date) / get_parking_lot_date_override(lot_id, date)。
UI 影響: 駐車場編集画面で「営業 / 入庫 / 出庫 / 時間外出庫」タブ分離、日付オーバーライドタブ新設(別対応)。
詳細: 2026-04-24 ADR。
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.admin_tasks (
id, task_kind, ref_id, assignee_id,
urgency, due_at, memo, ...,
UNIQUE (task_kind, ref_id),
CHECK urgency IN ('urgent','high','medium','low')
)
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
task_kind = 'parking_fee_mismatch' → parking_sessions.id (fee_mismatch_flag=true)
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. - コード分割: 47 ページすべてを
React.lazy()で動的読み込み。App.tsx直下ではLoginPage/PasswordResetPage/AppLayoutのみ即時 bundle。 - Code splitting: all 47 pages are dynamically imported via
React.lazy(). OnlyLoginPage,PasswordResetPageandAppLayoutstay in the entry bundle.
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 BFF (POST /v1/storage/upload-url) が aws4fetch で SigV4 presign URL を発行し、クライアントが presigned URL に直接 PUT。配信は cdn.parky.co.jp。Asset storage. The Cloudflare Workers BFF (POST /v1/storage/upload-url) mints a SigV4 presigned URL via aws4fetch; the client PUTs directly to it. Public delivery via cdn.parky.co.jp. |
| FCM v1 | プッシュ通知。Workers Queue (parky-fcm-dispatch) consumer が KV キャッシュした OAuth access_token で並列送信。Push notifications. The Workers Queue consumer (parky-fcm-dispatch) calls FCM in parallel using a KV-cached OAuth access_token. |
| PostHog | 管理者操作のプロダクト分析(identifyPostHog/resetPostHog)。Product analytics for admin actions (identifyPostHog/resetPostHog). |
| Sentry | @sentry/react による未処理例外・ErrorBoundary 捕捉。BFF request_id を tag に伝播。@sentry/react captures unhandled exceptions and ErrorBoundary; BFF request_id is propagated as a tag. |