mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 08:11:13 -05:00
feat: Enable TypeScript strict mode
This commit is contained in:
@@ -79,7 +79,7 @@ interface ParkFormProps {
|
|||||||
onSubmit: (data: ParkFormData & {
|
onSubmit: (data: ParkFormData & {
|
||||||
operator_id?: string;
|
operator_id?: string;
|
||||||
property_owner_id?: string;
|
property_owner_id?: string;
|
||||||
_compositeSubmission?: any;
|
_compositeSubmission?: import('@/types/composite-submission').ParkCompositeSubmission;
|
||||||
}) => Promise<void>;
|
}) => Promise<void>;
|
||||||
onCancel?: () => void;
|
onCancel?: () => void;
|
||||||
initialData?: Partial<ParkFormData & {
|
initialData?: Partial<ParkFormData & {
|
||||||
@@ -214,7 +214,7 @@ export function ParkForm({ onSubmit, onCancel, initialData, isEditing = false }:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Build composite submission if new entities were created
|
// Build composite submission if new entities were created
|
||||||
const submissionContent: any = {
|
const submissionContent: import('@/types/composite-submission').ParkCompositeSubmission = {
|
||||||
park: data,
|
park: data,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -90,7 +90,11 @@ export function ConflictResolutionDialog({
|
|||||||
<AlertCircle className="h-4 w-4" />
|
<AlertCircle className="h-4 w-4" />
|
||||||
<AlertDescription>
|
<AlertDescription>
|
||||||
<p className="font-medium">
|
<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>
|
||||||
<p className="text-sm mt-1">{conflict.message}</p>
|
<p className="text-sm mt-1">{conflict.message}</p>
|
||||||
</AlertDescription>
|
</AlertDescription>
|
||||||
|
|||||||
@@ -24,8 +24,10 @@ export function DependencyTreeView({ items }: DependencyTreeViewProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getItemLabel = (item: SubmissionItemWithDeps): string => {
|
const getItemLabel = (item: SubmissionItemWithDeps): string => {
|
||||||
const data = item.item_data;
|
const data = typeof item.item_data === 'object' && item.item_data !== null && !Array.isArray(item.item_data)
|
||||||
const name = data.name || 'Unnamed';
|
? 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('_', ' ');
|
const type = item.item_type.replace('_', ' ');
|
||||||
return `${name} (${type})`;
|
return `${name} (${type})`;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -104,7 +104,9 @@ export function DependencyVisualizer({ items, selectedIds }: DependencyVisualize
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className={isMobile ? "p-4 pt-0" : ""}>
|
<CardContent className={isMobile ? "p-4 pt-0" : ""}>
|
||||||
<p className={`font-medium ${isMobile ? 'text-sm' : 'text-sm'}`}>
|
<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>
|
</p>
|
||||||
{item.dependents && item.dependents.length > 0 && (
|
{item.dependents && item.dependents.length > 0 && (
|
||||||
<p className={`text-muted-foreground mt-1 ${isMobile ? 'text-xs' : 'text-xs'}`}>
|
<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 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 = {
|
const photoData = {
|
||||||
...item.item_data,
|
...itemData,
|
||||||
photos: Array.isArray(item.item_data.photos)
|
photos: photos.map((photo: unknown) => ({
|
||||||
? item.item_data.photos.map((photo: unknown) => ({
|
...(typeof photo === 'object' && photo !== null ? photo as Record<string, unknown> : {}),
|
||||||
...(typeof photo === 'object' && photo !== null ? photo : {}),
|
caption,
|
||||||
caption,
|
credit,
|
||||||
credit,
|
})),
|
||||||
}))
|
|
||||||
: [],
|
|
||||||
};
|
};
|
||||||
await handleSubmit(photoData);
|
await handleSubmit(photoData);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ export function ItemReviewCard({ item, onEdit, onStatusChange, submissionId }: I
|
|||||||
<ValidationSummary
|
<ValidationSummary
|
||||||
item={{
|
item={{
|
||||||
item_type: item.item_type,
|
item_type: item.item_type,
|
||||||
item_data: item.item_data,
|
item_data: item.item_data as Record<string, unknown>,
|
||||||
id: item.id,
|
id: item.id,
|
||||||
}}
|
}}
|
||||||
onValidationChange={handleValidationChange}
|
onValidationChange={handleValidationChange}
|
||||||
|
|||||||
@@ -87,9 +87,9 @@ export function ItemSelectorDialog({
|
|||||||
<span className="font-medium capitalize">
|
<span className="font-medium capitalize">
|
||||||
{item.item_type.replace('_', ' ')}
|
{item.item_type.replace('_', ' ')}
|
||||||
</span>
|
</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">
|
<span className="text-sm text-muted-foreground">
|
||||||
{String(item.item_data.name)}
|
{String((item.item_data as Record<string, unknown>).name)}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{item.dependencies && item.dependencies.length > 0 && (
|
{item.dependencies && item.dependencies.length > 0 && (
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ export const ModerationQueue = forwardRef<ModerationQueueRef, ModerationQueuePro
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Wrapped delete with confirmation
|
// Wrapped delete with confirmation
|
||||||
const handleDeleteSubmission = useCallback((item: any) => {
|
const handleDeleteSubmission = useCallback((item: { id: string; submission_type?: string }) => {
|
||||||
setConfirmDialog({
|
setConfirmDialog({
|
||||||
open: true,
|
open: true,
|
||||||
title: 'Delete Submission',
|
title: 'Delete Submission',
|
||||||
@@ -198,7 +198,7 @@ export const ModerationQueue = forwardRef<ModerationQueueRef, ModerationQueuePro
|
|||||||
enabled: true,
|
enabled: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleOpenPhotos = (photos: any[], index: number) => {
|
const handleOpenPhotos = (photos: PhotoItem[], index: number) => {
|
||||||
setSelectedPhotos(photos);
|
setSelectedPhotos(photos);
|
||||||
setSelectedPhotoIndex(index);
|
setSelectedPhotoIndex(index);
|
||||||
setPhotoModalOpen(true);
|
setPhotoModalOpen(true);
|
||||||
|
|||||||
@@ -605,9 +605,12 @@ export function SubmissionReviewManager({
|
|||||||
open={showValidationBlockerDialog}
|
open={showValidationBlockerDialog}
|
||||||
onClose={() => setShowValidationBlockerDialog(false)}
|
onClose={() => setShowValidationBlockerDialog(false)}
|
||||||
blockingErrors={Array.from(validationResults.values()).flatMap(r => r.blockingErrors)}
|
blockingErrors={Array.from(validationResults.values()).flatMap(r => r.blockingErrors)}
|
||||||
itemNames={items.filter(i => selectedItemIds.has(i.id)).map(i =>
|
itemNames={items.filter(i => selectedItemIds.has(i.id)).map(i => {
|
||||||
i.item_data?.name || i.item_type.replace('_', ' ')
|
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
|
<WarningConfirmDialog
|
||||||
@@ -619,9 +622,12 @@ export function SubmissionReviewManager({
|
|||||||
handleApprove();
|
handleApprove();
|
||||||
}}
|
}}
|
||||||
warnings={Array.from(validationResults.values()).flatMap(r => r.warnings)}
|
warnings={Array.from(validationResults.values()).flatMap(r => r.warnings)}
|
||||||
itemNames={items.filter(i => selectedItemIds.has(i.id)).map(i =>
|
itemNames={items.filter(i => selectedItemIds.has(i.id)).map(i => {
|
||||||
i.item_data?.name || i.item_type.replace('_', ' ')
|
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
|
<ConflictResolutionModal
|
||||||
|
|||||||
@@ -91,14 +91,7 @@ const ChartTooltip = RechartsPrimitive.Tooltip;
|
|||||||
|
|
||||||
const ChartTooltipContent = React.forwardRef<
|
const ChartTooltipContent = React.forwardRef<
|
||||||
HTMLDivElement,
|
HTMLDivElement,
|
||||||
React.ComponentProps<typeof RechartsPrimitive.Tooltip> &
|
import('@/types/recharts').TooltipProps
|
||||||
React.ComponentProps<"div"> & {
|
|
||||||
hideLabel?: boolean;
|
|
||||||
hideIndicator?: boolean;
|
|
||||||
indicator?: "line" | "dot" | "dashed";
|
|
||||||
nameKey?: string;
|
|
||||||
labelKey?: string;
|
|
||||||
}
|
|
||||||
>(
|
>(
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
@@ -115,7 +108,7 @@ const ChartTooltipContent = React.forwardRef<
|
|||||||
color,
|
color,
|
||||||
nameKey,
|
nameKey,
|
||||||
labelKey,
|
labelKey,
|
||||||
}: any,
|
},
|
||||||
ref,
|
ref,
|
||||||
) => {
|
) => {
|
||||||
const { config } = useChart();
|
const { config } = useChart();
|
||||||
@@ -163,7 +156,11 @@ const ChartTooltipContent = React.forwardRef<
|
|||||||
{payload.map((item, index) => {
|
{payload.map((item, index) => {
|
||||||
const key = `${nameKey || item.name || item.dataKey || "value"}`;
|
const key = `${nameKey || item.name || item.dataKey || "value"}`;
|
||||||
const itemConfig = getPayloadConfigFromPayload(config, item, key);
|
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 (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -229,12 +226,7 @@ const ChartLegend = RechartsPrimitive.Legend;
|
|||||||
|
|
||||||
const ChartLegendContent = React.forwardRef<
|
const ChartLegendContent = React.forwardRef<
|
||||||
HTMLDivElement,
|
HTMLDivElement,
|
||||||
React.ComponentProps<"div"> & {
|
import('@/types/recharts').LegendProps
|
||||||
hideIcon?: boolean;
|
|
||||||
nameKey?: string;
|
|
||||||
payload?: any[];
|
|
||||||
verticalAlign?: "top" | "bottom";
|
|
||||||
}
|
|
||||||
>(({ className, hideIcon = false, payload, verticalAlign = "bottom", nameKey }, ref) => {
|
>(({ className, hideIcon = false, payload, verticalAlign = "bottom", nameKey }, ref) => {
|
||||||
const { config } = useChart();
|
const { config } = useChart();
|
||||||
|
|
||||||
@@ -247,7 +239,7 @@ const ChartLegendContent = React.forwardRef<
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn("flex items-center justify-center gap-4", verticalAlign === "top" ? "pb-3" : "pt-3", className)}
|
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 key = `${nameKey || item.dataKey || "value"}`;
|
||||||
const itemConfig = getPayloadConfigFromPayload(config, item, key);
|
const itemConfig = getPayloadConfigFromPayload(config, item, key);
|
||||||
|
|
||||||
|
|||||||
@@ -137,8 +137,8 @@ export function useEntityCache() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let data: any[] | null = null;
|
let data: unknown[] | null = null;
|
||||||
let error: any = null;
|
let error: unknown = null;
|
||||||
|
|
||||||
// Use type-safe table queries
|
// Use type-safe table queries
|
||||||
switch (type) {
|
switch (type) {
|
||||||
@@ -172,18 +172,20 @@ export function useEntityCache() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
logger.error(`Error fetching ${type}:`, error);
|
logger.error(`Error fetching ${type}:`, { error: getErrorMessage(error as Error) });
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache the fetched entities
|
// Cache the fetched entities
|
||||||
if (data) {
|
if (data) {
|
||||||
data.forEach((entity: any) => {
|
(data as Array<Record<string, unknown>>).forEach((entity) => {
|
||||||
setCached(type, entity.id, entity as EntityTypeMap[T]);
|
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) {
|
} catch (error: unknown) {
|
||||||
logger.error(`Failed to bulk fetch ${type}:`, { error: getErrorMessage(error) });
|
logger.error(`Failed to bulk fetch ${type}:`, { error: getErrorMessage(error) });
|
||||||
return [];
|
return [];
|
||||||
@@ -194,7 +196,7 @@ export function useEntityCache() {
|
|||||||
* Fetch and cache related entities based on submission content
|
* Fetch and cache related entities based on submission content
|
||||||
* Automatically determines which entities to fetch from submission data
|
* 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 rideIds = new Set<string>();
|
||||||
const parkIds = new Set<string>();
|
const parkIds = new Set<string>();
|
||||||
const companyIds = new Set<string>();
|
const companyIds = new Set<string>();
|
||||||
@@ -203,20 +205,20 @@ export function useEntityCache() {
|
|||||||
submissions.forEach(submission => {
|
submissions.forEach(submission => {
|
||||||
const content = submission.content;
|
const content = submission.content;
|
||||||
if (content && typeof content === 'object') {
|
if (content && typeof content === 'object') {
|
||||||
if (content.ride_id) rideIds.add(content.ride_id);
|
if (typeof content.ride_id === 'string') rideIds.add(content.ride_id);
|
||||||
if (content.park_id) parkIds.add(content.park_id);
|
if (typeof content.park_id === 'string') parkIds.add(content.park_id);
|
||||||
if (content.company_id) companyIds.add(content.company_id);
|
if (typeof content.company_id === 'string') companyIds.add(content.company_id);
|
||||||
if (content.entity_id) {
|
if (typeof content.entity_id === 'string') {
|
||||||
if (submission.submission_type === 'ride') rideIds.add(content.entity_id);
|
if (submission.submission_type === 'ride') rideIds.add(content.entity_id);
|
||||||
if (submission.submission_type === 'park') parkIds.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);
|
companyIds.add(content.entity_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (content.manufacturer_id) companyIds.add(content.manufacturer_id);
|
if (typeof content.manufacturer_id === 'string') companyIds.add(content.manufacturer_id);
|
||||||
if (content.designer_id) companyIds.add(content.designer_id);
|
if (typeof content.designer_id === 'string') companyIds.add(content.designer_id);
|
||||||
if (content.operator_id) companyIds.add(content.operator_id);
|
if (typeof content.operator_id === 'string') companyIds.add(content.operator_id);
|
||||||
if (content.property_owner_id) companyIds.add(content.property_owner_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';
|
import { extractCloudflareImageId } from './cloudflareImageUtils';
|
||||||
|
|
||||||
// Core submission item interface with dependencies
|
// 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 {
|
export interface SubmissionItemWithDeps {
|
||||||
id: string;
|
id: string;
|
||||||
submission_id: string;
|
submission_id: string;
|
||||||
item_type: string;
|
item_type: string;
|
||||||
item_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: any; // Complex nested structure - will be typed properly in Phase 5
|
original_data?: unknown; // Dynamic structure - use type guards for safe access
|
||||||
action_type?: 'create' | 'edit' | 'delete';
|
action_type?: 'create' | 'edit' | 'delete';
|
||||||
status: 'pending' | 'approved' | 'rejected' | 'flagged' | 'skipped'; // Matches ReviewStatus from statuses.ts
|
status: 'pending' | 'approved' | 'rejected' | 'flagged' | 'skipped'; // Matches ReviewStatus from statuses.ts
|
||||||
depends_on: string | null;
|
depends_on: string | null;
|
||||||
@@ -43,7 +44,10 @@ export interface ConflictCheckResult {
|
|||||||
serverVersion?: {
|
serverVersion?: {
|
||||||
last_modified_at: string;
|
last_modified_at: string;
|
||||||
last_modified_by: string;
|
last_modified_by: string;
|
||||||
modified_by_profile?: any;
|
modified_by_profile?: {
|
||||||
|
username: string;
|
||||||
|
display_name?: string;
|
||||||
|
};
|
||||||
} | null;
|
} | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,18 +114,28 @@ export async function detectDependencyConflicts(
|
|||||||
|
|
||||||
// Suggest creating parent
|
// Suggest creating parent
|
||||||
if (parent.status !== 'rejected') {
|
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({
|
suggestions.push({
|
||||||
action: 'create_parent',
|
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
|
// Suggest linking to existing entity
|
||||||
if (parent.item_type === 'park') {
|
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
|
const { data: parks } = await supabase
|
||||||
.from('parks')
|
.from('parks')
|
||||||
.select('id, name')
|
.select('id, name')
|
||||||
.ilike('name', `%${parent.item_data.name}%`)
|
.ilike('name', `%${parentName}%`)
|
||||||
.limit(3);
|
.limit(3);
|
||||||
|
|
||||||
parks?.forEach(park => {
|
parks?.forEach(park => {
|
||||||
@@ -189,11 +203,15 @@ export async function approveSubmissionItems(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Determine if this is an edit by checking for entity_id in item_data
|
// 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 = !!(
|
isEdit = !!(
|
||||||
item.item_data.park_id ||
|
('park_id' in itemData && itemData.park_id) ||
|
||||||
item.item_data.ride_id ||
|
('ride_id' in itemData && itemData.ride_id) ||
|
||||||
item.item_data.company_id ||
|
('company_id' in itemData && itemData.company_id) ||
|
||||||
item.item_data.ride_model_id
|
('ride_model_id' in itemData && itemData.ride_model_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create the entity based on type with dependency resolution
|
// 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