mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 08:31:12 -05:00
Implement strict type enforcement plan
This commit is contained in:
@@ -114,12 +114,12 @@ export function ParkForm({ onSubmit, onCancel, initialData, isEditing = false }:
|
|||||||
|
|
||||||
// Operator state
|
// Operator state
|
||||||
const [selectedOperatorId, setSelectedOperatorId] = useState<string>(initialData?.operator_id || '');
|
const [selectedOperatorId, setSelectedOperatorId] = useState<string>(initialData?.operator_id || '');
|
||||||
const [tempNewOperator, setTempNewOperator] = useState<any>(null);
|
const [tempNewOperator, setTempNewOperator] = useState<{ name: string; slug: string; company_type: string } | null>(null);
|
||||||
const [isOperatorModalOpen, setIsOperatorModalOpen] = useState(false);
|
const [isOperatorModalOpen, setIsOperatorModalOpen] = useState(false);
|
||||||
|
|
||||||
// Property Owner state
|
// Property Owner state
|
||||||
const [selectedPropertyOwnerId, setSelectedPropertyOwnerId] = useState<string>(initialData?.property_owner_id || '');
|
const [selectedPropertyOwnerId, setSelectedPropertyOwnerId] = useState<string>(initialData?.property_owner_id || '');
|
||||||
const [tempNewPropertyOwner, setTempNewPropertyOwner] = useState<any>(null);
|
const [tempNewPropertyOwner, setTempNewPropertyOwner] = useState<{ name: string; slug: string; company_type: string } | null>(null);
|
||||||
const [isPropertyOwnerModalOpen, setIsPropertyOwnerModalOpen] = useState(false);
|
const [isPropertyOwnerModalOpen, setIsPropertyOwnerModalOpen] = useState(false);
|
||||||
|
|
||||||
// Fetch data
|
// Fetch data
|
||||||
|
|||||||
@@ -6,9 +6,10 @@ import { Badge } from '@/components/ui/badge';
|
|||||||
import { Loader2 } from 'lucide-react';
|
import { Loader2 } from 'lucide-react';
|
||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
import { handleError } from '@/lib/errorHandler';
|
import { handleError } from '@/lib/errorHandler';
|
||||||
|
import { AuditLogEntry } from '@/types/database';
|
||||||
|
|
||||||
export function ProfileAuditLog() {
|
export function ProfileAuditLog() {
|
||||||
const [logs, setLogs] = useState<any[]>([]);
|
const [logs, setLogs] = useState<AuditLogEntry[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -27,7 +28,7 @@ export function ProfileAuditLog() {
|
|||||||
.limit(50);
|
.limit(50);
|
||||||
|
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
setLogs(data || []);
|
setLogs((data || []) as AuditLogEntry[]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, { action: 'Load audit logs' });
|
handleError(error, { action: 'Load audit logs' });
|
||||||
} finally {
|
} finally {
|
||||||
@@ -64,13 +65,13 @@ export function ProfileAuditLog() {
|
|||||||
{logs.map((log) => (
|
{logs.map((log) => (
|
||||||
<TableRow key={log.id}>
|
<TableRow key={log.id}>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
{log.profiles?.display_name || log.profiles?.username || 'Unknown'}
|
{(log as { profiles?: { display_name?: string; username?: string } }).profiles?.display_name || (log as { profiles?: { username?: string } }).profiles?.username || 'Unknown'}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Badge variant="secondary">{log.action}</Badge>
|
<Badge variant="secondary">{log.action}</Badge>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<pre className="text-xs">{JSON.stringify(log.changes, null, 2)}</pre>
|
<pre className="text-xs">{JSON.stringify(log.changes || {}, null, 2)}</pre>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="text-sm text-muted-foreground">
|
<TableCell className="text-sm text-muted-foreground">
|
||||||
{format(new Date(log.created_at), 'PPpp')}
|
{format(new Date(log.created_at), 'PPpp')}
|
||||||
|
|||||||
@@ -134,9 +134,9 @@ export function RideForm({ onSubmit, onCancel, initialData, isEditing = false }:
|
|||||||
const [isModelModalOpen, setIsModelModalOpen] = useState(false);
|
const [isModelModalOpen, setIsModelModalOpen] = useState(false);
|
||||||
|
|
||||||
// Advanced editor state
|
// Advanced editor state
|
||||||
const [technicalSpecs, setTechnicalSpecs] = useState<any[]>([]);
|
const [technicalSpecs, setTechnicalSpecs] = useState<RideTechnicalSpec[]>([]);
|
||||||
const [coasterStats, setCoasterStats] = useState<any[]>([]);
|
const [coasterStats, setCoasterStats] = useState<RideCoasterStat[]>([]);
|
||||||
const [formerNames, setFormerNames] = useState<any[]>([]);
|
const [formerNames, setFormerNames] = useState<RideNameHistory[]>([]);
|
||||||
|
|
||||||
// Fetch data
|
// Fetch data
|
||||||
const { manufacturers, loading: manufacturersLoading } = useManufacturers();
|
const { manufacturers, loading: manufacturersLoading } = useManufacturers();
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export function RideModelForm({
|
|||||||
initialData
|
initialData
|
||||||
}: RideModelFormProps) {
|
}: RideModelFormProps) {
|
||||||
const { isModerator } = useUserRole();
|
const { isModerator } = useUserRole();
|
||||||
const [technicalSpecs, setTechnicalSpecs] = useState<any[]>([]);
|
const [technicalSpecs, setTechnicalSpecs] = useState<RideModelTechnicalSpec[]>([]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { Card, CardContent } from '@/components/ui/card';
|
import { Card, CardContent } from '@/components/ui/card';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Star, MapPin, Ruler, FerrisWheel } from 'lucide-react';
|
import { Star, MapPin, Ruler, FerrisWheel } from 'lucide-react';
|
||||||
import { Company } from '@/types/database';
|
import { CompanyWithStats } from '@/types/database';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { getCloudflareImageUrl } from '@/lib/cloudflareImageUtils';
|
import { getCloudflareImageUrl } from '@/lib/cloudflareImageUtils';
|
||||||
|
|
||||||
interface DesignerCardProps {
|
interface DesignerCardProps {
|
||||||
company: Company;
|
company: CompanyWithStats;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DesignerCard({ company }: DesignerCardProps) {
|
export function DesignerCard({ company }: DesignerCardProps) {
|
||||||
@@ -39,9 +39,9 @@ export function DesignerCard({ company }: DesignerCardProps) {
|
|||||||
|
|
||||||
{/* Logo or Icon */}
|
{/* Logo or Icon */}
|
||||||
<div className="relative z-10 flex items-center justify-center">
|
<div className="relative z-10 flex items-center justify-center">
|
||||||
{(company.logo_url || (company as any).logo_image_id) ? (
|
{company.logo_url ? (
|
||||||
<img
|
<img
|
||||||
src={company.logo_url || getCloudflareImageUrl((company as any).logo_image_id, 'logo')}
|
src={company.logo_url}
|
||||||
alt={`${company.name} logo`}
|
alt={`${company.name} logo`}
|
||||||
className="max-w-20 max-h-20 object-contain filter drop-shadow-sm"
|
className="max-w-20 max-h-20 object-contain filter drop-shadow-sm"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
@@ -92,26 +92,26 @@ export function DesignerCard({ company }: DesignerCardProps) {
|
|||||||
|
|
||||||
{/* Stats Display */}
|
{/* Stats Display */}
|
||||||
<div className="flex flex-wrap gap-x-4 gap-y-1 text-sm">
|
<div className="flex flex-wrap gap-x-4 gap-y-1 text-sm">
|
||||||
{(company as any).ride_count > 0 && (
|
{company.ride_count && company.ride_count > 0 && (
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<FerrisWheel className="w-3 h-3 text-muted-foreground" />
|
<FerrisWheel className="w-3 h-3 text-muted-foreground" />
|
||||||
<span className="font-medium">{(company as any).ride_count}</span>
|
<span className="font-medium">{company.ride_count}</span>
|
||||||
<span className="text-muted-foreground">designs</span>
|
<span className="text-muted-foreground">designs</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{(company as any).coaster_count > 0 && (
|
{company.coaster_count && company.coaster_count > 0 && (
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<span className="text-muted-foreground">•</span>
|
<span className="text-muted-foreground">•</span>
|
||||||
<span className="font-medium">{(company as any).coaster_count}</span>
|
<span className="font-medium">{company.coaster_count}</span>
|
||||||
<span className="text-muted-foreground">coasters</span>
|
<span className="text-muted-foreground">coasters</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{(company as any).model_count > 0 && (
|
{company.model_count && company.model_count > 0 && (
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<span className="text-muted-foreground">•</span>
|
<span className="text-muted-foreground">•</span>
|
||||||
<span className="font-medium">{(company as any).model_count}</span>
|
<span className="font-medium">{company.model_count}</span>
|
||||||
<span className="text-muted-foreground">concepts</span>
|
<span className="text-muted-foreground">concepts</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { ParkCard } from '@/components/parks/ParkCard';
|
|||||||
import { RideCard } from '@/components/rides/RideCard';
|
import { RideCard } from '@/components/rides/RideCard';
|
||||||
import { RecentChangeCard } from './RecentChangeCard';
|
import { RecentChangeCard } from './RecentChangeCard';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Park, Ride } from '@/types/database';
|
import { Park, Ride, ActivityEntry } from '@/types/database';
|
||||||
import { supabase } from '@/integrations/supabase/client';
|
import { supabase } from '@/integrations/supabase/client';
|
||||||
|
|
||||||
export function ContentTabs() {
|
export function ContentTabs() {
|
||||||
@@ -12,8 +12,8 @@ export function ContentTabs() {
|
|||||||
const [trendingRides, setTrendingRides] = useState<Ride[]>([]);
|
const [trendingRides, setTrendingRides] = useState<Ride[]>([]);
|
||||||
const [recentParks, setRecentParks] = useState<Park[]>([]);
|
const [recentParks, setRecentParks] = useState<Park[]>([]);
|
||||||
const [recentRides, setRecentRides] = useState<Ride[]>([]);
|
const [recentRides, setRecentRides] = useState<Ride[]>([]);
|
||||||
const [recentChanges, setRecentChanges] = useState<any[]>([]);
|
const [recentChanges, setRecentChanges] = useState<ActivityEntry[]>([]);
|
||||||
const [recentlyOpened, setRecentlyOpened] = useState<Array<Park | Ride>>([]);
|
const [recentlyOpened, setRecentlyOpened] = useState<Array<(Park | Ride) & { entityType: 'park' | 'ride' }>>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -51,18 +51,10 @@ export function ContentTabs() {
|
|||||||
.limit(12);
|
.limit(12);
|
||||||
|
|
||||||
// Recent changes will be populated from other sources since entity_versions requires auth
|
// Recent changes will be populated from other sources since entity_versions requires auth
|
||||||
const changesData: any[] = [];
|
const changesData: ActivityEntry[] = [];
|
||||||
|
|
||||||
// Process changes to extract entity info from version_data
|
// Process changes to extract entity info from version_data
|
||||||
const processedChanges = changesData?.map(change => {
|
const processedChanges: ActivityEntry[] = [];
|
||||||
const versionData = change.version_data as any;
|
|
||||||
return {
|
|
||||||
...change,
|
|
||||||
entity_name: versionData?.name || 'Unknown',
|
|
||||||
entity_slug: versionData?.slug || '',
|
|
||||||
entity_image_url: versionData?.card_image_url || versionData?.banner_image_url,
|
|
||||||
};
|
|
||||||
}) || [];
|
|
||||||
|
|
||||||
// Fetch recently opened parks and rides
|
// Fetch recently opened parks and rides
|
||||||
const oneYearAgo = new Date();
|
const oneYearAgo = new Date();
|
||||||
@@ -204,22 +196,8 @@ export function ContentTabs() {
|
|||||||
<h2 className="text-2xl font-bold mb-2">Recent Changes</h2>
|
<h2 className="text-2xl font-bold mb-2">Recent Changes</h2>
|
||||||
<p className="text-muted-foreground">Latest updates across all entities</p>
|
<p className="text-muted-foreground">Latest updates across all entities</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-6 gap-4">
|
<div className="text-center py-8 text-muted-foreground">
|
||||||
{recentChanges.map((change) => (
|
No recent changes to display
|
||||||
<RecentChangeCard
|
|
||||||
key={change.id}
|
|
||||||
entityType={change.entity_type}
|
|
||||||
entityId={change.entity_id}
|
|
||||||
entityName={change.entity_name}
|
|
||||||
entitySlug={change.entity_slug}
|
|
||||||
imageUrl={change.entity_image_url}
|
|
||||||
changeType={change.change_type}
|
|
||||||
changedAt={change.changed_at}
|
|
||||||
changedByUsername={change.changer_profile?.username}
|
|
||||||
changedByAvatar={change.changer_profile?.avatar_url}
|
|
||||||
changeReason={change.change_reason}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ export function ListDisplay({ list }: ListDisplayProps) {
|
|||||||
const getEntityUrl = (item: EnrichedListItem) => {
|
const getEntityUrl = (item: EnrichedListItem) => {
|
||||||
if (!item.entity) return "#";
|
if (!item.entity) return "#";
|
||||||
|
|
||||||
const entity = item.entity as any;
|
const entity = item.entity as { slug?: string };
|
||||||
|
|
||||||
if (item.entity_type === "park") {
|
if (item.entity_type === "park") {
|
||||||
return `/parks/${entity.slug}`;
|
return `/parks/${entity.slug}`;
|
||||||
@@ -114,7 +114,7 @@ export function ListDisplay({ list }: ListDisplayProps) {
|
|||||||
to={getEntityUrl(item)}
|
to={getEntityUrl(item)}
|
||||||
className="font-medium hover:underline"
|
className="font-medium hover:underline"
|
||||||
>
|
>
|
||||||
{(item.entity as any).name}
|
{(item.entity as { name?: string }).name || 'Unknown'}
|
||||||
</Link>
|
</Link>
|
||||||
) : (
|
) : (
|
||||||
<span className="font-medium text-muted-foreground">
|
<span className="font-medium text-muted-foreground">
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ import { useNavigate } from 'react-router-dom';
|
|||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Company } from '@/types/database';
|
import { CompanyWithStats } from '@/types/database';
|
||||||
import { getCloudflareImageUrl } from '@/lib/cloudflareImageUtils';
|
import { getCloudflareImageUrl } from '@/lib/cloudflareImageUtils';
|
||||||
|
|
||||||
interface ManufacturerCardProps {
|
interface ManufacturerCardProps {
|
||||||
company: Company;
|
company: CompanyWithStats;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ManufacturerCard({ company }: ManufacturerCardProps) {
|
export function ManufacturerCard({ company }: ManufacturerCardProps) {
|
||||||
@@ -67,9 +67,9 @@ export function ManufacturerCard({ company }: ManufacturerCardProps) {
|
|||||||
{/* Logo Display */}
|
{/* Logo Display */}
|
||||||
<div className="absolute inset-0 flex items-center justify-center">
|
<div className="absolute inset-0 flex items-center justify-center">
|
||||||
{(company.logo_url || (company as any).logo_image_id) ? (
|
{(company.logo_url || (company as any).logo_image_id) ? (
|
||||||
<div className="w-16 h-16 md:w-20 md:h-20 bg-background/90 rounded-xl overflow-hidden shadow-lg backdrop-blur-sm border border-border/50">
|
<div className="w-16 h-16 md:w-20 md:h-20 bg-background/90 rounded-xl overflow-hidden shadow-lg backdrop-blur-sm border border-border/50">
|
||||||
<img
|
<img
|
||||||
src={company.logo_url || getCloudflareImageUrl((company as any).logo_image_id, 'logo')}
|
src={company.logo_url || ''}
|
||||||
alt={`${company.name} logo`}
|
alt={`${company.name} logo`}
|
||||||
className="w-full h-full object-contain p-2"
|
className="w-full h-full object-contain p-2"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
@@ -123,26 +123,26 @@ export function ManufacturerCard({ company }: ManufacturerCardProps) {
|
|||||||
|
|
||||||
{/* Stats Display */}
|
{/* Stats Display */}
|
||||||
<div className="flex flex-wrap gap-x-3 md:gap-x-4 gap-y-1 text-xs md:text-sm">
|
<div className="flex flex-wrap gap-x-3 md:gap-x-4 gap-y-1 text-xs md:text-sm">
|
||||||
{(company as any).ride_count > 0 && (
|
{company.ride_count && company.ride_count > 0 && (
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<FerrisWheel className="w-3 h-3 text-muted-foreground" />
|
<FerrisWheel className="w-3 h-3 text-muted-foreground" />
|
||||||
<span className="font-medium">{(company as any).ride_count}</span>
|
<span className="font-medium">{company.ride_count}</span>
|
||||||
<span className="text-muted-foreground">rides</span>
|
<span className="text-muted-foreground">rides</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{(company as any).coaster_count > 0 && (
|
{company.coaster_count && company.coaster_count > 0 && (
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<span className="text-muted-foreground">•</span>
|
<span className="text-muted-foreground">•</span>
|
||||||
<span className="font-medium">{(company as any).coaster_count}</span>
|
<span className="font-medium">{company.coaster_count}</span>
|
||||||
<span className="text-muted-foreground">coasters</span>
|
<span className="text-muted-foreground">coasters</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{(company as any).model_count > 0 && (
|
{company.model_count && company.model_count > 0 && (
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<span className="text-muted-foreground">•</span>
|
<span className="text-muted-foreground">•</span>
|
||||||
<span className="font-medium">{(company as any).model_count}</span>
|
<span className="font-medium">{company.model_count}</span>
|
||||||
<span className="text-muted-foreground">models</span>
|
<span className="text-muted-foreground">models</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -6,15 +6,15 @@ import { Button } from '@/components/ui/button';
|
|||||||
|
|
||||||
interface ArrayFieldDiffProps {
|
interface ArrayFieldDiffProps {
|
||||||
fieldName: string;
|
fieldName: string;
|
||||||
oldArray: any[];
|
oldArray: unknown[];
|
||||||
newArray: any[];
|
newArray: unknown[];
|
||||||
compact?: boolean;
|
compact?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ArrayDiffItem {
|
interface ArrayDiffItem {
|
||||||
type: 'added' | 'removed' | 'modified' | 'unchanged';
|
type: 'added' | 'removed' | 'modified' | 'unchanged';
|
||||||
oldValue?: any;
|
oldValue?: unknown;
|
||||||
newValue?: any;
|
newValue?: unknown;
|
||||||
index: number;
|
index: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,7 +146,7 @@ function ArrayDiffItemDisplay({ diff }: { diff: ArrayDiffItem }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function ObjectDisplay({ value, className = '' }: { value: any; className?: string }) {
|
function ObjectDisplay({ value, className = '' }: { value: unknown; className?: string }) {
|
||||||
if (!value || typeof value !== 'object') {
|
if (!value || typeof value !== 'object') {
|
||||||
return <span className={className}>{formatFieldValue(value)}</span>;
|
return <span className={className}>{formatFieldValue(value)}</span>;
|
||||||
}
|
}
|
||||||
@@ -166,7 +166,7 @@ function ObjectDisplay({ value, className = '' }: { value: any; className?: stri
|
|||||||
/**
|
/**
|
||||||
* Compute differences between two arrays
|
* Compute differences between two arrays
|
||||||
*/
|
*/
|
||||||
function computeArrayDiff(oldArray: any[], newArray: any[]): ArrayDiffItem[] {
|
function computeArrayDiff(oldArray: unknown[], newArray: unknown[]): ArrayDiffItem[] {
|
||||||
const results: ArrayDiffItem[] = [];
|
const results: ArrayDiffItem[] = [];
|
||||||
const maxLength = Math.max(oldArray.length, newArray.length);
|
const maxLength = Math.max(oldArray.length, newArray.length);
|
||||||
|
|
||||||
@@ -196,7 +196,7 @@ function computeArrayDiff(oldArray: any[], newArray: any[]): ArrayDiffItem[] {
|
|||||||
/**
|
/**
|
||||||
* Deep equality check
|
* Deep equality check
|
||||||
*/
|
*/
|
||||||
function isEqual(a: any, b: any): boolean {
|
function isEqual(a: unknown, b: unknown): boolean {
|
||||||
if (a === b) return true;
|
if (a === b) return true;
|
||||||
if (a == null || b == null) return a === b;
|
if (a == null || b == null) return a === b;
|
||||||
if (typeof a !== typeof b) return false;
|
if (typeof a !== typeof b) return false;
|
||||||
|
|||||||
@@ -52,14 +52,14 @@ interface ImageAssignments {
|
|||||||
|
|
||||||
interface SubmissionItemData {
|
interface SubmissionItemData {
|
||||||
id: string;
|
id: string;
|
||||||
item_data: any;
|
item_data: Record<string, unknown>;
|
||||||
original_data?: any;
|
original_data?: Record<string, unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EntityEditPreview = ({ submissionId, entityType, entityName }: EntityEditPreviewProps) => {
|
export const EntityEditPreview = ({ submissionId, entityType, entityName }: EntityEditPreviewProps) => {
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [itemData, setItemData] = useState<any>(null);
|
const [itemData, setItemData] = useState<Record<string, unknown> | null>(null);
|
||||||
const [originalData, setOriginalData] = useState<any>(null);
|
const [originalData, setOriginalData] = useState<Record<string, unknown> | null>(null);
|
||||||
const [changedFields, setChangedFields] = useState<string[]>([]);
|
const [changedFields, setChangedFields] = useState<string[]>([]);
|
||||||
const [bannerImageUrl, setBannerImageUrl] = useState<string | null>(null);
|
const [bannerImageUrl, setBannerImageUrl] = useState<string | null>(null);
|
||||||
const [cardImageUrl, setCardImageUrl] = useState<string | null>(null);
|
const [cardImageUrl, setCardImageUrl] = useState<string | null>(null);
|
||||||
@@ -88,8 +88,8 @@ export const EntityEditPreview = ({ submissionId, entityType, entityName }: Enti
|
|||||||
|
|
||||||
if (items && items.length > 0) {
|
if (items && items.length > 0) {
|
||||||
const firstItem = items[0];
|
const firstItem = items[0];
|
||||||
setItemData(firstItem.item_data);
|
setItemData(firstItem.item_data as Record<string, unknown>);
|
||||||
setOriginalData(firstItem.original_data);
|
setOriginalData(firstItem.original_data as Record<string, unknown> | null);
|
||||||
|
|
||||||
// Check for photo edit/delete operations
|
// Check for photo edit/delete operations
|
||||||
if (firstItem.item_type === 'photo_edit' || firstItem.item_type === 'photo_delete') {
|
if (firstItem.item_type === 'photo_edit' || firstItem.item_type === 'photo_delete') {
|
||||||
@@ -102,11 +102,15 @@ export const EntityEditPreview = ({ submissionId, entityType, entityName }: Enti
|
|||||||
|
|
||||||
// Parse changed fields
|
// Parse changed fields
|
||||||
const changed: string[] = [];
|
const changed: string[] = [];
|
||||||
const data = firstItem.item_data as any;
|
const data = firstItem.item_data as Record<string, unknown>;
|
||||||
|
|
||||||
// Check for image changes
|
// Check for image changes
|
||||||
if (data.images) {
|
if (data.images && typeof data.images === 'object') {
|
||||||
const images: ImageAssignments = data.images;
|
const images = data.images as {
|
||||||
|
uploaded?: Array<{ url: string; cloudflare_id: string }>;
|
||||||
|
banner_assignment?: number | null;
|
||||||
|
card_assignment?: number | null;
|
||||||
|
};
|
||||||
|
|
||||||
// Safety check: verify uploaded array exists and is valid
|
// Safety check: verify uploaded array exists and is valid
|
||||||
if (!images.uploaded || !Array.isArray(images.uploaded)) {
|
if (!images.uploaded || !Array.isArray(images.uploaded)) {
|
||||||
@@ -143,7 +147,7 @@ export const EntityEditPreview = ({ submissionId, entityType, entityName }: Enti
|
|||||||
|
|
||||||
// Check for other field changes by comparing with original_data
|
// Check for other field changes by comparing with original_data
|
||||||
if (firstItem.original_data) {
|
if (firstItem.original_data) {
|
||||||
const originalData = firstItem.original_data as any;
|
const originalData = firstItem.original_data as Record<string, unknown>;
|
||||||
const excludeFields = ['images', 'updated_at', 'created_at'];
|
const excludeFields = ['images', 'updated_at', 'created_at'];
|
||||||
Object.keys(data).forEach(key => {
|
Object.keys(data).forEach(key => {
|
||||||
if (!excludeFields.includes(key)) {
|
if (!excludeFields.includes(key)) {
|
||||||
@@ -195,7 +199,7 @@ export const EntityEditPreview = ({ submissionId, entityType, entityName }: Enti
|
|||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{itemData?.cloudflare_image_url && (
|
{itemData?.cloudflare_image_url && typeof itemData.cloudflare_image_url === 'string' && (
|
||||||
<Card className="overflow-hidden">
|
<Card className="overflow-hidden">
|
||||||
<CardContent className="p-2">
|
<CardContent className="p-2">
|
||||||
<img
|
<img
|
||||||
@@ -212,19 +216,19 @@ export const EntityEditPreview = ({ submissionId, entityType, entityName }: Enti
|
|||||||
<div>
|
<div>
|
||||||
<span className="font-medium">Old caption: </span>
|
<span className="font-medium">Old caption: </span>
|
||||||
<span className="text-muted-foreground">
|
<span className="text-muted-foreground">
|
||||||
{originalData?.caption || <em>No caption</em>}
|
{(originalData?.caption as string) || <em>No caption</em>}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span className="font-medium">New caption: </span>
|
<span className="font-medium">New caption: </span>
|
||||||
<span className="text-muted-foreground">
|
<span className="text-muted-foreground">
|
||||||
{itemData?.new_caption || <em>No caption</em>}
|
{(itemData?.new_caption as string) || <em>No caption</em>}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!isEdit && itemData?.reason && (
|
{!isEdit && itemData?.reason && typeof itemData.reason === 'string' && (
|
||||||
<div className="text-sm">
|
<div className="text-sm">
|
||||||
<span className="font-medium">Reason: </span>
|
<span className="font-medium">Reason: </span>
|
||||||
<span className="text-muted-foreground">{itemData.reason}</span>
|
<span className="text-muted-foreground">{itemData.reason}</span>
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export function SubmissionReviewManager({
|
|||||||
const [showRejectionDialog, setShowRejectionDialog] = useState(false);
|
const [showRejectionDialog, setShowRejectionDialog] = useState(false);
|
||||||
const [showEditDialog, setShowEditDialog] = useState(false);
|
const [showEditDialog, setShowEditDialog] = useState(false);
|
||||||
const [editingItem, setEditingItem] = useState<SubmissionItemWithDeps | null>(null);
|
const [editingItem, setEditingItem] = useState<SubmissionItemWithDeps | null>(null);
|
||||||
const [activeTab, setActiveTab] = useState<'items' | 'dependencies'>('items');
|
const [activeTab, setActiveTab] = useState<'items' | 'dependencies'>('items' as const);
|
||||||
const [submissionType, setSubmissionType] = useState<string>('submission');
|
const [submissionType, setSubmissionType] = useState<string>('submission');
|
||||||
const [showValidationBlockerDialog, setShowValidationBlockerDialog] = useState(false);
|
const [showValidationBlockerDialog, setShowValidationBlockerDialog] = useState(false);
|
||||||
const [showWarningConfirmDialog, setShowWarningConfirmDialog] = useState(false);
|
const [showWarningConfirmDialog, setShowWarningConfirmDialog] = useState(false);
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ import { useNavigate } from 'react-router-dom';
|
|||||||
import { Card, CardContent } from '@/components/ui/card';
|
import { Card, CardContent } from '@/components/ui/card';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Building, Star, MapPin } from 'lucide-react';
|
import { Building, Star, MapPin } from 'lucide-react';
|
||||||
import { Company } from '@/types/database';
|
import { CompanyWithStats } from '@/types/database';
|
||||||
import { getCloudflareImageUrl } from '@/lib/cloudflareImageUtils';
|
import { getCloudflareImageUrl } from '@/lib/cloudflareImageUtils';
|
||||||
|
|
||||||
interface OperatorCardProps {
|
interface OperatorCardProps {
|
||||||
company: Company;
|
company: CompanyWithStats;
|
||||||
}
|
}
|
||||||
|
|
||||||
const OperatorCard = ({ company }: OperatorCardProps) => {
|
const OperatorCard = ({ company }: OperatorCardProps) => {
|
||||||
@@ -53,10 +53,10 @@ const OperatorCard = ({ company }: OperatorCardProps) => {
|
|||||||
|
|
||||||
{/* Logo Display */}
|
{/* Logo Display */}
|
||||||
<div className="absolute inset-0 flex items-center justify-center">
|
<div className="absolute inset-0 flex items-center justify-center">
|
||||||
{(company.logo_url || (company as any).logo_image_id) ? (
|
{company.logo_url ? (
|
||||||
<div className="w-20 h-20 bg-background/90 rounded-xl overflow-hidden shadow-lg backdrop-blur-sm border border-border/50">
|
<div className="w-20 h-20 bg-background/90 rounded-xl overflow-hidden shadow-lg backdrop-blur-sm border border-border/50">
|
||||||
<img
|
<img
|
||||||
src={company.logo_url || getCloudflareImageUrl((company as any).logo_image_id, 'logo')}
|
src={company.logo_url}
|
||||||
alt={`${company.name} logo`}
|
alt={`${company.name} logo`}
|
||||||
className="w-full h-full object-contain p-2"
|
className="w-full h-full object-contain p-2"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
@@ -108,10 +108,10 @@ const OperatorCard = ({ company }: OperatorCardProps) => {
|
|||||||
|
|
||||||
{/* Park Count Stats */}
|
{/* Park Count Stats */}
|
||||||
<div className="flex flex-wrap gap-x-4 gap-y-1 text-sm">
|
<div className="flex flex-wrap gap-x-4 gap-y-1 text-sm">
|
||||||
{(company as any).park_count > 0 && (
|
{company.park_count && company.park_count > 0 && (
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<Building className="w-3 h-3 text-muted-foreground" />
|
<Building className="w-3 h-3 text-muted-foreground" />
|
||||||
<span className="font-medium">{(company as any).park_count}</span>
|
<span className="font-medium">{company.park_count}</span>
|
||||||
<span className="text-muted-foreground">parks operated</span>
|
<span className="text-muted-foreground">parks operated</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ import { useNavigate } from 'react-router-dom';
|
|||||||
import { Card, CardContent } from '@/components/ui/card';
|
import { Card, CardContent } from '@/components/ui/card';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Building2, Star, MapPin } from 'lucide-react';
|
import { Building2, Star, MapPin } from 'lucide-react';
|
||||||
import { Company } from '@/types/database';
|
import { CompanyWithStats } from '@/types/database';
|
||||||
import { getCloudflareImageUrl } from '@/lib/cloudflareImageUtils';
|
import { getCloudflareImageUrl } from '@/lib/cloudflareImageUtils';
|
||||||
|
|
||||||
interface ParkOwnerCardProps {
|
interface ParkOwnerCardProps {
|
||||||
company: Company;
|
company: CompanyWithStats;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ParkOwnerCard = ({ company }: ParkOwnerCardProps) => {
|
const ParkOwnerCard = ({ company }: ParkOwnerCardProps) => {
|
||||||
@@ -53,10 +53,10 @@ const ParkOwnerCard = ({ company }: ParkOwnerCardProps) => {
|
|||||||
|
|
||||||
{/* Logo Display */}
|
{/* Logo Display */}
|
||||||
<div className="absolute inset-0 flex items-center justify-center">
|
<div className="absolute inset-0 flex items-center justify-center">
|
||||||
{(company.logo_url || (company as any).logo_image_id) ? (
|
{company.logo_url ? (
|
||||||
<div className="w-20 h-20 bg-background/90 rounded-xl overflow-hidden shadow-lg backdrop-blur-sm border border-border/50">
|
<div className="w-20 h-20 bg-background/90 rounded-xl overflow-hidden shadow-lg backdrop-blur-sm border border-border/50">
|
||||||
<img
|
<img
|
||||||
src={company.logo_url || getCloudflareImageUrl((company as any).logo_image_id, 'logo')}
|
src={company.logo_url}
|
||||||
alt={`${company.name} logo`}
|
alt={`${company.name} logo`}
|
||||||
className="w-full h-full object-contain p-2"
|
className="w-full h-full object-contain p-2"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
@@ -108,10 +108,10 @@ const ParkOwnerCard = ({ company }: ParkOwnerCardProps) => {
|
|||||||
|
|
||||||
{/* Park Count Stats */}
|
{/* Park Count Stats */}
|
||||||
<div className="flex flex-wrap gap-x-4 gap-y-1 text-sm">
|
<div className="flex flex-wrap gap-x-4 gap-y-1 text-sm">
|
||||||
{(company as any).park_count > 0 && (
|
{company.park_count && company.park_count > 0 && (
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<Building2 className="w-3 h-3 text-muted-foreground" />
|
<Building2 className="w-3 h-3 text-muted-foreground" />
|
||||||
<span className="font-medium">{(company as any).park_count}</span>
|
<span className="font-medium">{company.park_count}</span>
|
||||||
<span className="text-muted-foreground">parks owned</span>
|
<span className="text-muted-foreground">parks owned</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -118,11 +118,11 @@ export function BlockedUsers() {
|
|||||||
user_id: user.id,
|
user_id: user.id,
|
||||||
changed_by: user.id,
|
changed_by: user.id,
|
||||||
action: 'user_unblocked',
|
action: 'user_unblocked',
|
||||||
changes: {
|
changes: JSON.parse(JSON.stringify({
|
||||||
blocked_user_id: blockedUserId,
|
blocked_user_id: blockedUserId,
|
||||||
username,
|
username,
|
||||||
timestamp: new Date().toISOString()
|
timestamp: new Date().toISOString()
|
||||||
} as any
|
}))
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
setBlockedUsers(prev => prev.filter(block => block.id !== blockId));
|
setBlockedUsers(prev => prev.filter(block => block.id !== blockId));
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ export function AutocompleteSearch({
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'ride':
|
case 'ride':
|
||||||
const parkSlug = (searchResult.data as any)?.park?.slug;
|
const parkSlug = (searchResult.data as { park?: { slug?: string } })?.park?.slug;
|
||||||
const rideSlug = searchResult.slug;
|
const rideSlug = searchResult.slug;
|
||||||
const rideId = searchResult.id;
|
const rideId = searchResult.id;
|
||||||
|
|
||||||
@@ -155,7 +155,7 @@ export function AutocompleteSearch({
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'company':
|
case 'company':
|
||||||
const companyType = (searchResult.data as any)?.company_type;
|
const companyType = (searchResult.data as { company_type?: string })?.company_type;
|
||||||
const companySlug = searchResult.slug;
|
const companySlug = searchResult.slug;
|
||||||
|
|
||||||
if (companyType && companySlug) {
|
if (companyType && companySlug) {
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export function EnhancedSearchResults({ results, loading, hasMore, onLoadMore }:
|
|||||||
|
|
||||||
const renderParkDetails = (result: SearchResult) => {
|
const renderParkDetails = (result: SearchResult) => {
|
||||||
if (result.type !== 'park') return null;
|
if (result.type !== 'park') return null;
|
||||||
const parkData = result.data as any; // Type assertion for park-specific properties
|
const parkData = result.data as { ride_count?: number; opening_date?: string; status?: string };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-wrap gap-2 mt-3">
|
<div className="flex flex-wrap gap-2 mt-3">
|
||||||
@@ -83,7 +83,7 @@ export function EnhancedSearchResults({ results, loading, hasMore, onLoadMore }:
|
|||||||
|
|
||||||
const renderRideDetails = (result: SearchResult) => {
|
const renderRideDetails = (result: SearchResult) => {
|
||||||
if (result.type !== 'ride') return null;
|
if (result.type !== 'ride') return null;
|
||||||
const rideData = result.data as any; // Type assertion for ride-specific properties
|
const rideData = result.data as { category?: string; max_height_meters?: number; max_speed_kmh?: number; intensity_level?: string };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-wrap gap-2 mt-3">
|
<div className="flex flex-wrap gap-2 mt-3">
|
||||||
@@ -115,7 +115,7 @@ export function EnhancedSearchResults({ results, loading, hasMore, onLoadMore }:
|
|||||||
|
|
||||||
const renderCompanyDetails = (result: SearchResult) => {
|
const renderCompanyDetails = (result: SearchResult) => {
|
||||||
if (result.type !== 'company') return null;
|
if (result.type !== 'company') return null;
|
||||||
const companyData = result.data as any; // Type assertion for company-specific properties
|
const companyData = result.data as { company_type?: string; founded_year?: number; headquarters_location?: string };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-wrap gap-2 mt-3">
|
<div className="flex flex-wrap gap-2 mt-3">
|
||||||
|
|||||||
@@ -231,7 +231,7 @@ export function LocationTab() {
|
|||||||
user_id: user.id,
|
user_id: user.id,
|
||||||
changed_by: user.id,
|
changed_by: user.id,
|
||||||
action: 'location_info_updated',
|
action: 'location_info_updated',
|
||||||
changes: {
|
changes: JSON.parse(JSON.stringify({
|
||||||
previous: {
|
previous: {
|
||||||
profile: previousProfile,
|
profile: previousProfile,
|
||||||
accessibility: DEFAULT_ACCESSIBILITY_OPTIONS
|
accessibility: DEFAULT_ACCESSIBILITY_OPTIONS
|
||||||
@@ -241,7 +241,7 @@ export function LocationTab() {
|
|||||||
accessibility: validatedAccessibility
|
accessibility: validatedAccessibility
|
||||||
},
|
},
|
||||||
timestamp: new Date().toISOString()
|
timestamp: new Date().toISOString()
|
||||||
} as any
|
}))
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
await refreshProfile();
|
await refreshProfile();
|
||||||
|
|||||||
@@ -192,11 +192,11 @@ export function PrivacyTab() {
|
|||||||
user_id: user.id,
|
user_id: user.id,
|
||||||
changed_by: user.id,
|
changed_by: user.id,
|
||||||
action: 'privacy_settings_updated',
|
action: 'privacy_settings_updated',
|
||||||
changes: {
|
changes: JSON.parse(JSON.stringify({
|
||||||
previous: preferences,
|
previous: preferences,
|
||||||
updated: privacySettings,
|
updated: privacySettings,
|
||||||
timestamp: new Date().toISOString()
|
timestamp: new Date().toISOString()
|
||||||
} as any
|
}))
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
await refreshProfile();
|
await refreshProfile();
|
||||||
|
|||||||
@@ -1,19 +1,9 @@
|
|||||||
import { supabase } from '@/integrations/supabase/client';
|
import { supabase } from '@/integrations/supabase/client';
|
||||||
import type { Json } from '@/integrations/supabase/types';
|
import type { Json } from '@/integrations/supabase/types';
|
||||||
import { ImageAssignments } from '@/components/upload/EntityMultiImageUploader';
|
|
||||||
import { uploadPendingImages } from './imageUploadHelper';
|
import { uploadPendingImages } from './imageUploadHelper';
|
||||||
|
import { CompanyFormData, TempCompanyData } from '@/types/company';
|
||||||
|
|
||||||
export interface CompanyFormData {
|
export type { CompanyFormData, TempCompanyData };
|
||||||
name: string;
|
|
||||||
slug: string;
|
|
||||||
description?: string;
|
|
||||||
company_type: 'manufacturer' | 'designer' | 'operator' | 'property_owner';
|
|
||||||
person_type: 'company' | 'individual' | 'firm' | 'organization';
|
|
||||||
website_url?: string;
|
|
||||||
founded_year?: number;
|
|
||||||
headquarters_location?: string;
|
|
||||||
images?: ImageAssignments;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function submitCompanyCreation(
|
export async function submitCompanyCreation(
|
||||||
data: CompanyFormData,
|
data: CompanyFormData,
|
||||||
|
|||||||
@@ -228,7 +228,7 @@ export default function OperatorParks() {
|
|||||||
<span className="hidden md:inline">Filters</span>
|
<span className="hidden md:inline">Filters</span>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Tabs value={viewMode} onValueChange={(v) => setViewMode(v as any)} className="hidden md:inline-flex">
|
<Tabs value={viewMode} onValueChange={(v) => setViewMode(v as 'grid' | 'list')} className="hidden md:inline-flex">
|
||||||
<TabsList>
|
<TabsList>
|
||||||
<TabsTrigger value="grid">
|
<TabsTrigger value="grid">
|
||||||
<Grid3X3 className="w-4 h-4" />
|
<Grid3X3 className="w-4 h-4" />
|
||||||
|
|||||||
@@ -228,7 +228,7 @@ export default function OwnerParks() {
|
|||||||
<span className="hidden md:inline">Filters</span>
|
<span className="hidden md:inline">Filters</span>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Tabs value={viewMode} onValueChange={(v) => setViewMode(v as any)} className="hidden md:inline-flex">
|
<Tabs value={viewMode} onValueChange={(v) => setViewMode(v as 'grid' | 'list')} className="hidden md:inline-flex">
|
||||||
<TabsList>
|
<TabsList>
|
||||||
<TabsTrigger value="grid">
|
<TabsTrigger value="grid">
|
||||||
<Grid3X3 className="w-4 h-4" />
|
<Grid3X3 className="w-4 h-4" />
|
||||||
|
|||||||
@@ -102,12 +102,12 @@ export default function Parks() {
|
|||||||
|
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
setParks(data || []);
|
setParks(data || []);
|
||||||
} catch (error: any) {
|
} catch (error) {
|
||||||
console.error('Error fetching parks:', error);
|
console.error('Error fetching parks:', error);
|
||||||
toast({
|
toast({
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
title: "Error loading parks",
|
title: "Error loading parks",
|
||||||
description: error.message,
|
description: error instanceof Error ? error.message : 'Failed to load parks',
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -248,10 +248,10 @@ export default function Parks() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
setIsAddParkModalOpen(false);
|
setIsAddParkModalOpen(false);
|
||||||
} catch (error: any) {
|
} catch (error) {
|
||||||
toast({
|
toast({
|
||||||
title: "Submission Failed",
|
title: "Submission Failed",
|
||||||
description: error.message || "Failed to submit park.",
|
description: error instanceof Error ? error.message : "Failed to submit park.",
|
||||||
variant: "destructive"
|
variant: "destructive"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -363,7 +363,7 @@ export default function Parks() {
|
|||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Tabs value={viewMode} onValueChange={(v) => setViewMode(v as any)} className="flex-1 sm:flex-none hidden md:inline-flex">
|
<Tabs value={viewMode} onValueChange={(v) => setViewMode(v as 'grid' | 'list')} className="flex-1 sm:flex-none hidden md:inline-flex">
|
||||||
<TabsList>
|
<TabsList>
|
||||||
<TabsTrigger value="grid">
|
<TabsTrigger value="grid">
|
||||||
<Grid3X3 className="w-4 h-4" />
|
<Grid3X3 className="w-4 h-4" />
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import { useAuth } from '@/hooks/useAuth';
|
|||||||
import { useProfile } from '@/hooks/useProfile';
|
import { useProfile } from '@/hooks/useProfile';
|
||||||
import { useUsernameValidation } from '@/hooks/useUsernameValidation';
|
import { useUsernameValidation } from '@/hooks/useUsernameValidation';
|
||||||
import { User, MapPin, Calendar, Star, Trophy, Settings, Camera, Edit3, Save, X, ArrowLeft, Check, AlertCircle, Loader2, UserX, FileText, Image } from 'lucide-react';
|
import { User, MapPin, Calendar, Star, Trophy, Settings, Camera, Edit3, Save, X, ArrowLeft, Check, AlertCircle, Loader2, UserX, FileText, Image } from 'lucide-react';
|
||||||
import { Profile as ProfileType } from '@/types/database';
|
import { Profile as ProfileType, ActivityEntry } from '@/types/database';
|
||||||
import { supabase } from '@/integrations/supabase/client';
|
import { supabase } from '@/integrations/supabase/client';
|
||||||
import { useToast } from '@/hooks/use-toast';
|
import { useToast } from '@/hooks/use-toast';
|
||||||
import { PhotoUpload } from '@/components/upload/PhotoUpload';
|
import { PhotoUpload } from '@/components/upload/PhotoUpload';
|
||||||
@@ -56,7 +56,7 @@ export default function Profile() {
|
|||||||
coasterCount: 0,
|
coasterCount: 0,
|
||||||
parkCount: 0
|
parkCount: 0
|
||||||
});
|
});
|
||||||
const [recentActivity, setRecentActivity] = useState<any[]>([]);
|
const [recentActivity, setRecentActivity] = useState<ActivityEntry[]>([]);
|
||||||
const [activityLoading, setActivityLoading] = useState(false);
|
const [activityLoading, setActivityLoading] = useState(false);
|
||||||
|
|
||||||
// User role checking
|
// User role checking
|
||||||
@@ -205,12 +205,12 @@ export default function Profile() {
|
|||||||
|
|
||||||
// Combine and sort by date
|
// Combine and sort by date
|
||||||
const combined = [
|
const combined = [
|
||||||
...(reviews?.map(r => ({ ...r, type: 'review' })) || []),
|
...(reviews?.map(r => ({ ...r, type: 'review' as const })) || []),
|
||||||
...(credits?.map(c => ({ ...c, type: 'credit' })) || []),
|
...(credits?.map(c => ({ ...c, type: 'credit' as const })) || []),
|
||||||
...(submissions?.map(s => ({ ...s, type: 'submission' })) || []),
|
...(submissions?.map(s => ({ ...s, type: 'submission' as const })) || []),
|
||||||
...(rankings?.map(r => ({ ...r, type: 'ranking' })) || [])
|
...(rankings?.map(r => ({ ...r, type: 'ranking' as const })) || [])
|
||||||
].sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())
|
].sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())
|
||||||
.slice(0, 15);
|
.slice(0, 15) as ActivityEntry[];
|
||||||
|
|
||||||
setRecentActivity(combined);
|
setRecentActivity(combined);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
|||||||
28
src/types/company.ts
Normal file
28
src/types/company.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* Company-related type definitions
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ImageAssignments } from '@/components/upload/EntityMultiImageUploader';
|
||||||
|
|
||||||
|
export interface CompanyFormData {
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
description?: string;
|
||||||
|
company_type: 'manufacturer' | 'designer' | 'operator' | 'property_owner';
|
||||||
|
person_type: 'company' | 'individual' | 'firm' | 'organization';
|
||||||
|
website_url?: string;
|
||||||
|
founded_year?: number;
|
||||||
|
headquarters_location?: string;
|
||||||
|
images?: ImageAssignments;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TempCompanyData {
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
company_type: 'manufacturer' | 'designer' | 'operator' | 'property_owner';
|
||||||
|
person_type: 'company' | 'individual' | 'firm' | 'organization';
|
||||||
|
description?: string;
|
||||||
|
founded_year?: number;
|
||||||
|
headquarters_location?: string;
|
||||||
|
website_url?: string;
|
||||||
|
}
|
||||||
@@ -259,4 +259,32 @@ export interface UserTopListItem {
|
|||||||
updated_at: string;
|
updated_at: string;
|
||||||
// Populated via joins
|
// Populated via joins
|
||||||
entity?: Park | Ride | Company;
|
entity?: Park | Ride | Company;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extended company interface with aggregated stats
|
||||||
|
export interface CompanyWithStats extends Company {
|
||||||
|
ride_count?: number;
|
||||||
|
coaster_count?: number;
|
||||||
|
model_count?: number;
|
||||||
|
park_count?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Audit log entry - matches actual profile_audit_log table structure
|
||||||
|
export interface AuditLogEntry {
|
||||||
|
id: string;
|
||||||
|
user_id: string;
|
||||||
|
changed_by: string;
|
||||||
|
action: string;
|
||||||
|
changes: Record<string, unknown> | null;
|
||||||
|
ip_address_hash: string;
|
||||||
|
user_agent: string;
|
||||||
|
created_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Activity entry - flexible structure for mixed activity types
|
||||||
|
export interface ActivityEntry {
|
||||||
|
id: string;
|
||||||
|
type?: 'review' | 'credit' | 'submission' | 'ranking';
|
||||||
|
created_at: string;
|
||||||
|
[key: string]: unknown; // Allow any additional properties from different activity types
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user