Add Ratings Functionality
This commit is contained in:
parent
3f354d2271
commit
ab1aa64695
@ -11,7 +11,7 @@ export async function GET() {
|
||||
},
|
||||
distinct: ['cid'],
|
||||
})
|
||||
console.log(controllers)
|
||||
// console.log(controllers)
|
||||
return NextResponse.json(controllers)
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch controller data:', error)
|
||||
|
@ -11,6 +11,22 @@ const facilityTypes = {
|
||||
6: "CTR",
|
||||
} as const;
|
||||
|
||||
const ratings = {
|
||||
"-1": "INA",
|
||||
"0": "SUS",
|
||||
"1": "OBS",
|
||||
"2": "S1",
|
||||
"3": "S2",
|
||||
"4": "S3",
|
||||
"5": "C1",
|
||||
"6": "C2",
|
||||
"7": "C3",
|
||||
"8": "I1",
|
||||
"9": "I2",
|
||||
"10": "I3",
|
||||
"11": "SUP",
|
||||
"12": "ADM",
|
||||
}
|
||||
const CZQM_AIRPORTS = ["CYHZ", "CYFC", "CYQM", "CYSJ", "CYZX", "CYYG", "CYYT", "CYQX", "CYYR", "LFVP", "CYQI", "CYAY", "CYDF", "CYJT"];
|
||||
export const dynamic = 'force-dynamic'
|
||||
export async function GET() {
|
||||
@ -22,7 +38,7 @@ export async function GET() {
|
||||
// Filter controllers in CZQM airspace
|
||||
const czqmControllers = data.controllers.filter((controller: any) => {
|
||||
const callsign = controller.callsign;
|
||||
return callsign.startsWith('CZQM_') ||
|
||||
return callsign.startsWith('CZQM_') || callsign.startsWith('CZQX_') ||
|
||||
CZQM_AIRPORTS.some(airport => callsign.startsWith(airport));
|
||||
});
|
||||
|
||||
@ -41,6 +57,7 @@ export async function GET() {
|
||||
callsign: controller.callsign,
|
||||
facilityType: facilityType,
|
||||
frequency: controller.frequency,
|
||||
rating: ratings[controller.rating],
|
||||
airport: airport,
|
||||
lastSeen: new Date(),
|
||||
logonTime: new Date(controller.logon_time),
|
||||
|
@ -9,8 +9,23 @@ import {
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { formatDistanceToNow, format } from "date-fns";
|
||||
import { Clock, Calendar, Radio } from "lucide-react";
|
||||
|
||||
import { Clock, Calendar, Radio, Star } from "lucide-react";
|
||||
const ratings = {
|
||||
"-1": "INA",
|
||||
"0": "SUS",
|
||||
"1": "OBS",
|
||||
"2": "S1",
|
||||
"3": "S2",
|
||||
"4": "S3",
|
||||
"5": "C1",
|
||||
"6": "C2",
|
||||
"7": "C3",
|
||||
"8": "I1",
|
||||
"9": "I2",
|
||||
"10": "I3",
|
||||
"11": "SUP",
|
||||
"12": "ADM",
|
||||
}
|
||||
interface ControllerProfileProps {
|
||||
sessions: any[];
|
||||
loading: boolean;
|
||||
@ -34,41 +49,48 @@ export function ControllerProfile({ sessions, loading }: ControllerProfileProps)
|
||||
|
||||
return (
|
||||
<div className="space-y-6 p-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<Card className="p-6">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<Clock className="h-5 w-5 text-blue-500" />
|
||||
<h3 className="text-lg font-semibold">Total Time</h3>
|
||||
</div>
|
||||
<p className="text-3xl font-bold">
|
||||
{totalHours}h {totalMinutes}m
|
||||
</p>
|
||||
</Card>
|
||||
|
||||
<Card className="p-6">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<Calendar className="h-5 w-5 text-blue-500" />
|
||||
<h3 className="text-lg font-semibold">Total Sessions</h3>
|
||||
</div>
|
||||
<p className="text-3xl font-bold">{sessions.length}</p>
|
||||
</Card>
|
||||
|
||||
<Card className="p-6">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<Radio className="h-5 w-5 text-blue-500" />
|
||||
<h3 className="text-lg font-semibold">Positions</h3>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{positions.map(position => (
|
||||
<span
|
||||
key={position}
|
||||
className="inline-flex items-center rounded-full bg-blue-100 px-2.5 py-0.5 text-xs font-medium text-blue-800 dark:bg-blue-900 dark:text-blue-200"
|
||||
>
|
||||
{position}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
|
||||
{[
|
||||
{
|
||||
icon: <Clock className="h-5 w-5 text-blue-500" />,
|
||||
title: "Total Time",
|
||||
value: `${totalHours}h ${totalMinutes}m`,
|
||||
},
|
||||
{
|
||||
icon: <Calendar className="h-5 w-5 text-blue-500" />,
|
||||
title: "Total Sessions",
|
||||
value: sessions.length,
|
||||
},
|
||||
{
|
||||
icon: <Radio className="h-5 w-5 text-blue-500" />,
|
||||
title: "Positions",
|
||||
value: (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{positions.map(position => (
|
||||
<span
|
||||
key={position}
|
||||
className="inline-flex items-center rounded-full bg-blue-100 px-2.5 py-0.5 text-xs font-medium text-blue-800 dark:bg-blue-900 dark:text-blue-200"
|
||||
>
|
||||
{position}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
icon: <Star className="h-5 w-5 text-blue-500" aria-hidden="true" />,
|
||||
title: "Highest Rating",
|
||||
value: sessions[0].rating || "N/A",
|
||||
},
|
||||
].map(({ icon, title, value }, i) => (
|
||||
<Card key={i} className="p-6 h-full flex flex-col justify-between">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
{icon}
|
||||
<h3 className="text-lg font-semibold">{title}</h3>
|
||||
</div>
|
||||
<p className="text-3xl font-bold">{value}</p>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="mt-8">
|
||||
@ -82,6 +104,7 @@ export function ControllerProfile({ sessions, loading }: ControllerProfileProps)
|
||||
<TableHead>Airport</TableHead>
|
||||
<TableHead>Frequency</TableHead>
|
||||
<TableHead>Duration</TableHead>
|
||||
<TableHead>Rating</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
@ -97,6 +120,7 @@ export function ControllerProfile({ sessions, loading }: ControllerProfileProps)
|
||||
<TableCell>
|
||||
{formatSessionDuration(session.logonTime, session.lastSeen)}
|
||||
</TableCell>
|
||||
<TableCell>{session.rating}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
@ -116,7 +140,8 @@ function formatSessionDuration(start: string, end: string) {
|
||||
function LoadingSkeleton() {
|
||||
return (
|
||||
<div className="space-y-6 p-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
|
||||
<Skeleton className="h-[140px] rounded-lg" />
|
||||
<Skeleton className="h-[140px] rounded-lg" />
|
||||
<Skeleton className="h-[140px] rounded-lg" />
|
||||
<Skeleton className="h-[140px] rounded-lg" />
|
||||
|
@ -36,6 +36,7 @@ export function ControllerTable({ data, loading }: ControllerTableProps) {
|
||||
<TableHead>Current Position</TableHead>
|
||||
<TableHead>Airport</TableHead>
|
||||
<TableHead>Frequency</TableHead>
|
||||
<TableHead>Rating</TableHead>
|
||||
<TableHead>Last Seen</TableHead>
|
||||
<TableHead>Session Duration</TableHead>
|
||||
</TableRow>
|
||||
@ -52,6 +53,7 @@ export function ControllerTable({ data, loading }: ControllerTableProps) {
|
||||
<TableCell>{controller.callsign}</TableCell>
|
||||
<TableCell>{controller.airport}</TableCell>
|
||||
<TableCell>{controller.frequency}</TableCell>
|
||||
<TableCell>{controller.rating}</TableCell>
|
||||
<TableCell>
|
||||
{formatDistanceToNow(new Date(controller.lastSeen), { addSuffix: true })}
|
||||
</TableCell>
|
||||
@ -82,6 +84,7 @@ function LoadingSkeleton() {
|
||||
<Skeleton className="h-4 w-[120px]" />
|
||||
<Skeleton className="h-4 w-[80px]" />
|
||||
<Skeleton className="h-4 w-[100px]" />
|
||||
<Skeleton className="h-4 w-[80px]" />
|
||||
<Skeleton className="h-4 w-[120px]" />
|
||||
<Skeleton className="h-4 w-[100px]" />
|
||||
</div>
|
||||
|
22
prisma/migrations/20241129063208_/migration.sql
Normal file
22
prisma/migrations/20241129063208_/migration.sql
Normal file
@ -0,0 +1,22 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "controller_sessions" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"cid" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"callsign" TEXT NOT NULL,
|
||||
"facilityType" TEXT NOT NULL,
|
||||
"frequency" TEXT NOT NULL,
|
||||
"airport" TEXT NOT NULL,
|
||||
"rating" TEXT NOT NULL,
|
||||
"last_seen" TIMESTAMP(3) NOT NULL,
|
||||
"logon_time" TIMESTAMP(3) NOT NULL,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "controller_sessions_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "controller_sessions_cid_idx" ON "controller_sessions"("cid");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "controller_sessions_last_seen_idx" ON "controller_sessions"("last_seen");
|
2
prisma/migrations/20241129063418_migrate/migration.sql
Normal file
2
prisma/migrations/20241129063418_migrate/migration.sql
Normal file
@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "controller_sessions" ALTER COLUMN "rating" DROP NOT NULL;
|
3
prisma/migrations/migration_lock.toml
Normal file
3
prisma/migrations/migration_lock.toml
Normal file
@ -0,0 +1,3 @@
|
||||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (i.e. Git)
|
||||
provider = "postgresql"
|
@ -15,6 +15,7 @@ model ControllerSession {
|
||||
facilityType String
|
||||
frequency String
|
||||
airport String
|
||||
rating String?
|
||||
lastSeen DateTime @map("last_seen")
|
||||
logonTime DateTime @map("logon_time")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
Loading…
x
Reference in New Issue
Block a user