Skip to content

Commit e3d2389

Browse files
authored
Review board (#577)
* issuing officer added * filter behaviour corrected for deputy directors * lint fix
1 parent 9a807a5 commit e3d2389

File tree

6 files changed

+113
-70
lines changed

6 files changed

+113
-70
lines changed

compliance-api/src/compliance_api/schemas/review_board.py

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ class ReviewBoardInspectionRecordSchema(Schema): # pylint: disable=no-self-use
2828

2929
# Approval fields - if under review
3030
send_for_review_date = fields.Method("get_send_for_review_date", dump_only=True)
31-
deputy_director_name = fields.Method("get_deputy_director_name", dump_only=True)
31+
deputy_director = fields.Method("get_deputy_director", dump_only=True)
3232
approved_date = fields.Method("get_approved_date", dump_only=True)
3333

3434
# Approval fields from inspection record approval
@@ -71,11 +71,17 @@ def get_send_for_review_date(self, obj): # pylint: disable=no-self-use
7171
return latest_approval.created_date.strftime(INPUT_DATE_TIME_FORMAT)
7272
return None
7373

74-
def get_deputy_director_name(self, obj): # pylint: disable=no-self-use
74+
def get_deputy_director(self, obj): # pylint: disable=no-self-use
7575
"""Get deputy director name from latest approval."""
7676
latest_approval = self._get_latest_approval(obj)
7777
if latest_approval and latest_approval.approved_by:
78-
return f"{latest_approval.approved_by.first_name} {latest_approval.approved_by.last_name}"
78+
officer = latest_approval.approved_by
79+
return {
80+
"id": officer.id,
81+
"first_name": officer.first_name,
82+
"last_name": officer.last_name,
83+
"name": f"{officer.first_name} {officer.last_name}",
84+
}
7985
return None
8086

8187
def get_approved_date(self, obj): # pylint: disable=no-self-use
@@ -158,6 +164,7 @@ class ReviewBoardWarningLetterSchema(Schema): # pylint: disable=no-self-use
158164
approved_date = fields.Method("get_approved_date")
159165
review_requested_date = fields.Method("get_review_requested_date")
160166
approved_by = fields.Method("get_approved_by")
167+
deputy_director = fields.Method("get_deputy_director")
161168
intended_issuance_date = fields.DateTime(
162169
format=INPUT_DATE_TIME_FORMAT, dump_only=True
163170
)
@@ -229,6 +236,21 @@ def get_approved_by(self, obj): # pylint: disable=no-self-use
229236
return f"{staff_user.first_name} {staff_user.last_name}"
230237
return None
231238

239+
def get_deputy_director(self, obj): # pylint: disable=no-self-use
240+
"""Get deputy director name from latest approval."""
241+
latest_approval = self._get_latest_approval(obj)
242+
if latest_approval and latest_approval.approved_by_id:
243+
# Need to fetch the staff user for the name
244+
staff_user = StaffUser.query.get(latest_approval.approved_by_id)
245+
if staff_user:
246+
return {
247+
"id": staff_user.id,
248+
"first_name": staff_user.first_name,
249+
"last_name": staff_user.last_name,
250+
"name": f"{staff_user.first_name} {staff_user.last_name}",
251+
}
252+
return None
253+
232254
def get_warning_letter_status(self, obj): # pylint: disable=no-self-use
233255
"""Get warning letter status."""
234256
if hasattr(obj, "warning_letter_status") and obj.warning_letter_status:
@@ -287,7 +309,7 @@ class ReviewBoardOrderSchema(Schema): # pylint: disable=no-self-use
287309
issuing_officer = fields.Method("get_issuing_officer", dump_only=True)
288310
# Approval fields
289311
send_for_review_date = fields.Method("get_send_for_review_date", dump_only=True)
290-
deputy_director_name = fields.Method("get_deputy_director_name", dump_only=True)
312+
deputy_director = fields.Method("get_deputy_director", dump_only=True)
291313
approved_date = fields.Method("get_approved_date", dump_only=True)
292314

293315
# Order specific fields
@@ -334,14 +356,19 @@ def get_send_for_review_date(self, obj): # pylint: disable=no-self-use
334356
return latest_approval.created_date.strftime(INPUT_DATE_TIME_FORMAT)
335357
return None
336358

337-
def get_deputy_director_name(self, obj): # pylint: disable=no-self-use
359+
def get_deputy_director(self, obj): # pylint: disable=no-self-use
338360
"""Get deputy director name from latest approval."""
339361
latest_approval = self._get_latest_approval(obj)
340362
if latest_approval and latest_approval.approved_by_id:
341363
# Need to fetch the staff user for the name
342364
staff_user = StaffUser.query.get(latest_approval.approved_by_id)
343365
if staff_user:
344-
return f"{staff_user.first_name} {staff_user.last_name}"
366+
return {
367+
"id": staff_user.id,
368+
"first_name": staff_user.first_name,
369+
"last_name": staff_user.last_name,
370+
"name": f"{staff_user.first_name} {staff_user.last_name}",
371+
}
345372
return None
346373

347374
def get_approved_date(self, obj): # pylint: disable=no-self-use

compliance-web/src/components/App/ReviewBoard/ReviewBoardFilters.tsx

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,18 +69,38 @@ const ReviewBoardFilters: React.FC<ReviewBoardFiltersProps> = ({
6969

7070
if (newChecked && currentStaff) {
7171
// When turning ON, set the primary officer filter to current user
72-
onFilterChange("primary_officer_id", [currentStaff.id.toString()]);
73-
onFilterChange("is_deputy_director", isDeputyDirector.toString());
72+
if (isDeputyDirector) {
73+
onFilterChange("deputy_director_id", [currentStaff.id.toString()]);
74+
} else {
75+
onFilterChange("primary_officer_id", [currentStaff.id.toString()]);
76+
}
7477
} else {
7578
// When turning OFF, clear the primary officer filter
76-
onFilterChange("primary_officer_id", []);
77-
onFilterChange("is_deputy_director", "");
79+
// Remove only currentStaff.id from the "primary_officer_id" filter
80+
if (
81+
externalFilters.primary_officer_id &&
82+
Array.isArray(externalFilters.primary_officer_id)
83+
) {
84+
const filtered = externalFilters.primary_officer_id.filter(
85+
(id) => id !== currentStaff?.id?.toString()
86+
);
87+
onFilterChange("primary_officer_id", filtered);
88+
} else {
89+
onFilterChange("primary_officer_id", []);
90+
}
91+
onFilterChange("deputy_director_id", []);
7892
}
7993

8094
// Notify parent so it can persist switch state explicitly
8195
onSwitchChange?.(newChecked);
8296
},
83-
[currentStaff, onFilterChange, onSwitchChange, isDeputyDirector]
97+
[
98+
currentStaff,
99+
onSwitchChange,
100+
isDeputyDirector,
101+
onFilterChange,
102+
externalFilters,
103+
]
84104
);
85105

86106
const switchLabel = useMemo(() => {

compliance-web/src/components/App/ReviewBoard/ReviewBoardSectionItem.tsx

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -191,16 +191,14 @@ const ReviewBoardSectionItem = ({
191191
{getFormattedDate(item.send_for_review_date)}
192192
</Typography>
193193
</Typography>
194-
{item.deputy_director_name && (
194+
{item.deputy_director && (
195195
<Typography
196196
variant="caption"
197197
color={BCDesignTokens.typographyColorPlaceholder}
198198
>
199199
Deputy Director:
200200
<Typography variant="caption" ml={0.25}>
201-
{item.deputy_director_name.substring(
202-
item.deputy_director_name.indexOf(" ") + 1
203-
)}
201+
{item.deputy_director.last_name}
204202
</Typography>
205203
</Typography>
206204
)}
@@ -244,15 +242,28 @@ const ReviewBoardSectionItem = ({
244242
</Typography>
245243
)}
246244
{sectionId === ReviewBoardCardTypeEnum.PENDING_ISSUANCE && (
247-
<Typography
248-
variant="caption"
249-
color={BCDesignTokens.typographyColorPlaceholder}
250-
>
251-
Issuance Date:
252-
<Typography variant="caption" ml={0.25}>
253-
{getFormattedDate(item.intended_issuance_date)}
245+
<>
246+
<Typography
247+
variant="caption"
248+
color={BCDesignTokens.typographyColorPlaceholder}
249+
>
250+
Issuance Date:
251+
<Typography variant="caption" ml={0.25}>
252+
{getFormattedDate(item.intended_issuance_date)}
253+
</Typography>
254254
</Typography>
255-
</Typography>
255+
{item.issuing_officer && (
256+
<Typography
257+
variant="caption"
258+
color={BCDesignTokens.typographyColorPlaceholder}
259+
>
260+
Issuing Officer:
261+
<Typography variant="caption" ml={0.25}>
262+
{item.issuing_officer?.last_name}
263+
</Typography>
264+
</Typography>
265+
)}
266+
</>
256267
)}
257268
</Box>
258269
</>

compliance-web/src/components/App/ReviewBoard/ReviewBoardUtils.tsx

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,14 @@ export const formatInspectionRecordsToReviewBoardItems = (
4141
ir_progress: record.ir_progress,
4242
ir_number: record.ir_number,
4343
intended_issuance_date: record.intended_issuance_date,
44-
deputy_director_name: record.deputy_director_name,
44+
deputy_director: record.deputy_director,
4545
send_for_review_date: record.send_for_review_date,
4646
date_report_sent: record.date_report_sent,
4747
expected_return_date: record.expected_return_date,
4848
date_response: record.date_response,
49+
issuing_officer: record.intended_issuance_date
50+
? record.deputy_director
51+
: undefined,
4952
}));
5053
};
5154

@@ -67,8 +70,9 @@ export const formatOrderRecordsToReviewBoardItems = (
6770
review_date: record.approved_date,
6871
ir_number: record.ir_number,
6972
intended_issuance_date: record.intended_issuance_date,
70-
deputy_director_name: record.deputy_director_name,
73+
deputy_director: record.deputy_director,
7174
send_for_review_date: record.send_for_review_date,
75+
issuing_officer: record.issuing_officer,
7276
}));
7377
};
7478

@@ -90,8 +94,9 @@ export const formatWarningLettersToReviewBoardItems = (
9094
review_date: record.approved_date,
9195
ir_number: record.ir_number,
9296
intended_issuance_date: record.intended_issuance_date,
93-
deputy_director_name: record.deputy_director_name,
97+
deputy_director: record.deputy_director,
9498
send_for_review_date: record.review_requested_date,
99+
issuing_officer: record.issuing_officer,
95100
}));
96101
};
97102

@@ -264,9 +269,16 @@ export const generateDynamicSections = (
264269
orderRecords?: OrderReviewBoardItem[],
265270
warningLetters?: WarningLetterReviewBoardItem[],
266271
administrativePenalties?: APReviewBoardItem[],
267-
primaryOfficerFilter?: string[],
268-
isDeputyDirectorFilter?: boolean
272+
externalFilters?: Record<string, string[] | string>
269273
): ReviewBoardSection[] => {
274+
// Extract primary officer filter from external filters
275+
const primaryOfficerFilter = externalFilters?.primary_officer_id as
276+
| string[]
277+
| undefined;
278+
const deputyDirectorFilter = externalFilters?.deputy_director_id as
279+
| string[]
280+
| undefined;
281+
270282
// Initialize the 6 sections with empty items
271283
const sections = createInitialSections();
272284

@@ -292,11 +304,14 @@ export const generateDynamicSections = (
292304
primaryOfficerFilter.includes(item.primary_officer.id.toString())
293305
);
294306
}
295-
if (isDeputyDirectorFilter) {
307+
if (deputyDirectorFilter && deputyDirectorFilter.length > 0) {
296308
allItems = allItems.filter(
297309
(item) =>
298-
item.approval_status?.id === APPROVAL_STATUS.APPROVAL_PENDING ||
299-
item.approval_status?.id === AdministrativePenaltyStatus.DEPUTY_REVIEW
310+
(item.approval_status?.id === APPROVAL_STATUS.APPROVAL_PENDING ||
311+
item.approval_status?.id ===
312+
AdministrativePenaltyStatus.DEPUTY_REVIEW) &&
313+
item.deputy_director &&
314+
deputyDirectorFilter.includes(item.deputy_director.id.toString())
300315
);
301316
}
302317

compliance-web/src/models/ReviewBoard.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,12 @@ export interface ReviewBoardItem {
2626
ir_progress?: IRProgress;
2727
ir_number?: string;
2828
intended_issuance_date?: string;
29-
deputy_director_name?: string;
29+
deputy_director?: StaffUser;
3030
send_for_review_date?: string;
3131
date_report_sent?: string;
3232
expected_return_date?: string;
3333
date_response?: string;
34+
issuing_officer?: StaffUser;
3435
}
3536

3637
export interface ReviewBoardSection {
@@ -45,7 +46,7 @@ export interface IRReviewBoardItem {
4546
inspection_start_date: string,
4647
primary_officer: StaffUser,
4748
send_for_review_date: string,
48-
deputy_director_name: string,
49+
deputy_director: StaffUser,
4950
approved_date: string,
5051
date_report_sent: string,
5152
expected_return_date: string,
@@ -54,6 +55,7 @@ export interface IRReviewBoardItem {
5455
ir_status: IRStatus,
5556
ir_progress: IRProgress,
5657
approval_status: ApprovalStatus,
58+
issuing_officer: StaffUser,
5759
}
5860

5961
export interface OrderReviewBoardItem {
@@ -64,7 +66,7 @@ export interface OrderReviewBoardItem {
6466
primary_officer: StaffUser,
6567
issuing_officer: StaffUser,
6668
send_for_review_date: string,
67-
deputy_director_name: string,
69+
deputy_director: StaffUser,
6870
approved_date: string,
6971
order_status: OrderStatus,
7072
order_progress: OrderProgress,
@@ -80,7 +82,7 @@ export interface WarningLetterReviewBoardItem {
8082
primary_officer: StaffUser,
8183
issuing_officer: StaffUser,
8284
approved_date: string,
83-
deputy_director_name: string,
85+
deputy_director: StaffUser,
8486
review_requested_date: string,
8587
approved_by: StaffUser,
8688
intended_issuance_date: string,

compliance-web/src/routes/_authenticated/review-board.tsx

Lines changed: 3 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { createFileRoute } from "@tanstack/react-router";
1414
import { BCDesignTokens } from "epic.theme";
1515
import { useState, useEffect, useRef, useCallback, useMemo } from "react";
1616
import { useAuth } from "react-oidc-context";
17-
import { STAFF_USER_POSITION } from "@/utils/constants";
17+
// STAFF_USER_POSITION no longer needed after removing default ON logic
1818

1919
export const Route = createFileRoute("/_authenticated/review-board")({
2020
component: ReviewBoard,
@@ -37,20 +37,12 @@ function ReviewBoard() {
3737

3838
// Create the exact 6 sections like mockReviewBoard and populate with dynamic data
3939
const dynamicSections = useMemo(() => {
40-
// Extract primary officer filter from external filters
41-
const primaryOfficerFilter = externalFilters.primary_officer_id as
42-
| string[]
43-
| undefined;
44-
const isDeputyDirectorFilter =
45-
(externalFilters.is_deputy_director as string | undefined) === "true";
46-
4740
return generateDynamicSections(
4841
inspectionRecords,
4942
orderRecords,
5043
warningLetters,
5144
administrativePenalties,
52-
primaryOfficerFilter,
53-
isDeputyDirectorFilter
45+
externalFilters
5446
);
5547
}, [
5648
inspectionRecords,
@@ -105,31 +97,7 @@ function ReviewBoard() {
10597
setInitialChecked(derivedSwitchState);
10698
}
10799
} else {
108-
// No cached filters - apply default "My Files" filter for first-time users
109-
if (currentUser?.profile?.preferred_username && staffUsers) {
110-
const currentStaff = staffUsers.find(
111-
(staff) =>
112-
staff.auth_user_guid === currentUser.profile.preferred_username
113-
);
114-
if (currentStaff) {
115-
const isDeputyDirector =
116-
currentStaff.position_id === STAFF_USER_POSITION.DEPUTY_DIRECTOR;
117-
const defaultExternalFilters = {
118-
primary_officer_id: [currentStaff.id.toString()],
119-
is_deputy_director: isDeputyDirector.toString(),
120-
};
121-
122-
setExternalFilters(defaultExternalFilters);
123-
setInitialChecked(true);
124-
125-
// Update prevFilters to prevent unnecessary caching during initial setup
126-
prevFilters.current = {
127-
...prevFilters.current,
128-
externalFilters: defaultExternalFilters,
129-
initialChecked: true,
130-
};
131-
}
132-
}
100+
// No cached filters - do not auto-apply any filters or switch state
133101
}
134102

135103
// Mark restoration as complete and initial load as complete

0 commit comments

Comments
 (0)