MotorSignal-App/website/components/group-rides/edit-ride-modal.tsx

314 lines
13 KiB
TypeScript

'use client';
import React, { useState, useEffect } from 'react';
import { GroupRideSummary, EditRideFormData } from './types';
interface EditRideModalProps {
isOpen: boolean;
ride: GroupRideSummary | null;
onClose: () => void;
onSaveRide: (updatedRide: GroupRideSummary) => void;
}
export function EditRideModal({
isOpen,
ride,
onClose,
onSaveRide,
}: EditRideModalProps) {
const [formData, setFormData] = useState<EditRideFormData>({
title: '',
region: '',
description: '',
paceClassification: 'Brisk Pace',
formationProfile: 'Staggered Paceline',
joinPolicy: 'requestToJoin',
scheduledDate: '',
scheduledTime: '',
distance: '',
meetingPoint: '',
participantLimit: 12,
status: 'scheduled',
});
useEffect(() => {
if (ride) {
setFormData({
title: ride.title,
region: ride.region,
description: ride.description,
paceClassification: ride.paceClassification,
formationProfile: ride.formationProfile,
joinPolicy: ride.joinPolicy,
scheduledDate: ride.scheduledDate,
scheduledTime: ride.scheduledTime,
distance: ride.distance,
meetingPoint: ride.meetingPoint,
participantLimit: ride.participantLimit,
status: ride.status ?? 'scheduled',
});
}
}, [ride]);
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 handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (!formData.title.trim() || !formData.region.trim()) return;
const updated: GroupRideSummary = {
...ride,
title: formData.title.trim(),
region: formData.region.trim(),
description: formData.description.trim(),
paceClassification: formData.paceClassification,
formationProfile: formData.formationProfile.trim(),
joinPolicy: formData.joinPolicy,
scheduledDate: formData.scheduledDate.trim(),
scheduledTime: formData.scheduledTime.trim(),
distance: formData.distance.trim(),
meetingPoint: formData.meetingPoint.trim(),
participantLimit: formData.participantLimit,
status: formData.status,
};
onSaveRide(updated);
onClose();
};
return (
<div
role="dialog"
aria-modal="true"
aria-labelledby="edit-modal-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"
>
<div className="relative w-full max-w-xl 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-9 h-9 rounded-xl bg-primary-container/20 text-primary-container flex items-center justify-center">
<span className="material-symbols-outlined text-[20px]">edit_note</span>
</div>
<div>
<h2 id="edit-modal-title" className="font-headline-md text-lg font-bold text-on-surface">
Alter Group Ride Parameters
</h2>
<span className="font-mono text-[10px] text-primary-fixed-dim">
ID: {ride.id} {ride.rideCode ? `• Code: ${ride.rideCode}` : ''}
</span>
</div>
</div>
<button
type="button"
onClick={onClose}
className="w-8 h-8 rounded-lg bg-surface-container hover:bg-surface-container-high text-on-surface-variant hover:text-on-surface flex items-center justify-center transition cursor-pointer"
aria-label="Close edit dialog"
>
<span className="material-symbols-outlined text-[18px]">close</span>
</button>
</div>
{/* Edit Form */}
<form onSubmit={handleSubmit} className="space-y-4">
{/* Title */}
<div>
<label htmlFor="edit-title" className="block text-xs font-semibold text-on-surface mb-1">
Ride Title *
</label>
<input
id="edit-title"
type="text"
required
value={formData.title}
onChange={(e) => setFormData({ ...formData, title: e.target.value })}
className="w-full px-3.5 py-2 rounded-xl bg-surface-container border border-outline-variant/30 text-on-surface text-sm focus:outline-none focus:ring-2 focus:ring-primary-container font-sans"
placeholder="e.g. Skyline Dawn Patrol"
/>
</div>
{/* Region & Rally Point */}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
<div>
<label htmlFor="edit-region" className="block text-xs font-semibold text-on-surface mb-1">
Region / City *
</label>
<input
id="edit-region"
type="text"
required
value={formData.region}
onChange={(e) => setFormData({ ...formData, region: e.target.value })}
className="w-full px-3.5 py-2 rounded-xl bg-surface-container border border-outline-variant/30 text-on-surface text-sm focus:outline-none focus:ring-2 focus:ring-primary-container"
placeholder="e.g. San Francisco, CA"
/>
</div>
<div>
<label htmlFor="edit-meetingPoint" className="block text-xs font-semibold text-on-surface mb-1">
Rally / Meeting Point
</label>
<input
id="edit-meetingPoint"
type="text"
value={formData.meetingPoint}
onChange={(e) => setFormData({ ...formData, meetingPoint: e.target.value })}
className="w-full px-3.5 py-2 rounded-xl bg-surface-container border border-outline-variant/30 text-on-surface text-sm focus:outline-none focus:ring-2 focus:ring-primary-container"
placeholder="e.g. Alice's Restaurant"
/>
</div>
</div>
{/* Date, Time, Distance */}
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3">
<div>
<label htmlFor="edit-date" className="block text-xs font-semibold text-on-surface mb-1">
Date / Day
</label>
<input
id="edit-date"
type="text"
value={formData.scheduledDate}
onChange={(e) => setFormData({ ...formData, scheduledDate: e.target.value })}
className="w-full px-3.5 py-2 rounded-xl bg-surface-container border border-outline-variant/30 text-on-surface text-sm focus:outline-none focus:ring-2 focus:ring-primary-container"
placeholder="e.g. Saturday"
/>
</div>
<div>
<label htmlFor="edit-time" className="block text-xs font-semibold text-on-surface mb-1">
Time
</label>
<input
id="edit-time"
type="text"
value={formData.scheduledTime}
onChange={(e) => setFormData({ ...formData, scheduledTime: e.target.value })}
className="w-full px-3.5 py-2 rounded-xl bg-surface-container border border-outline-variant/30 text-on-surface text-sm focus:outline-none focus:ring-2 focus:ring-primary-container"
placeholder="e.g. 06:30 AM PST"
/>
</div>
<div>
<label htmlFor="edit-distance" className="block text-xs font-semibold text-on-surface mb-1">
Distance / Route
</label>
<input
id="edit-distance"
type="text"
value={formData.distance}
onChange={(e) => setFormData({ ...formData, distance: e.target.value })}
className="w-full px-3.5 py-2 rounded-xl bg-surface-container border border-outline-variant/30 text-on-surface text-sm focus:outline-none focus:ring-2 focus:ring-primary-container"
placeholder="e.g. 74 Miles"
/>
</div>
</div>
{/* Pace Classification, Status, Rider Limit */}
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3">
<div>
<label htmlFor="edit-pace" className="block text-xs font-semibold text-on-surface mb-1">
Pace Classification
</label>
<select
id="edit-pace"
value={formData.paceClassification}
onChange={(e) =>
setFormData({
...formData,
paceClassification: e.target.value as GroupRideSummary['paceClassification'],
})
}
className="w-full px-3.5 py-2 rounded-xl bg-surface-container border border-outline-variant/30 text-on-surface text-sm focus:outline-none focus:ring-2 focus:ring-primary-container"
>
<option value="Track Pace">Track Pace (Advanced)</option>
<option value="Brisk Pace">Brisk Pace (Spirited)</option>
<option value="Endurance / Chill">Endurance / Chill</option>
<option value="Weekend Tours">Weekend Tours</option>
</select>
</div>
<div>
<label htmlFor="edit-status" className="block text-xs font-semibold text-on-surface mb-1">
Ride Lifecycle Status
</label>
<select
id="edit-status"
value={formData.status}
onChange={(e) =>
setFormData({
...formData,
status: e.target.value as EditRideFormData['status'],
})
}
className="w-full px-3.5 py-2 rounded-xl bg-surface-container border border-outline-variant/30 text-on-surface text-sm focus:outline-none focus:ring-2 focus:ring-primary-container"
>
<option value="scheduled">Scheduled</option>
<option value="assembling">Assembling at Rally Point</option>
<option value="underway">Underway (Live Radar Active)</option>
<option value="completed">Completed</option>
<option value="canceled">Canceled</option>
</select>
</div>
<div>
<label htmlFor="edit-limit" className="block text-xs font-semibold text-on-surface mb-1">
Max Pack Riders
</label>
<input
id="edit-limit"
type="number"
min="2"
max="50"
value={formData.participantLimit}
onChange={(e) =>
setFormData({ ...formData, participantLimit: parseInt(e.target.value) || 10 })
}
className="w-full px-3.5 py-2 rounded-xl bg-surface-container border border-outline-variant/30 text-on-surface text-sm focus:outline-none focus:ring-2 focus:ring-primary-container font-mono"
/>
</div>
</div>
{/* Description */}
<div>
<label htmlFor="edit-description" className="block text-xs font-semibold text-on-surface mb-1">
Ride Briefing &amp; Safety Notes
</label>
<textarea
id="edit-description"
rows={3}
value={formData.description}
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
className="w-full px-3.5 py-2 rounded-xl bg-surface-container border border-outline-variant/30 text-on-surface text-sm focus:outline-none focus:ring-2 focus:ring-primary-container"
placeholder="Outline corner entry rules, checkpoint stops, and radar frequency..."
/>
</div>
{/* Action Buttons */}
<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 border border-outline-variant/30 text-xs font-semibold text-on-surface transition cursor-pointer"
>
Cancel
</button>
<button
type="submit"
className="px-5 py-2 rounded-xl bg-gradient-to-r from-primary-container to-primary-fixed text-on-primary-container text-xs font-bold hover:shadow-[0_0_20px_rgba(0,242,254,0.4)] transition cursor-pointer flex items-center gap-1.5"
>
<span className="material-symbols-outlined text-[16px]">save</span>
<span>Save Changes</span>
</button>
</div>
</form>
</div>
</div>
);
}