This commit is contained in:
Joyce
2026-02-05 13:45:32 -05:00
parent 7a0f11ee88
commit 26e553bfd0
10 changed files with 569 additions and 57 deletions

View File

@@ -11,30 +11,88 @@ import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Textarea } from '@/components/ui/textarea';
import { Label } from '@/components/ui/label';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { toast } from '@/hooks/use-toast';
import { Calendar, Clock, Users, Send, AlertCircle } from 'lucide-react';
const DURATION_OPTIONS = [
{ value: 15, label: '15 minutes' },
{ value: 30, label: '30 minutes' },
{ value: 45, label: '45 minutes' },
{ value: 60, label: '1 hour' },
{ value: 90, label: '1 hour 30 minutes' },
{ value: 120, label: '2 hours' },
{ value: 150, label: '2 hours 30 minutes' },
];
interface ScheduleModalProps {
isOpen: boolean;
onClose: () => void;
slot: TimeSlot | null;
participants: Participant[];
displayTimezone?: string;
onSuccess?: () => void;
}
// Get user's local timezone
const getUserTimezone = (): string => {
try {
return Intl.DateTimeFormat().resolvedOptions().timeZone;
} catch {
return 'America/Toronto';
}
};
// Format timezone for display (e.g., "America/Toronto" -> "EST")
const getTimezoneAbbrev = (timezone: string): string => {
try {
const now = new Date();
const formatter = new Intl.DateTimeFormat('en-US', {
timeZone: timezone,
timeZoneName: 'short',
});
const parts = formatter.formatToParts(now);
return parts.find((p) => p.type === 'timeZoneName')?.value || '';
} catch {
return '';
}
};
export const ScheduleModal = ({
isOpen,
onClose,
slot,
participants,
displayTimezone = getUserTimezone(),
onSuccess,
}: ScheduleModalProps) => {
const [title, setTitle] = useState('');
const [notes, setNotes] = useState('');
const [duration, setDuration] = useState(60); // default 1 hour
const [isSubmitting, setIsSubmitting] = useState(false);
const formatHour = (hour: number) => {
return `${hour.toString().padStart(2, '0')}:00`;
};
const formatTime = (hour: number, minutes: number) => {
const totalMinutes = hour * 60 + minutes;
const h = Math.floor(totalMinutes / 60) % 24;
const m = totalMinutes % 60;
return `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}`;
};
const getEndTime = () => {
if (!slot) return '';
return formatTime(slot.hour, duration);
};
const formatDate = (dateStr: string) => {
const date = new Date(dateStr + 'T00:00:00');
return date.toLocaleDateString('en-US', { weekday: 'short', month: 'short', day: 'numeric' });
@@ -68,7 +126,7 @@ export const ScheduleModal = ({
// Calculate start and end times
// slot.day is YYYY-MM-DD
const startDateTime = new Date(`${slot.day}T${formatHour(slot.hour)}:00Z`);
const endDateTime = new Date(`${slot.day}T${formatHour(slot.hour + 1)}:00Z`);
const endDateTime = new Date(startDateTime.getTime() + duration * 60 * 1000);
await scheduleMeeting(
participants.map(p => p.id),
@@ -86,6 +144,7 @@ export const ScheduleModal = ({
setTitle('');
setNotes('');
onClose();
onSuccess?.();
} catch (error) {
toast({
title: "Scheduling failed",
@@ -115,7 +174,7 @@ export const ScheduleModal = ({
</div>
<div className="flex items-center gap-3 text-sm">
<Clock className="w-4 h-4 text-primary" />
<span>{formatHour(slot.hour)} {formatHour(slot.hour + 1)}</span>
<span><span className="text-primary font-medium">{formatHour(slot.hour)} {getEndTime()}</span> <span className="text-muted-foreground">({getTimezoneAbbrev(displayTimezone)})</span></span>
</div>
<div className="flex items-center gap-3 text-sm">
<Users className="w-4 h-4 text-primary" />
@@ -125,6 +184,25 @@ export const ScheduleModal = ({
{/* Form */}
<div className="space-y-4">
<div className="space-y-2">
<Label htmlFor="duration">Duration</Label>
<Select
value={duration.toString()}
onValueChange={(value) => setDuration(Number(value))}
>
<SelectTrigger className="h-12">
<SelectValue />
</SelectTrigger>
<SelectContent>
{DURATION_OPTIONS.map((option) => (
<SelectItem key={option.value} value={option.value.toString()}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="title">Meeting Title</Label>
<Input