feat: Implement dynamic join solution

This commit is contained in:
gpt-engineer-app[bot]
2025-11-03 16:20:28 +00:00
parent 9ae4a7b743
commit c68b88de86
4 changed files with 152 additions and 70 deletions

View File

@@ -118,6 +118,35 @@ export type SubmissionItemData =
| RideModelItemData
| PhotoItemData;
/**
* Submission item with resolved entity data from database view
*/
export interface SubmissionItem {
id: string;
item_type: string;
item_data_id: string | null;
action_type: 'create' | 'edit' | 'delete';
status: string;
order_index: number;
depends_on: string | null;
approved_entity_id: string | null;
rejection_reason: string | null;
// Entity data from dynamic join (pre-loaded from view)
entity_data?: {
id: string;
submission_id: string;
name: string;
slug: string;
// Other fields vary by entity type
[key: string]: any;
} | null;
// Legacy support for moderation actions
item_data?: SubmissionItemData;
original_data?: SubmissionItemData;
}
/**
* Represents a single item in the moderation queue.
* Can be either a review or a content submission.
@@ -135,6 +164,9 @@ export interface ModerationItem {
/** Timestamp when the item was created */
created_at: string;
/** Timestamp when the item was submitted */
submitted_at?: string;
/** Timestamp when the item was last updated */
updated_at?: string;
@@ -147,7 +179,23 @@ export interface ModerationItem {
/** Type of submission (e.g., 'park', 'ride', 'review') */
submission_type?: string;
/** Profile information of the submitting user */
/** Pre-loaded submitter profile from view */
submitter?: {
user_id: string;
username: string;
display_name?: string;
avatar_url?: string;
};
/** Pre-loaded reviewer profile from view */
reviewer?: {
user_id: string;
username: string;
display_name?: string;
avatar_url?: string;
};
/** Legacy: Profile information of the submitting user */
user_profile?: {
username: string;
display_name?: string;
@@ -172,7 +220,7 @@ export interface ModerationItem {
/** Notes left by the reviewing moderator */
reviewer_notes?: string;
/** Profile information of the reviewing moderator */
/** Legacy: Profile information of the reviewing moderator */
reviewer_profile?: {
username: string;
display_name?: string;
@@ -191,14 +239,8 @@ export interface ModerationItem {
/** Internal flag indicating item is being removed (optimistic update) */
_removing?: boolean;
/** Sub-items for composite submissions */
submission_items?: Array<{
id: string;
item_type: string;
item_data: SubmissionItemData;
original_data?: SubmissionItemData;
status: string;
}>;
/** Pre-loaded submission items with entity data from view */
submission_items?: SubmissionItem[];
}
/**

View File

@@ -70,22 +70,41 @@ export function hasRideModelId(data: Json): data is { ride_model_id: string } &
}
/**
* Safely get name from item_data
* Safely get name from item_data or entity_data
* Works with both Json (legacy) and pre-loaded entity_data from view
*/
export function getItemName(data: Json): string {
export function getItemName(data: Json | Record<string, any> | null | undefined): string {
if (!data) return 'Unnamed';
// Handle entity_data object (from view)
if (typeof data === 'object' && !Array.isArray(data) && 'name' in data) {
return String(data.name);
}
// Handle Json (legacy)
if (hasName(data)) {
return data.name;
}
return 'Unnamed';
}
/**
* Safely get photos from item_data
* Safely get photos from item_data or entity_data
*/
export function getItemPhotos(data: Json): Array<Record<string, Json>> {
export function getItemPhotos(data: Json | Record<string, any> | null | undefined): Array<Record<string, Json>> {
if (!data) return [];
// Handle entity_data object (from view)
if (typeof data === 'object' && !Array.isArray(data) && 'photos' in data && Array.isArray(data.photos)) {
return data.photos as Array<Record<string, Json>>;
}
// Handle Json (legacy)
if (hasPhotos(data)) {
return data.photos;
}
return [];
}