From 851e8e07a692efcdf2e579f8cde8d380ed3ba040 Mon Sep 17 00:00:00 2001 From: Hydra Builder Date: Sat, 18 Apr 2026 18:06:28 +0000 Subject: [PATCH 01/10] feat(case-management): add case list filters, search, and integrate custom properties and documents panels (#180) --- src/utils/caseValidation.js | 15 ++- src/views/cases/CaseDetail.vue | 21 ++++ src/views/cases/CaseList.vue | 214 ++++++++++++++++++++++++++++++++- 3 files changed, 246 insertions(+), 4 deletions(-) diff --git a/src/utils/caseValidation.js b/src/utils/caseValidation.js index 0b01442e..eac6770c 100644 --- a/src/utils/caseValidation.js +++ b/src/utils/caseValidation.js @@ -33,6 +33,7 @@ export function isCaseTypeUsable(caseType) { /** * Get a specific unusable reason for a case type. + * Enhanced with detailed validity window error messages. * * @param {object} caseType Case type object * @return {string|null} Reason why the case type cannot be used, or null if usable @@ -41,7 +42,7 @@ export function getCaseTypeUnusableReason(caseType) { if (!caseType) return t('procest', 'Case type not found') if (caseType.isDraft === true || caseType.isDraft === 'true') { - return t('procest', 'Cannot create a case with a draft case type. The case type must be published first.') + return t('procest', 'Cannot create a case: this case type is still in draft status. The case type must be published before cases can be created. Contact your administrator.') } const today = new Date() @@ -52,7 +53,11 @@ export function getCaseTypeUnusableReason(caseType) { validFrom.setHours(0, 0, 0, 0) if (validFrom > today) { const dateStr = caseType.validFrom.split('T')[0] - return t('procest', 'Cannot create a case with a case type that is not yet valid. The case type is valid from {date}.', { date: dateStr }) + const daysUntil = Math.ceil((validFrom - today) / (1000 * 60 * 60 * 24)) + return t('procest', 'Cannot create a case: this case type is not yet valid. It becomes available on {date} ({days} days from now). Check back later or contact your administrator if this is incorrect.', { + date: dateStr, + days: daysUntil, + }) } } @@ -61,7 +66,11 @@ export function getCaseTypeUnusableReason(caseType) { validUntil.setHours(0, 0, 0, 0) if (validUntil < today) { const dateStr = caseType.validUntil.split('T')[0] - return t('procest', 'Cannot create a case with an expired case type. The case type was valid until {date}.', { date: dateStr }) + const daysAgo = Math.ceil((today - validUntil) / (1000 * 60 * 60 * 24)) + return t('procest', 'Cannot create a case: this case type has expired. It was valid until {date} ({days} days ago). If you need to use this case type again, contact your administrator.', { + date: dateStr, + days: daysAgo, + }) } } diff --git a/src/views/cases/CaseDetail.vue b/src/views/cases/CaseDetail.vue index 565bc68f..36ff3c1f 100644 --- a/src/views/cases/CaseDetail.vue +++ b/src/views/cases/CaseDetail.vue @@ -193,6 +193,23 @@ @extend="showExtensionDialog" /> + + + + + + + + + + import(/* webpackChunkName: "map" */ './components/LocationTab.vue') @@ -490,6 +509,8 @@ export default { DeadlineIndicator, BeroepEscalationPanel, CourtProceedingsPanel, + CustomPropertiesPanel, + DocumentChecklist, }, props: { caseId: { diff --git a/src/views/cases/CaseList.vue b/src/views/cases/CaseList.vue index 6cd60e13..f77877d0 100644 --- a/src/views/cases/CaseList.vue +++ b/src/views/cases/CaseList.vue @@ -5,11 +5,60 @@ @created="onCaseCreated" @close="showCreateDialog = false" /> + +
+
+ +
+ +
+
+ + +
+ +
+ + +
+ +
+ +
+ + +
+
+ { + // Search filter (title, description, identifier) + if (this.searchQuery) { + const query = this.searchQuery.toLowerCase() + const title = (caseObj.title || '').toLowerCase() + const description = (caseObj.description || '').toLowerCase() + const identifier = (caseObj.identifier || '').toLowerCase() + + if (!title.includes(query) && !description.includes(query) && !identifier.includes(query)) { + return false + } + } + + // Priority filter + if (this.filterPriority && caseObj.priority !== this.filterPriority) { + return false + } + + // Handler filter + if (this.filterHandler) { + const handler = (caseObj.assignee || '').toLowerCase() + if (!handler.includes(this.filterHandler.toLowerCase())) { + return false + } + } + + // Overdue filter + if (this.filterOverdue) { + const isFinal = this.isAtFinalStatus(caseObj) + if (!isCaseOverdue(caseObj, isFinal)) { + return false + } + } + + return true + }) + }, + }, + watch: { objects: { handler(newObjects) { @@ -211,11 +311,123 @@ export default { } } }, + + onSearchInput() { + // Search is applied via the computed filteredObjects property + // This method can be used for debounced API calls if needed in future + }, + + onFilterChange() { + // Filters are applied via the computed filteredObjects property + // This method can be extended for advanced filtering in future versions + }, + + clearFilters() { + this.searchQuery = '' + this.filterPriority = '' + this.filterHandler = '' + this.filterOverdue = false + }, }, }