This commit is contained in:
phaichayon
2026-06-22 21:26:43 +07:00
parent 5c28080e9b
commit 99a4087099
27 changed files with 6863 additions and 17 deletions

View File

@@ -0,0 +1,72 @@
import { HydrationBoundary, dehydrate } from '@tanstack/react-query';
import { auth } from '@/auth';
import PageContainer from '@/components/layout/page-container';
import {
crmEnquiryAgingReportQueryOptions,
crmReportFiltersQueryOptions
} from '@/features/crm/reports/api/queries';
import { AgingReportView } from '@/features/crm/reports/components/aging-report-view';
import { PERMISSIONS } from '@/lib/auth/rbac';
import { getQueryClient } from '@/lib/query-client';
import type { SearchParams } from 'nuqs/server';
export const metadata = {
title: 'CRM Enquiry Aging Report'
};
type PageProps = {
searchParams: Promise<SearchParams>;
};
function getFilterValue(value: string | string[] | undefined): string | null {
return typeof value === 'string' ? value : null;
}
export default async function CrmEnquiryAgingRoute(props: PageProps) {
const searchParams = await props.searchParams;
const session = await auth();
const canRead =
session?.user?.systemRole === 'super_admin' ||
(!!session?.user?.activeOrganizationId &&
(session.user.activeMembershipRole === 'admin' ||
session.user.activePermissions.includes(PERMISSIONS.crmReportRead)));
const canExport =
session?.user?.systemRole === 'super_admin' ||
(!!session?.user?.activeOrganizationId &&
(session.user.activeMembershipRole === 'admin' ||
session.user.activePermissions.includes(PERMISSIONS.crmReportExport)));
const queryClient = getQueryClient();
const filters = {
dateFrom: getFilterValue(searchParams.dateFrom),
dateTo: getFilterValue(searchParams.dateTo),
branch: getFilterValue(searchParams.branch),
productType: getFilterValue(searchParams.productType),
sales: getFilterValue(searchParams.sales),
customerOwner: null,
leadSource: null
};
if (canRead) {
void queryClient.prefetchQuery(crmReportFiltersQueryOptions());
void queryClient.prefetchQuery(crmEnquiryAgingReportQueryOptions(filters));
}
return (
<PageContainer
pageTitle='Enquiry Aging Report'
pageDescription='Identify stalled enquiries that need follow-up or progression.'
access={canRead}
accessFallback={
<div className='text-muted-foreground rounded-lg border border-dashed p-8 text-center'>
You do not have access to CRM enquiry aging reports.
</div>
}
>
{canRead ? (
<HydrationBoundary state={dehydrate(queryClient)}>
<AgingReportView variant='enquiry' canExport={canExport} />
</HydrationBoundary>
) : null}
</PageContainer>
);
}

View File

@@ -0,0 +1,72 @@
import { HydrationBoundary, dehydrate } from '@tanstack/react-query';
import { auth } from '@/auth';
import PageContainer from '@/components/layout/page-container';
import {
crmLeadAgingReportQueryOptions,
crmReportFiltersQueryOptions
} from '@/features/crm/reports/api/queries';
import { AgingReportView } from '@/features/crm/reports/components/aging-report-view';
import { PERMISSIONS } from '@/lib/auth/rbac';
import { getQueryClient } from '@/lib/query-client';
import type { SearchParams } from 'nuqs/server';
export const metadata = {
title: 'CRM Lead Aging Report'
};
type PageProps = {
searchParams: Promise<SearchParams>;
};
function getFilterValue(value: string | string[] | undefined): string | null {
return typeof value === 'string' ? value : null;
}
export default async function CrmLeadAgingRoute(props: PageProps) {
const searchParams = await props.searchParams;
const session = await auth();
const canRead =
session?.user?.systemRole === 'super_admin' ||
(!!session?.user?.activeOrganizationId &&
(session.user.activeMembershipRole === 'admin' ||
session.user.activePermissions.includes(PERMISSIONS.crmReportRead)));
const canExport =
session?.user?.systemRole === 'super_admin' ||
(!!session?.user?.activeOrganizationId &&
(session.user.activeMembershipRole === 'admin' ||
session.user.activePermissions.includes(PERMISSIONS.crmReportExport)));
const queryClient = getQueryClient();
const filters = {
dateFrom: getFilterValue(searchParams.dateFrom),
dateTo: getFilterValue(searchParams.dateTo),
branch: getFilterValue(searchParams.branch),
productType: getFilterValue(searchParams.productType),
sales: null,
customerOwner: getFilterValue(searchParams.customerOwner),
leadSource: getFilterValue(searchParams.leadSource)
};
if (canRead) {
void queryClient.prefetchQuery(crmReportFiltersQueryOptions());
void queryClient.prefetchQuery(crmLeadAgingReportQueryOptions(filters));
}
return (
<PageContainer
pageTitle='Lead Aging Report'
pageDescription='Identify stagnant leads before they move deeper into the pipeline.'
access={canRead}
accessFallback={
<div className='text-muted-foreground rounded-lg border border-dashed p-8 text-center'>
You do not have access to CRM lead aging reports.
</div>
}
>
{canRead ? (
<HydrationBoundary state={dehydrate(queryClient)}>
<AgingReportView variant='lead' canExport={canExport} />
</HydrationBoundary>
) : null}
</PageContainer>
);
}

View File

@@ -30,7 +30,7 @@ export default async function CrmReportsRoute() {
return (
<PageContainer
pageTitle='CRM Reports'
pageDescription='Report center foundation, shared filters, and catalog for upcoming CRM analytics modules.'
pageDescription='CRM report center with production pipeline reports, aging views, and shared analytics filters.'
access={canRead}
accessFallback={
<div className='text-muted-foreground rounded-lg border border-dashed p-8 text-center'>

View File

@@ -0,0 +1,74 @@
import { HydrationBoundary, dehydrate } from '@tanstack/react-query';
import { auth } from '@/auth';
import PageContainer from '@/components/layout/page-container';
import {
crmPipelineReportQueryOptions,
crmReportFiltersQueryOptions
} from '@/features/crm/reports/api/queries';
import { PipelineReportView } from '@/features/crm/reports/components/pipeline-report-view';
import { PERMISSIONS } from '@/lib/auth/rbac';
import { getQueryClient } from '@/lib/query-client';
import type { SearchParams } from 'nuqs/server';
export const metadata = {
title: 'CRM Pipeline Reports'
};
type PageProps = {
searchParams: Promise<SearchParams>;
};
function getFilterValue(
value: string | string[] | undefined
): string | null {
return typeof value === 'string' ? value : null;
}
export default async function CrmPipelineReportsRoute(props: PageProps) {
const searchParams = await props.searchParams;
const session = await auth();
const canRead =
session?.user?.systemRole === 'super_admin' ||
(!!session?.user?.activeOrganizationId &&
(session.user.activeMembershipRole === 'admin' ||
session.user.activePermissions.includes(PERMISSIONS.crmReportRead)));
const canExport =
session?.user?.systemRole === 'super_admin' ||
(!!session?.user?.activeOrganizationId &&
(session.user.activeMembershipRole === 'admin' ||
session.user.activePermissions.includes(PERMISSIONS.crmReportExport)));
const queryClient = getQueryClient();
const filters = {
dateFrom: getFilterValue(searchParams.dateFrom),
dateTo: getFilterValue(searchParams.dateTo),
branch: getFilterValue(searchParams.branch),
productType: getFilterValue(searchParams.productType),
sales: getFilterValue(searchParams.sales),
customerOwner: getFilterValue(searchParams.customerOwner),
leadSource: getFilterValue(searchParams.leadSource)
};
if (canRead) {
void queryClient.prefetchQuery(crmReportFiltersQueryOptions());
void queryClient.prefetchQuery(crmPipelineReportQueryOptions(filters));
}
return (
<PageContainer
pageTitle='Pipeline Reports'
pageDescription='Lead, enquiry, conversion, and funnel analytics for the CRM pipeline lifecycle.'
access={canRead}
accessFallback={
<div className='text-muted-foreground rounded-lg border border-dashed p-8 text-center'>
You do not have access to CRM pipeline reports.
</div>
}
>
{canRead ? (
<HydrationBoundary state={dehydrate(queryClient)}>
<PipelineReportView canExport={canExport} />
</HydrationBoundary>
) : null}
</PageContainer>
);
}