diff --git a/backend/src/types/models/complaints/complaint-filter-parameters.ts b/backend/src/types/models/complaints/complaint-filter-parameters.ts index e8cc04555..72c83b422 100644 --- a/backend/src/types/models/complaints/complaint-filter-parameters.ts +++ b/backend/src/types/models/complaints/complaint-filter-parameters.ts @@ -13,4 +13,6 @@ export interface ComplaintFilterParameters { complaintMethod?: string; actionTaken?: string; outcomeAnimal?: string; + outcomeAnimalStartDate?: Date; + outcomeAnimalEndDate?: Date; } diff --git a/backend/src/v1/complaint/complaint.service.ts b/backend/src/v1/complaint/complaint.service.ts index 133c2795f..114bf62fd 100644 --- a/backend/src/v1/complaint/complaint.service.ts +++ b/backend/src/v1/complaint/complaint.service.ts @@ -817,9 +817,11 @@ export class ComplaintService { private readonly _getComplaintsByOutcomeAnimal = async ( token: string, outcomeAnimalCode: string, + startDate: Date | undefined, + endDate: Date | undefined, ): Promise => { const { data, errors } = await get(token, { - query: `{getLeadsByOutcomeAnimal (outcomeAnimalCode: "${outcomeAnimalCode}")}`, + query: `{getLeadsByOutcomeAnimal (outcomeAnimalCode: "${outcomeAnimalCode}", startDate: "${startDate}" , endDate: "${endDate}")}`, }); if (errors) { this.logger.error("GraphQL errors:", errors); @@ -975,8 +977,13 @@ export class ComplaintService { } // -- filter by complaint identifiers returned by case management if outcome animal filter is present - if (agency === "COS" && filters.outcomeAnimal) { - const complaintIdentifiers = await this._getComplaintsByOutcomeAnimal(token, filters.outcomeAnimal); + if (agency === "COS" && (filters.outcomeAnimal || filters.outcomeAnimalStartDate)) { + const complaintIdentifiers = await this._getComplaintsByOutcomeAnimal( + token, + filters.outcomeAnimal, + filters.outcomeAnimalStartDate, + filters.outcomeAnimalEndDate, + ); builder.andWhere("complaint.complaint_identifier IN(:...complaint_identifiers)", { complaint_identifiers: complaintIdentifiers, @@ -1144,8 +1151,13 @@ export class ComplaintService { } // -- filter by complaint identifiers returned by case management if outcome animal filter is present - if (agency === "COS" && filters.outcomeAnimal) { - const complaintIdentifiers = await this._getComplaintsByOutcomeAnimal(token, filters.outcomeAnimal); + if (agency === "COS" && (filters.outcomeAnimal || filters.outcomeAnimalStartDate)) { + const complaintIdentifiers = await this._getComplaintsByOutcomeAnimal( + token, + filters.outcomeAnimal, + filters.outcomeAnimalStartDate, + filters.outcomeAnimalEndDate, + ); complaintBuilder.andWhere("complaint.complaint_identifier IN(:...complaint_identifiers)", { complaint_identifiers: complaintIdentifiers, }); diff --git a/frontend/src/app/components/common/filter-date.tsx b/frontend/src/app/components/common/filter-date.tsx new file mode 100644 index 000000000..2e547dda8 --- /dev/null +++ b/frontend/src/app/components/common/filter-date.tsx @@ -0,0 +1,97 @@ +import { FC } from "react"; +import DatePicker from "react-datepicker"; + +interface Props { + id: string; + label: string; + startDate: Date | undefined; + endDate: Date | undefined; + handleDateChange: (dates: [Date, Date]) => void; +} + +export const FilterDate: FC = ({ id, label, startDate, endDate, handleDateChange }) => { + // manual entry of date change listener. Looks for a date range format of {yyyy-mm-dd} - {yyyy-mm-dd} + const handleManualDateChange = (e: React.ChangeEvent) => { + if (e?.target?.value?.includes(" - ")) { + const [startDateStr, endDateStr] = e.target.value.split(" - "); + const startDate = new Date(startDateStr); + const endDate = new Date(endDateStr); + + if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) { + // Invalid date format + return [null, null]; + } else { + // add 1 to date because days start at 0 + startDate.setDate(startDate.getDate() + 1); + endDate.setDate(endDate.getDate() + 1); + + handleDateChange([startDate, endDate]); + } + } + return [null, null]; + }; + + return ( +
+ +
+ ( +
+ + + {monthDate.toLocaleString("en-US", { + month: "long", + year: "numeric", + })} + + +
+ )} + selected={startDate} + onChange={handleDateChange} + onChangeRaw={handleManualDateChange} + startDate={startDate} + endDate={endDate} + dateFormat="yyyy-MM-dd" + monthsShown={2} + selectsRange={true} + isClearable={true} + wrapperClassName="comp-filter-calendar-input" + showPreviousMonths + maxDate={new Date()} + /> +
+
+ ); +}; diff --git a/frontend/src/app/components/containers/complaints/complaint-filter-bar.tsx b/frontend/src/app/components/containers/complaints/complaint-filter-bar.tsx index 3f82e76c4..4fddf27b7 100644 --- a/frontend/src/app/components/containers/complaints/complaint-filter-bar.tsx +++ b/frontend/src/app/components/containers/complaints/complaint-filter-bar.tsx @@ -43,9 +43,11 @@ export const ComplaintFilterBar: FC = ({ complaintMethod, actionTaken, outcomeAnimal, + outcomeAnimalStartDate, + outcomeAnimalEndDate, } = state; - const dateRangeLabel = (): string | undefined => { + const dateRangeLabel = (startDate: Date | undefined | null, endDate: Date | undefined | null): string | undefined => { const currentDate = new Date().toLocaleDateString(); if (startDate !== null && endDate !== null) { return `${startDate?.toLocaleDateString()} - ${endDate?.toLocaleDateString()}`; @@ -58,7 +60,7 @@ export const ComplaintFilterBar: FC = ({ } }; - const hasDate = () => { + const hasDate = (startDate: Date | undefined | null, endDate: Date | undefined | null) => { if ((startDate === undefined || startDate === null) && (endDate === undefined || endDate === null)) { return false; } @@ -78,6 +80,10 @@ export const ComplaintFilterBar: FC = ({ dispatch(clearFilter("startDate")); dispatch(clearFilter("endDate")); break; + case "outcomeAnimalDateRange": + dispatch(clearFilter("outcomeAnimalStartDate")); + dispatch(clearFilter("outcomeAnimalEndDate")); + break; default: dispatch(clearFilter(name)); break; @@ -133,10 +139,10 @@ export const ComplaintFilterBar: FC = ({ /> )} - {hasDate() && ( + {hasDate(startDate, endDate) && ( @@ -240,6 +246,15 @@ export const ComplaintFilterBar: FC = ({ clear={removeFilter} /> )} + + {hasDate(outcomeAnimalStartDate, outcomeAnimalEndDate) && ( + + )} ); diff --git a/frontend/src/app/components/containers/complaints/complaint-filter.tsx b/frontend/src/app/components/containers/complaints/complaint-filter.tsx index 48f66d3fd..b4e01203f 100644 --- a/frontend/src/app/components/containers/complaints/complaint-filter.tsx +++ b/frontend/src/app/components/containers/complaints/complaint-filter.tsx @@ -17,7 +17,6 @@ import { import { selectOfficersByAgencyDropdownUsingPersonGuid } from "@store/reducers/officer"; import { selectDecisionTypeDropdown } from "@store/reducers/code-table-selectors"; import COMPLAINT_TYPES from "@apptypes/app/complaint-types"; -import DatePicker from "react-datepicker"; import { CompSelect } from "@components/common/comp-select"; import { ComplaintFilterContext } from "@providers/complaint-filter-provider"; import { ComplaintFilterPayload, updateFilter } from "@store/reducers/complaint-filters"; @@ -25,6 +24,7 @@ import Option from "@apptypes/app/option"; import { listActiveFilters } from "@store/reducers/app"; import UserService from "@service/user-service"; import Roles from "@apptypes/app/roles"; +import { FilterDate } from "../../common/filter-date"; type Props = { type: string; @@ -47,6 +47,8 @@ export const ComplaintFilter: FC = ({ type }) => { complaintMethod, actionTaken, outcomeAnimal, + outcomeAnimalStartDate, + outcomeAnimalEndDate, }, dispatch, } = useContext(ComplaintFilterContext); @@ -96,24 +98,20 @@ export const ComplaintFilter: FC = ({ type }) => { setFilter("endDate", end); }; - // manual entry of date change listener. Looks for a date range format of {yyyy-mm-dd} - {yyyy-mm-dd} - const handleManualDateRangeChange = (e: React.ChangeEvent) => { - if (e?.target?.value?.includes(" - ")) { - const [startDateStr, endDateStr] = e.target.value.split(" - "); - const startDate = new Date(startDateStr); - const endDate = new Date(endDateStr); - - if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) { - // Invalid date format - return [null, null]; - } else { - // add 1 to date because days start at 0 - startDate.setDate(startDate.getDate() + 1); - endDate.setDate(endDate.getDate() + 1); - handleDateRangeChange([startDate, endDate]); - } + const handleOutcomeDateRangeChange = (dates: [Date, Date]) => { + const [start, end] = dates; + //set the time to be end of day to ensure that we also search for records after the beginning of the selected day. + if (start) { + start.setHours(0, 0, 0); + start.setMilliseconds(0); + } + if (end) { + end.setHours(23, 59, 59); + end.setMilliseconds(999); } - return [null, null]; + + setFilter("outcomeAnimalStartDate", start); + setFilter("outcomeAnimalEndDate", end); }; ///-- @@ -221,67 +219,13 @@ export const ComplaintFilter: FC = ({ type }) => { )} {activeFilters.showDateFilter && ( -
- -
- ( -
- - - {monthDate.toLocaleString("en-US", { - month: "long", - year: "numeric", - })} - - -
- )} - selected={startDate} - onChange={handleDateRangeChange} - onChangeRaw={handleManualDateRangeChange} - startDate={startDate} - endDate={endDate} - dateFormat="yyyy-MM-dd" - monthsShown={2} - selectsRange={true} - isClearable={true} - wrapperClassName="comp-filter-calendar-input" - showPreviousMonths - maxDate={new Date()} - /> -
-
+ )} {activeFilters.showStatusFilter && ( @@ -364,7 +308,7 @@ export const ComplaintFilter: FC = ({ type }) => { setFilter("outcomeAnimal", option); }} classNames={{ - menu: () => "top-layer-select", + menu: () => "top-layer-select outcome-animal-select", }} options={outcomeAnimalTypes} placeholder="Select" @@ -375,6 +319,16 @@ export const ComplaintFilter: FC = ({ type }) => { )} + + {COMPLAINT_TYPES.HWCR === type && activeFilters.showOutcomeAnimalDateFilter && ( + + )} ); }; diff --git a/frontend/src/app/components/containers/complaints/complaint-list.tsx b/frontend/src/app/components/containers/complaints/complaint-list.tsx index a3e543033..5367adbf4 100644 --- a/frontend/src/app/components/containers/complaints/complaint-list.tsx +++ b/frontend/src/app/components/containers/complaints/complaint-list.tsx @@ -55,6 +55,8 @@ export const generateComplaintRequestPayload = ( complaintMethod, actionTaken, outcomeAnimal, + outcomeAnimalStartDate, + outcomeAnimalEndDate, } = filters; const common = { @@ -92,6 +94,8 @@ export const generateComplaintRequestPayload = ( speciesCodeFilter: species, natureOfComplaintFilter: natureOfComplaint, outcomeAnimalFilter: outcomeAnimal, + outcomeAnimalStartDateFilter: outcomeAnimalStartDate, + outcomeAnimalEndDateFilter: outcomeAnimalEndDate, } as ComplaintRequestPayload; } }; diff --git a/frontend/src/app/components/containers/complaints/complaint-map.tsx b/frontend/src/app/components/containers/complaints/complaint-map.tsx index e0f3b5aba..63c872f42 100644 --- a/frontend/src/app/components/containers/complaints/complaint-map.tsx +++ b/frontend/src/app/components/containers/complaints/complaint-map.tsx @@ -38,6 +38,8 @@ export const generateMapComplaintRequestPayload = ( complaintMethod, actionTaken, outcomeAnimal, + outcomeAnimalStartDate, + outcomeAnimalEndDate, } = filters; const common = { @@ -67,6 +69,8 @@ export const generateMapComplaintRequestPayload = ( speciesCodeFilter: species, natureOfComplaintFilter: natureOfComplaint, outcomeAnimalFilter: outcomeAnimal, + outcomeAnimalStartDateFilter: outcomeAnimalStartDate, + outcomeAnimalEndDateFilter: outcomeAnimalEndDate, } as ComplaintRequestPayload; } }; diff --git a/frontend/src/app/constants/feature-flag-types.ts b/frontend/src/app/constants/feature-flag-types.ts index 309fdfb78..1a93cfa6a 100644 --- a/frontend/src/app/constants/feature-flag-types.ts +++ b/frontend/src/app/constants/feature-flag-types.ts @@ -18,4 +18,5 @@ export const FEATURE_TYPES = { ENABLE_OFFICE: "ENBL_OFF", EXTERNAL_FILE_REFERENCE: "EXTRNALREF", OUTCOME_ANIMAL_FILTER: "OUT_A_FLTR", + OUTCOME_ANIMAL_DATE_FILTER: "OUT_D_FLTR", }; diff --git a/frontend/src/app/providers/complaint-filter-provider.tsx b/frontend/src/app/providers/complaint-filter-provider.tsx index 9f4678565..ab1229205 100644 --- a/frontend/src/app/providers/complaint-filter-provider.tsx +++ b/frontend/src/app/providers/complaint-filter-provider.tsx @@ -28,6 +28,8 @@ let initialState: ComplaintFilters = { complaintMethod: null, actionTaken: null, outcomeAnimal: null, + outcomeAnimalStartDate: undefined, + outcomeAnimalEndDate: undefined, }; const mapFilters = (complaintFilters: Partial) => { @@ -51,6 +53,8 @@ const mapFilters = (complaintFilters: Partial) => { speciesCodeFilter, natureOfComplaintFilter, outcomeAnimalFilter, + outcomeAnimalStartDateFilter, + outcomeAnimalEndDateFilter, } = complaintFilters; // Parse the start and end date filters into Date objects if they exist. @@ -63,6 +67,17 @@ const mapFilters = (complaintFilters: Partial) => { parsedEndDate = new Date(); } + //Parse outcomeAnimalDates + const parsedOutcomeAnimalStartDate = outcomeAnimalStartDateFilter + ? new Date(outcomeAnimalStartDateFilter) + : undefined; + let parsedOutcomeAnimalEndDate = undefined; + if (outcomeAnimalEndDateFilter) { + parsedOutcomeAnimalEndDate = new Date(outcomeAnimalEndDateFilter); + } else if (parsedOutcomeAnimalStartDate) { + parsedOutcomeAnimalEndDate = new Date(); + } + const allFilters: Partial = { region: regionCodeFilter, zone: zoneCodeFilter, @@ -78,6 +93,8 @@ const mapFilters = (complaintFilters: Partial) => { girType: girTypeFilter, actionTaken: actionTakenFilter, outcomeAnimal: outcomeAnimalFilter, + outcomeAnimalStartDate: parsedOutcomeAnimalStartDate, + outcomeAnimalEndDate: parsedOutcomeAnimalEndDate, }; // Only return filters that have a value set diff --git a/frontend/src/app/store/reducers/app.ts b/frontend/src/app/store/reducers/app.ts index b41c02a54..28a7d6feb 100644 --- a/frontend/src/app/store/reducers/app.ts +++ b/frontend/src/app/store/reducers/app.ts @@ -335,6 +335,9 @@ export const listActiveFilters = showOutcomeAnimalFilter: features.some( (feature: any) => feature.featureCode === FEATURE_TYPES.OUTCOME_ANIMAL_FILTER && feature.isActive === true, ), + showOutcomeAnimalDateFilter: features.some( + (feature: any) => feature.featureCode === FEATURE_TYPES.OUTCOME_ANIMAL_DATE_FILTER && feature.isActive === true, + ), }; return filters; }; diff --git a/frontend/src/app/store/reducers/complaints.ts b/frontend/src/app/store/reducers/complaints.ts index ab21563ce..8d9eb94a9 100644 --- a/frontend/src/app/store/reducers/complaints.ts +++ b/frontend/src/app/store/reducers/complaints.ts @@ -305,6 +305,8 @@ export const getComplaints = complaintMethodFilter, actionTakenFilter, outcomeAnimalFilter, + outcomeAnimalStartDateFilter, + outcomeAnimalEndDateFilter, page, pageSize, query, @@ -331,6 +333,8 @@ export const getComplaints = complaintMethod: complaintMethodFilter?.value, actionTaken: actionTakenFilter?.value, outcomeAnimal: outcomeAnimalFilter?.value, + outcomeAnimalStartDate: outcomeAnimalStartDateFilter, + outcomeAnimalEndDate: outcomeAnimalEndDateFilter, page: page, pageSize: pageSize, query: query, @@ -364,6 +368,8 @@ export const getMappedComplaints = complaintMethodFilter, actionTakenFilter, outcomeAnimalFilter, + outcomeAnimalStartDateFilter, + outcomeAnimalEndDateFilter, page, pageSize, query, @@ -389,6 +395,8 @@ export const getMappedComplaints = complaintMethod: complaintMethodFilter?.value, actionTaken: actionTakenFilter?.value, outcomeAnimal: outcomeAnimalFilter?.value, + outcomeAnimalStartDate: outcomeAnimalStartDateFilter, + outcomeAnimalEndDate: outcomeAnimalEndDateFilter, page: page, pageSize: pageSize, query: query, diff --git a/frontend/src/app/types/app/active-filters.ts b/frontend/src/app/types/app/active-filters.ts index 0ad55384f..6ebe6d993 100644 --- a/frontend/src/app/types/app/active-filters.ts +++ b/frontend/src/app/types/app/active-filters.ts @@ -12,4 +12,5 @@ export interface ActiveFilters { showViolationFilter: boolean; showZoneFilter: boolean; showOutcomeAnimalFilter: boolean; + showOutcomeAnimalDateFilter: boolean; } diff --git a/frontend/src/app/types/complaints/complaint-filters.ts b/frontend/src/app/types/complaints/complaint-filters.ts index 68100e079..5b1c08c86 100644 --- a/frontend/src/app/types/complaints/complaint-filters.ts +++ b/frontend/src/app/types/complaints/complaint-filters.ts @@ -17,6 +17,8 @@ export interface ComplaintFilters { complaintMethodFilter?: Option; actionTakenFilter?: Option; outcomeAnimalFilter?: Option; + outcomeAnimalStartDateFilter?: Date; + outcomeAnimalEndDateFilter?: Date; page?: number; pageSize?: number; query?: string; diff --git a/frontend/src/app/types/complaints/complaint-filters/complaint-filters.ts b/frontend/src/app/types/complaints/complaint-filters/complaint-filters.ts index 88f9b8d5d..5dfdb585c 100644 --- a/frontend/src/app/types/complaints/complaint-filters/complaint-filters.ts +++ b/frontend/src/app/types/complaints/complaint-filters/complaint-filters.ts @@ -22,6 +22,8 @@ export type ComplaintFilters = { actionTaken?: DropdownOption | null; outcomeAnimal?: DropdownOption | null; + outcomeAnimalStartDate?: Date; + outcomeAnimalEndDate?: Date; filters: Array; }; diff --git a/frontend/src/assets/sass/complaint.scss b/frontend/src/assets/sass/complaint.scss index 874b921ba..f8092e479 100644 --- a/frontend/src/assets/sass/complaint.scss +++ b/frontend/src/assets/sass/complaint.scss @@ -1363,6 +1363,13 @@ p { z-index: 7000 !important; } +.top-layer-select.outcome-animal-select { + max-height: 250px !important; + .comp-select__menu-list { + max-height: 250px !important; + } +} + .comp-details-edit-container { display: flex; } diff --git a/migrations/migrations/R__Create-Test-Data.sql b/migrations/migrations/R__Create-Test-Data.sql index 76576b0c0..7f603cd0d 100644 --- a/migrations/migrations/R__Create-Test-Data.sql +++ b/migrations/migrations/R__Create-Test-Data.sql @@ -4347,6 +4347,30 @@ SELECT now() ON CONFLICT DO NOTHING; +INSERT INTO + feature_code ( + feature_code, + short_description, + long_description, + display_order, + active_ind, + create_user_id, + create_utc_timestamp, + update_user_id, + update_utc_timestamp + ) +SELECT + 'OUT_D_FLTR', + 'Outcome Animal Date Filter', + 'Outcome Animal Date Filter', + 190, + 'Y', + user, + now(), + user, + now() ON CONFLICT +DO NOTHING; + ------------------------- -- Insert Feature / Agency XREF ------------------------- @@ -4370,6 +4394,26 @@ SELECT now() ON CONFLICT DO NOTHING; +INSERT INTO + feature_agency_xref ( + feature_code, + agency_code, + active_ind, + create_user_id, + create_utc_timestamp, + update_user_id, + update_utc_timestamp + ) +SELECT + 'OUT_D_FLTR', + 'COS', + 'Y', + user, + now(), + user, + now() ON CONFLICT +DO NOTHING; + INSERT INTO feature_agency_xref ( feature_code,