From 935ffb04b909f370c2d2bef6d2a7facbeb8ae9b1 Mon Sep 17 00:00:00 2001 From: Jerry Wang Date: Thu, 5 Dec 2024 07:39:45 -0800 Subject: [PATCH 01/11] add reengaged milestone --- .../1733160287697-AddReEngagedMilestone.ts | 36 + .../src/report/types/milestone-table-entry.ts | 2 + apps/web/package.json | 15 +- .../applicant/ApplicantMilestones.tsx | 23 +- .../src/components/milestone-logs/System.tsx | 19 + .../milestone-logs/system/SystemContext.tsx | 43 + .../milestone-logs/system/SystemForm.tsx | 231 ++++++ .../system/SystemMilestoneTable.tsx | 196 +++++ apps/web/src/components/ui/button.tsx | 57 ++ apps/web/src/components/ui/calendar.tsx | 78 ++ apps/web/src/components/ui/dialog.tsx | 110 +++ apps/web/src/components/ui/form.tsx | 176 +++++ apps/web/src/components/ui/label.tsx | 24 + apps/web/src/components/ui/popover.tsx | 33 + apps/web/src/components/ui/select.tsx | 152 ++++ apps/web/src/components/ui/textarea.tsx | 21 + apps/web/src/lib/utils.ts | 19 + .../constants/tab-header.constants.ts | 1 + apps/web/tailwind.config.js | 1 + apps/web/tsconfig.json | 3 +- packages/common/src/enum/milestone-status.ts | 7 +- packages/common/src/enum/status-category.ts | 1 + yarn.lock | 747 +++++++++++++++++- 23 files changed, 1986 insertions(+), 9 deletions(-) create mode 100644 apps/api/src/migration/1733160287697-AddReEngagedMilestone.ts create mode 100644 apps/web/src/components/milestone-logs/System.tsx create mode 100644 apps/web/src/components/milestone-logs/system/SystemContext.tsx create mode 100644 apps/web/src/components/milestone-logs/system/SystemForm.tsx create mode 100644 apps/web/src/components/milestone-logs/system/SystemMilestoneTable.tsx create mode 100644 apps/web/src/components/ui/button.tsx create mode 100644 apps/web/src/components/ui/calendar.tsx create mode 100644 apps/web/src/components/ui/dialog.tsx create mode 100644 apps/web/src/components/ui/form.tsx create mode 100644 apps/web/src/components/ui/label.tsx create mode 100644 apps/web/src/components/ui/popover.tsx create mode 100644 apps/web/src/components/ui/select.tsx create mode 100644 apps/web/src/components/ui/textarea.tsx create mode 100644 apps/web/src/lib/utils.ts diff --git a/apps/api/src/migration/1733160287697-AddReEngagedMilestone.ts b/apps/api/src/migration/1733160287697-AddReEngagedMilestone.ts new file mode 100644 index 000000000..9ff469088 --- /dev/null +++ b/apps/api/src/migration/1733160287697-AddReEngagedMilestone.ts @@ -0,0 +1,36 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; +import { StatusCategory } from '@ien/common'; + +export class AddReEngagedMilestone1733160287697 implements MigrationInterface { + milestones = [ + { + id: 'e1a1b3f4-2d4b-4c3e-9a1b-6f4e7d8c9b0a', + status: 'Re-engaged', + category: StatusCategory.SYSTEM, + }, + ]; + + private async addMilestones(queryRunner: QueryRunner): Promise { + await queryRunner.manager + .createQueryBuilder() + .insert() + .into('ien_applicant_status') + .values(this.milestones) + .orIgnore(true) + .execute(); + } + public async up(queryRunner: QueryRunner): Promise { + await this.addMilestones(queryRunner); + } + + public async down(queryRunner: QueryRunner): Promise { + const ids = this.milestones.map(milestone => milestone.id); + + await queryRunner.manager + .createQueryBuilder() + .delete() + .from('ien_applicant_status') + .whereInIds(ids) + .execute(); + } +} diff --git a/apps/api/src/report/types/milestone-table-entry.ts b/apps/api/src/report/types/milestone-table-entry.ts index e65d02c7a..922704463 100644 --- a/apps/api/src/report/types/milestone-table-entry.ts +++ b/apps/api/src/report/types/milestone-table-entry.ts @@ -144,4 +144,6 @@ export type MilestoneDurationTableEntry = { [STATUS.BCCNM_DECISION_DATE]?: T; [STATUS.BCCNM_REGISTRATION_DATE]?: T; + + [STATUS.RE_ENGAGED]?: T; }; diff --git a/apps/web/package.json b/apps/web/package.json index a920ae1ac..f2952c607 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -17,25 +17,38 @@ "@fortawesome/free-solid-svg-icons": "6.5.2", "@fortawesome/react-fontawesome": "0.2.2", "@headlessui/react": "2.1.2", + "@hookform/resolvers": "^3.9.1", "@ien/common": "1.0.2", + "@radix-ui/react-dialog": "^1.1.2", + "@radix-ui/react-label": "^2.1.0", + "@radix-ui/react-popover": "^1.1.2", + "@radix-ui/react-select": "^2.1.2", + "@radix-ui/react-slot": "^1.1.0", "@types/cookie": "0.4.1", "@types/cookies": "0.7.7", "axios": "0.28.0", + "class-variance-authority": "^0.7.1", "classnames": "2.3.1", "cookie": "0.4.2", + "date-fns": "^4.1.0", "eventemitter3": "4.0.7", "formik": "2.4.6", "lodash": "4.17.21", + "lucide-react": "^0.464.0", "next": "14.2.5", "oidc-client-ts": "3.0.1", "react": "18.3.1", "react-datepicker": "4.8.0", + "react-day-picker": "^9.4.1", "react-dom": "18.3.1", + "react-hook-form": "^7.53.2", "react-oidc-context": "2.3.0", "react-select": "5.8.0", "react-toastify": "10.0.5", + "tailwind-merge": "^2.5.5", "use-http": "1.0.26", - "xlsx-js-style": "1.2.0" + "xlsx-js-style": "1.2.0", + "zod": "^3.23.8" }, "devDependencies": { "@next/eslint-plugin-next": "14.2.5", diff --git a/apps/web/src/components/applicant/ApplicantMilestones.tsx b/apps/web/src/components/applicant/ApplicantMilestones.tsx index 850e973f9..daac14c71 100644 --- a/apps/web/src/components/applicant/ApplicantMilestones.tsx +++ b/apps/web/src/components/applicant/ApplicantMilestones.tsx @@ -8,6 +8,8 @@ import { useApplicantContext } from './ApplicantContext'; import { MilestoneTable } from '../milestone-logs/MilestoneTable'; import { Recruitment } from '../milestone-logs/Recruitment'; import { useAuthContext } from '../AuthContexts'; +import { System } from '../milestone-logs/System'; +import { SystemProvider } from '../milestone-logs/system/SystemContext'; export const ApplicantMilestones = () => { const { applicant } = useApplicantContext(); @@ -27,6 +29,21 @@ export const ApplicantMilestones = () => { [applicant, statusCategory], ); + const renderTabContent = () => { + switch (statusCategory) { + case StatusCategory.RECRUITMENT: + return ; + case StatusCategory.SYSTEM: + return ( + + + + ); + default: + return ; + } + }; + return (
@@ -40,11 +57,7 @@ export const ApplicantMilestones = () => { categoryIndex={statusCategory} onTabClick={(value: string) => setStatusCategory(value as StatusCategory)} /> - {statusCategory === StatusCategory.RECRUITMENT ? ( - - ) : ( - - )} + {renderTabContent()} ) : ( <> diff --git a/apps/web/src/components/milestone-logs/System.tsx b/apps/web/src/components/milestone-logs/System.tsx new file mode 100644 index 000000000..5fc59463c --- /dev/null +++ b/apps/web/src/components/milestone-logs/System.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { SystemForm } from './system/SystemForm'; +import { StatusCategory } from '@ien/common'; +import { SystemMilestoneTable } from './system/SystemMilestoneTable'; + +type Props = {}; + +export const System = (props: Props) => { + return ( +
+
+ +
+
+ +
+
+ ); +}; diff --git a/apps/web/src/components/milestone-logs/system/SystemContext.tsx b/apps/web/src/components/milestone-logs/system/SystemContext.tsx new file mode 100644 index 000000000..5ff9de272 --- /dev/null +++ b/apps/web/src/components/milestone-logs/system/SystemContext.tsx @@ -0,0 +1,43 @@ +import React, { createContext, useContext, useState, ReactNode } from 'react'; + +// 1. Define the shape of the context state +type SystemMilestone = { + id: string; + start_date: string | undefined; + status: string; + notes: string | undefined; +}; +interface SystemContextType { + open: boolean; + setOpen: (open: boolean) => void; + selectedMilestone: SystemMilestone | null; + setSelectedMilestone: (milestone: SystemMilestone | null) => void; +} + +// 2. Create the context with an initial undefined value +const SystemContext = createContext(undefined); + +// 3. Create the provider component and props type +interface SystemProviderProps { + children: ReactNode; +} + +export const SystemProvider: React.FC = ({ children }) => { + const [open, setOpen] = useState(false); + const [selectedMilestone, setSelectedMilestone] = useState(null); + + return ( + + {children} + + ); +}; + +// 4. Custom hook to use the context +export const useSystem = (): SystemContextType => { + const context = useContext(SystemContext); + if (!context) { + throw new Error('useSystem must be used within a SystemProvider'); + } + return context; +}; diff --git a/apps/web/src/components/milestone-logs/system/SystemForm.tsx b/apps/web/src/components/milestone-logs/system/SystemForm.tsx new file mode 100644 index 000000000..9f4c238bd --- /dev/null +++ b/apps/web/src/components/milestone-logs/system/SystemForm.tsx @@ -0,0 +1,231 @@ +import { z } from 'zod'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { useForm } from 'react-hook-form'; +import { useEffect } from 'react'; +import { CalendarIcon } from 'lucide-react'; +import { format, parseISO } from 'date-fns'; + +import { Button, buttonBase, buttonColor } from '@/components/Button'; +import addIcon from '@assets/img/add.svg'; +import { Calendar } from '@/components/ui/calendar'; +import { + Dialog, + DialogClose, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from '@/components/ui/dialog'; + +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from '@/components/ui/form'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; +import { Textarea } from '@/components/ui/textarea'; +import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; +import { Button as UiButton } from '@/components/ui/button'; +import { addMilestone, updateMilestone, useGetMilestoneOptions } from '@services'; +import { IENApplicantAddStatusDTO, IENApplicantUpdateStatusDTO, StatusCategory } from '@ien/common'; +import { cn } from '@/lib/utils'; +import { useApplicantContext } from '@/components/applicant/ApplicantContext'; +import { useSystem } from './SystemContext'; + +const formSchema = z.object({ + id: z.string().optional(), + status: z.string(), + start_date: z.date(), + notes: z.string().optional(), +}); + +const DEFAULT_VALUES = { + id: undefined, + status: '', + start_date: new Date(), + notes: '', +}; + +export function SystemForm() { + const { open, setOpen, selectedMilestone, setSelectedMilestone } = useSystem(); + + const milestones = useGetMilestoneOptions(StatusCategory.SYSTEM); + const { applicant, fetchApplicant } = useApplicantContext(); + + // 1. Define your form. + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: DEFAULT_VALUES, + }); + + const handleCreateMilestone = async (id: string, values: IENApplicantAddStatusDTO) => { + const milestone = await addMilestone(id, values); + if (milestone) { + fetchApplicant(); + } + setOpen(false); + }; + + const handleUpdateMilestone = async (id: string, values: IENApplicantUpdateStatusDTO) => { + const milestone = await updateMilestone(applicant.id, id, values); + if (milestone) { + fetchApplicant(); + } + setOpen(false); + }; + + // 2. Define a submit handler. + async function onSubmit(values: z.infer) { + // update + if (selectedMilestone) { + return await handleUpdateMilestone(selectedMilestone.id, { + ...values, + start_date: format(values.start_date, 'yyyy-MM-dd'), + }); + } + + // create + await handleCreateMilestone(applicant.id, { + ...values, + start_date: format(values.start_date, 'yyyy-MM-dd'), + }); + } + + useEffect(() => { + if (selectedMilestone) { + form.reset({ + ...selectedMilestone, + start_date: selectedMilestone?.start_date + ? parseISO(selectedMilestone.start_date as string) + : new Date(), + }); + } + }, [selectedMilestone, form]); + + return ( + + + + + +
+ + + Add Milestone + + +
+ ( + + Milestone + + + + )} + /> + + ( + + Start date + + + + + {field.value ? format(field.value, 'PPP') : Pick a date} + + + + + + + + + + + )} + /> + ( + + Notes + +