193 lines
8.1 KiB
TypeScript
193 lines
8.1 KiB
TypeScript
'use client';
|
|
|
|
import React, { useEffect } from 'react';
|
|
import { GroupRideSummary } from './types';
|
|
|
|
interface RideDetailsModalProps {
|
|
isOpen: boolean;
|
|
ride: GroupRideSummary | null;
|
|
onClose: () => void;
|
|
onEdit?: (ride: GroupRideSummary) => void;
|
|
onRequestSlot?: (rideId: string) => void;
|
|
}
|
|
|
|
export function RideDetailsModal({
|
|
isOpen,
|
|
ride,
|
|
onClose,
|
|
onEdit,
|
|
onRequestSlot,
|
|
}: RideDetailsModalProps) {
|
|
useEffect(() => {
|
|
function handleKeyDown(e: KeyboardEvent) {
|
|
if (e.key === 'Escape') onClose();
|
|
}
|
|
if (isOpen) {
|
|
document.addEventListener('keydown', handleKeyDown);
|
|
}
|
|
return () => document.removeEventListener('keydown', handleKeyDown);
|
|
}, [isOpen, onClose]);
|
|
|
|
if (!isOpen || !ride) return null;
|
|
|
|
const isFull = ride.participantCount >= ride.participantLimit;
|
|
const isJoined = ride.userStatus === 'joined' || ride.isOrganizer;
|
|
|
|
return (
|
|
<div
|
|
role="dialog"
|
|
aria-modal="true"
|
|
aria-labelledby="ride-details-title"
|
|
className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-slate-950/80 backdrop-blur-md overflow-y-auto animate-in fade-in duration-200"
|
|
onClick={(e) => {
|
|
if (e.target === e.currentTarget) onClose();
|
|
}}
|
|
>
|
|
<div className="relative w-full max-w-lg bg-surface-container-low border border-outline-variant/40 rounded-2xl p-6 shadow-2xl space-y-5 my-8">
|
|
{/* Header */}
|
|
<div className="flex items-center justify-between pb-3 border-b border-outline-variant/20">
|
|
<div className="flex items-center gap-2.5">
|
|
<div className="w-10 h-10 rounded-xl bg-primary-container/20 text-primary-container flex items-center justify-center">
|
|
<span className="material-symbols-outlined text-[24px]">sports_motorsports</span>
|
|
</div>
|
|
<div>
|
|
<h2 id="ride-details-title" className="font-headline text-lg font-bold text-on-surface">
|
|
{ride.title}
|
|
</h2>
|
|
<span className="font-mono text-xs text-primary-container font-semibold">
|
|
{ride.discipline || 'Sport / Canyon'} • {ride.paceClassification}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<button
|
|
onClick={onClose}
|
|
className="w-8 h-8 rounded-lg bg-surface-container hover:bg-surface-container-high text-on-surface-variant flex items-center justify-center transition cursor-pointer"
|
|
aria-label="Close dialog"
|
|
>
|
|
<span className="material-symbols-outlined text-[18px]">close</span>
|
|
</button>
|
|
</div>
|
|
|
|
{/* Status & Code Row */}
|
|
<div className="flex items-center justify-between p-3 rounded-xl bg-surface-container border border-outline-variant/20 text-xs font-mono">
|
|
<div className="flex items-center gap-2">
|
|
<span className="text-on-surface-variant">Status:</span>
|
|
<span className="px-2 py-0.5 rounded bg-primary-container/20 text-primary-container font-bold uppercase text-[10px]">
|
|
{ride.status || 'scheduled'}
|
|
</span>
|
|
</div>
|
|
{ride.rideCode && (
|
|
<div className="flex items-center gap-1.5">
|
|
<span className="text-on-surface-variant">Code:</span>
|
|
<span className="px-2 py-0.5 rounded bg-surface-container-highest text-secondary-fixed font-bold">
|
|
{ride.rideCode}
|
|
</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Detailed Metadata Grid */}
|
|
<div className="space-y-3 text-xs font-mono">
|
|
<div className="p-3 rounded-xl bg-surface-container/60 border border-outline-variant/20 space-y-2">
|
|
<div className="flex items-center justify-between">
|
|
<span className="text-on-surface-variant">Date & Time:</span>
|
|
<span className="text-on-surface font-semibold">
|
|
{ride.scheduledDate} • {ride.scheduledTime}
|
|
</span>
|
|
</div>
|
|
<div className="flex items-center justify-between">
|
|
<span className="text-on-surface-variant">Region:</span>
|
|
<span className="text-on-surface">{ride.region}</span>
|
|
</div>
|
|
<div className="flex items-center justify-between">
|
|
<span className="text-on-surface-variant">Total Distance:</span>
|
|
<span className="text-on-surface">{ride.distance}</span>
|
|
</div>
|
|
<div className="flex items-center justify-between">
|
|
<span className="text-on-surface-variant">Formation Profile:</span>
|
|
<span className="text-secondary-fixed">{ride.formationProfile}</span>
|
|
</div>
|
|
<div className="flex items-center justify-between">
|
|
<span className="text-on-surface-variant">Rider Roster:</span>
|
|
<span className="text-on-surface font-bold">
|
|
{ride.participantCount} / {ride.participantLimit} Riders
|
|
</span>
|
|
</div>
|
|
<div className="flex items-center justify-between">
|
|
<span className="text-on-surface-variant">Road Captain / Lead:</span>
|
|
<span className="text-primary-container font-bold">{ride.leadHandle}</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Meeting Point */}
|
|
<div>
|
|
<span className="block text-[11px] text-outline font-semibold uppercase mb-1">
|
|
Meeting Point & Rally Location
|
|
</span>
|
|
<div className="p-3 rounded-xl bg-surface-container/80 border border-outline-variant/20 text-on-surface">
|
|
{ride.meetingPoint}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Route Preview Snippet */}
|
|
<div>
|
|
<span className="block text-[11px] text-outline font-semibold uppercase mb-1">
|
|
Route Preview Waypoints
|
|
</span>
|
|
<div className="p-3 rounded-xl bg-surface-container/80 border border-outline-variant/20 text-primary-fixed-dim">
|
|
{ride.routeSnippet || 'Waypoints synchronized via MotorSignal GNSS Multi-Band Mesh.'}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Description & Safety Briefing */}
|
|
<div>
|
|
<span className="block text-[11px] text-outline font-semibold uppercase mb-1">
|
|
Briefing & Safety Protocol
|
|
</span>
|
|
<p className="p-3 rounded-xl bg-surface-container/80 border border-outline-variant/20 font-body text-xs text-on-surface-variant leading-relaxed">
|
|
{ride.description}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Footer Actions */}
|
|
<div className="flex items-center justify-end gap-3 pt-3 border-t border-outline-variant/20">
|
|
<button
|
|
type="button"
|
|
onClick={onClose}
|
|
className="px-4 py-2 rounded-xl bg-surface-container hover:bg-surface-container-high text-xs font-semibold text-on-surface transition cursor-pointer"
|
|
>
|
|
Close
|
|
</button>
|
|
{ride.isOrganizer && onEdit && (
|
|
<button
|
|
type="button"
|
|
onClick={() => {
|
|
onClose();
|
|
onEdit(ride);
|
|
}}
|
|
className="px-4 py-2 rounded-xl bg-primary-container text-on-primary-container text-xs font-bold hover:shadow-lg transition cursor-pointer flex items-center gap-1.5"
|
|
>
|
|
<span className="material-symbols-outlined text-[16px]">edit</span>
|
|
<span>Edit Ride</span>
|
|
</button>
|
|
)}
|
|
{!ride.isOrganizer && !isJoined && onRequestSlot && !isFull && (
|
|
<button
|
|
type="button"
|
|
onClick={() => {
|
|
onClose();
|
|
onRequestSlot(ride.id);
|
|
}}
|
|
className="px-4 py-2 rounded-xl bg-gradient-to-r from-primary-container to-primary-fixed text-on-primary-container text-xs font-bold hover:shadow-lg transition cursor-pointer flex items-center gap-1.5"
|
|
>
|
|
<span className="material-symbols-outlined text-[16px]">how_to_reg</span>
|
|
<span>Join Pack Run</span>
|
|
</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|