diff --git a/components/admin/permit-holders/Header.tsx b/components/admin/permit-holders/Header.tsx
index 8553ec85..b62018ca 100644
--- a/components/admin/permit-holders/Header.tsx
+++ b/components/admin/permit-holders/Header.tsx
@@ -18,6 +18,7 @@ import {
import { ChevronDownIcon, ChevronLeftIcon } from '@chakra-ui/icons'; // Chakra UI icon
import Link from 'next/link'; // Link
import { ApplicantStatus } from '@lib/graphql/types';
+import { CurrentApplication } from '@tools/admin/permit-holders/current-application';
import PermitHolderStatusBadge from '@components/admin/PermitHolderStatusBadge';
import ConfirmDeleteApplicantModal from '@components/admin/permit-holders/table/ConfirmDeleteApplicantModal';
import SetPermitHolderToInactiveModal from '@components/admin/permit-holders/table/ConfirmSetInactiveModal';
@@ -31,12 +32,13 @@ type PermitHolderHeaderProps = {
status: ApplicantStatus;
inactiveReason?: string;
notes: string;
+ mostRecentApplication: CurrentApplication | null;
};
readonly refetch: () => void;
};
export default function PermitHolderHeader({
- applicant: { id, name, status, inactiveReason, notes },
+ applicant: { id, name, status, inactiveReason, notes, mostRecentApplication },
refetch,
}: PermitHolderHeaderProps) {
const router = useRouter();
@@ -108,13 +110,15 @@ export default function PermitHolderHeader({
>
{`Set as ${status === 'ACTIVE' ? 'Inactive' : 'Active'}`}
-
+ {mostRecentApplication?.processing?.status == 'COMPLETED' ? null : (
+
+ )}
diff --git a/components/admin/requests/Header.tsx b/components/admin/requests/Header.tsx
index 878aedb8..fd84db69 100644
--- a/components/admin/requests/Header.tsx
+++ b/components/admin/requests/Header.tsx
@@ -25,6 +25,7 @@ import { ApplicationStatus, ApplicationType, PermitType } from '@lib/graphql/typ
import { titlecase } from '@tools/string';
import { formatDateYYYYMMDD, formatDateYYYYMMDDLocal } from '@lib/utils/date';
import { getPermanentPermitExpiryDate } from '@lib/utils/permit-expiry';
+import { useEffect, useState } from 'react'; // React
type RequestHeaderProps = {
readonly id: number;
@@ -87,6 +88,22 @@ export default function RequestHeader({
const router = useRouter();
+ const [backLink, setBackLink] = useState('/admin');
+ const generateBackLink = () => {
+ let status;
+ const routerQuery = router.query;
+ if (routerQuery === undefined || routerQuery.origin === undefined) {
+ status = applicationStatus;
+ } else {
+ status = routerQuery.origin;
+ }
+ setBackLink(`/admin?tab=${status}`);
+ };
+
+ useEffect(() => {
+ generateBackLink();
+ }, []);
+
// Delete application modal state
const {
isOpen: isDeleteApplicationModalOpen,
@@ -96,12 +113,13 @@ export default function RequestHeader({
return (
-
+
All requests
+
diff --git a/lib/graphql/types.ts b/lib/graphql/types.ts
index dc050b9b..01950c8f 100644
--- a/lib/graphql/types.ts
+++ b/lib/graphql/types.ts
@@ -178,6 +178,8 @@ export type ApplicationsReportColumn =
| 'APPLICANT_NAME'
| 'APPLICANT_DATE_OF_BIRTH'
| 'APP_NUMBER'
+ | 'PHONE_NUMBER'
+ | 'HOME_ADDRESS'
| 'APPLICATION_DATE'
| 'PAYMENT_METHOD'
| 'FEE_AMOUNT'
diff --git a/lib/reports/resolvers.ts b/lib/reports/resolvers.ts
index 939c5eb4..87969820 100644
--- a/lib/reports/resolvers.ts
+++ b/lib/reports/resolvers.ts
@@ -223,6 +223,12 @@ export const generateApplicationsReport: Resolver<
firstName: true,
middleName: true,
lastName: true,
+ addressLine1: true,
+ addressLine2: true,
+ city: true,
+ province: true,
+ postalCode: true,
+ phone: true,
type: true,
createdAt: true,
paymentMethod: true,
@@ -262,6 +268,10 @@ export const generateApplicationsReport: Resolver<
firstName,
middleName,
lastName,
+ phone,
+ addressLine1,
+ addressLine2,
+ postalCode,
type,
createdAt,
processingFee,
@@ -292,6 +302,9 @@ export const generateApplicationsReport: Resolver<
id: applicant?.id,
applicantName: formatFullName(firstName, middleName, lastName),
dateOfBirth: dateOfBirth && formatDateYYYYMMDD(dateOfBirth),
+ address: formatStreetAddress(addressLine1, addressLine2),
+ postalCode: formatPostalCode(postalCode),
+ phone: formatPhoneNumber(phone),
rcdPermitId: permit?.rcdPermitId ? `#${permit.rcdPermitId}` : null,
applicationDate: createdAt ? formatDateYYYYMMDDLocal(createdAt, true) : null,
processingFee: `$${processingFee}`,
@@ -314,9 +327,17 @@ export const generateApplicationsReport: Resolver<
}
);
- const csvHeaders = APPLICATIONS_COLUMNS.filter(({ value }) => columnsSet.has(value)).map(
- ({ name, reportColumnId }) => ({ id: reportColumnId, title: name })
- );
+ const filteredColumns = APPLICATIONS_COLUMNS.filter(({ value }) => columnsSet.has(value));
+ const csvHeaders: Array<{ id: string; title: string }> = [];
+ for (const { name, reportColumnId } of filteredColumns) {
+ if (typeof reportColumnId === 'string') {
+ csvHeaders.push({ id: reportColumnId, title: name });
+ } else {
+ for (const [columnLabel, columnId] of reportColumnId) {
+ csvHeaders.push({ id: columnId, title: columnLabel });
+ }
+ }
+ }
// Generate CSV string from csv object.
const csvStringifier = createObjectCsvStringifier({
diff --git a/lib/reports/schema.ts b/lib/reports/schema.ts
index 62ea95f4..c0485c19 100644
--- a/lib/reports/schema.ts
+++ b/lib/reports/schema.ts
@@ -25,6 +25,8 @@ export default gql`
APPLICANT_NAME
APPLICANT_DATE_OF_BIRTH
APP_NUMBER
+ PHONE_NUMBER
+ HOME_ADDRESS
APPLICATION_DATE
PAYMENT_METHOD
FEE_AMOUNT
diff --git a/pages/admin/index.tsx b/pages/admin/index.tsx
index a6d9da1b..d2fb8c95 100644
--- a/pages/admin/index.tsx
+++ b/pages/admin/index.tsx
@@ -44,6 +44,39 @@ import { formatFullName } from '@lib/utils/format'; // String formatter util
import { formatDateYYYYMMDDLocal } from '@lib/utils/date'; // Date Formatter Util
import EmptyMessage from '@components/EmptyMessage';
+interface RouterQuery {
+ tab?: string;
+}
+
+export const getTabIndex = (routerQuery: RouterQuery): number => {
+ if (routerQuery === undefined || routerQuery.tab === undefined) {
+ return 1;
+ }
+ const tabName = routerQuery.tab;
+ switch (tabName) {
+ case 'ALL':
+ return 0;
+ case 'PENDING':
+ return 1;
+ case 'IN_PROGRESS':
+ return 2;
+ case 'COMPLETED':
+ return 3;
+ case 'REJECTED':
+ return 4;
+ default:
+ return 1;
+ }
+};
+
+const tabIndexToStatus: { [key: number]: ApplicationStatus | 'ALL' } = {
+ 0: 'ALL',
+ 1: 'PENDING',
+ 2: 'IN_PROGRESS',
+ 3: 'COMPLETED',
+ 4: 'REJECTED',
+};
+
// Placeholder columns
const COLUMNS: Column[] = [
{
@@ -126,6 +159,7 @@ const PAGE_SIZE = 20;
const Requests: NextPage = () => {
// Router
const router = useRouter();
+ const routerQuery: RouterQuery = router.query;
// Filters
const [statusFilter, setStatusFilter] = useState('PENDING');
@@ -145,6 +179,20 @@ const Requests: NextPage = () => {
const [pageNumber, setPageNumber] = useState(0);
const [recordsCount, setRecordsCount] = useState(0);
+ // Tabs
+ const [tabIndex, setTabIndex] = useState(0);
+ const getTabFromRoute = (): number => {
+ const index = getTabIndex(routerQuery);
+ setTabIndex(index);
+ return index;
+ };
+
+ const handleTabChange = () => {
+ const status = tabIndexToStatus[tabIndex];
+ setStatusFilter(status === 'ALL' ? null : status);
+ router.push({ query: { tab: status } });
+ };
+
// Make query to applications resolver
const { refetch, loading } = useQuery(
GET_APPLICATIONS_QUERY,
@@ -191,6 +239,16 @@ const Requests: NextPage = () => {
}
);
+ // Determine the active tab on page load based on the route
+ useEffect(() => {
+ getTabFromRoute();
+ }, []);
+
+ useEffect(() => {
+ if (tabIndex === null) return;
+ handleTabChange();
+ }, [tabIndex]);
+
// Set page number to 0 after every filter or sort change
useEffect(() => {
setPageNumber(0);
@@ -228,48 +286,19 @@ const Requests: NextPage = () => {
-
-
- {
- setStatusFilter(null);
- }}
- >
- All
-
- {
- setStatusFilter('PENDING');
- }}
- >
- Pending
-
- {
- setStatusFilter('IN_PROGRESS');
- }}
- >
- In Progress
-
- {
- setStatusFilter('COMPLETED');
- }}
- >
- Completed
-
- {
- setStatusFilter('REJECTED');
- }}
- >
- Rejected
-
+ {
+ setTabIndex(index);
+ }}
+ >
+
+ All
+ Pending
+ In Progress
+ Completed
+ Rejected
@@ -379,13 +408,23 @@ const Requests: NextPage = () => {
{requestsData.length > 0 ? (
<>
+
+
+
router.push(`/admin/request/${id}`)}
+ onRowClick={({ id }) =>
+ router.push(`/admin/request/${id}?origin=${tabIndexToStatus[tabIndex]}`)
+ }
/>
diff --git a/pages/admin/permit-holders.tsx b/pages/admin/permit-holders.tsx
index 49d38aae..8ada9cde 100644
--- a/pages/admin/permit-holders.tsx
+++ b/pages/admin/permit-holders.tsx
@@ -39,6 +39,7 @@ import {
USER_STATUSES,
PermitHolderToUpdateStatus,
} from '@tools/admin/permit-holders/permit-holders-table';
+import { CurrentApplication } from '@tools/admin/permit-holders/current-application';
import DateRangePicker from '@components/DateRangePicker'; // Day Picker component
import useDateRangePicker from '@tools/hooks/useDateRangePicker'; // Day Picker hook
import { SortOptions, SortOrder } from '@tools/types'; // Sorting types
@@ -273,10 +274,16 @@ const PermitHolders: NextPage = () => {
Header: 'Actions',
Cell: ({
row: {
- original: { id, status },
+ original: { id, status, mostRecentApplication },
},
}: {
- row: { original: { id: number; status: ApplicantStatus } };
+ row: {
+ original: {
+ id: number;
+ status: ApplicantStatus;
+ mostRecentApplication: CurrentApplication | null;
+ };
+ };
}) => {
return (
);
@@ -494,6 +503,14 @@ const PermitHolders: NextPage = () => {
{permitHolderData && permitHolderData.length > 0 ? (
<>
+
+
+
= [
@@ -41,6 +42,7 @@ export type PermitHolderRow = Pick;
+ mostRecentApplication: CurrentApplication | null;
};
/**
@@ -70,6 +72,11 @@ export const GET_PERMIT_HOLDERS_QUERY = gql`
rcdPermitId
}
status
+ mostRecentApplication {
+ processing {
+ status
+ }
+ }
}
totalCount
}
@@ -95,6 +102,7 @@ export type PermitHolder = Pick<
| 'status'
> & {
mostRecentPermit: Pick;
+ mostRecentApplication: CurrentApplication | null;
};
export type GetPermitHoldersResponse = {
diff --git a/tools/admin/reports.ts b/tools/admin/reports.ts
index c019d3e5..ce75b470 100644
--- a/tools/admin/reports.ts
+++ b/tools/admin/reports.ts
@@ -12,7 +12,7 @@ export enum GenerateReportStep {
export const APPLICATIONS_COLUMNS: Array<{
name: string;
value: ApplicationsReportColumn;
- reportColumnId: string;
+ reportColumnId: string | Array<[string, string]>;
}> = [
{
name: 'User ID',
@@ -35,9 +35,24 @@ export const APPLICATIONS_COLUMNS: Array<{
reportColumnId: 'rcdPermitId',
},
{
- name: 'Application Date',
- value: 'APPLICATION_DATE',
- reportColumnId: 'applicationDate',
+ name: 'Phone Number',
+ value: 'PHONE_NUMBER',
+ reportColumnId: 'phone',
+ },
+ {
+ name: 'Home Address',
+ value: 'HOME_ADDRESS',
+ reportColumnId: [
+ ['Address', 'address'],
+ ['City', 'city'],
+ ['Province', 'province'],
+ ['Postal Code', 'postalCode'],
+ ],
+ },
+ {
+ name: 'Donation Amount',
+ value: 'DONATION_AMOUNT',
+ reportColumnId: 'donationAmount',
},
{
name: 'Payment Method',
@@ -50,9 +65,9 @@ export const APPLICATIONS_COLUMNS: Array<{
reportColumnId: 'processingFee',
},
{
- name: 'Donation Amount',
- value: 'DONATION_AMOUNT',
- reportColumnId: 'donationAmount',
+ name: 'Second Donation Amount',
+ value: 'SECOND_DONATION_AMOUNT',
+ reportColumnId: 'secondDonationAmount',
},
{
name: 'Second Payment Method',
@@ -65,9 +80,9 @@ export const APPLICATIONS_COLUMNS: Array<{
reportColumnId: 'secondProcessingFee',
},
{
- name: 'Second Donation Amount',
- value: 'SECOND_DONATION_AMOUNT',
- reportColumnId: 'secondDonationAmount',
+ name: 'Application Date',
+ value: 'APPLICATION_DATE',
+ reportColumnId: 'applicationDate',
},
{
name: 'Total Amount',