diff --git a/src/packages/shared-types/seatool.ts b/src/packages/shared-types/seatool.ts index 0381804f56..f73929dffb 100644 --- a/src/packages/shared-types/seatool.ts +++ b/src/packages/shared-types/seatool.ts @@ -75,16 +75,16 @@ const getRaiDate = (data: SeaToolSink) => { }; }; +const zActionOfficer = z.object({ + OFFICER_ID: z.number(), + FIRST_NAME: z.string(), + LAST_NAME: z.string(), +}); +type ActionOfficer = z.infer; + export const seatoolSchema = z.object({ - LEAD_ANALYST: z - .array( - z.object({ - OFFICER_ID: z.number(), - FIRST_NAME: z.string(), - LAST_NAME: z.string(), - }) - ) - .nullable(), + ACTION_OFFICERS: z.array(zActionOfficer).nullish(), + LEAD_ANALYST: z.array(zActionOfficer).nullable(), PLAN_TYPES: z .array( z.object({ @@ -102,6 +102,9 @@ export const seatoolSchema = z.object({ PROPOSED_DATE: z.number().nullable(), SPW_STATUS_ID: z.number().nullable(), STATE_CODE: z.string().nullish(), + STATUS_DATE: z.number().nullish(), + SUMMARY_MEMO: z.string().nullish(), + TITLE_NAME: z.string().nullish(), }), SPW_STATUS: z .array( @@ -131,11 +134,25 @@ export const seatoolSchema = z.object({ .nullable(), }); -const getDateStringOrNullFromEpoc = (epocDate: number | null) => { - if (epocDate !== null) { - return new Date(epocDate).toISOString(); - } - return null; +const getDateStringOrNullFromEpoc = (epocDate: number | null | undefined) => + epocDate !== null && epocDate !== undefined + ? new Date(epocDate).toISOString() + : null; + +const compileSrtList = ( + officers: ActionOfficer[] | null | undefined +): string[] => + officers?.length ? officers.map((o) => `${o.FIRST_NAME} ${o.LAST_NAME}`) : []; + +const getFinalDispositionDate = (status: string, record: SeaToolSink) => { + const finalDispositionStatuses = [ + SEATOOL_STATUS.APPROVED, + SEATOOL_STATUS.DISAPPROVED, + SEATOOL_STATUS.WITHDRAWN, + ]; + return status && finalDispositionStatuses.includes(status) + ? getDateStringOrNullFromEpoc(record.STATE_PLAN.STATUS_DATE) + : null; }; export const transformSeatoolData = (id: string) => { @@ -178,6 +195,8 @@ export const transformSeatoolData = (id: string) => { ), authority: authorityLookup(data.STATE_PLAN.PLAN_TYPE), changedDate: getDateStringOrNullFromEpoc(data.STATE_PLAN.CHANGED_DATE), + description: data.STATE_PLAN.SUMMARY_MEMO, + finalDispositionDate: getFinalDispositionDate(seatoolStatus, data), leadAnalystOfficerId, leadAnalystName, planType: data.PLAN_TYPES?.[0].PLAN_TYPE_NAME, @@ -187,13 +206,16 @@ export const transformSeatoolData = (id: string) => { raiRequestedDate, raiWithdrawnDate, rais, + reviewTeam: compileSrtList(data.ACTION_OFFICERS), state: data.STATE_PLAN.STATE_CODE, stateStatus: stateStatus || SEATOOL_STATUS.UNKNOWN, + statusDate: getDateStringOrNullFromEpoc(data.STATE_PLAN.STATUS_DATE), cmsStatus: cmsStatus || SEATOOL_STATUS.UNKNOWN, seatoolStatus, submissionDate: getDateStringOrNullFromEpoc( data.STATE_PLAN.SUBMISSION_DATE ), + subject: data.STATE_PLAN.TITLE_NAME, }; }); }; diff --git a/src/services/data/handlers/sink.ts b/src/services/data/handlers/sink.ts index d5fdc5068d..36c5ec4fab 100644 --- a/src/services/data/handlers/sink.ts +++ b/src/services/data/handlers/sink.ts @@ -73,6 +73,8 @@ export const seatool: Handler = async (event) => { approvedEffectiveDate: null, authority: null, changedDate: null, + description: null, + finalDispositionDate: null, leadAnalystName: null, leadAnalystOfficerId: null, planType: null, @@ -81,11 +83,14 @@ export const seatool: Handler = async (event) => { raiReceivedDate: null, raiRequestedDate: null, raiWithdrawnDate: null, + reviewTeam: null, state: null, cmsStatus: null, stateStatus: null, seatoolStatus: null, + statusDate: null, submissionDate: null, + subject: null, }; docObject[id] = seaTombstone; diff --git a/src/services/ui/src/components/PackageDetails/ChipSpaPackageDetails.tsx b/src/services/ui/src/components/PackageDetails/ChipSpaPackageDetails.tsx deleted file mode 100644 index 6d3d430e7d..0000000000 --- a/src/services/ui/src/components/PackageDetails/ChipSpaPackageDetails.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import { format } from "date-fns"; -import { isSpa, OsMainSourceItem } from "shared-types"; -import { removeUnderscoresAndCapitalize } from "@/utils"; -import { LABELS } from "@/lib"; -import { BLANK_VALUE } from "@/consts"; - -export const ChipSpaPackageDetails = (data: OsMainSourceItem) => { - if (!data) return null; - const actionType = isSpa(data.planType) - ? [] - : [ - { - label: "Action Type", - value: data.actionType - ? LABELS[data.actionType as keyof typeof LABELS] || data.actionType - : BLANK_VALUE, - }, - ]; - - const detailFields = [ - { - label: "Submission ID", - value: data.id, - }, - { - label: "State", - value: data.state, - }, - { - label: "Type", - value: removeUnderscoresAndCapitalize(data.planType), - }, - ...actionType, - { - label: "Initial Submission Date", - value: data.submissionDate - ? format(new Date(data.submissionDate), "MM/dd/yyyy h:mm:ss a") - : BLANK_VALUE, - }, - { - label: "Proposed Effective Date", - value: data.proposedDate - ? format(new Date(data.proposedDate), "MM/dd/yyyy") - : BLANK_VALUE, - }, - { - label: "Approved Effective Date", - value: data.approvedEffectiveDate - ? format(new Date(data.approvedEffectiveDate), "MM/dd/yyyy h:mm:ss a") - : BLANK_VALUE, - }, - { - label: "Change Date", - value: data.changedDate - ? format(new Date(data.changedDate), "MM/dd/yyyy h:mm:ss a") - : BLANK_VALUE, - }, - ]; - return ( -
- {detailFields.map(({ label, value }) => { - return ( -
-

{label}

-

{value}

-
- ); - })} -
- ); -}; diff --git a/src/services/ui/src/components/PackageDetails/DetailItemsGrid.tsx b/src/services/ui/src/components/PackageDetails/DetailItemsGrid.tsx new file mode 100644 index 0000000000..cf866349c5 --- /dev/null +++ b/src/services/ui/src/components/PackageDetails/DetailItemsGrid.tsx @@ -0,0 +1,25 @@ +import { useGetUser } from "@/api/useGetUser"; +import { DetailSectionItem } from "@/pages/detail/setup/spa"; + +export const DetailItemsGrid = ({ + displayItems, +}: { + displayItems: DetailSectionItem[]; +}) => { + const { data: user } = useGetUser(); + return ( + <> +
+ {displayItems.map(({ label, value, canView }) => { + return !canView(user) ? null : ( +
+

{label}

+ {value} +
+ ); + })} +
+
+ + ); +}; diff --git a/src/services/ui/src/components/PackageDetails/ReviewTeamList.tsx b/src/services/ui/src/components/PackageDetails/ReviewTeamList.tsx new file mode 100644 index 0000000000..edb2b3c8d8 --- /dev/null +++ b/src/services/ui/src/components/PackageDetails/ReviewTeamList.tsx @@ -0,0 +1,26 @@ +import { useMemo, useState } from "react"; +import { BLANK_VALUE } from "@/consts"; + +export const ReviewTeamList = ({ team }: { team: string[] | undefined }) => { + const [expanded, setExpanded] = useState(false); + const displayTeam = useMemo( + () => (expanded ? team : team?.slice(0, 3)), + [expanded, team] + ); + return !displayTeam || !displayTeam.length ? ( + BLANK_VALUE + ) : ( + + ); +}; diff --git a/src/services/ui/src/components/PackageDetails/index.ts b/src/services/ui/src/components/PackageDetails/index.ts index 1345859498..d9ae2a9ce1 100644 --- a/src/services/ui/src/components/PackageDetails/index.ts +++ b/src/services/ui/src/components/PackageDetails/index.ts @@ -1 +1,2 @@ -export * from "./ChipSpaPackageDetails"; +export * from "./DetailItemsGrid"; +export * from "./ReviewTeamList"; diff --git a/src/services/ui/src/components/SubmissionInfo/index.tsx b/src/services/ui/src/components/SubmissionInfo/index.tsx deleted file mode 100644 index fd3856fc51..0000000000 --- a/src/services/ui/src/components/SubmissionInfo/index.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { BLANK_VALUE } from "@/consts"; -import { OsMainSourceItem } from "shared-types"; - -export const SubmissionInfo = (data: OsMainSourceItem) => { - let cpocName = ""; - if (data.leadAnalystName) { - cpocName = data.leadAnalystName; - } - - const submissionSource = () => { - if (data?.origin?.toLowerCase() === "onemac") { - return "OneMAC"; - } else { - return BLANK_VALUE; - } - }; - const submissionDetails = [ - { - label: "Submitted By", - value:

{data.submitterName || BLANK_VALUE}

, - }, - { - label: "CPOC Name", - value:

{cpocName || BLANK_VALUE}

, - }, - { - label: "Submission Source", - value:

{submissionSource()}

, - }, - ]; - return ( - <> -
-
- {submissionDetails.map(({ label, value }) => { - return ( -
-

{label}

- {value} -
- ); - })} -
- - ); -}; diff --git a/src/services/ui/src/components/index.tsx b/src/services/ui/src/components/index.tsx index c84ff3c7fe..8ecfffad35 100644 --- a/src/services/ui/src/components/index.tsx +++ b/src/services/ui/src/components/index.tsx @@ -14,7 +14,6 @@ export * from "./LoadingSpinner"; export * from "./PackageDetails"; export * from "./RaiList"; export * from "./SearchForm"; -export * from "./SubmissionInfo"; export * from "./Modal"; export * from "./Dialog"; export * from "./Modal/ConfirmationModal"; diff --git a/src/services/ui/src/pages/detail/index.tsx b/src/services/ui/src/pages/detail/index.tsx index 1afefdd30f..192613d4c1 100644 --- a/src/services/ui/src/pages/detail/index.tsx +++ b/src/services/ui/src/pages/detail/index.tsx @@ -3,12 +3,10 @@ import { Alert, Attachmentslist, CardWithTopBorder, - ChipSpaPackageDetails, DetailsSection, ErrorAlert, LoadingSpinner, RaiList, - SubmissionInfo, ConfirmationModal, } from "@/components"; import { useGetUser } from "@/api/useGetUser"; @@ -28,6 +26,8 @@ import { useGetPackageActions } from "@/api/useGetPackageActions"; import { PropsWithChildren, useState } from "react"; import { DETAILS_AND_ACTIONS_CRUMBS } from "@/pages/actions/actions-breadcrumbs"; import { API } from "aws-amplify"; +import { DetailItemsGrid } from "@/components"; +import { spaDetails, submissionDetails } from "@/pages/detail/setup/spa"; const DetailCardWrapper = ({ title, @@ -199,10 +199,9 @@ export const DetailsContent = ({ data }: { data?: ItemResult }) => { /> - - - - +

{"Package Details"}

+ + {/* Below is used for spacing. Keep it simple */}
diff --git a/src/services/ui/src/pages/detail/setup/spa.tsx b/src/services/ui/src/pages/detail/setup/spa.tsx new file mode 100644 index 0000000000..0714fff621 --- /dev/null +++ b/src/services/ui/src/pages/detail/setup/spa.tsx @@ -0,0 +1,114 @@ +import { removeUnderscoresAndCapitalize } from "@/utils"; +import { isCmsUser } from "shared-utils"; +import { LABELS } from "@/lib"; +import { BLANK_VALUE } from "@/consts"; +import { format } from "date-fns"; +import { OsMainSourceItem } from "shared-types"; +import { ReactNode } from "react"; +import { OneMacUser } from "@/api/useGetUser"; +import { ReviewTeamList } from "@/components/PackageDetails/ReviewTeamList"; + +export type DetailSectionItem = { + label: string; + value: ReactNode; + canView: (u: OneMacUser | undefined) => boolean; +}; +export const spaDetails = (data: OsMainSourceItem): DetailSectionItem[] => [ + { + label: "Submission ID", + value: data.id, + canView: () => true, + }, + { + label: "State", + value: data.state, + canView: () => true, + }, + { + label: "Type", + value: removeUnderscoresAndCapitalize(data.planType), + canView: () => true, + }, + { + // TODO: Redo logic to hide for SPAs + label: "Action Type", + value: data.actionType + ? LABELS[data.actionType as keyof typeof LABELS] || data.actionType + : BLANK_VALUE, + canView: () => true, + }, + { + label: "Initial Submission Date", + value: data.submissionDate + ? format(new Date(data.submissionDate), "MM/dd/yyyy h:mm:ss a") + : BLANK_VALUE, + canView: () => true, + }, + { + label: "Proposed Effective Date", + value: data.proposedDate + ? format(new Date(data.proposedDate), "MM/dd/yyyy") + : BLANK_VALUE, + canView: () => true, + }, + { + label: "Approved Effective Date", + value: data.approvedEffectiveDate + ? format(new Date(data.approvedEffectiveDate), "MM/dd/yyyy h:mm:ss a") + : BLANK_VALUE, + canView: () => true, + }, + { + label: "Status Date", + value: data.statusDate + ? format(new Date(data.statusDate), "MM/dd/yyyy h:mm:ss a") + : BLANK_VALUE, + canView: (u) => (!u || !u.user ? false : isCmsUser(u.user)), + }, + { + label: "Final Disposition Date", + value: data.finalDispositionDate + ? format(new Date(data.finalDispositionDate), "MM/dd/yyyy h:mm:ss a") + : BLANK_VALUE, + canView: () => true, + }, +]; + +export const submissionDetails = ( + data: OsMainSourceItem +): DetailSectionItem[] => [ + { + label: "Submitted By", + value:

{data?.submitterName || BLANK_VALUE}

, + canView: () => true, + }, + { + label: "Submission Source", + value: ( +

+ {data?.origin?.toLowerCase() === "onemac" ? "OneMAC" : BLANK_VALUE} +

+ ), + canView: () => true, + }, + { + label: "Subject", + value:

{data?.subject || BLANK_VALUE}

, + canView: (u) => (!u || !u.user ? false : isCmsUser(u.user)), + }, + { + label: "Description", + value:

{data?.description || BLANK_VALUE}

, + canView: (u) => (!u || !u.user ? false : isCmsUser(u.user)), + }, + { + label: "CPOC", + value:

{data?.leadAnalystName || BLANK_VALUE}

, + canView: () => true, + }, + { + label: "Review Team (SRT)", + value: , + canView: (u) => (!u || !u.user ? false : isCmsUser(u.user)), + }, +];