add contracts overview tab with KPIs, recent contracts, and charts

- Introduced OverviewContractKpisDto and OverviewRecentContractDto for contract metrics.
- Implemented OverviewContractsTabDto to structure the contracts tab response.
- Added contract-related constants for status groupings and pipeline stages.
- Created OverviewContractsTabPanel and OverviewRecentContractsTable components for UI representation.
- Updated OverviewService and OverviewRepository to fetch contract data.
- Integrated contracts tab into OverviewController and OverviewTabContent.
- Added hooks for fetching contracts data in useOverview.
- Updated types in the overview module to include contracts.
- Enhanced the contract detail page with a "View contract" button for generated PDFs.
This commit is contained in:
Marshal
2026-06-28 17:54:03 +00:00
parent 11c7f1bb74
commit a34e846d90
18 changed files with 688 additions and 11 deletions

View File

@@ -9,11 +9,15 @@ import type { OverviewResponseDto } from './dto/overview-response.dto';
import type {
OverviewBillingTabDto,
OverviewBookingsTabDto,
OverviewContractsTabDto,
OverviewCustomersTabDto,
OverviewOperationsTabDto,
OverviewStaffTabDto,
} from './dto/overview-tab-response.dto';
import { OVERVIEW_RANGE_DAYS } from './overview.constants';
import {
OVERVIEW_RANGE_DAYS,
mapContractStatusCountsToPipeline,
} from './overview.constants';
import { OverviewRepository } from './overview.repository';
@Injectable()
@@ -41,6 +45,7 @@ export class OverviewService {
const [
bookingKpis,
contractKpis,
operationsKpis,
customerKpis,
billingKpis,
@@ -51,6 +56,7 @@ export class OverviewService {
recentBookings,
] = await Promise.all([
this.overviewRepository.getBookingKpis(),
this.overviewRepository.getContractKpis(),
this.overviewRepository.getOperationsKpis(),
this.overviewRepository.getCustomerKpis(),
this.overviewRepository.getBillingKpis(),
@@ -67,6 +73,7 @@ export class OverviewService {
return {
kpis: {
bookings: bookingKpis,
contracts: contractKpis,
operations: operationsKpis,
customers: customerKpis,
billing: billingKpis,
@@ -121,6 +128,48 @@ export class OverviewService {
};
}
async getContractsTab(
range: OverviewRangeQuery = '30d',
): Promise<OverviewContractsTabDto> {
const days = OVERVIEW_RANGE_DAYS[range];
const [
kpis,
contractTrend,
statusCounts,
contractsByKind,
contractsByFreightType,
recentContracts,
] = await Promise.all([
this.overviewRepository.getContractKpis(),
this.overviewRepository.getContractTrend(days),
this.overviewRepository.getContractStatusCounts(),
this.overviewRepository.getContractsByKind(),
this.overviewRepository.getContractsByFreightType(),
this.overviewRepository.getRecentContracts(8),
]);
const contractsByStatus = Object.entries(statusCounts)
.map(([status, count]) => ({ status, count }))
.sort((a, b) => b.count - a.count);
const contractsByPipeline = mapContractStatusCountsToPipeline(statusCounts);
return {
kpis,
contractTrend,
contractsByStatus,
contractsByPipeline,
contractsByKind,
contractsByFreightType,
recentContracts: recentContracts.map((row) => ({
...row,
validUntil: row.validUntil ? row.validUntil.toISOString() : null,
createdAt: row.createdAt.toISOString(),
})),
generatedAt: new Date().toISOString(),
};
}
async getBillingTab(range: OverviewRangeQuery = '30d'): Promise<OverviewBillingTabDto> {
const days = OVERVIEW_RANGE_DAYS[range];