Skip to content

Commit c9b4e75

Browse files
committed
ISSUE #5374 - set default tickets filters
1 parent 8fa75e4 commit c9b4e75

File tree

6 files changed

+129
-76
lines changed

6 files changed

+129
-76
lines changed

frontend/src/v5/store/tickets/card/ticketsCard.redux.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export const { Types: TicketsCardTypes, Creators: TicketsCardActions } = createA
2828
setSelectedTicket: ['ticketId'],
2929
setSelectedTemplate: ['templateId'],
3030
setSelectedTicketPin: ['pinId'],
31+
setFilters: ['filters'],
3132
upsertFilter: ['filter'],
3233
deleteFilter: ['filter'],
3334
resetFilters: [],
@@ -93,6 +94,13 @@ export const setPinToDrop = (state: ITicketsCardState, { pinToDrop }: SetPinToDr
9394
};
9495

9596
const getFilterKey = ({ module, property, type }: CardFilter): TicketFilterKey => `${module}.${property}.${type}`;
97+
export const setFilters = (state: ITicketsCardState, { filters }: SetFiltersAction) => {
98+
filters.forEach((filter) => {
99+
const path = getFilterKey(filter);
100+
state.filters[path] = filter.filter;
101+
});
102+
};
103+
96104
export const upsertFilter = (state: ITicketsCardState, { filter }: UpsertFilterAction) => {
97105
const path = getFilterKey(filter);
98106
state.filters[path] = filter.filter;
@@ -144,6 +152,7 @@ export const ticketsCardReducer = createReducer(INITIAL_STATE, produceAll({
144152
[TicketsCardTypes.SET_SELECTED_TEMPLATE]: setSelectedTemplate,
145153
[TicketsCardTypes.SET_SELECTED_TICKET_PIN]: setSelectedTicketPin,
146154
[TicketsCardTypes.SET_PIN_TO_DROP]: setPinToDrop,
155+
[TicketsCardTypes.SET_FILTERS]: setFilters,
147156
[TicketsCardTypes.UPSERT_FILTER]: upsertFilter,
148157
[TicketsCardTypes.DELETE_FILTER]: deleteFilter,
149158
[TicketsCardTypes.RESET_FILTERS]: resetFilters,
@@ -160,6 +169,7 @@ export type SetSelectedTicketAction = Action<'SET_SELECTED_TICKET'> & { ticketId
160169
export type SetSelectedTemplateAction = Action<'SET_SELECTED_TEMPLATE'> & { templateId: string };
161170
export type SetSelectedTicketPinAction = Action<'SET_SELECTED_TICKET_PIN'> & { pinId: string };
162171
export type SetPinToDropAction = Action<'SET_PIN_TO_DROP'> & { pinToDrop: string };
172+
export type SetFiltersAction = Action<'SET_FILTERS'> & { filters: CardFilter[] };
163173
export type UpsertFilterAction = Action<'UPSERT_FILTER'> & { filter: CardFilter };
164174
export type DeleteFilterAction = Action<'DELETE_FILTER'> & { filter: CardFilter };
165175
export type ResetFiltersAction = Action<'RESET_FILTERS'>;
@@ -179,6 +189,7 @@ export interface ITicketsCardActionCreators {
179189
setSelectedTemplate: (templateId: string) => SetSelectedTemplateAction,
180190
setSelectedTicketPin: (pinId: string) => SetSelectedTicketPinAction,
181191
setPinToDrop: (pinToDrop: string) => SetPinToDropAction,
192+
setFilters: (filters: CardFilter[]) => SetFiltersAction,
182193
upsertFilter: (filter: CardFilter) => UpsertFilterAction,
183194
deleteFilter: (filter: CardFilter) => DeleteFilterAction,
184195
resetFilters: () => ResetFiltersAction,

frontend/src/v5/ui/controls/chip/chip.types.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ export const STATUS_TYPE_MAP = {
176176
},
177177
};
178178

179-
enum TicketStatusDefaultValues {
179+
export enum TicketStatusDefaultValues {
180180
OPEN = 'Open',
181181
IN_PROGRESS = 'In Progress',
182182
FOR_APPROVAL = 'For Approval',
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/**
2+
* Copyright (C) 2025 3D Repo Ltd
3+
*
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU Affero General Public License as
6+
* published by the Free Software Foundation, either version 3 of the
7+
* License, or (at your option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU Affero General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU Affero General Public License
15+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
18+
import { TicketsCardActionsDispatchers, ViewerGuiActionsDispatchers } from '@/v5/services/actionsDispatchers';
19+
import { TicketsHooksSelectors } from '@/v5/services/selectorsHooks';
20+
import { isEmpty, uniq } from 'lodash';
21+
import { useParams } from 'react-router-dom';
22+
import { useEffect } from 'react';
23+
import { ViewerParams } from '../../routes.constants';
24+
import { Transformers, useSearchParam } from '../../useSearchParam';
25+
import { VIEWER_PANELS } from '@/v4/constants/viewerGui';
26+
import { CardFilter } from '@components/viewer/cards/cardFilters/cardFilters.types';
27+
import { StatusValue } from '@/v5/store/tickets/tickets.types';
28+
import { TicketStatusDefaultValues, TicketStatusTypes, TreatmentStatuses } from '@controls/chip/chip.types';
29+
import { selectStatusConfigByTemplateId } from '@/v5/store/tickets/tickets.selectors';
30+
import { getState } from '@/v5/helpers/redux.helpers';
31+
32+
const TICKET_CODE_REGEX = /^[a-zA-Z]{3}:\d+$/;
33+
export const DefaultTicketFiltersSetter = () => {
34+
const { containerOrFederation } = useParams<ViewerParams>();
35+
const [ticketSearchParam, setTicketSearchParam] = useSearchParam('ticketSearch', Transformers.STRING_ARRAY);
36+
37+
const tickets = TicketsHooksSelectors.selectTickets(containerOrFederation);
38+
const templates = TicketsHooksSelectors.selectTemplates(containerOrFederation);
39+
const hasTicketData = !isEmpty(tickets) && !isEmpty(templates);
40+
41+
const getTicketFiltersFromURL = (values): CardFilter[] => [{
42+
module: '',
43+
property: 'Ticket ID',
44+
type: 'ticketId',
45+
filter: {
46+
operator: 'eq',
47+
values,
48+
},
49+
}];
50+
51+
const getNonCompletedTicketFiltersByStatus = (): CardFilter => {
52+
const isCompletedValue = ({ type }: StatusValue) => [TicketStatusTypes.DONE, TicketStatusTypes.VOID].includes(type);
53+
const getValuesByTemplate = ({ _id }) => selectStatusConfigByTemplateId(getState(), containerOrFederation, _id).values;
54+
55+
const completedValueNames = templates
56+
.flatMap(getValuesByTemplate)
57+
.filter(isCompletedValue)
58+
.map((v) => v.name);
59+
60+
const values = uniq([
61+
TicketStatusDefaultValues.CLOSED,
62+
TicketStatusDefaultValues.VOID,
63+
...completedValueNames,
64+
]);
65+
66+
return {
67+
module: '',
68+
property: 'Status',
69+
type: 'oneOf',
70+
filter: {
71+
operator: 'neq',
72+
values,
73+
},
74+
};
75+
};
76+
const getNonCompletedTicketFiltersBySafetibase = (): CardFilter => ({
77+
module: 'safetibase',
78+
property: 'Treatment Status',
79+
type: 'oneOf',
80+
filter: {
81+
operator: 'neq',
82+
values: [
83+
TreatmentStatuses.REJECTED,
84+
TreatmentStatuses.VOID,
85+
],
86+
},
87+
});
88+
89+
const getNonCompletedTicketFilters = (): CardFilter[] => {
90+
let filters = [getNonCompletedTicketFiltersByStatus()];
91+
const hasSafetibase = templates.some((t) => t?.modules?.some((module) => module.type === 'safetibase'));
92+
if (hasSafetibase) {
93+
filters.push(getNonCompletedTicketFiltersBySafetibase());
94+
}
95+
return filters;
96+
};
97+
98+
useEffect(() => {
99+
if (hasTicketData) {
100+
const ticketCodes = ticketSearchParam.filter((query) => TICKET_CODE_REGEX.test(query)).map((q) => q.toUpperCase());
101+
const filters: CardFilter[] = ticketCodes.length ? getTicketFiltersFromURL(ticketCodes) : getNonCompletedTicketFilters();
102+
TicketsCardActionsDispatchers.setFilters(filters);
103+
ViewerGuiActionsDispatchers.setPanelVisibility(VIEWER_PANELS.TICKETS, true);
104+
setTicketSearchParam();
105+
}
106+
}, [hasTicketData]);
107+
108+
return <></>;
109+
};

frontend/src/v5/ui/routes/viewer/handleTicketsCardSearchParams/handleTicketsCardSearchParams.component.tsx

Lines changed: 0 additions & 73 deletions
This file was deleted.

frontend/src/v5/ui/routes/viewer/viewer.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import { VIEWER_EVENTS } from '@/v4/constants/viewer';
2424
import { CheckLatestRevisionReadiness } from './checkLatestRevisionReadiness/checkLatestRevisionReadiness.container';
2525
import { ViewerParams } from '../routes.constants';
2626
import { InvalidContainerOverlay, InvalidFederationOverlay } from './invalidViewerOverlay';
27-
import { HandleTicketsCardSearchParams } from './handleTicketsCardSearchParams/handleTicketsCardSearchParams.component';
27+
import { DefaultTicketFiltersSetter } from './defaultTicketFiltersSetter/defaultTicketFiltersSetter.component';
2828
import { SpinnerLoader } from '@controls/spinnerLoader';
2929
import { CentredContainer } from '@controls/centredContainer';
3030
import { TicketsCardViews } from './tickets/tickets.constants';
@@ -111,7 +111,7 @@ export const Viewer = () => {
111111

112112
return (
113113
<>
114-
<HandleTicketsCardSearchParams />
114+
<DefaultTicketFiltersSetter />
115115
<OpenDrawingFromUrl />
116116
<CheckLatestRevisionReadiness />
117117
<ViewerCanvases />

frontend/test/tickets/card/ticketsCard.store.spec.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,12 @@ describe('Tickets: store', () => {
7878
const updatedTicketTitleCardFilter: CardFilter = { ...ticketTitleCardFilter, filter: editedBaseFilter };
7979

8080
describe('existing filters', () => {
81+
it('should set 2 filters', () => {
82+
dispatch(TicketsCardActions.setFilters([ticketIdCardFilter, ticketTitleCardFilter]));
83+
const filtersInStore = selectCardFilters(getState());
84+
expect(filtersInStore).toEqual([ticketIdCardFilter, ticketTitleCardFilter]);
85+
});
86+
8187
it('should add a filter', () => {
8288
dispatch(TicketsCardActions.upsertFilter(ticketTitleCardFilter));
8389
const filtersInStore = selectCardFilters(getState());

0 commit comments

Comments
 (0)