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;
Preview:
downloadDownload PNG
downloadDownload JPEG
downloadDownload SVG
Tip: You can change the style, width & colours of the snippet with the inspect tool before clicking Download!
Click to optimize width for Twitter