mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 10:11:13 -05:00
feat: Enable TypeScript strict mode
This commit is contained in:
@@ -79,7 +79,7 @@ interface ParkFormProps {
|
||||
onSubmit: (data: ParkFormData & {
|
||||
operator_id?: string;
|
||||
property_owner_id?: string;
|
||||
_compositeSubmission?: any;
|
||||
_compositeSubmission?: import('@/types/composite-submission').ParkCompositeSubmission;
|
||||
}) => Promise<void>;
|
||||
onCancel?: () => void;
|
||||
initialData?: Partial<ParkFormData & {
|
||||
@@ -214,7 +214,7 @@ export function ParkForm({ onSubmit, onCancel, initialData, isEditing = false }:
|
||||
}
|
||||
|
||||
// Build composite submission if new entities were created
|
||||
const submissionContent: any = {
|
||||
const submissionContent: import('@/types/composite-submission').ParkCompositeSubmission = {
|
||||
park: data,
|
||||
};
|
||||
|
||||
|
||||
@@ -90,7 +90,11 @@ export function ConflictResolutionDialog({
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertDescription>
|
||||
<p className="font-medium">
|
||||
{item?.item_type.replace('_', ' ').toUpperCase()}: {item?.item_data.name}
|
||||
{item?.item_type.replace('_', ' ').toUpperCase()}: {
|
||||
item && typeof item.item_data === 'object' && item.item_data !== null && !Array.isArray(item.item_data) && 'name' in item.item_data
|
||||
? String((item.item_data as Record<string, unknown>).name)
|
||||
: 'Unnamed'
|
||||
}
|
||||
</p>
|
||||
<p className="text-sm mt-1">{conflict.message}</p>
|
||||
</AlertDescription>
|
||||
|
||||
@@ -24,8 +24,10 @@ export function DependencyTreeView({ items }: DependencyTreeViewProps) {
|
||||
};
|
||||
|
||||
const getItemLabel = (item: SubmissionItemWithDeps): string => {
|
||||
const data = item.item_data;
|
||||
const name = data.name || 'Unnamed';
|
||||
const data = typeof item.item_data === 'object' && item.item_data !== null && !Array.isArray(item.item_data)
|
||||
? item.item_data as Record<string, unknown>
|
||||
: {};
|
||||
const name = 'name' in data && typeof data.name === 'string' ? data.name : 'Unnamed';
|
||||
const type = item.item_type.replace('_', ' ');
|
||||
return `${name} (${type})`;
|
||||
};
|
||||
|
||||
@@ -104,7 +104,9 @@ export function DependencyVisualizer({ items, selectedIds }: DependencyVisualize
|
||||
</CardHeader>
|
||||
<CardContent className={isMobile ? "p-4 pt-0" : ""}>
|
||||
<p className={`font-medium ${isMobile ? 'text-sm' : 'text-sm'}`}>
|
||||
{item.item_data.name || 'Unnamed'}
|
||||
{typeof item.item_data === 'object' && item.item_data !== null && !Array.isArray(item.item_data) && 'name' in item.item_data
|
||||
? String((item.item_data as Record<string, unknown>).name)
|
||||
: 'Unnamed'}
|
||||
</p>
|
||||
{item.dependents && item.dependents.length > 0 && (
|
||||
<p className={`text-muted-foreground mt-1 ${isMobile ? 'text-xs' : 'text-xs'}`}>
|
||||
|
||||
@@ -93,15 +93,21 @@ export function ItemEditDialog({ item, items, open, onOpenChange, onComplete }:
|
||||
};
|
||||
|
||||
const handlePhotoSubmit = async (caption: string, credit: string) => {
|
||||
const itemData = typeof item.item_data === 'object' && item.item_data !== null && !Array.isArray(item.item_data)
|
||||
? item.item_data as Record<string, unknown>
|
||||
: {};
|
||||
|
||||
const photos = 'photos' in itemData && Array.isArray(itemData.photos)
|
||||
? itemData.photos
|
||||
: [];
|
||||
|
||||
const photoData = {
|
||||
...item.item_data,
|
||||
photos: Array.isArray(item.item_data.photos)
|
||||
? item.item_data.photos.map((photo: unknown) => ({
|
||||
...(typeof photo === 'object' && photo !== null ? photo : {}),
|
||||
caption,
|
||||
credit,
|
||||
}))
|
||||
: [],
|
||||
...itemData,
|
||||
photos: photos.map((photo: unknown) => ({
|
||||
...(typeof photo === 'object' && photo !== null ? photo as Record<string, unknown> : {}),
|
||||
caption,
|
||||
credit,
|
||||
})),
|
||||
};
|
||||
await handleSubmit(photoData);
|
||||
};
|
||||
|
||||
@@ -127,7 +127,7 @@ export function ItemReviewCard({ item, onEdit, onStatusChange, submissionId }: I
|
||||
<ValidationSummary
|
||||
item={{
|
||||
item_type: item.item_type,
|
||||
item_data: item.item_data,
|
||||
item_data: item.item_data as Record<string, unknown>,
|
||||
id: item.id,
|
||||
}}
|
||||
onValidationChange={handleValidationChange}
|
||||
|
||||
@@ -87,9 +87,9 @@ export function ItemSelectorDialog({
|
||||
<span className="font-medium capitalize">
|
||||
{item.item_type.replace('_', ' ')}
|
||||
</span>
|
||||
{item.item_data.name && (
|
||||
{typeof item.item_data === 'object' && item.item_data !== null && !Array.isArray(item.item_data) && 'name' in item.item_data && (
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{String(item.item_data.name)}
|
||||
{String((item.item_data as Record<string, unknown>).name)}
|
||||
</span>
|
||||
)}
|
||||
{item.dependencies && item.dependencies.length > 0 && (
|
||||
|
||||
@@ -145,7 +145,7 @@ export const ModerationQueue = forwardRef<ModerationQueueRef, ModerationQueuePro
|
||||
};
|
||||
|
||||
// Wrapped delete with confirmation
|
||||
const handleDeleteSubmission = useCallback((item: any) => {
|
||||
const handleDeleteSubmission = useCallback((item: { id: string; submission_type?: string }) => {
|
||||
setConfirmDialog({
|
||||
open: true,
|
||||
title: 'Delete Submission',
|
||||
@@ -198,7 +198,7 @@ export const ModerationQueue = forwardRef<ModerationQueueRef, ModerationQueuePro
|
||||
enabled: true,
|
||||
});
|
||||
|
||||
const handleOpenPhotos = (photos: any[], index: number) => {
|
||||
const handleOpenPhotos = (photos: PhotoItem[], index: number) => {
|
||||
setSelectedPhotos(photos);
|
||||
setSelectedPhotoIndex(index);
|
||||
setPhotoModalOpen(true);
|
||||
|
||||
@@ -605,9 +605,12 @@ export function SubmissionReviewManager({
|
||||
open={showValidationBlockerDialog}
|
||||
onClose={() => setShowValidationBlockerDialog(false)}
|
||||
blockingErrors={Array.from(validationResults.values()).flatMap(r => r.blockingErrors)}
|
||||
itemNames={items.filter(i => selectedItemIds.has(i.id)).map(i =>
|
||||
i.item_data?.name || i.item_type.replace('_', ' ')
|
||||
)}
|
||||
itemNames={items.filter(i => selectedItemIds.has(i.id)).map(i => {
|
||||
const name = typeof i.item_data === 'object' && i.item_data !== null && !Array.isArray(i.item_data) && 'name' in i.item_data
|
||||
? String((i.item_data as Record<string, unknown>).name)
|
||||
: i.item_type.replace('_', ' ');
|
||||
return name;
|
||||
})}
|
||||
/>
|
||||
|
||||
<WarningConfirmDialog
|
||||
@@ -619,9 +622,12 @@ export function SubmissionReviewManager({
|
||||
handleApprove();
|
||||
}}
|
||||
warnings={Array.from(validationResults.values()).flatMap(r => r.warnings)}
|
||||
itemNames={items.filter(i => selectedItemIds.has(i.id)).map(i =>
|
||||
i.item_data?.name || i.item_type.replace('_', ' ')
|
||||
)}
|
||||
itemNames={items.filter(i => selectedItemIds.has(i.id)).map(i => {
|
||||
const name = typeof i.item_data === 'object' && i.item_data !== null && !Array.isArray(i.item_data) && 'name' in i.item_data
|
||||
? String((i.item_data as Record<string, unknown>).name)
|
||||
: i.item_type.replace('_', ' ');
|
||||
return name;
|
||||
})}
|
||||
/>
|
||||
|
||||
<ConflictResolutionModal
|
||||
|
||||
@@ -91,14 +91,7 @@ const ChartTooltip = RechartsPrimitive.Tooltip;
|
||||
|
||||
const ChartTooltipContent = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.ComponentProps<typeof RechartsPrimitive.Tooltip> &
|
||||
React.ComponentProps<"div"> & {
|
||||
hideLabel?: boolean;
|
||||
hideIndicator?: boolean;
|
||||
indicator?: "line" | "dot" | "dashed";
|
||||
nameKey?: string;
|
||||
labelKey?: string;
|
||||
}
|
||||
import('@/types/recharts').TooltipProps
|
||||
>(
|
||||
(
|
||||
{
|
||||
@@ -115,7 +108,7 @@ const ChartTooltipContent = React.forwardRef<
|
||||
color,
|
||||
nameKey,
|
||||
labelKey,
|
||||
}: any,
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const { config } = useChart();
|
||||
@@ -163,7 +156,11 @@ const ChartTooltipContent = React.forwardRef<
|
||||
{payload.map((item, index) => {
|
||||
const key = `${nameKey || item.name || item.dataKey || "value"}`;
|
||||
const itemConfig = getPayloadConfigFromPayload(config, item, key);
|
||||
const indicatorColor = color || item.payload.fill || item.color;
|
||||
const indicatorColor = color ||
|
||||
(typeof item.payload === 'object' && item.payload !== null && 'fill' in item.payload
|
||||
? (item.payload as Record<string, unknown>).fill as string
|
||||
: undefined) ||
|
||||
item.color;
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -229,12 +226,7 @@ const ChartLegend = RechartsPrimitive.Legend;
|
||||
|
||||
const ChartLegendContent = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.ComponentProps<"div"> & {
|
||||
hideIcon?: boolean;
|
||||
nameKey?: string;
|
||||
payload?: any[];
|
||||
verticalAlign?: "top" | "bottom";
|
||||
}
|
||||
import('@/types/recharts').LegendProps
|
||||
>(({ className, hideIcon = false, payload, verticalAlign = "bottom", nameKey }, ref) => {
|
||||
const { config } = useChart();
|
||||
|
||||
@@ -247,7 +239,7 @@ const ChartLegendContent = React.forwardRef<
|
||||
ref={ref}
|
||||
className={cn("flex items-center justify-center gap-4", verticalAlign === "top" ? "pb-3" : "pt-3", className)}
|
||||
>
|
||||
{payload.map((item: any) => {
|
||||
{payload.map((item) => {
|
||||
const key = `${nameKey || item.dataKey || "value"}`;
|
||||
const itemConfig = getPayloadConfigFromPayload(config, item, key);
|
||||
|
||||
|
||||
@@ -137,8 +137,8 @@ export function useEntityCache() {
|
||||
}
|
||||
|
||||
try {
|
||||
let data: any[] | null = null;
|
||||
let error: any = null;
|
||||
let data: unknown[] | null = null;
|
||||
let error: unknown = null;
|
||||
|
||||
// Use type-safe table queries
|
||||
switch (type) {
|
||||
@@ -172,18 +172,20 @@ export function useEntityCache() {
|
||||
}
|
||||
|
||||
if (error) {
|
||||
logger.error(`Error fetching ${type}:`, error);
|
||||
logger.error(`Error fetching ${type}:`, { error: getErrorMessage(error as Error) });
|
||||
return [];
|
||||
}
|
||||
|
||||
// Cache the fetched entities
|
||||
if (data) {
|
||||
data.forEach((entity: any) => {
|
||||
setCached(type, entity.id, entity as EntityTypeMap[T]);
|
||||
(data as Array<Record<string, unknown>>).forEach((entity) => {
|
||||
if (entity && typeof entity === 'object' && 'id' in entity && 'name' in entity) {
|
||||
setCached(type, entity.id as string, entity as EntityTypeMap[T]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return data || [];
|
||||
return (data as EntityTypeMap[T][]) || [];
|
||||
} catch (error: unknown) {
|
||||
logger.error(`Failed to bulk fetch ${type}:`, { error: getErrorMessage(error) });
|
||||
return [];
|
||||
@@ -194,7 +196,7 @@ export function useEntityCache() {
|
||||
* Fetch and cache related entities based on submission content
|
||||
* Automatically determines which entities to fetch from submission data
|
||||
*/
|
||||
const fetchRelatedEntities = useCallback(async (submissions: any[]): Promise<void> => {
|
||||
const fetchRelatedEntities = useCallback(async (submissions: Array<{ content?: Record<string, string | number>; submission_type?: string }>): Promise<void> => {
|
||||
const rideIds = new Set<string>();
|
||||
const parkIds = new Set<string>();
|
||||
const companyIds = new Set<string>();
|
||||
@@ -203,20 +205,20 @@ export function useEntityCache() {
|
||||
submissions.forEach(submission => {
|
||||
const content = submission.content;
|
||||
if (content && typeof content === 'object') {
|
||||
if (content.ride_id) rideIds.add(content.ride_id);
|
||||
if (content.park_id) parkIds.add(content.park_id);
|
||||
if (content.company_id) companyIds.add(content.company_id);
|
||||
if (content.entity_id) {
|
||||
if (typeof content.ride_id === 'string') rideIds.add(content.ride_id);
|
||||
if (typeof content.park_id === 'string') parkIds.add(content.park_id);
|
||||
if (typeof content.company_id === 'string') companyIds.add(content.company_id);
|
||||
if (typeof content.entity_id === 'string') {
|
||||
if (submission.submission_type === 'ride') rideIds.add(content.entity_id);
|
||||
if (submission.submission_type === 'park') parkIds.add(content.entity_id);
|
||||
if (['manufacturer', 'operator', 'designer', 'property_owner'].includes(submission.submission_type)) {
|
||||
if (['manufacturer', 'operator', 'designer', 'property_owner'].includes(submission.submission_type || '')) {
|
||||
companyIds.add(content.entity_id);
|
||||
}
|
||||
}
|
||||
if (content.manufacturer_id) companyIds.add(content.manufacturer_id);
|
||||
if (content.designer_id) companyIds.add(content.designer_id);
|
||||
if (content.operator_id) companyIds.add(content.operator_id);
|
||||
if (content.property_owner_id) companyIds.add(content.property_owner_id);
|
||||
if (typeof content.manufacturer_id === 'string') companyIds.add(content.manufacturer_id);
|
||||
if (typeof content.designer_id === 'string') companyIds.add(content.designer_id);
|
||||
if (typeof content.operator_id === 'string') companyIds.add(content.operator_id);
|
||||
if (typeof content.property_owner_id === 'string') companyIds.add(content.property_owner_id);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -4,14 +4,15 @@ import { logger } from './logger';
|
||||
import { extractCloudflareImageId } from './cloudflareImageUtils';
|
||||
|
||||
// Core submission item interface with dependencies
|
||||
// Type safety for item_data will be added in Phase 5 after fixing components
|
||||
// NOTE: item_data and original_data use `unknown` because they contain dynamic structures
|
||||
// that vary by item_type. Use type guards from @/types/submission-item-data.ts to access safely.
|
||||
|
||||
export interface SubmissionItemWithDeps {
|
||||
id: string;
|
||||
submission_id: string;
|
||||
item_type: string;
|
||||
item_data: any; // Complex nested structure - will be typed properly in Phase 5
|
||||
original_data: any; // Complex nested structure - will be typed properly in Phase 5
|
||||
item_data: unknown; // Dynamic structure - use type guards for safe access
|
||||
original_data?: unknown; // Dynamic structure - use type guards for safe access
|
||||
action_type?: 'create' | 'edit' | 'delete';
|
||||
status: 'pending' | 'approved' | 'rejected' | 'flagged' | 'skipped'; // Matches ReviewStatus from statuses.ts
|
||||
depends_on: string | null;
|
||||
@@ -43,7 +44,10 @@ export interface ConflictCheckResult {
|
||||
serverVersion?: {
|
||||
last_modified_at: string;
|
||||
last_modified_by: string;
|
||||
modified_by_profile?: any;
|
||||
modified_by_profile?: {
|
||||
username: string;
|
||||
display_name?: string;
|
||||
};
|
||||
} | null;
|
||||
}
|
||||
|
||||
@@ -110,18 +114,28 @@ export async function detectDependencyConflicts(
|
||||
|
||||
// Suggest creating parent
|
||||
if (parent.status !== 'rejected') {
|
||||
const parentData = typeof parent.item_data === 'object' && parent.item_data !== null && !Array.isArray(parent.item_data)
|
||||
? parent.item_data as Record<string, unknown>
|
||||
: {};
|
||||
const parentName = 'name' in parentData && typeof parentData.name === 'string' ? parentData.name : 'Unnamed';
|
||||
|
||||
suggestions.push({
|
||||
action: 'create_parent',
|
||||
label: `Also approve ${parent.item_type}: ${parent.item_data.name}`,
|
||||
label: `Also approve ${parent.item_type}: ${parentName}`,
|
||||
});
|
||||
}
|
||||
|
||||
// Suggest linking to existing entity
|
||||
if (parent.item_type === 'park') {
|
||||
const parentData = typeof parent.item_data === 'object' && parent.item_data !== null && !Array.isArray(parent.item_data)
|
||||
? parent.item_data as Record<string, unknown>
|
||||
: {};
|
||||
const parentName = 'name' in parentData && typeof parentData.name === 'string' ? parentData.name : '';
|
||||
|
||||
const { data: parks } = await supabase
|
||||
.from('parks')
|
||||
.select('id, name')
|
||||
.ilike('name', `%${parent.item_data.name}%`)
|
||||
.ilike('name', `%${parentName}%`)
|
||||
.limit(3);
|
||||
|
||||
parks?.forEach(park => {
|
||||
@@ -189,11 +203,15 @@ export async function approveSubmissionItems(
|
||||
|
||||
try {
|
||||
// Determine if this is an edit by checking for entity_id in item_data
|
||||
const itemData = typeof item.item_data === 'object' && item.item_data !== null && !Array.isArray(item.item_data)
|
||||
? item.item_data as Record<string, unknown>
|
||||
: {};
|
||||
|
||||
isEdit = !!(
|
||||
item.item_data.park_id ||
|
||||
item.item_data.ride_id ||
|
||||
item.item_data.company_id ||
|
||||
item.item_data.ride_model_id
|
||||
('park_id' in itemData && itemData.park_id) ||
|
||||
('ride_id' in itemData && itemData.ride_id) ||
|
||||
('company_id' in itemData && itemData.company_id) ||
|
||||
('ride_model_id' in itemData && itemData.ride_model_id)
|
||||
);
|
||||
|
||||
// Create the entity based on type with dependency resolution
|
||||
|
||||
40
src/types/composite-submission.ts
Normal file
40
src/types/composite-submission.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Type definitions for composite submissions
|
||||
* Used when creating multiple related entities in a single submission
|
||||
*/
|
||||
|
||||
import type { TempCompanyData } from './company';
|
||||
|
||||
/**
|
||||
* Composite submission structure for park creation with related entities
|
||||
*/
|
||||
export interface ParkCompositeSubmission {
|
||||
park: {
|
||||
name: string;
|
||||
slug: string;
|
||||
description?: string;
|
||||
park_type: string;
|
||||
status: string;
|
||||
opening_date?: string;
|
||||
closing_date?: string;
|
||||
location_id?: string;
|
||||
website_url?: string;
|
||||
phone?: string;
|
||||
email?: string;
|
||||
operator_id?: string | null;
|
||||
property_owner_id?: string | null;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
new_operator?: TempCompanyData;
|
||||
new_property_owner?: TempCompanyData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic composite submission content type
|
||||
* Supports any entity type with optional related entities
|
||||
*/
|
||||
export interface CompositeSubmissionContent {
|
||||
[entityKey: string]: {
|
||||
[key: string]: unknown;
|
||||
} | TempCompanyData | undefined;
|
||||
}
|
||||
55
src/types/recharts.ts
Normal file
55
src/types/recharts.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Type definitions for Recharts payloads
|
||||
* Provides type-safe alternatives to `any` in chart components
|
||||
*/
|
||||
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
/**
|
||||
* Generic chart payload item structure
|
||||
*/
|
||||
export interface ChartPayloadItem<T = unknown> {
|
||||
value?: number | string;
|
||||
name?: string;
|
||||
dataKey?: string;
|
||||
color?: string;
|
||||
fill?: string;
|
||||
payload?: T;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tooltip content props from Recharts
|
||||
*/
|
||||
export interface TooltipProps<T = unknown> {
|
||||
active?: boolean;
|
||||
payload?: ChartPayloadItem<T>[];
|
||||
label?: string | number;
|
||||
labelFormatter?: (value: unknown, payload: ChartPayloadItem<T>[]) => ReactNode;
|
||||
formatter?: (
|
||||
value: number | string,
|
||||
name: string,
|
||||
item: ChartPayloadItem<T>,
|
||||
index: number,
|
||||
payload: unknown
|
||||
) => ReactNode;
|
||||
className?: string;
|
||||
indicator?: 'line' | 'dot' | 'dashed';
|
||||
hideLabel?: boolean;
|
||||
hideIndicator?: boolean;
|
||||
color?: string;
|
||||
nameKey?: string;
|
||||
labelKey?: string;
|
||||
labelClassName?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Legend content props from Recharts
|
||||
*/
|
||||
export interface LegendProps<T = unknown> {
|
||||
payload?: ChartPayloadItem<T>[];
|
||||
className?: string;
|
||||
hideIcon?: boolean;
|
||||
nameKey?: string;
|
||||
verticalAlign?: 'top' | 'bottom';
|
||||
}
|
||||
113
src/types/submission-item-data.ts
Normal file
113
src/types/submission-item-data.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* Type-safe helpers for accessing submission item data
|
||||
* Provides type guards and type narrowing for Json fields
|
||||
*/
|
||||
|
||||
import type { Json } from '@/integrations/supabase/types';
|
||||
|
||||
/**
|
||||
* Base structure that all item_data objects should have
|
||||
*/
|
||||
export interface BaseItemData {
|
||||
name?: string;
|
||||
slug?: string;
|
||||
description?: string;
|
||||
[key: string]: Json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard to safely check if Json is an object with a name property
|
||||
*/
|
||||
export function hasName(data: Json): data is { name: string } & Record<string, Json> {
|
||||
return typeof data === 'object' && data !== null && !Array.isArray(data) && 'name' in data && typeof (data as Record<string, Json>).name === 'string';
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard to check if Json is a photos array
|
||||
*/
|
||||
export function hasPhotos(data: Json): data is { photos: Array<Record<string, Json>> } & Record<string, Json> {
|
||||
return typeof data === 'object' && data !== null && !Array.isArray(data) && 'photos' in data && Array.isArray((data as Record<string, Json>).photos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for manufacturer data
|
||||
*/
|
||||
export function hasManufacturer(data: Json): data is { manufacturer_id: string; manufacturer_name: string } & Record<string, Json> {
|
||||
return (
|
||||
typeof data === 'object' &&
|
||||
data !== null &&
|
||||
!Array.isArray(data) &&
|
||||
'manufacturer_id' in data &&
|
||||
'manufacturer_name' in data
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for park data
|
||||
*/
|
||||
export function hasParkId(data: Json): data is { park_id: string } & Record<string, Json> {
|
||||
return typeof data === 'object' && data !== null && !Array.isArray(data) && 'park_id' in data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for ride data
|
||||
*/
|
||||
export function hasRideId(data: Json): data is { ride_id: string } & Record<string, Json> {
|
||||
return typeof data === 'object' && data !== null && !Array.isArray(data) && 'ride_id' in data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for company data
|
||||
*/
|
||||
export function hasCompanyId(data: Json): data is { company_id: string } & Record<string, Json> {
|
||||
return typeof data === 'object' && data !== null && !Array.isArray(data) && 'company_id' in data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for ride model data
|
||||
*/
|
||||
export function hasRideModelId(data: Json): data is { ride_model_id: string } & Record<string, Json> {
|
||||
return typeof data === 'object' && data !== null && !Array.isArray(data) && 'ride_model_id' in data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely get name from item_data
|
||||
*/
|
||||
export function getItemName(data: Json): string {
|
||||
if (hasName(data)) {
|
||||
return data.name;
|
||||
}
|
||||
return 'Unnamed';
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely get photos from item_data
|
||||
*/
|
||||
export function getItemPhotos(data: Json): Array<Record<string, Json>> {
|
||||
if (hasPhotos(data)) {
|
||||
return data.photos;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Json to a record for form usage
|
||||
* Only use when you need to pass data to form components
|
||||
*/
|
||||
export function jsonToRecord(data: Json): Record<string, Json> {
|
||||
if (typeof data === 'object' && data !== null && !Array.isArray(data)) {
|
||||
return data as Record<string, Json>;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Type-safe way to access nested Json properties
|
||||
*/
|
||||
export function getProperty<T = Json>(data: Json, key: string): T | undefined {
|
||||
if (typeof data === 'object' && data !== null && !Array.isArray(data)) {
|
||||
const obj = data as Record<string, Json>;
|
||||
return obj[key] as T | undefined;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
Reference in New Issue
Block a user