import type { NextPage } from "next"; import { CommandInterface, EmailUtitlities, EmailViewer, ScoreCargoResults, ScoreClassificationResults, ScoreLineupResults, ScorePositionResults, UserAutocomplete, } from "@/components"; import { Content, DataTable, DialogButton, DisclosureButton, FormCheckboxField, FormDateTimeRangeField, HeadingLink, If, IfElse, MinimalPage, PageHeading, Subheading, ToolTip, } from "ui"; import { useState } from "react"; import type { Json, User } from "@/types"; import type { DateValueType } from "react-tailwindcss-datepicker/dist/types"; import { useGetEmail, useGetEmailStatus, useUserHistory } from "@/hooks"; import type { ColumnDef } from "@tanstack/react-table"; import dayjs from "dayjs"; interface historyDetails { email?: string; cargo?: string; position?: string; classification?: string; broker?: string; source?: string; duration?: string; } const UsersScores: NextPage = () => { const [user, setUser] = useState<User>({ id: null, email: null }); const [timeRange, setTimeRange] = useState<DateValueType>({ startDate: dayjs(new Date()).startOf("day").toDate(), endDate: dayjs(new Date()).endOf("day").toDate(), }); const [event, setEvent] = useState<{ created_at: string | null; action: string; detail: historyDetails; }>({ created_at: "", action: "", detail: {}, }); const [isOpen, setIsOpen] = useState<boolean>(false); const [filters, setFilters] = useState({ read: true, classified: true, positions: true, cargos: true, lineups: true, pause: true, addVessel: true, editVessel: true, addPort: true, shiftStatus: true, breakStatus: true, }); const { data: history } = useUserHistory(user, timeRange); const { data: email } = useGetEmail(event.detail?.email ?? "", false); const { data: emailStatus } = useGetEmailStatus( event.detail?.email ?? "", false, ); const emptyResults = { read: 0, classified: 0, positions: 0, cargos: 0, lineups: 0, pause: 0, addVessel: 0, editVessel: 0, addPort: 0, shiftStatus: 0, breakStatus: 0, }; const results = history?.reduce((count, event) => { switch (event.action) { case "READ": return { ...count, read: count.read + 1 }; case "CLASSIFICATION": return { ...count, classified: count.classified + 1 }; case "CARGO": return { ...count, cargos: count.cargos + 1 }; case "LINE-UP": return { ...count, lineups: count.lineups + 1 }; case "POSITION": return { ...count, positions: count.positions + 1 }; case "PAUSE": return { ...count, pause: count.pause + 1 }; case "ADD_VESSEL": return { ...count, addVessel: count.addVessel + 1 }; case "EDIT_VESSEL": return { ...count, editVessel: count.editVessel + 1 }; case "ADD_PORT": return { ...count, addPort: count.addPort + 1 }; case "SHIFT START": case "SHIFT END": return { ...count, shiftStatus: count.shiftStatus + 1 }; case "BREAK START": case "BREAK END": return { ...count, breakStatus: count.breakStatus + 1 }; default: return count; } }, emptyResults) ?? emptyResults; const filteredHistory = history?.filter((event) => { switch (event.action) { case "READ": return filters.read; case "CLASSIFICATION": return filters.classified; case "CARGO": return filters.cargos; case "LINE-UP": return filters.lineups; case "POSITION": return filters.positions; case "PAUSE": return filters.pause; case "ADD_VESSEL": return filters.addVessel; case "EDIT_VESSEL": return filters.editVessel; case "ADD_PORT": return filters.addPort; case "SHIFT START": case "SHIFT END": return filters.shiftStatus; case "BREAK START": case "BREAK END": return filters.breakStatus; default: return true; } }) ?? []; const columns: ColumnDef<{ created_at: string | null; action: string; detail: Json; }>[] = [ { accessorKey: "created_at", header: "Time", cell: ({ row }) => { const request = row.original; return ( <ToolTip text={request.created_at ?? ""}> <div> <Content text={ request.created_at ? new Date(request.created_at).toLocaleString("en-GB", { year: "numeric", month: "short", day: "numeric", hour: "2-digit", minute: "2-digit", second: "2-digit", }) : "" } /> </div> </ToolTip> ); }, }, { accessorKey: "action", header: "Action", }, { accessorKey: "detail", header: "Details", cell: ({ row }) => { const request = row.original; if (request.detail === null) return; const details = request.detail as historyDetails; return ( <div> <If condition={ request.action !== "SHIFT END" && request.action !== "BREAK END" } > <Content text={`Email ID: ${details.email ?? ""}`} /> </If> <If condition={details.broker !== undefined}> <Content text={`Broker: ${details.broker ?? ""}`} /> </If> <If condition={details.source !== undefined}> <Content text={`Source: ${details.source ?? ""}`} /> </If> <If condition={details.classification !== undefined}> <Content text={`Classification: ${details.classification ?? ""}`} /> </If> <If condition={details.position !== undefined}> <Content text={`Position Report ID: ${details.position ?? ""}`} /> </If> <If condition={details.cargo !== undefined}> <Content text={`Cargo Report ID: ${details.cargo ?? ""}`} /> </If> <If condition={ request.action === "SHIFT END" && details.duration !== undefined } > <Content text={`Shift Duration: ${details.duration}`} /> </If> <If condition={ request.action === "BREAK END" && details.duration !== undefined } > <Content text={`Break Duration: ${details.duration}`} /> </If> </div> ); }, }, ]; const ModalContent = () => { const details = event.detail as historyDetails; return ( <div className="grid grid-cols-2 justify-items-center gap-4"> <div className="w-full"> <div className="pb-4"> <Subheading text={`User Event: ${event.action}`} /> <DisclosureButton buttonText="Email Status" colour="Primary"> <Content text={`Classifications: ${emailStatus?.classification}`} /> <Content text={`Priority: ${emailStatus?.priority}`} /> <IfElse condition={emailStatus?.complete ?? false} elseChildren={<Content text="Completed: No" />} > <Content text={`Completed: ${ emailStatus?.completed_by ? emailStatus.completed_by : "UNKNOWN" } at ${emailStatus?.completed_at}`} /> </IfElse> <If condition={ emailStatus?.classification ?.split(",") .includes("POSITION") ?? false } > <Content text={`Position Reports: ${emailStatus?.position_reports.length}`} /> </If> <If condition={ emailStatus?.classification?.split(",").includes("CARGO") ?? false } > <Content text={`Cargo Reports: ${emailStatus?.cargo_reports.length}`} /> </If> </DisclosureButton> </div> <EmailViewer email={email} historyNeedsUpdating={false} allowNextEmail={false} /> </div> <If condition={event.action === "CLASSIFICATION"}> <ScoreClassificationResults classification={event.detail.classification} emailId={details.email} user={user.id ?? undefined} /> </If> <If condition={event.action === "POSITION"}> <div className="flex w-4/5 flex-col items-center"> <Subheading text="Score Position Reports" /> <div className="flex w-4/5 flex-col gap-4"> {emailStatus?.position_reports.map((report) => { return ( <DisclosureButton key={report.id} buttonText={`Report: ${report.id}`} colour={ `${report.id}` == details.position ? "Success" : "Accent" } > <ScorePositionResults report={report} emailId={details.email} /> </DisclosureButton> ); })} </div> </div> </If> <If condition={event.action === "CARGO"}> <div className="flex w-4/5 flex-col items-center"> <div className="flex w-4/5 flex-col gap-4"> {emailStatus?.cargo_reports.map((report) => { return ( <DisclosureButton key={report.id} buttonText={`Report: ${report.id}`} colour={ `${report.id}` == details.position ? "Success" : "Accent" } > <ScoreCargoResults report={report} emailId={details.email} /> </DisclosureButton> ); })} </div> </div> </If> <If condition={event.action === "LINE-UP"}> <div className="flex w-4/5 flex-col items-center"> <div className="flex w-4/5 flex-col gap-4"> {emailStatus?.lineup_reports.map((report) => { return ( <DisclosureButton key={report.id} buttonText={`Report: ${report.id}`} colour={ `${report.id}` == details.position ? "Success" : "Accent" } > <ScoreLineupResults report={report} emailId={details.email} /> </DisclosureButton> ); })} </div> </div> </If> <div className="col-span-2 w-full"> <EmailUtitlities /> </div> </div> ); }; return ( <MinimalPage pageTitle={"Review Users | Email Interface"} pageDescription={"Spot Ship Email Interface | Review Users"} commandPrompt > <CommandInterface /> <div className="w-full"> <HeadingLink icon={"back"} text={"Home"} href={`/secure/home`} /> </div> <div> <PageHeading text="User Scores" /> </div> <div className="w-full p-8"> <UserAutocomplete id="user" label="Who:" labelWidth="w-52" value={user} onChange={(data) => { if (data) setUser(data); }} /> <FormDateTimeRangeField id="date" label="Time Frame" labelWidth="w-52" value={timeRange} onChange={(date: DateValueType) => { setTimeRange(date); }} warning warningDaysInPast={30} warningDaysInFuture={1} pastWarning={"should be no earlier than 30 days from now"} /> <DisclosureButton buttonText={`Results: ${history?.length}${ history?.length === 1000 ? "Max results reached, narrow time range" : "" }`} colour="Primary" > <Subheading text="Include Events" /> <div className="mb-2 grid gap-1 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 xl:grid-cols-8"> <FormCheckboxField id="filter-read" label="READ" caption={`Emails Read: ${results.read}`} action={(enabled: boolean) => { setFilters((prevState) => { return { ...prevState, read: enabled, }; }); }} defaultState={filters.read} /> <FormCheckboxField id="filter-classifications" label="CLASSIFICATION" caption={`Classifications: ${results.classified}`} action={(enabled: boolean) => { setFilters((prevState) => { return { ...prevState, classified: enabled, }; }); }} defaultState={filters.classified} /> <FormCheckboxField id="filter-positions" label="POSITION" caption={`Position Submissions: ${results.positions}`} action={(enabled: boolean) => { setFilters((prevState) => { return { ...prevState, positions: enabled, }; }); }} defaultState={filters.positions} /> <FormCheckboxField id="filter-cargos" label="CARGO" caption={`Cargo Submissions: ${results.cargos}`} action={(enabled: boolean) => { setFilters((prevState) => { return { ...prevState, cargos: enabled, }; }); }} defaultState={filters.cargos} /> <FormCheckboxField id="filter-lineups" label="LINE-UP" caption={`Line Up Submissions: ${results.lineups}`} action={(enabled: boolean) => { setFilters((prevState) => { return { ...prevState, lineups: enabled, }; }); }} defaultState={filters.lineups} /> <FormCheckboxField id="filter-pause" label="PAUSE" caption={`Pauses: ${results.pause}`} action={(enabled: boolean) => { setFilters((prevState) => { return { ...prevState, pause: enabled, }; }); }} defaultState={filters.pause} /> <FormCheckboxField id="filter-add-vessel" label="ADD_VESSEL" caption={`Added Vessels: ${results.addVessel}`} action={(enabled: boolean) => { setFilters((prevState) => { return { ...prevState, addVessel: enabled, }; }); }} defaultState={filters.addVessel} /> <FormCheckboxField id="filter-edit-vessel" label="EDIT_VESSEL" caption={`Edited Vessels: ${results.editVessel}`} action={(enabled: boolean) => { setFilters((prevState) => { return { ...prevState, editVessel: enabled, }; }); }} defaultState={filters.editVessel} /> <FormCheckboxField id="filter-add-port" label="ADD_PORT" caption={`Added Ports : ${results.addPort}`} action={(enabled: boolean) => { setFilters((prevState) => { return { ...prevState, addPort: enabled, }; }); }} defaultState={filters.addPort} /> <FormCheckboxField id="filter-shift-status" label="SHIFT STATUS" caption={`Shift Status Events: ${results.shiftStatus}`} action={(enabled: boolean) => { setFilters((prevState) => { return { ...prevState, shiftStatus: enabled, }; }); }} defaultState={filters.shiftStatus} /> <FormCheckboxField id="filter-break-status" label="BREAK STATUS" caption={`Break Status Events: ${results.breakStatus}`} action={(enabled: boolean) => { setFilters((prevState) => { return { ...prevState, breakStatus: enabled, }; }); }} defaultState={filters.breakStatus} /> </div> <div className="max-h-[600px] w-full gap-8 overflow-scroll p-8"> <DataTable action={(obj: { created_at: string | null; action: string; detail: historyDetails; }) => { setEvent(obj); if (obj.detail && obj.detail.email !== undefined) { setIsOpen(true); } }} showFilterInput filterValue="created_at" data={filteredHistory} columns={columns} /> </div> </DisclosureButton> <DialogButton action={() => setIsOpen(false)} isOpen={isOpen}> <ModalContent /> </DialogButton> </div> </MinimalPage> ); }; export default UsersScores;