added graph and potentialmining
This commit is contained in:
parent
0ada5c4735
commit
7835cd3630
@ -60,6 +60,7 @@ export interface CallDetailProps {
|
|||||||
placeOrderAction?: ReactNode;
|
placeOrderAction?: ReactNode;
|
||||||
refreshKey?: number;
|
refreshKey?: number;
|
||||||
onBack?: () => void;
|
onBack?: () => void;
|
||||||
|
onDataLoad?: (data: Record<string, unknown>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CallDetail({
|
export function CallDetail({
|
||||||
@ -69,6 +70,7 @@ export function CallDetail({
|
|||||||
placeOrderAction,
|
placeOrderAction,
|
||||||
refreshKey,
|
refreshKey,
|
||||||
onBack,
|
onBack,
|
||||||
|
onDataLoad,
|
||||||
}: CallDetailProps) {
|
}: CallDetailProps) {
|
||||||
const [data, setData] = useState<Record<string, unknown> | null>(null);
|
const [data, setData] = useState<Record<string, unknown> | null>(null);
|
||||||
const [potentialData, setPotentialData] = useState<any[] | null>(null);
|
const [potentialData, setPotentialData] = useState<any[] | null>(null);
|
||||||
@ -84,8 +86,9 @@ export function CallDetail({
|
|||||||
const r = await orderBookingClient.detailView(ORDER_BOOKING.detailViews.CALLS, instanceId);
|
const r = await orderBookingClient.detailView(ORDER_BOOKING.detailViews.CALLS, instanceId);
|
||||||
if (live) {
|
if (live) {
|
||||||
setData(r.data || {});
|
setData(r.data || {});
|
||||||
|
if (onDataLoad) onDataLoad(r.data || {});
|
||||||
|
|
||||||
const storeCode = (r.data?.select_store as any)?.store_code || (r.data?.select_store as any)?.code;
|
const storeCode = (r.data?.select_store as any)?.store_code_2 || (r.data?.select_store as any)?.code || (r.data as any)?.store_code || (r.data as any)?.store?.store_code;
|
||||||
if (storeCode) {
|
if (storeCode) {
|
||||||
orderBookingClient.request<{ potential: { potential: any[] } }>(
|
orderBookingClient.request<{ potential: { potential: any[] } }>(
|
||||||
'POST',
|
'POST',
|
||||||
@ -107,7 +110,7 @@ export function CallDetail({
|
|||||||
return () => {
|
return () => {
|
||||||
live = false;
|
live = false;
|
||||||
};
|
};
|
||||||
}, [instanceId, refreshKey]);
|
}, [instanceId, refreshKey, onDataLoad]);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -52,7 +52,7 @@ export function OrderDetail({ instanceId }: WiredDetailViewProps) {
|
|||||||
|
|
||||||
if (!data || Object.keys(data).length === 0) {
|
if (!data || Object.keys(data).length === 0) {
|
||||||
return (
|
return (
|
||||||
<Card title={`Order #${instanceId}`}>
|
<Card title={`Order`}>
|
||||||
<EmptyState title="No details found" />
|
<EmptyState title="No details found" />
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
@ -117,13 +117,6 @@ export function OrderDetail({ instanceId }: WiredDetailViewProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-6 bg-transparent">
|
<div className="flex flex-col gap-6 bg-transparent">
|
||||||
{/* Header */}
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<div>
|
|
||||||
<h1 className="text-xl font-semibold tracking-tight text-slate-900">Order Detail</h1>
|
|
||||||
<p className="text-sm text-slate-500">#{orderId}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Status + Date ribbon */}
|
{/* Status + Date ribbon */}
|
||||||
<div className="flex flex-wrap items-center justify-between gap-3 rounded-2xl border border-slate-200 bg-[var(--tiles-card-bg)] p-4 shadow-sm">
|
<div className="flex flex-wrap items-center justify-between gap-3 rounded-2xl border border-slate-200 bg-[var(--tiles-card-bg)] p-4 shadow-sm">
|
||||||
|
|||||||
@ -47,7 +47,7 @@ export function AnalyticsChart({ data, gridCols }: AnalyticsChartProps) {
|
|||||||
: "grid grid-cols-1 lg:grid-cols-3 gap-6 w-full";
|
: "grid grid-cols-1 lg:grid-cols-3 gap-6 w-full";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={gridClass}>
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 w-full">
|
||||||
{charts.map((chart, idx) => {
|
{charts.map((chart, idx) => {
|
||||||
const title = (chart.key || `Chart ${idx + 1}`)
|
const title = (chart.key || `Chart ${idx + 1}`)
|
||||||
.replace(/_/g, ' ')
|
.replace(/_/g, ' ')
|
||||||
@ -93,10 +93,20 @@ export function AnalyticsChart({ data, gridCols }: AnalyticsChartProps) {
|
|||||||
chartType = 4; // Donut
|
chartType = 4; // Donut
|
||||||
} else if (lowerKey.includes('brand') || lowerKey.includes('product')) {
|
} else if (lowerKey.includes('brand') || lowerKey.includes('product')) {
|
||||||
chartType = 5; // Table
|
chartType = 5; // Table
|
||||||
} else if (lowerKey.includes('monthly')) {
|
} else if (lowerKey.includes('monthly') || lowerKey.includes('yearly')) {
|
||||||
chartType = 0; // Bar
|
chartType = 0; // Bar
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Determine if it should span full width
|
||||||
|
const isFullWidth = (gridCols === 1) || (!gridCols && charts.length <= 2) || hasSeries || lowerKey.includes('monthly') || lowerKey.includes('yearly') || finalData.length > 8;
|
||||||
|
|
||||||
|
const fullSpanClass = gridCols === 2 ? 'lg:col-span-2' : 'lg:col-span-3';
|
||||||
|
|
||||||
|
// Calculate a dynamic width if there's a lot of data to allow horizontal scrolling
|
||||||
|
const minChartWidth = chartType === 0 || chartType === 1 || chartType === 2
|
||||||
|
? Math.max(100, finalData.length * (hasSeries ? seriesKeys.length * 15 + 40 : 60))
|
||||||
|
: 100;
|
||||||
|
|
||||||
const renderChartContent = () => {
|
const renderChartContent = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -177,8 +187,8 @@ export function AnalyticsChart({ data, gridCols }: AnalyticsChartProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card key={chart.chart_uid || idx} title={title} className="shadow-sm border border-gray-100 !bg-[var(--tiles-card-bg)]">
|
<Card key={chart.chart_uid || idx} title={title} className={`shadow-sm border border-gray-100 !bg-[var(--tiles-card-bg)] ${isFullWidth ? fullSpanClass : ''}`}>
|
||||||
<div className="h-[320px] w-full mt-4">
|
<div className={`h-[320px] w-full mt-4 overflow-y-hidden ${minChartWidth > 100 ? 'overflow-x-auto scrollbar-slim' : ''}`}>
|
||||||
{chartType === 5 ? (
|
{chartType === 5 ? (
|
||||||
<div className="w-full h-full overflow-y-auto pr-2 scrollbar-slim">
|
<div className="w-full h-full overflow-y-auto pr-2 scrollbar-slim">
|
||||||
<table className="w-full text-left text-sm text-gray-600 border-collapse">
|
<table className="w-full text-left text-sm text-gray-600 border-collapse">
|
||||||
@ -203,6 +213,7 @@ export function AnalyticsChart({ data, gridCols }: AnalyticsChartProps) {
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
<div style={{ minWidth: minChartWidth > 100 ? `${minChartWidth}px` : '100%', height: '100%' }}>
|
||||||
<ResponsiveContainer width="100%" height="100%" className="focus:outline-none [&_.recharts-wrapper]:outline-none [&_.recharts-surface]:outline-none" style={{ outline: 'none' }}>
|
<ResponsiveContainer width="100%" height="100%" className="focus:outline-none [&_.recharts-wrapper]:outline-none [&_.recharts-surface]:outline-none" style={{ outline: 'none' }}>
|
||||||
{chartType === 3 || chartType === 4 ? (
|
{chartType === 3 || chartType === 4 ? (
|
||||||
<PieChart margin={{ top: 10, right: 10, left: 10, bottom: 10 }} className="focus:outline-none outline-none" style={{ outline: 'none' }}>
|
<PieChart margin={{ top: 10, right: 10, left: 10, bottom: 10 }} className="focus:outline-none outline-none" style={{ outline: 'none' }}>
|
||||||
@ -242,6 +253,7 @@ export function AnalyticsChart({ data, gridCols }: AnalyticsChartProps) {
|
|||||||
</BarChart>
|
</BarChart>
|
||||||
)}
|
)}
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@ -15,7 +15,7 @@ export function CallsView({ onRowClick, pageSize, headerActions, rowActions, ref
|
|||||||
headerActions={headerActions}
|
headerActions={headerActions}
|
||||||
rowActions={rowActions}
|
rowActions={rowActions}
|
||||||
refreshKey={refreshKey}
|
refreshKey={refreshKey}
|
||||||
sortBy="instance_id"
|
sortBy="visit_date"
|
||||||
sortDir="desc"
|
sortDir="desc"
|
||||||
omitColumns={['instance_id']}
|
omitColumns={['instance_id']}
|
||||||
initialFilters={initialFilters}
|
initialFilters={initialFilters}
|
||||||
|
|||||||
@ -98,21 +98,21 @@ export function RecordView({
|
|||||||
setPage(1);
|
setPage(1);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const initialFiltersStr = JSON.stringify(initialFilters || {});
|
||||||
const initialFiltersNormalized = useMemo(() => {
|
const initialFiltersNormalized = useMemo(() => {
|
||||||
const res: Record<string, string[]> = {};
|
const res: Record<string, string[]> = {};
|
||||||
if (initialFilters) {
|
const parsed = JSON.parse(initialFiltersStr);
|
||||||
Object.entries(initialFilters).forEach(([k, v]) => {
|
Object.entries(parsed).forEach(([k, v]) => {
|
||||||
if (Array.isArray(v)) {
|
if (Array.isArray(v)) {
|
||||||
res[k] = v;
|
res[k] = v as string[];
|
||||||
} else if (v) {
|
} else if (v) {
|
||||||
res[k] = [v];
|
res[k] = [v as string];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
return res;
|
return res;
|
||||||
}, [initialFilters]);
|
}, [initialFiltersStr]);
|
||||||
|
|
||||||
const [activeFilters, setActiveFilters] = useState<Record<string, string[]>>(initialFiltersNormalized);
|
const [activeFilters, setActiveFilters] = useState<Record<string, string[]>>({});
|
||||||
const [resp, setResp] = useState<RecordViewResponse | null>(null);
|
const [resp, setResp] = useState<RecordViewResponse | null>(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
@ -134,14 +134,23 @@ export function RecordView({
|
|||||||
|
|
||||||
const filtersParam = useMemo(() => {
|
const filtersParam = useMemo(() => {
|
||||||
const parsed = JSON.parse(activeFiltersStr) as Record<string, string[]>;
|
const parsed = JSON.parse(activeFiltersStr) as Record<string, string[]>;
|
||||||
return Object.entries(parsed)
|
|
||||||
|
// Merge user active filters on top of initial prefilters
|
||||||
|
const merged = { ...initialFiltersNormalized };
|
||||||
|
Object.entries(parsed).forEach(([k, v]) => {
|
||||||
|
if (v && v.length > 0) {
|
||||||
|
merged[k] = v;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return Object.entries(merged)
|
||||||
.filter(([_, vals]) => vals && vals.length > 0)
|
.filter(([_, vals]) => vals && vals.length > 0)
|
||||||
.map(([key, vals]) => ({
|
.map(([key, vals]) => ({
|
||||||
field_key: key,
|
field_key: key,
|
||||||
values: vals,
|
values: vals,
|
||||||
data_type: 'character varying',
|
data_type: 'character varying',
|
||||||
}));
|
}));
|
||||||
}, [activeFiltersStr]);
|
}, [activeFiltersStr, initialFiltersNormalized]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let live = true;
|
let live = true;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user