Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IEN-941 | [Undo EoJ] Create Re-Engage milestone #687

Merged
merged 11 commits into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions apps/api/src/migration/1733160287697-AddReEngagedMilestone.ts
Original file line number Diff line number Diff line change
@@ -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<void> {
await queryRunner.manager
.createQueryBuilder()
.insert()
.into('ien_applicant_status')
.values(this.milestones)
.orIgnore(true)
.execute();
}
public async up(queryRunner: QueryRunner): Promise<void> {
await this.addMilestones(queryRunner);
}

public async down(queryRunner: QueryRunner): Promise<void> {
const ids = this.milestones.map(milestone => milestone.id);

await queryRunner.manager
.createQueryBuilder()
.delete()
.from('ien_applicant_status')
.whereInIds(ids)
.execute();
}
}
36 changes: 36 additions & 0 deletions apps/api/src/migration/1733414195778-AddSystemMilestoneRole.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { MigrationInterface, QueryRunner } from 'typeorm';

export class AddSystemMilestoneRole1733414195778 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
const ADMIN_SLUG = 'admin';
const READ_SYSTEM_MILESTONE_SLUG = 'read-system-milestone';
const WRITE_SYSTEM_MILESTONE_SLUG = 'write-system-milestone';
const SYSTEM_MILESTONE_SLUG = 'system-milestone';
await queryRunner.query(`
INSERT INTO "access" ("name", "description", "slug")
VALUES ('Read System Milestone', 'View system milestones', '${READ_SYSTEM_MILESTONE_SLUG}'),
('Write System Milestone', 'Add, update or delete system milestones', '${WRITE_SYSTEM_MILESTONE_SLUG}');
INSERT INTO "role" ("name", "description", "slug")
VALUES ('System Milestone', 'Manage system milestones', '${SYSTEM_MILESTONE_SLUG}');
INSERT INTO "role_acl_access" VALUES
((SELECT id FROM "role" WHERE "slug" = '${ADMIN_SLUG}'), (SELECT id FROM "access" WHERE "slug" = '${READ_SYSTEM_MILESTONE_SLUG}')),
((SELECT id FROM "role" WHERE "slug" = '${ADMIN_SLUG}'), (SELECT id FROM "access" WHERE "slug" = '${WRITE_SYSTEM_MILESTONE_SLUG}')),
((SELECT id FROM "role" WHERE "slug" = '${SYSTEM_MILESTONE_SLUG}'), (SELECT id FROM "access" WHERE "slug" = '${READ_SYSTEM_MILESTONE_SLUG}')),
((SELECT id FROM "role" WHERE "slug" = '${SYSTEM_MILESTONE_SLUG}'), (SELECT id FROM "access" WHERE "slug" = '${WRITE_SYSTEM_MILESTONE_SLUG}'));
`);
}

public async down(queryRunner: QueryRunner): Promise<void> {
const READ_SYSTEM_MILESTONE_SLUG = 'read-system-milestone';
const WRITE_SYSTEM_MILESTONE_SLUG = 'write-system-milestone';
const SYSTEM_MILESTONE_SLUG = 'system-milestone';

await queryRunner.query(`
DELETE FROM "role_acl_access" WHERE "accessId" IN (
SELECT id FROM "access" WHERE "slug" IN ('${READ_SYSTEM_MILESTONE_SLUG}', '${WRITE_SYSTEM_MILESTONE_SLUG}')
);
DELETE FROM "role" WHERE "slug" = '${SYSTEM_MILESTONE_SLUG}';
DELETE FROM "access" WHERE "slug" IN ('${READ_SYSTEM_MILESTONE_SLUG}', '${WRITE_SYSTEM_MILESTONE_SLUG}');
`);
}
}
2 changes: 2 additions & 0 deletions apps/api/src/report/types/milestone-table-entry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,4 +144,6 @@ export type MilestoneDurationTableEntry<T> = {
[STATUS.BCCNM_DECISION_DATE]?: T;

[STATUS.BCCNM_REGISTRATION_DATE]?: T;

[STATUS.RE_ENGAGED]?: T;
};
17 changes: 15 additions & 2 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
jerry-ey marked this conversation as resolved.
Show resolved Hide resolved
"@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",
Expand All @@ -60,6 +73,6 @@
"postcss-loader": "8.1.1",
"react-test-renderer": "17.0.2",
"tailwindcss": "2.2.19",
"typescript": "4.5.2"
"typescript": "5.7.2"
}
}
55 changes: 55 additions & 0 deletions apps/web/src/components/admin/hooks/useMilestones.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// src/hooks/useMilestones.ts

import { useState, useEffect } from 'react';
import dayjs from 'dayjs';
import { ApplicantStatusAuditRO } from '@ien/common';

interface UseMilestonesProps {
milestones: ApplicantStatusAuditRO[];
category: string;
applicant: any;
}
const DEFAULT_TAB_PAGE_SIZE = 5;
export const useMilestones = ({ milestones, category, applicant }: UseMilestonesProps) => {
const [filteredMilestones, setFilteredMilestones] = useState<ApplicantStatusAuditRO[]>([]);
const [milestonesInPage, setMilestonesInPage] = useState<ApplicantStatusAuditRO[]>([]);
const [pageIndex, setPageIndex] = useState(1);
const [pageSize, setPageSize] = useState(DEFAULT_TAB_PAGE_SIZE);
const [editing, setEditing] = useState<ApplicantStatusAuditRO | null>(null);
const [activeEdit, setActiveEdit] = useState(0);

useEffect(() => {
const audits =
milestones
?.filter(audit => audit.status.category === category)
.sort((b, a) => {
if (a.start_date === b.start_date) {
return dayjs(a.updated_date).diff(b.updated_date);
}
return dayjs(a.start_date).diff(b.start_date);
}) || [];
setFilteredMilestones(audits);
}, [milestones, category, applicant]);

useEffect(() => {
if (!filteredMilestones || (pageIndex - 1) * pageSize > filteredMilestones.length) {
setPageIndex(1);
}
const start = (pageIndex - 1) * pageSize;
const end = pageIndex * pageSize;
setMilestonesInPage(filteredMilestones?.slice(start, end) || []);
}, [filteredMilestones, pageIndex, pageSize]);

return {
filteredMilestones,
milestonesInPage,
pageIndex,
pageSize,
setPageIndex,
setPageSize,
editing,
setEditing,
activeEdit,
setActiveEdit,
};
};
23 changes: 18 additions & 5 deletions apps/web/src/components/applicant/ApplicantMilestones.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -27,6 +29,21 @@ export const ApplicantMilestones = () => {
[applicant, statusCategory],
);

const renderTabContent = () => {
switch (statusCategory) {
case StatusCategory.RECRUITMENT:
return <Recruitment />;
case StatusCategory.SYSTEM:
return (
<SystemProvider>
<System />
</SystemProvider>
);
default:
return <MilestoneTable category={statusCategory} />;
}
};

return (
<div className='border-2 rounded px-5 my-5 pb-6 bg-white'>
<div className='flex items-center border-b py-4'>
Expand All @@ -40,11 +57,7 @@ export const ApplicantMilestones = () => {
categoryIndex={statusCategory}
onTabClick={(value: string) => setStatusCategory(value as StatusCategory)}
/>
{statusCategory === StatusCategory.RECRUITMENT ? (
<Recruitment />
) : (
<MilestoneTable category={statusCategory} />
)}
{renderTabContent()}
</>
) : (
<>
Expand Down
22 changes: 22 additions & 0 deletions apps/web/src/components/milestone-logs/System.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from 'react';
import { SystemForm } from './system/SystemForm';
import { Access, StatusCategory } from '@ien/common';
import { SystemMilestoneTable } from './system/SystemMilestoneTable';
import { AclMask } from '../user';

export const System = () => {
return (
<main className='flex flex-col gap-4 py-4'>
<section className='flex justify-end'>
<AclMask acl={[Access.READ_SYSTEM_MILESTONE, Access.WRITE_SYSTEM_MILESTONE]}>
<SystemForm />
</AclMask>
</section>
<section>
<AclMask acl={[Access.READ_SYSTEM_MILESTONE]}>
<SystemMilestoneTable category={StatusCategory.SYSTEM} />
</AclMask>
</section>
</main>
);
};
44 changes: 44 additions & 0 deletions apps/web/src/components/milestone-logs/system/SystemContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React, { createContext, useContext, useState, ReactNode, useMemo } 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<SystemContextType | undefined>(undefined);

// 3. Create the provider component and props type
interface SystemProviderProps {
children: ReactNode;
}

export const SystemProvider: React.FC<SystemProviderProps> = ({ children }) => {
const [open, setOpen] = useState<boolean>(false);
const [selectedMilestone, setSelectedMilestone] = useState<SystemMilestone | null>(null);

const value = useMemo(
() => ({ open, setOpen, selectedMilestone, setSelectedMilestone }),
[open, selectedMilestone],
);

return <SystemContext.Provider value={value}>{children}</SystemContext.Provider>;
};

// 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;
};
Loading
Loading