Skip to content

Commit 5f93af1

Browse files
jcabannesZorin95670
authored andcommitted
feat(catalog-ui): add "Date Search Filter Panel" component
Close #164
1 parent e582082 commit 5f93af1

6 files changed

Lines changed: 632 additions & 0 deletions

File tree

apps/catalog-ui/docs/components/smart-filter/LinidSmartFilter.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,13 @@ The component uses the LinID design system through `useUiDesign()` and applies p
112112
| `{uiNamespace}.linid-smart-filter.linid-favorite-panel.content.no-data-label-section` | `q-item-section` | Label section for no-data item (name) |
113113
| `{uiNamespace}.linid-smart-filter.tree-search-filter-panel` | `GenericTree` | Tree component styling and configuration |
114114
| `{uiNamespace}.linid-smart-filter.tree-search-filter-panel` | `q-btn` | Search button styling |
115+
| `{uiNamespace}.linid-smart-filter.date-search-filter-panel` | `q-input` | Date text input field styling |
116+
| `{uiNamespace}.linid-smart-filter.date-search-filter-panel` | `q-icon` | Calendar icon trigger styling |
117+
| `{uiNamespace}.linid-smart-filter.date-search-filter-panel` | `q-date` | Date picker styling |
118+
| `{uiNamespace}.linid-smart-filter.date-search-filter-panel` | `q-checkbox` | Negation checkbox styling |
119+
| `{uiNamespace}.linid-smart-filter.date-search-filter-panel` | `q-option-group` | Operator radio group styling |
120+
| `{uiNamespace}.linid-smart-filter.date-search-filter-panel` | `q-btn` | Search button styling |
121+
| `{uiNamespace}.linid-smart-filter.date-search-filter-panel.close` | `q-btn` | Date picker close button styling |
115122

116123
- **Design Configuration Example:** See [design.md](../../design.md#LinidSmartFilter).
117124

@@ -161,6 +168,19 @@ The component uses scoped i18n with the following translation keys:
161168
| `[INSTANCE_ID].LinidSmartFilter.LinidFavoritePanel.deleteButton` | Label for delete button (optional, defaults to empty string) | QBtn label | - |
162169
| `[INSTANCE_ID].LinidSmartFilter.TreeSearchFilterPanel.searchButton` | Search button label | QBtn label | - |
163170
| `[INSTANCE_ID].LinidSmartFilter.ListSearchFilterPanel.searchButton` | Search button label | QBtn label | - |
171+
| `[INSTANCE_ID].LinidSmartFilter.DateSearchFilterPanel.inputLabel` | Date input label (optional, defaults to empty string) | QInput label | - |
172+
| `[INSTANCE_ID].LinidSmartFilter.DateSearchFilterPanel.inputHint` | Date input hint (optional, defaults to empty string) | QInput hint | - |
173+
| `[INSTANCE_ID].LinidSmartFilter.DateSearchFilterPanel.inputPrefix` | Date input prefix (optional, defaults to empty string) | QInput prefix | - |
174+
| `[INSTANCE_ID].LinidSmartFilter.DateSearchFilterPanel.inputSuffix` | Date input suffix (optional, defaults to empty string) | QInput suffix | - |
175+
| `[INSTANCE_ID].LinidSmartFilter.DateSearchFilterPanel.negateLabel` | Negation checkbox label | QCheckbox label | - |
176+
| `[INSTANCE_ID].LinidSmartFilter.DateSearchFilterPanel.close` | Date picker close button label | QBtn label | - |
177+
| `[INSTANCE_ID].LinidSmartFilter.DateSearchFilterPanel.searchButton` | Search button label | QBtn label | - |
178+
| `[INSTANCE_ID].LinidSmartFilter.DateSearchFilterPanel.operators.inferior` | "inferior" operator label | Radio option label | - |
179+
| `[INSTANCE_ID].LinidSmartFilter.DateSearchFilterPanel.operators.superior` | "superior" operator label | Radio option label | - |
180+
| `[INSTANCE_ID].LinidSmartFilter.DateSearchFilterPanel.operators.equals` | "equals" operator label | Radio option label | - |
181+
| `[INSTANCE_ID].LinidSmartFilter.DateSearchFilterPanel.operators.notInferior` | Negated "inferior" label | Radio option label | - |
182+
| `[INSTANCE_ID].LinidSmartFilter.DateSearchFilterPanel.operators.notSuperior` | Negated "superior" label | Radio option label | - |
183+
| `[INSTANCE_ID].LinidSmartFilter.DateSearchFilterPanel.operators.notEquals` | Negated "equals" label | Radio option label | - |
164184

165185
Example:
166186

@@ -224,6 +244,23 @@ Example:
224244
},
225245
"ListSearchFilterPanel": {
226246
"searchButton": "Search"
247+
},
248+
"DateSearchFilterPanel": {
249+
"inputLabel": "Date",
250+
"inputHint": "Select or enter a date",
251+
"inputPrefix": "",
252+
"inputSuffix": "(UTC)",
253+
"negateLabel": "Negate",
254+
"close": "Close",
255+
"searchButton": "Search",
256+
"operators": {
257+
"inferior": "inferior",
258+
"superior": "superior",
259+
"equals": "equals",
260+
"notInferior": "not inferior",
261+
"notSuperior": "not superior",
262+
"notEquals": "not equals"
263+
}
227264
}
228265
}
229266
}

apps/catalog-ui/docs/design.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,17 @@ Smart filtering component with toggle-able menu for managing active filters and
444444
"GenericTree": {
445445
"q-tree": {}
446446
}
447+
},
448+
"date-search-filter-panel": {
449+
"q-input": { "outlined": true, "dense": true },
450+
"q-icon": { "name": "event" },
451+
"q-date": { "today-btn": true },
452+
"q-checkbox": { "color": "primary", "dense": true },
453+
"q-option-group": { "color": "primary", "dense": true },
454+
"q-btn": { "color": "primary", "unelevated": true },
455+
"close": {
456+
"q-btn": { "flat": true }
457+
}
447458
}
448459
}
449460
}

apps/catalog-ui/docs/i18n.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,19 @@ The `FormDialog` component uses the following keys for internationalization:
449449
| `[INSTANCE_ID].LinidSmartFilter.LinidFavoritePanel.deleteButton` | Label for delete button (optional, defaults to empty string) | QBtn label | - |
450450
| `[INSTANCE_ID].LinidSmartFilter.TreeSearchFilterPanel.searchButton` | Search button label | QBtn label | - |
451451
| `[INSTANCE_ID].LinidSmartFilter.ListSearchFilterPanel.searchButton` | Search button label | QBtn label | - |
452+
| `[INSTANCE_ID].LinidSmartFilter.DateSearchFilterPanel.inputLabel` | Date input label (optional, defaults to empty string) | QInput label | - |
453+
| `[INSTANCE_ID].LinidSmartFilter.DateSearchFilterPanel.inputHint` | Date input hint (optional, defaults to empty string) | QInput hint | - |
454+
| `[INSTANCE_ID].LinidSmartFilter.DateSearchFilterPanel.inputPrefix` | Date input prefix (optional, defaults to empty string) | QInput prefix | - |
455+
| `[INSTANCE_ID].LinidSmartFilter.DateSearchFilterPanel.inputSuffix` | Date input suffix (optional, defaults to empty string) | QInput suffix | - |
456+
| `[INSTANCE_ID].LinidSmartFilter.DateSearchFilterPanel.negateLabel` | Negation checkbox label | QCheckbox label | - |
457+
| `[INSTANCE_ID].LinidSmartFilter.DateSearchFilterPanel.close` | Date picker close button label | QBtn label | - |
458+
| `[INSTANCE_ID].LinidSmartFilter.DateSearchFilterPanel.searchButton` | Search button label | QBtn label | - |
459+
| `[INSTANCE_ID].LinidSmartFilter.DateSearchFilterPanel.operators.inferior` | "inferior" operator label | Radio option label | - |
460+
| `[INSTANCE_ID].LinidSmartFilter.DateSearchFilterPanel.operators.superior` | "superior" operator label | Radio option label | - |
461+
| `[INSTANCE_ID].LinidSmartFilter.DateSearchFilterPanel.operators.equals` | "equals" operator label | Radio option label | - |
462+
| `[INSTANCE_ID].LinidSmartFilter.DateSearchFilterPanel.operators.notInferior` | Negated "inferior" label | Radio option label | - |
463+
| `[INSTANCE_ID].LinidSmartFilter.DateSearchFilterPanel.operators.notSuperior` | Negated "superior" label | Radio option label | - |
464+
| `[INSTANCE_ID].LinidSmartFilter.DateSearchFilterPanel.operators.notEquals` | Negated "equals" label | Radio option label | - |
452465

453466
**Example:**
454467

@@ -513,6 +526,23 @@ The `FormDialog` component uses the following keys for internationalization:
513526
},
514527
"ListSearchFilterPanel": {
515528
"searchButton": "Search"
529+
},
530+
"DateSearchFilterPanel": {
531+
"inputLabel": "Date",
532+
"inputHint": "Select or enter a date",
533+
"inputPrefix": "",
534+
"inputSuffix": "(UTC)",
535+
"negateLabel": "Negate",
536+
"close": "Close",
537+
"searchButton": "Search",
538+
"operators": {
539+
"inferior": "inferior",
540+
"superior": "superior",
541+
"equals": "equals",
542+
"notInferior": "not inferior",
543+
"notSuperior": "not superior",
544+
"notEquals": "not equals"
545+
}
516546
}
517547
}
518548
}
@@ -633,6 +663,21 @@ export default {
633663
ListSearchFilterPanel: {
634664
searchButton: 'Search',
635665
},
666+
DateSearchFilterPanel: {
667+
inputLabel: 'Date',
668+
inputHint: 'Select or enter a date',
669+
negateLabel: 'Negate',
670+
close: 'Close',
671+
searchButton: 'Search',
672+
operators: {
673+
inferior: 'inferior',
674+
superior: 'superior',
675+
equals: 'equals',
676+
notInferior: 'not inferior',
677+
notSuperior: 'not superior',
678+
notEquals: 'not equals',
679+
},
680+
},
636681
},
637682
[CONFIRMATION_DIALOG_NAME]: {
638683
title: 'Create user',
Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
<!--
2+
Copyright (C) 2026 Linagora
3+
4+
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
5+
Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option)
6+
any later version, provided you comply with the Additional Terms applicable for LinID Identity Manager software by
7+
LINAGORA pursuant to Section 7 of the GNU Affero General Public License, subsections (b), (c), and (e), pursuant to
8+
which these Appropriate Legal Notices must notably (i) retain the display of the "LinID™" trademark/logo at the top
9+
of the interface window, the display of the “You are using the Open Source and free version of LinID™, powered by
10+
Linagora © 2009–2013. Contribute to LinID R&D by subscribing to an Enterprise offer!” infobox and in the e-mails
11+
sent with the Program, notice appended to any type of outbound messages (e.g. e-mail and meeting requests) as well
12+
as in the LinID Identity Manager user interface, (ii) retain all hypertext links between LinID Identity Manager
13+
and https://linid.org/, as well as between LINAGORA and LINAGORA.com, and (iii) refrain from infringing LINAGORA
14+
intellectual property rights over its trademarks and commercial brands. Other Additional Terms apply, see
15+
<http://www.linagora.com/licenses/> for more details.
16+
17+
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
18+
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
19+
details.
20+
21+
You should have received a copy of the GNU Affero General Public License and its applicable Additional Terms for
22+
LinID Identity Manager along with this program. If not, see <http://www.gnu.org/licenses/> for the GNU Affero
23+
General Public License version 3 and <http://www.linagora.com/licenses/> for the Additional Terms applicable to the
24+
LinID Identity Manager software.
25+
-->
26+
27+
<template>
28+
<!-- v8 ignore start -->
29+
<div
30+
class="date-search-filter-panel"
31+
data-cy="date-search-filter-panel"
32+
>
33+
<q-input
34+
v-model="inputValue"
35+
v-bind="uiProps.input"
36+
type="text"
37+
:label="translateOrDefault('', 'inputLabel')"
38+
:hint="translateOrDefault('', 'inputHint')"
39+
:prefix="translateOrDefault('', 'inputPrefix')"
40+
:suffix="translateOrDefault('', 'inputSuffix')"
41+
:rules="rules"
42+
data-cy="date-search-filter-panel_input"
43+
>
44+
<template #append>
45+
<q-icon
46+
name="event"
47+
class="cursor-pointer"
48+
v-bind="uiProps.icon"
49+
>
50+
<q-popup-proxy
51+
cover
52+
transition-show="scale"
53+
transition-hide="scale"
54+
>
55+
<q-date
56+
v-model="inputValue"
57+
:mask="computedMask"
58+
v-bind="uiProps.date"
59+
data-cy="date-search-filter-panel_datepicker"
60+
>
61+
<div class="row items-center justify-end">
62+
<q-btn
63+
v-close-popup
64+
:label="t('close')"
65+
v-bind="uiProps.closeButton"
66+
/>
67+
</div>
68+
</q-date>
69+
</q-popup-proxy>
70+
</q-icon>
71+
</template>
72+
</q-input>
73+
74+
<q-checkbox
75+
v-model="isNegation"
76+
v-bind="uiProps.checkbox"
77+
:label="t('negateLabel')"
78+
data-cy="date-search-filter-panel_negate"
79+
/>
80+
81+
<q-option-group
82+
v-model="selectedOperatorKey"
83+
v-bind="uiProps.optionGroup"
84+
:options="operators"
85+
type="radio"
86+
data-cy="date-search-filter-panel_operators"
87+
/>
88+
89+
<q-btn
90+
v-bind="uiProps.searchButton"
91+
:label="t('searchButton')"
92+
data-cy="date-search-filter-panel_search"
93+
@click="onSearch"
94+
/>
95+
</div>
96+
<!-- v8 ignore stop -->
97+
</template>
98+
99+
<script setup lang="ts">
100+
import type {
101+
LinidFilterOperator,
102+
LinidQBtnProps,
103+
LinidQCheckboxProps,
104+
LinidQDateProps,
105+
LinidQIconProps,
106+
LinidQInputProps,
107+
LinidQOptionGroupProps,
108+
} from '@linagora/linid-im-front-corelib';
109+
import {
110+
getI18nInstance,
111+
LinidFilterValue,
112+
QDATE_DEFAULT_MASK,
113+
useQuasarFieldValidation,
114+
useScopedI18n,
115+
useUiDesign,
116+
} from '@linagora/linid-im-front-corelib';
117+
import type { ValidationRule } from 'quasar';
118+
import { computed, ref } from 'vue';
119+
import type {
120+
DateFilterOperatorKey,
121+
DateSearchFilterPanelProps,
122+
DateSearchFilterPanelUIProps,
123+
} from '../../types/dateSearchFilterPanel';
124+
import type { LinidFilterPanelSearchOutputs } from '../../types/linidFilterPanel';
125+
126+
const OPERATOR_KEYS: DateFilterOperatorKey[] = [
127+
'inferior',
128+
'superior',
129+
'equals',
130+
];
131+
132+
const OPERATOR_MAP: Record<DateFilterOperatorKey, LinidFilterOperator> = {
133+
inferior: 'lt_',
134+
superior: 'gt_',
135+
equals: '',
136+
};
137+
138+
const props = defineProps<DateSearchFilterPanelProps>();
139+
const emit = defineEmits<LinidFilterPanelSearchOutputs>();
140+
141+
const { t, translateOrDefault } = useScopedI18n(
142+
`${props.i18nScope}.DateSearchFilterPanel`
143+
);
144+
const { ui } = useUiDesign();
145+
146+
const localUiNamespace = `${props.uiNamespace}.date-search-filter-panel`;
147+
148+
const uiProps: DateSearchFilterPanelUIProps = {
149+
input: ui<LinidQInputProps>(localUiNamespace, 'q-input'),
150+
icon: ui<LinidQIconProps>(localUiNamespace, 'q-icon'),
151+
date: ui<LinidQDateProps>(localUiNamespace, 'q-date'),
152+
closeButton: ui<LinidQBtnProps>(`${localUiNamespace}.close`, 'q-btn'),
153+
checkbox: ui<LinidQCheckboxProps>(localUiNamespace, 'q-checkbox'),
154+
optionGroup: ui<LinidQOptionGroupProps>(localUiNamespace, 'q-option-group'),
155+
searchButton: ui<LinidQBtnProps>(localUiNamespace, 'q-btn'),
156+
};
157+
158+
const inputValue = ref('');
159+
const isNegation = ref(false);
160+
const selectedOperatorKey = ref<DateFilterOperatorKey>('equals');
161+
162+
const computedMask = computed(() => {
163+
const { te, t } = getI18nInstance().global;
164+
if (props.maskI18NKey && te(props.maskI18NKey)) {
165+
return t(props.maskI18NKey);
166+
}
167+
return props.mask ?? QDATE_DEFAULT_MASK;
168+
});
169+
170+
const { validDate } = useQuasarFieldValidation(
171+
`${props.i18nScope}.DateSearchFilterPanel`
172+
);
173+
174+
const rules = computed<ValidationRule[]>(() => [validDate(computedMask.value)]);
175+
176+
const operators = computed(() =>
177+
OPERATOR_KEYS.map((key) => ({
178+
value: key,
179+
label: isNegation.value
180+
? t(`operators.not${key.charAt(0).toUpperCase()}${key.slice(1)}`)
181+
: t(`operators.${key}`),
182+
}))
183+
);
184+
185+
/**
186+
* Formats the raw input value into the date string expected by the backend for the selected operator.
187+
* @returns The formatted date string.
188+
*/
189+
function buildDateValue(): string {
190+
const v = inputValue.value;
191+
switch (selectedOperatorKey.value) {
192+
case 'equals':
193+
return `${v} 00:00:00_bt_${v} 23:59:59`;
194+
case 'inferior':
195+
return `${v} 00:00:00`;
196+
case 'superior':
197+
return `${v} 23:59:59`;
198+
}
199+
}
200+
201+
/**
202+
* Builds the LinidFilterValue for the current form state.
203+
* @returns The constructed filter value.
204+
*/
205+
function buildFilterValue(): LinidFilterValue {
206+
return new LinidFilterValue(
207+
isNegation.value,
208+
OPERATOR_MAP[selectedOperatorKey.value],
209+
buildDateValue()
210+
);
211+
}
212+
213+
/**
214+
* Emits the search event with the constructed filter value.
215+
*/
216+
function onSearch(): void {
217+
emit('search', {
218+
field: props.fieldName,
219+
values: [buildFilterValue()],
220+
});
221+
}
222+
</script>

0 commit comments

Comments
 (0)