"use client"; import { useMemo, useState } from "react"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { CalendarDaysIcon, ChevronRightIcon, PlusIcon } from "lucide-react"; import { Item, ItemContent, ItemHeader, ItemTitle } from "@/components/ui/item"; import { Button } from "@/components/ui/button"; import { CreateSideOpDialog, type SideOpDialogOption } from "./CreateSideOpDialog"; export interface FridayCalendarMission { id: number; name: string; codeName: string; status: string; startDateTime: string; operationType: string; mapName: string | null; isOwn: boolean; } export interface FridayCalendarEntry { fridayIso: string; missions: FridayCalendarMission[]; } interface FridayCalendarProps { entries: FridayCalendarEntry[]; remainingSlots: number; maxSlots: number; maps: SideOpDialogOption[]; campaigns: SideOpDialogOption[]; modLists: SideOpDialogOption[]; } const statusColors: Record = { Concept: "bg-muted text-muted-foreground", Planning: "bg-muted text-muted-foreground", Ready: "bg-blue-500/20 text-blue-400", Scheduled: "bg-amber-500/20 text-amber-400", Active: "bg-green-500/20 text-green-400", Completed: "bg-emerald-500/20 text-emerald-400", Cancelled: "bg-red-500/20 text-red-400", }; function formatFriday(iso: string): string { return new Date(iso).toLocaleDateString(undefined, { weekday: "long", month: "long", day: "numeric", }); } function formatTime(iso: string): string { return new Date(iso).toLocaleTimeString(undefined, { hour: "2-digit", minute: "2-digit", }); } function defaultStartForFriday(fridayIso: string): string { const date = new Date(fridayIso); date.setHours(20, 0, 0, 0); return date.toISOString(); } export function FridayCalendar({ entries, remainingSlots, maxSlots, maps, campaigns, modLists, }: FridayCalendarProps) { const router = useRouter(); const [scheduleFor, setScheduleFor] = useState(null); const upcoming = useMemo(() => entries, [entries]); return (

Friday Operations

Schedule and manage player-run Friday side operations. Each Friday gets Discord roll-call and RSVP automatically.

0 ? "border-emerald-500/30 bg-emerald-500/10 text-emerald-400" : "border-red-500/30 bg-red-500/10 text-red-400" }`} > {remainingSlots > 0 ? `You can schedule ${remainingSlots} more mission${remainingSlots === 1 ? "" : "s"}` : `Scheduling limit reached (${maxSlots} ahead)`}
{upcoming.map((entry) => { const fridayTaken = entry.missions.some( (m) => m.status !== "Completed" && m.status !== "Cancelled", ); const canSchedule = remainingSlots > 0 && !fridayTaken; return (
{formatFriday(entry.fridayIso)}
{entry.missions.length === 0 ? (

Nothing scheduled yet — claim this Friday.

) : (
{entry.missions.map((m) => ( {formatTime(m.startDateTime)}
{m.name} {m.codeName} {m.mapName ? ` · ${m.mapName}` : ""} {m.isOwn ? " · yours" : ""}
{m.status} ))}
)}
); })}

Fridays beyond this date are unavailable for scheduling via the web app.

If you want to schedule an op further out than the dates listed here, please contact Jason (Z8MB1E) directly.

{ if (!open) setScheduleFor(null); }} maps={maps} campaigns={campaigns} modLists={modLists} defaultStartDateTime={scheduleFor ? defaultStartForFriday(scheduleFor) : undefined} onSaved={() => router.refresh()} />
); }