Skip to content

Commit 4d03f0e

Browse files
authored
Merge pull request #339 from bcgov/feature/prop-project-listing-updates
Tracker Improvements - My Projects
2 parents 9d659e9 + 5d90d93 commit 4d03f0e

8 files changed

Lines changed: 189 additions & 28 deletions

File tree

frontend/src/components/authorization/AuthorizationForm.vue

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -154,11 +154,15 @@ function onDelete() {
154154
}
155155
156156
const formSchema = object({
157-
permitNote: string().when('status', {
158-
is: (status: string) => status !== initialFormValues.value?.status,
159-
then: (schema) => schema.required(t('authorization.authorizationForm.noteRequired')),
160-
otherwise: () => string().notRequired()
161-
}),
157+
permitNote: string()
158+
.when('status', {
159+
is: (status: string) => status !== initialFormValues.value?.status,
160+
then: (schema) => schema.required(t('authorization.authorizationForm.noteRequired'))
161+
})
162+
.when('authStatus', {
163+
is: (authStatus: string) => authStatus !== initialFormValues.value?.authStatus,
164+
then: (schema) => schema.required(t('authorization.authorizationForm.noteRequired'))
165+
}),
162166
authorizationType: object().required().label(t('authorization.authorizationForm.authorizationType')),
163167
needed: string().required().label(t('authorization.authorizationForm.needed')),
164168
status: string().required().oneOf(PERMIT_STATUS_LIST).label(t('authorization.authorizationForm.applicationStage')),

frontend/src/components/authorization/AuthorizationStatusPill.vue

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,14 @@ import { computed } from 'vue';
44
import { PermitAuthorizationStatus, PermitAuthorizationStatusDescriptions } from '@/utils/enums/permit';
55
66
// Props
7-
const { authStatus, enlarge = false } = defineProps<{
7+
const {
8+
authStatus,
9+
enlarge = false,
10+
displayText
11+
} = defineProps<{
812
authStatus?: string;
913
enlarge?: boolean;
14+
displayText?: string;
1015
}>();
1116
1217
const defaultDimensions = {
@@ -106,7 +111,7 @@ const getState = computed(() => {
106111
:class="[getState?.iconClass]"
107112
:icon="getState?.iconString"
108113
/>
109-
<span class="text-color">{{ authStatus }}</span>
114+
<span class="text-color">{{ displayText ?? authStatus }}</span>
110115
</div>
111116
</div>
112117
</div>

frontend/src/components/housing/submission/SubmissionIntakeForm.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@ async function onSubmit(data: any) {
309309
};
310310
311311
// Show the trackingNumber of all appliedPermits to the proponent
312-
submissionData.appliedPermits.forEach((x: Permit) => {
312+
submissionData.appliedPermits?.forEach((x: Permit) => {
313313
if (x?.permitTracking?.[0]) x.permitTracking[0].shownToProponent = true;
314314
});
315315

frontend/src/locales/en-CA.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@
8585
"myProjects": "My projects",
8686
"of": "of",
8787
"onThisPage": "On this page",
88+
"pendingAuths": "There are authorizations pending your action.",
8889
"projectState": "Project state",
8990
"projectsEmpty": "Submit a new project to the Navigator Service to find out what permits are required",
9091
"projectsTooltip": "Submit an electrification project to the Navigator service to find out what permits are required for your projects, track permit applications, and submit related enquiries.",
@@ -146,6 +147,7 @@
146147
"myProjects": "My projects",
147148
"onThisPage": "On this page",
148149
"of": "of",
150+
"pendingAuths": "There are authorizations pending your action.",
149151
"projectState": "Project state",
150152
"projectsEmpty": "Submit a new project to the Navigator Service to find out what permits are required",
151153
"projectsTooltip": "Submit a housing project to the Navigator service to find out what permits are required for your projects, track permit applications, and submit related enquiries.",

frontend/src/views/external/electrification/ElectrificationView.vue

Lines changed: 78 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,20 @@ import { computed, nextTick, onBeforeMount, provide, ref, watch } from 'vue';
33
import { useI18n } from 'vue-i18n';
44
import { useRoute, useRouter } from 'vue-router';
55
6+
import AuthorizationStatusPill from '@/components/authorization/AuthorizationStatusPill.vue';
67
import Tooltip from '@/components/common/Tooltip.vue';
78
import SubmissionDraftListProponent from '@/components/projectCommon/submission/SubmissionDraftListProponent.vue';
89
import { Button, Paginator } from '@/lib/primevue';
9-
import { electrificationProjectService } from '@/services';
10+
import { electrificationProjectService, permitService } from '@/services';
1011
import { useContactStore } from '@/store';
1112
import { RouteName } from '@/utils/enums/application';
13+
import { PermitAuthorizationStatus } from '@/utils/enums/permit';
14+
import { ApplicationStatus } from '@/utils/enums/projectCommon';
1215
import { formatDate } from '@/utils/formatters';
1316
import { draftableProjectServiceKey, projectRouteNameKey } from '@/utils/keys';
1417
1518
import type { Ref } from 'vue';
16-
import type { ElectrificationProject } from '@/types';
19+
import type { ElectrificationProject, Permit } from '@/types';
1720
1821
// Constants
1922
const PAGE_ROWS = 5;
@@ -24,7 +27,7 @@ const route = useRoute();
2427
const router = useRouter();
2528
2629
// State
27-
const displayedProjects = computed(() => projects.value.slice(first.value, first.value + PAGE_ROWS));
30+
const authorizations: Ref<Array<Permit>> = ref([]);
2831
const drafts: Ref<Array<any>> = ref([]);
2932
const first: Ref<number> = ref(0);
3033
const loading: Ref<boolean> = ref(true);
@@ -60,6 +63,35 @@ async function createIntake() {
6063
});
6164
}
6265
66+
const displayedProjectsInOrder = computed(() => {
67+
// Filter projects to only include completed projects and sort them by last updated
68+
const completed = projects.value
69+
.filter((p) => p.applicationStatus === ApplicationStatus.COMPLETED)
70+
.sort(sortByLastUpdated);
71+
72+
// Filter projects to only include active projects and sort them by last updated
73+
const active = projects.value
74+
.filter((p) => [ApplicationStatus.IN_PROGRESS, ApplicationStatus.NEW].includes(p.applicationStatus))
75+
.sort((a, b) => {
76+
if (a.submittedAt && b.submittedAt) {
77+
return a.submittedAt > b.submittedAt ? -1 : 1;
78+
} else {
79+
if (!a.submittedAt) return 1;
80+
if (!b.submittedAt) return -1;
81+
return 0;
82+
}
83+
});
84+
85+
// Combine active and completed projects, then slice based on pagination
86+
return [...active, ...completed].slice(first.value, first.value + PAGE_ROWS);
87+
});
88+
89+
const hasPendingAuth = computed(() => (activityId: string) => {
90+
return authorizations.value.some(
91+
(auth) => auth.activityId === activityId && auth.authStatus === PermitAuthorizationStatus.PENDING
92+
);
93+
});
94+
6395
function onElectrificationProjectDraftDelete(draftId: string) {
6496
drafts.value = drafts.value.filter((x) => x.draftId !== draftId);
6597
}
@@ -88,8 +120,9 @@ watch(
88120
);
89121
90122
onBeforeMount(async () => {
91-
[projects.value, drafts.value] = (
123+
[authorizations.value, projects.value, drafts.value] = (
92124
await Promise.all([
125+
permitService.listPermits(),
93126
electrificationProjectService.searchProjects({ includeDeleted: false }),
94127
electrificationProjectService.getDrafts()
95128
])
@@ -178,27 +211,53 @@ onBeforeMount(async () => {
178211
<p class="font-bold">{{ t('e.electrification.electrificationView.projectsEmpty') }}</p>
179212
</div>
180213
<div
181-
v-for="(project, index) in displayedProjects"
214+
v-for="(project, index) in displayedProjectsInOrder"
182215
v-else
183216
:key="project.activityId"
184217
:index="index"
185218
class="rounded-sm shadow-md hover:shadow-lg px-6 py-4 custom-card hover-hand"
186-
:class="{ 'mb-2': index != displayedProjects.length - 1 }"
219+
:class="{
220+
'mb-2': index != displayedProjectsInOrder.length - 1,
221+
'custom-card-completed': project.applicationStatus === ApplicationStatus.COMPLETED
222+
}"
187223
>
224+
<router-link
225+
v-if="hasPendingAuth(project.activityId)"
226+
class="no-underline"
227+
:to="{
228+
name: RouteName.EXT_ELECTRIFICATION_PROJECT,
229+
params: { projectId: project.electrificationProjectId }
230+
}"
231+
>
232+
<h5 class="font-bold mb-4">{{ project.projectName }}</h5>
233+
</router-link>
188234
<div class="grid grid-cols-12 gap-4">
189235
<div class="col-span-3 flex items-center">
190236
<router-link
237+
v-if="!hasPendingAuth(project.activityId)"
191238
class="no-underline"
192239
:to="{
193240
name: RouteName.EXT_ELECTRIFICATION_PROJECT,
194241
params: { projectId: project.electrificationProjectId }
195242
}"
196243
>
197-
<h4 class="font-bold mb-0">{{ project.projectName }}</h4>
244+
<h5 class="font-bold mb-0">{{ project.projectName }}</h5>
198245
</router-link>
246+
<AuthorizationStatusPill
247+
v-if="hasPendingAuth(project.activityId)"
248+
class="my-1"
249+
:auth-status="PermitAuthorizationStatus.PENDING"
250+
:display-text="t('e.electrification.electrificationView.pendingAuths')"
251+
/>
199252
</div>
200253
<div class="col-span-3 flex items-center">
201-
<p>{{ t('e.electrification.electrificationView.projectState') }}: {{ project.applicationStatus }}</p>
254+
<p
255+
:class="{
256+
'font-bold': [ApplicationStatus.IN_PROGRESS, ApplicationStatus.NEW].includes(project.applicationStatus)
257+
}"
258+
>
259+
{{ project.applicationStatus }}
260+
</p>
202261
</div>
203262
<div class="col-span-3 flex items-center">
204263
<p>{{ t('e.electrification.electrificationView.confirmationId') }}: {{ project.activityId }}</p>
@@ -249,7 +308,17 @@ onBeforeMount(async () => {
249308

250309
<style scoped lang="scss">
251310
.custom-card {
252-
background-color: #f7f9fc;
311+
background-color: var(--p-white);
312+
border: 1px solid var(--p-greyscale-200);
313+
314+
&:hover {
315+
a {
316+
text-decoration: underline !important;
317+
}
318+
}
319+
}
320+
.custom-card-completed {
321+
background-color: var(--p-greyscale-50);
253322
254323
&:hover {
255324
a {

frontend/src/views/external/housing/HousingView.vue

Lines changed: 78 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,17 @@ import { computed, nextTick, onBeforeMount, provide, ref, watch } from 'vue';
33
import { useI18n } from 'vue-i18n';
44
import { useRoute, useRouter } from 'vue-router';
55
6+
import AuthorizationStatusPill from '@/components/authorization/AuthorizationStatusPill.vue';
67
import Tooltip from '@/components/common/Tooltip.vue';
78
import EnquiryListProponent from '@/components/projectCommon/enquiry/EnquiryListProponent.vue';
89
import SubmissionDraftListProponent from '@/components/projectCommon/submission/SubmissionDraftListProponent.vue';
910
import { Button, Paginator } from '@/lib/primevue';
10-
import { enquiryService, housingProjectService } from '@/services';
11+
import { enquiryService, housingProjectService, permitService } from '@/services';
1112
import { useContactStore } from '@/store';
1213
import { NavigationPermission } from '@/store/authzStore';
1314
import { RouteName } from '@/utils/enums/application';
15+
import { PermitAuthorizationStatus } from '@/utils/enums/permit';
16+
import { ApplicationStatus } from '@/utils/enums/projectCommon';
1417
import { formatDate } from '@/utils/formatters';
1518
import {
1619
draftableProjectServiceKey,
@@ -20,7 +23,7 @@ import {
2023
} from '@/utils/keys';
2124
2225
import type { Ref } from 'vue';
23-
import type { Enquiry, HousingProject } from '@/types';
26+
import type { Enquiry, HousingProject, Permit } from '@/types';
2427
2528
// Constants
2629
const PAGE_ROWS = 5;
@@ -31,7 +34,7 @@ const route = useRoute();
3134
const router = useRouter();
3235
3336
// State
34-
const displayedProjects = computed(() => projects.value.slice(first.value, first.value + PAGE_ROWS));
37+
const authorizations: Ref<Array<Permit>> = ref([]);
3538
const drafts: Ref<Array<any>> = ref([]);
3639
const enquiries: Ref<Array<Enquiry>> = ref([]);
3740
const first: Ref<number> = ref(0);
@@ -70,6 +73,35 @@ async function createIntake() {
7073
});
7174
}
7275
76+
const displayedProjectsInOrder = computed(() => {
77+
// Filter projects to only include completed projects and sort them by last updated
78+
const completed = projects.value
79+
.filter((p) => p.applicationStatus === ApplicationStatus.COMPLETED)
80+
.sort(sortByLastUpdated);
81+
82+
// Filter projects to only include active projects and sort them by last updated
83+
const active = projects.value
84+
.filter((p) => [ApplicationStatus.IN_PROGRESS, ApplicationStatus.NEW].includes(p.applicationStatus))
85+
.sort((a, b) => {
86+
if (a.submittedAt && b.submittedAt) {
87+
return a.submittedAt > b.submittedAt ? -1 : 1;
88+
} else {
89+
if (!a.submittedAt) return 1;
90+
if (!b.submittedAt) return -1;
91+
return 0;
92+
}
93+
});
94+
95+
// Combine active and completed projects, then slice based on pagination
96+
return [...active, ...completed].slice(first.value, first.value + PAGE_ROWS);
97+
});
98+
99+
const hasPendingAuth = computed(() => (activityId: string) => {
100+
return authorizations.value.some(
101+
(auth) => auth.activityId === activityId && auth.authStatus === PermitAuthorizationStatus.PENDING
102+
);
103+
});
104+
73105
function onHousingProjectDraftDelete(draftId: string) {
74106
drafts.value = drafts.value.filter((x) => x.draftId !== draftId);
75107
}
@@ -98,8 +130,9 @@ watch(
98130
);
99131
100132
onBeforeMount(async () => {
101-
[enquiries.value, projects.value, drafts.value] = (
133+
[authorizations.value, enquiries.value, projects.value, drafts.value] = (
102134
await Promise.all([
135+
permitService.listPermits(),
103136
enquiryService.getEnquiries(),
104137
housingProjectService.searchProjects({ includeDeleted: false }),
105138
housingProjectService.getDrafts()
@@ -202,27 +235,53 @@ onBeforeMount(async () => {
202235
<p class="font-bold">{{ t('e.housing.housingView.projectsEmpty') }}</p>
203236
</div>
204237
<div
205-
v-for="(project, index) in displayedProjects"
238+
v-for="(project, index) in displayedProjectsInOrder"
206239
v-else
207240
:key="project.activityId"
208241
:index="index"
209242
class="rounded-sm shadow-md hover:shadow-lg px-6 py-4 custom-card hover-hand"
210-
:class="{ 'mb-2': index != displayedProjects.length - 1 }"
243+
:class="{
244+
'mb-2': index != displayedProjectsInOrder.length - 1,
245+
'custom-card-completed': project.applicationStatus === ApplicationStatus.COMPLETED
246+
}"
211247
>
248+
<router-link
249+
v-if="hasPendingAuth(project.activityId)"
250+
class="no-underline"
251+
:to="{
252+
name: RouteName.EXT_HOUSING_PROJECT,
253+
params: { projectId: project.housingProjectId }
254+
}"
255+
>
256+
<h5 class="font-bold mb-4">{{ project.projectName }}</h5>
257+
</router-link>
212258
<div class="grid grid-cols-12 gap-4">
213259
<div class="col-span-3 flex items-center">
214260
<router-link
261+
v-if="!hasPendingAuth(project.activityId)"
215262
class="no-underline"
216263
:to="{
217264
name: RouteName.EXT_HOUSING_PROJECT,
218265
params: { projectId: project.housingProjectId }
219266
}"
220267
>
221-
<h4 class="font-bold mb-0">{{ project.projectName }}</h4>
268+
<h5 class="font-bold mb-0">{{ project.projectName }}</h5>
222269
</router-link>
270+
<AuthorizationStatusPill
271+
v-if="hasPendingAuth(project.activityId)"
272+
class="my-1"
273+
:auth-status="PermitAuthorizationStatus.PENDING"
274+
:display-text="t('e.housing.housingView.pendingAuths')"
275+
/>
223276
</div>
224277
<div class="col-span-3 flex items-center">
225-
<p>{{ t('e.housing.housingView.projectState') }}: {{ project.applicationStatus }}</p>
278+
<p
279+
:class="{
280+
'font-bold': [ApplicationStatus.IN_PROGRESS, ApplicationStatus.NEW].includes(project.applicationStatus)
281+
}"
282+
>
283+
{{ project.applicationStatus }}
284+
</p>
226285
</div>
227286
<div class="col-span-3 flex items-center">
228287
<p>{{ t('e.housing.housingView.confirmationId') }}: {{ project.activityId }}</p>
@@ -306,7 +365,17 @@ onBeforeMount(async () => {
306365

307366
<style scoped lang="scss">
308367
.custom-card {
309-
background-color: #f7f9fc;
368+
background-color: var(--p-white);
369+
border: 1px solid var(--p-greyscale-200);
370+
371+
&:hover {
372+
a {
373+
text-decoration: underline !important;
374+
}
375+
}
376+
}
377+
.custom-card-completed {
378+
background-color: var(--p-greyscale-50);
310379
311380
&:hover {
312381
a {

0 commit comments

Comments
 (0)