Compare commits
3 commits
dcc308c2ae
...
bf5c53249a
| Author | SHA1 | Date | |
|---|---|---|---|
| bf5c53249a | |||
| b64f4b26f4 | |||
| de3db4bd09 |
4 changed files with 42 additions and 28 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "polaris-task-force",
|
"name": "polaris-task-force",
|
||||||
"version": "0.2.8",
|
"version": "0.2.9",
|
||||||
"description": "Web app for the Arma 3 unit Polaris Task Force. Uses Next.js for client- and server-side rendering, with a Postgres database and some other neat libraries.",
|
"description": "Web app for the Arma 3 unit Polaris Task Force. Uses Next.js for client- and server-side rendering, with a Postgres database and some other neat libraries.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|
|
||||||
|
|
@ -241,9 +241,19 @@ export default async function MissionDetailPage({ params }: MissionDetailPagePro
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const attendanceCard = (
|
||||||
|
<MissionAttendance
|
||||||
|
missionId={mission.id}
|
||||||
|
currentUserId={currentUser ? (currentUser.id as number) : null}
|
||||||
|
currentResponse={currentResponse}
|
||||||
|
attendanceClosed={attendanceClosed}
|
||||||
|
{...attendanceProps}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<header className="relative min-h-[500px] w-full overflow-hidden bg-background">
|
<header className="relative flex min-h-[500px] w-full flex-col overflow-hidden bg-background">
|
||||||
<div className="absolute top-0 left-0 px-5 pt-5 pb-3 z-10">
|
<div className="absolute top-0 left-0 px-5 pt-5 pb-3 z-10">
|
||||||
<Link
|
<Link
|
||||||
href="/intelligence/missions"
|
href="/intelligence/missions"
|
||||||
|
|
@ -266,32 +276,31 @@ export default async function MissionDetailPage({ params }: MissionDetailPagePro
|
||||||
<div className="absolute inset-0 bg-gradient-to-b from-black/50 via-black/40 to-background" />
|
<div className="absolute inset-0 bg-gradient-to-b from-black/50 via-black/40 to-background" />
|
||||||
<div className="absolute inset-0 bg-gradient-to-r from-black/30 via-transparent to-black/40" />
|
<div className="absolute inset-0 bg-gradient-to-r from-black/30 via-transparent to-black/40" />
|
||||||
|
|
||||||
<div className="absolute top-4 right-4 z-20 flex gap-2">
|
<div className="relative z-10 mt-auto flex flex-col px-5 pb-6 pt-14 md:absolute md:inset-0 md:justify-end md:px-10 md:py-10 lg:px-12">
|
||||||
<span
|
|
||||||
className={`rounded-md border border-white/10 px-2 py-1 text-[10px] font-semibold uppercase tracking-wider backdrop-blur-sm ${statusColors[mission.ownershipAndStatus.status] ?? "bg-muted text-muted-foreground"}`}
|
|
||||||
>
|
|
||||||
{mission.ownershipAndStatus.status}
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
className={`rounded-md border border-white/10 px-2 py-1 text-[10px] font-semibold uppercase tracking-wider backdrop-blur-sm ${operationTypeColors[mission.operationType] ?? "bg-muted text-muted-foreground"}`}
|
|
||||||
>
|
|
||||||
{operationTypeLabels[mission.operationType] ?? mission.operationType}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="absolute inset-0 flex flex-col justify-end p-6 md:p-10 lg:p-12">
|
|
||||||
<div className="mx-auto flex w-full max-w-7xl flex-col">
|
<div className="mx-auto flex w-full max-w-7xl flex-col">
|
||||||
|
<div className="z-20 mb-4 flex flex-wrap gap-2 md:absolute md:top-4 md:right-4 md:mb-0">
|
||||||
|
<span
|
||||||
|
className={`rounded-md border border-white/10 px-2 py-1 text-[10px] font-semibold uppercase tracking-wider backdrop-blur-sm ${statusColors[mission.ownershipAndStatus.status] ?? "bg-muted text-muted-foreground"}`}
|
||||||
|
>
|
||||||
|
{mission.ownershipAndStatus.status}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className={`rounded-md border border-white/10 px-2 py-1 text-[10px] font-semibold uppercase tracking-wider backdrop-blur-sm ${operationTypeColors[mission.operationType] ?? "bg-muted text-muted-foreground"}`}
|
||||||
|
>
|
||||||
|
{operationTypeLabels[mission.operationType] ?? mission.operationType}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<div className="flex items-center gap-2 font-mono text-sm uppercase tracking-widest text-white/60">
|
<div className="flex items-center gap-2 font-mono text-sm uppercase tracking-widest text-white/60">
|
||||||
<CrosshairIcon className="size-4" />
|
<CrosshairIcon className="size-4" />
|
||||||
<span>{mission.codeName}</span>
|
<span>{mission.codeName}</span>
|
||||||
</div>
|
</div>
|
||||||
<h1
|
<h1
|
||||||
id="mission-title"
|
id="mission-title"
|
||||||
className="mt-3 text-5xl font-bold tracking-tight text-white drop-shadow-2xl md:text-6xl"
|
className="mt-3 text-4xl font-bold tracking-tight text-white drop-shadow-2xl md:text-6xl"
|
||||||
>
|
>
|
||||||
{mission.name}
|
{mission.name}
|
||||||
</h1>
|
</h1>
|
||||||
<p className="mt-2 max-w-3xl text-lg leading-relaxed text-white/80">
|
<p className="mt-2 max-w-3xl text-base leading-relaxed text-white/80 md:text-lg">
|
||||||
{mission.summary}
|
{mission.summary}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
@ -367,6 +376,8 @@ export default async function MissionDetailPage({ params }: MissionDetailPagePro
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div className="p-5 flex flex-col gap-6 max-w-7xl mx-auto w-full">
|
<div className="p-5 flex flex-col gap-6 max-w-7xl mx-auto w-full">
|
||||||
|
{/* Attendance sits right under the hero on stacked (below-xl) layouts */}
|
||||||
|
<div className="xl:hidden">{attendanceCard}</div>
|
||||||
<div className="grid grid-cols-1 xl:grid-cols-3 gap-6">
|
<div className="grid grid-cols-1 xl:grid-cols-3 gap-6">
|
||||||
{/* ── Main Content (2/3) ──────────────────────────── */}
|
{/* ── Main Content (2/3) ──────────────────────────── */}
|
||||||
<div className="xl:col-span-2 flex flex-col gap-6">
|
<div className="xl:col-span-2 flex flex-col gap-6">
|
||||||
|
|
@ -472,13 +483,7 @@ export default async function MissionDetailPage({ params }: MissionDetailPagePro
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<MissionAttendance
|
<div className="hidden xl:block">{attendanceCard}</div>
|
||||||
missionId={mission.id}
|
|
||||||
currentUserId={currentUser ? (currentUser.id as number) : null}
|
|
||||||
currentResponse={currentResponse}
|
|
||||||
attendanceClosed={attendanceClosed}
|
|
||||||
{...attendanceProps}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Campaign Card */}
|
{/* Campaign Card */}
|
||||||
{campaign && (
|
{campaign && (
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,10 @@ export default async function FridayCalendarPage() {
|
||||||
.map((m) => {
|
.map((m) => {
|
||||||
const map = m.classification.map;
|
const map = m.classification.map;
|
||||||
const createdBy = m.createdBy;
|
const createdBy = m.createdBy;
|
||||||
|
const creator =
|
||||||
|
typeof createdBy === "object" && createdBy !== null
|
||||||
|
? (createdBy as { displayName?: string | null; username?: string | null })
|
||||||
|
: null;
|
||||||
return {
|
return {
|
||||||
id: m.id,
|
id: m.id,
|
||||||
name: m.name,
|
name: m.name,
|
||||||
|
|
@ -86,6 +90,10 @@ export default async function FridayCalendarPage() {
|
||||||
startDateTime: m.classification.startDateTime,
|
startDateTime: m.classification.startDateTime,
|
||||||
operationType: m.operationType,
|
operationType: m.operationType,
|
||||||
mapName: typeof map === "object" && map !== null ? map.name : null,
|
mapName: typeof map === "object" && map !== null ? map.name : null,
|
||||||
|
hostName:
|
||||||
|
creator != null
|
||||||
|
? (creator.displayName ?? creator.username ?? null)
|
||||||
|
: null,
|
||||||
isOwn:
|
isOwn:
|
||||||
user != null &&
|
user != null &&
|
||||||
(typeof createdBy === "object" ? createdBy?.id : createdBy) === user.id,
|
(typeof createdBy === "object" ? createdBy?.id : createdBy) === user.id,
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ export interface FridayCalendarMission {
|
||||||
startDateTime: string;
|
startDateTime: string;
|
||||||
operationType: string;
|
operationType: string;
|
||||||
mapName: string | null;
|
mapName: string | null;
|
||||||
|
hostName: string | null;
|
||||||
isOwn: boolean;
|
isOwn: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -122,7 +123,7 @@ export function FridayCalendar({
|
||||||
{fridayTaken ? "Taken" : "Schedule"}
|
{fridayTaken ? "Taken" : "Schedule"}
|
||||||
</Button>
|
</Button>
|
||||||
</ItemHeader>
|
</ItemHeader>
|
||||||
<ItemContent>
|
<ItemContent className="min-w-0">
|
||||||
{entry.missions.length === 0 ? (
|
{entry.missions.length === 0 ? (
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
Nothing scheduled yet. Claim this Friday.
|
Nothing scheduled yet. Claim this Friday.
|
||||||
|
|
@ -133,7 +134,7 @@ export function FridayCalendar({
|
||||||
<Link
|
<Link
|
||||||
key={m.id}
|
key={m.id}
|
||||||
href={`/intelligence/missions/${m.id}`}
|
href={`/intelligence/missions/${m.id}`}
|
||||||
className="group flex items-center gap-3 rounded-md p-1.5 transition-colors hover:bg-muted/50"
|
className="group flex min-w-0 flex-1 items-center gap-3 rounded-md p-1.5 transition-colors hover:bg-muted/50"
|
||||||
>
|
>
|
||||||
<span className="w-14 shrink-0 font-mono text-xs text-muted-foreground">
|
<span className="w-14 shrink-0 font-mono text-xs text-muted-foreground">
|
||||||
{formatTime(m.startDateTime)}
|
{formatTime(m.startDateTime)}
|
||||||
|
|
@ -145,7 +146,7 @@ export function FridayCalendar({
|
||||||
<span className="truncate font-mono text-[10px] text-muted-foreground">
|
<span className="truncate font-mono text-[10px] text-muted-foreground">
|
||||||
{m.codeName}
|
{m.codeName}
|
||||||
{m.mapName ? ` · ${m.mapName}` : ""}
|
{m.mapName ? ` · ${m.mapName}` : ""}
|
||||||
{m.isOwn ? " · yours" : ""}
|
{m.isOwn ? " · yours" : m.hostName ? ` · hosted by ${m.hostName}` : ""}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue