Skip to content

Commit

Permalink
feat: [contracts] add statistics
Browse files Browse the repository at this point in the history
  • Loading branch information
dragos1195 committed Sep 26, 2024
1 parent 691b50e commit e6c13a3
Show file tree
Hide file tree
Showing 8 changed files with 52 additions and 108 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -62,54 +62,9 @@ export class DocumentContractListViewRepository extends RepositoryWithPagination
}

if (status) {
switch (status) {
case DocumentContractComputedStatuses.ACTIVE:
query
.andWhere('documentContractListView.status = :status', {
status: 'APPROVED',
})
.andWhere(
'documentContractListView.documentStartDate <= :currentDate',
{
currentDate: new Date(),
},
)
.andWhere(
'documentContractListView.documentEndDate >= :currentDate',
{
currentDate: new Date(),
},
);
break;
case DocumentContractComputedStatuses.NOT_STARTED:
query
.andWhere('documentContractListView.status = :status', {
status: 'APPROVED',
})
.andWhere(
'documentContractListView.documentStartDate > :currentDate',
{
currentDate: new Date(),
},
);
break;
case DocumentContractComputedStatuses.EXPIRED:
query
.andWhere('documentContractListView.status = :status', {
status: 'APPROVED',
})
.andWhere(
'documentContractListView.documentEndDate < :currentDate',
{
currentDate: new Date(),
},
);
break;
default:
query.andWhere('documentContractListView.status = :status', {
status,
});
}
query.andWhere('documentContractListView.status = :status', {
status,
});
}

if (documentStartDate && documentEndDate) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ export class DocumentContractRepositoryService extends RepositoryWithPagination<
COUNT(*) FILTER (WHERE status = $1) AS pending_ngo_representative_signature,
COUNT(*) FILTER (WHERE status = $2) AS pending_volunteer_signature,
COUNT(*) FILTER (WHERE CURRENT_DATE BETWEEN document_start_date AND document_end_date) AS active_contracts,
COUNT(*) FILTER (WHERE document_end_date - CURRENT_DATE <= 14) AS soon_to_expire
COUNT(*) FILTER (WHERE document_end_date - CURRENT_DATE <= 30 AND document_end_date - CURRENT_DATE > 0) AS soon_to_expire
FROM document_contract WHERE organization_id = $3
`,
[
Expand Down
9 changes: 5 additions & 4 deletions frontend/src/assets/locales/ro/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -1019,9 +1019,10 @@
},
"statistics": {
"active_contracts": "Contracte active",
"in_signing_contracts": "Contracte în curs de semnare",
"saved_contracts": "Contracte salvate (netrimise)",
"to_expire_soon": "Contracte care expiră curând"
"in_signing_contracts": "Contracte în curs de semnare la voluntari",
"saved_contracts": "Contracte verificate care necesită semnatură",
"to_expire_soon": "Contracte care expiră curând",
"view_list": "Vezi lista"
},
"table_header": {
"title": "Template-uri de contracte",
Expand Down Expand Up @@ -1320,4 +1321,4 @@
"clear": "Șterge",
"apply_all": "Aplică pentru toate"
}
}
}
1 change: 0 additions & 1 deletion frontend/src/common/enums/document-contract-status.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ export enum DocumentContractStatusForFilter {
PENDING_VOLUNTEER_SIGNATURE = 'PENDING_VOLUNTEER_SIGNATURE',
PENDING_APPROVAL_NGO = 'PENDING_APPROVAL_NGO',
PENDING_NGO_REPRESENTATIVE_SIGNATURE = 'PENDING_NGO_REPRESENTATIVE_SIGNATURE',
APPROVED = 'APPROVED',
REJECTED_VOLUNTEER = 'REJECTED_VOLUNTEER',
REJECTED_NGO = 'REJECTED_NGO',
ACTION_EXPIRED = 'ACTION_EXPIRED',
Expand Down
53 changes: 30 additions & 23 deletions frontend/src/components/ContractsStatistics.tsx
Original file line number Diff line number Diff line change
@@ -1,51 +1,58 @@
import React from 'react';
import StatisticsCard from './StatisticsCard';
import { useTranslation } from 'react-i18next';
import { IDocumentContractsStatistics } from '../common/interfaces/document-contract.interface';
import { UpdateType } from '../common/interfaces/hoc-query-props.interface';
import { DocumentContractsTableQueryProps } from './DocumentContractsTable';
import { DocumentContractStatusForFilter } from '../common/enums/document-contract-status.enum';

export const ContractsStatistics = () => {
interface ContractsStatisticsProps {
statistics: IDocumentContractsStatistics;
isLoading: boolean;
setQuery: (changes: DocumentContractsTableQueryProps, updateType?: UpdateType) => void;
}

export const ContractsStatistics = ({ statistics, isLoading, setQuery }: ContractsStatisticsProps) => {
const { t } = useTranslation('volunteering_contracts');

return (
<div className="flex flex-col sm:flex-row gap-2">
<StatisticsCard
label={t('statistics.active_contracts')}
// todo: get active contracts count
value={'13'}
label={t('statistics.saved_contracts')}
value={statistics?.pendingNgoRepresentativeSignature.toString()}
action={{
label: 'Vezi lista',
// todo: add functionality to view list of active contracts
onClick: () => {},
label: t('statistics.view_list'),
onClick: () => { setQuery({ status: DocumentContractStatusForFilter.PENDING_NGO_REPRESENTATIVE_SIGNATURE }, 'push') },
}}
isLoading={isLoading}
/>
<StatisticsCard
label={t('statistics.in_signing_contracts')}
// todo: get in signing contracts count
value={'1'}
value={statistics?.pendingVolunteerSignature.toString()}
action={{
label: 'Vezi lista',
// todo: add functionality to view list of in signing contracts
onClick: () => {},
label: t('statistics.view_list'),
onClick: () => { setQuery({ status: DocumentContractStatusForFilter.PENDING_VOLUNTEER_SIGNATURE }, 'push') },
}}
isLoading={isLoading}
/>

<StatisticsCard
label={t('statistics.saved_contracts')}
// todo: get saved contracts count
value={'5'}
label={t('statistics.active_contracts')}
value={statistics?.activeContracts.toString()}
action={{
label: 'Vezi lista',
// todo: add functionality to view list of saved contracts
onClick: () => {},
label: t('statistics.view_list'),
onClick: () => { setQuery({ status: DocumentContractStatusForFilter.ACTIVE }, 'push') },
}}
isLoading={isLoading}
/>
<StatisticsCard
label={t('statistics.to_expire_soon')}
// todo: get to expire soon contracts count
value={'12'}
value={statistics?.soonToExpire.toString()}
action={{
label: 'Vezi lista',
// todo: add functionality to view list of to expire soon contracts
onClick: () => {},
label: t('statistics.view_list'),
onClick: () => { setQuery({ endDate: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), status: DocumentContractStatusForFilter.ACTIVE }, 'push') },
}}
isLoading={isLoading}
/>
</div>
);
Expand Down
10 changes: 6 additions & 4 deletions frontend/src/components/DocumentContractsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,15 @@ import { useTranslation } from 'react-i18next';
import { getContractsForDownload } from '../services/contracts/contracts.api';
import {
useDeleteDocumentContractMutation,
useGetContractsStatisticsQuery,
useGetDocumentsContractsQuery,
} from '../services/document-contracts/document-contracts.service';
import {
DocumentContractStatusForFilter,
} from '../common/enums/document-contract-status.enum';
import { IPaginationQueryParams } from '../common/constants/pagination';

import { IDocumentContract } from '../common/interfaces/document-contract.interface';
import { IDocumentContract, IDocumentContractsStatistics } from '../common/interfaces/document-contract.interface';
import DocumentsContractSidePanel from './DocumentsContractSidePanel';
import VolunteerSelect from '../containers/VolunteerSelect';
import { ListItem } from '../common/interfaces/list-item.interface';
Expand Down Expand Up @@ -107,7 +108,7 @@ const ContractsTableHeader = [
},
];

interface DocumentContractsTableQueryProps extends IPaginationQueryParams {
export interface DocumentContractsTableQueryProps extends IPaginationQueryParams {
volunteerId?: string;
volunteerName?: string;
search?: string;
Expand Down Expand Up @@ -149,6 +150,8 @@ const DocumentContractsTable = ({ query, setQuery }: DocumentContractsTableBasic
...(query.endDate ? { documentEndDate: formatDate(query?.endDate as Date, 'yyyy-MM-dd') } : {}),
});

const { data: statistics, isLoading: isLoadingStatistics } = useGetContractsStatisticsQuery();

const { mutate: deleteContract } = useDeleteDocumentContractMutation();

const onView = (row: IDocumentContract) => {
Expand Down Expand Up @@ -223,7 +226,6 @@ const DocumentContractsTable = ({ query, setQuery }: DocumentContractsTableBasic

const mapContractStatusToPopoverItems = (status: DocumentContractStatusForFilter) => {
switch (status) {
case DocumentContractStatusForFilter.APPROVED:
case DocumentContractStatusForFilter.SCHEDULED:
case DocumentContractStatusForFilter.CREATED:
case DocumentContractStatusForFilter.PENDING_VOLUNTEER_SIGNATURE:
Expand Down Expand Up @@ -321,7 +323,7 @@ const DocumentContractsTable = ({ query, setQuery }: DocumentContractsTableBasic

return (
<>
<ContractsStatistics />
<ContractsStatistics statistics={statistics as IDocumentContractsStatistics} isLoading={isLoadingStatistics} setQuery={setQuery} />
<DataTableFilters
onSearch={onSearch}
searchValue={query?.search}
Expand Down
6 changes: 4 additions & 2 deletions frontend/src/components/StatisticsCard.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import { ReactNode } from 'react';
import Card from '../layouts/CardLayout';
import LoadingContent from './LoadingContent';

interface StatisticsCardProps {
label: string;
Expand All @@ -9,9 +10,10 @@ interface StatisticsCardProps {
icon?: ReactNode;
info?: ReactNode;
className?: string;
isLoading?: boolean;
}

const StatisticsCard = ({ label, value, action, icon, info, className }: StatisticsCardProps) => {
const StatisticsCard = ({ label, value, action, icon, info, className, isLoading }: StatisticsCardProps) => {
return (
<Card className={className}>
<div className="flex flex-col">
Expand All @@ -20,7 +22,7 @@ const StatisticsCard = ({ label, value, action, icon, info, className }: Statist
<div className="flex flex-col gap-2 shrink-[80]">
<p className="text-cool-gray-500 leading-5">{label}</p>
<div className="flex flex-wrap gap-2 items-end">
<p className="text-4xl leading-8 font-semibold ">{value}</p>
{isLoading ? <LoadingContent /> : <p className="text-4xl leading-8 font-semibold ">{value}</p>}
{info && (
<span className="bg-yellow-200 h-7 leading-7 rounded-xl px-2.5 whitespace-nowrap">
{info}
Expand Down
28 changes: 3 additions & 25 deletions frontend/src/services/document-contracts/document-contracts.api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { DocumentContractStatusForFilter } from '../../common/enums/document-contract-status.enum';
import {
IAddDocumentContractDTO,
IDocumentContract,
Expand All @@ -14,30 +13,9 @@ import API from '../api';
export const getDocumentsContracts = async (
params: IGetDocumentsContractsParams,
): Promise<IPaginatedEntity<IDocumentContract>> => {
const { data: contracts } = await API.get<IPaginatedEntity<IDocumentContract>>(
'/documents/contracts',
{
params,
},
);

// These enum values (ACTIVE, EXPIRED, NOT_STARTED) are not stored in the database
// They are derived statuses based on the contract's start and end dates
// We need to manually update the status here to reflect these derived states
const statusesToUpdate = [
DocumentContractStatusForFilter.ACTIVE,
DocumentContractStatusForFilter.EXPIRED,
DocumentContractStatusForFilter.NOT_STARTED,
];

if (params.status && statusesToUpdate.includes(params.status)) {
contracts.items = contracts.items.map((item: IDocumentContract) => ({
...item,
status: params.status as DocumentContractStatusForFilter,
}));
}

return contracts;
return API.get<IPaginatedEntity<IDocumentContract>>('/documents/contracts', {
params,
}).then((res) => res.data);
};

export const getDocumentContract = (id: string): Promise<IGetDocumentContractResponse> => {
Expand Down

0 comments on commit e6c13a3

Please sign in to comment.