314 lines
13 KiB
TypeScript
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 & 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>
|
|
);
|
|
}
|