order view calls view pages done
This commit is contained in:
parent
48c6a1cab9
commit
b5e84a34f7
@ -54,8 +54,8 @@ export const ORDER_BOOKING = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
recordViews: {
|
recordViews: {
|
||||||
ORDERS: '1e424561-9a27-44f5-9b68-64d63296d837',
|
ORDERS: '63714ac2-c9fb-40e2-abba-d4081a70b768',
|
||||||
CALLS: 'b4d7a710-24e7-416d-885a-31fd1e727aab',
|
CALLS: '56b21b46-6d2c-4e10-b5a3-c63d058d0afa',
|
||||||
},
|
},
|
||||||
detailViews: {
|
detailViews: {
|
||||||
ORDERS: '0804c6c3-6cf9-4050-94bb-fa48dd5de87d',
|
ORDERS: '0804c6c3-6cf9-4050-94bb-fa48dd5de87d',
|
||||||
|
|||||||
@ -146,7 +146,7 @@ export function DetailView({ client, instanceId, title, columns = 2 }: DetailVie
|
|||||||
<tr key={idx} className="hover:bg-slate-50/50">
|
<tr key={idx} className="hover:bg-slate-50/50">
|
||||||
{headers.map(h => (
|
{headers.map(h => (
|
||||||
<td key={h} className="px-4 py-2 whitespace-nowrap text-sm text-strong">
|
<td key={h} className="px-4 py-2 whitespace-nowrap text-sm text-strong">
|
||||||
{formatValue(row[h])}
|
{formatValue(row[h], h)}
|
||||||
</td>
|
</td>
|
||||||
))}
|
))}
|
||||||
</tr>
|
</tr>
|
||||||
@ -164,7 +164,7 @@ export function DetailView({ client, instanceId, title, columns = 2 }: DetailVie
|
|||||||
{key.replace(/_/g, ' ')}
|
{key.replace(/_/g, ' ')}
|
||||||
</dt>
|
</dt>
|
||||||
<dd className="m-0 text-sm text-strong font-medium break-words">
|
<dd className="m-0 text-sm text-strong font-medium break-words">
|
||||||
{formatValue(value)}
|
{formatValue(value, key)}
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -15,6 +15,9 @@ export function CallsView({ onRowClick, pageSize, headerActions, rowActions, ref
|
|||||||
headerActions={headerActions}
|
headerActions={headerActions}
|
||||||
rowActions={rowActions}
|
rowActions={rowActions}
|
||||||
refreshKey={refreshKey}
|
refreshKey={refreshKey}
|
||||||
|
sortBy="instance_id"
|
||||||
|
sortDir="desc"
|
||||||
|
omitColumns={['instance_id']}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,6 +22,9 @@ export function OrdersView({ onRowClick, pageSize, headerActions, rowActions, re
|
|||||||
headerActions={headerActions}
|
headerActions={headerActions}
|
||||||
rowActions={rowActions}
|
rowActions={rowActions}
|
||||||
refreshKey={refreshKey}
|
refreshKey={refreshKey}
|
||||||
|
sortBy="created_at"
|
||||||
|
sortDir="desc"
|
||||||
|
omitColumns={['created_at', 'instance_id']}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,6 +21,8 @@ export interface RecordViewProps {
|
|||||||
title?: string;
|
title?: string;
|
||||||
/** Restrict/order visible columns by field_key. Defaults to all fields. */
|
/** Restrict/order visible columns by field_key. Defaults to all fields. */
|
||||||
columns?: string[];
|
columns?: string[];
|
||||||
|
/** Columns to hide. */
|
||||||
|
omitColumns?: string[];
|
||||||
/** Rows per page. @default 25 */
|
/** Rows per page. @default 25 */
|
||||||
pageSize?: number;
|
pageSize?: number;
|
||||||
/** Click handler — receives the raw row + index. */
|
/** Click handler — receives the raw row + index. */
|
||||||
@ -33,6 +35,10 @@ export interface RecordViewProps {
|
|||||||
rowActions?: (row: Record<string, unknown>) => React.ReactNode;
|
rowActions?: (row: Record<string, unknown>) => React.ReactNode;
|
||||||
/** Pass a new value to trigger a refresh. */
|
/** Pass a new value to trigger a refresh. */
|
||||||
refreshKey?: number;
|
refreshKey?: number;
|
||||||
|
/** Field to sort by */
|
||||||
|
sortBy?: string;
|
||||||
|
/** Sort direction */
|
||||||
|
sortDir?: 'asc' | 'desc';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -51,6 +57,9 @@ export function RecordView({
|
|||||||
headerActions,
|
headerActions,
|
||||||
rowActions,
|
rowActions,
|
||||||
refreshKey,
|
refreshKey,
|
||||||
|
sortBy,
|
||||||
|
sortDir,
|
||||||
|
omitColumns,
|
||||||
}: RecordViewProps) {
|
}: RecordViewProps) {
|
||||||
const [page, setPage] = useState(1);
|
const [page, setPage] = useState(1);
|
||||||
const [search, setSearch] = useState('');
|
const [search, setSearch] = useState('');
|
||||||
@ -89,7 +98,14 @@ export function RecordView({
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
try {
|
try {
|
||||||
const r = await client.recordView(rvUid, { page, limit: pageSize, search: debounced, filters: filtersParam });
|
const r = await client.recordView(rvUid, {
|
||||||
|
page,
|
||||||
|
limit: pageSize,
|
||||||
|
search: debounced,
|
||||||
|
filters: filtersParam,
|
||||||
|
sortBy,
|
||||||
|
sortDir
|
||||||
|
});
|
||||||
console.log("RECORD VIEW RESP:", r);
|
console.log("RECORD VIEW RESP:", r);
|
||||||
if (live) setResp(r);
|
if (live) setResp(r);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -102,16 +118,23 @@ export function RecordView({
|
|||||||
return () => {
|
return () => {
|
||||||
live = false;
|
live = false;
|
||||||
};
|
};
|
||||||
}, [client, rvUid, page, pageSize, debounced, filtersParam, refreshKey]);
|
}, [client, rvUid, page, pageSize, debounced, filtersParam, refreshKey, sortBy, sortDir]);
|
||||||
|
|
||||||
const fields: RecordViewField[] = useMemo(() => {
|
const fields: RecordViewField[] = useMemo(() => {
|
||||||
const all = resp?.config.fields ?? [];
|
const all = resp?.config.fields ?? [];
|
||||||
if (!columns) return all;
|
let result = all;
|
||||||
const byKey = new Map(all.map((f) => [f.field_key, f]));
|
if (columns) {
|
||||||
return columns
|
const byKey = new Map(all.map((f) => [f.field_key, f]));
|
||||||
.map((k) => byKey.get(k) ?? ({ field_key: k, output_label: k, data_type: 'string', is_filter: false, is_search: false } as RecordViewField))
|
result = columns
|
||||||
.filter(Boolean);
|
.map((k) => byKey.get(k) ?? ({ field_key: k, output_label: k, data_type: 'string', is_filter: false, is_search: false } as RecordViewField))
|
||||||
}, [resp, columns]);
|
.filter(Boolean);
|
||||||
|
}
|
||||||
|
if (omitColumns) {
|
||||||
|
const omitSet = new Set(omitColumns);
|
||||||
|
result = result.filter((f) => !omitSet.has(f.field_key));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}, [resp, columns, omitColumns]);
|
||||||
|
|
||||||
const rows = resp?.data ?? [];
|
const rows = resp?.data ?? [];
|
||||||
const total = resp?.pagination?.total_count ?? rows.length;
|
const total = resp?.pagination?.total_count ?? rows.length;
|
||||||
@ -133,27 +156,55 @@ export function RecordView({
|
|||||||
>
|
>
|
||||||
<div className="flex flex-wrap items-center justify-between gap-4 p-4 border-b border-border-subtle bg-slate-50/50">
|
<div className="flex flex-wrap items-center justify-between gap-4 p-4 border-b border-border-subtle bg-slate-50/50">
|
||||||
<div className="flex flex-wrap items-center gap-3">
|
<div className="flex flex-wrap items-center gap-3">
|
||||||
{resp?.config?.filter_options && Object.entries(resp.config.filter_options).map(([key, options]) => {
|
{(() => {
|
||||||
if (!options || options.length === 0) return null;
|
if (!resp?.config) return null;
|
||||||
|
const filterKeys = new Set<string>();
|
||||||
|
resp.config.fields.forEach(f => {
|
||||||
|
if (f.is_filter) filterKeys.add(f.field_key);
|
||||||
|
});
|
||||||
|
if (resp.config.filter_options) {
|
||||||
|
Object.keys(resp.config.filter_options).forEach(k => filterKeys.add(k));
|
||||||
|
}
|
||||||
|
|
||||||
// Find the field in config to get its proper label
|
return Array.from(filterKeys).map(key => {
|
||||||
const fieldDef = resp.config.fields.find(f => f.field_key === key);
|
const fieldDef = resp.config.fields.find(f => f.field_key === key);
|
||||||
const label = fieldDef?.output_label || key;
|
const label = fieldDef?.output_label || key;
|
||||||
|
const isDate = fieldDef?.data_type === 'date' || fieldDef?.data_type === 'datetime' || key.toLowerCase().includes('date') || key.toLowerCase() === 'created_at' || key.toLowerCase() === 'updated_at';
|
||||||
|
|
||||||
const opts = options.map(o => ({ value: o, label: o }));
|
if (isDate) {
|
||||||
return (
|
return (
|
||||||
<Select
|
<input
|
||||||
key={key}
|
key={key}
|
||||||
value={activeFilters[key] || ''}
|
type="date"
|
||||||
onChange={(e) => {
|
title={`Filter by ${label}`}
|
||||||
setActiveFilters(prev => ({ ...prev, [key]: e.target.value }));
|
value={activeFilters[key] || ''}
|
||||||
setPage(1);
|
onChange={(e) => {
|
||||||
}}
|
setActiveFilters(prev => ({ ...prev, [key]: e.target.value }));
|
||||||
options={[{ value: '', label: `All ${label}` }, ...opts]}
|
setPage(1);
|
||||||
className="w-40 shrink-0"
|
}}
|
||||||
/>
|
className="w-36 sm:w-40 h-9 rounded-md border border-border-default bg-card px-3 font-sans text-sm text-strong outline-none focus-ring shrink-0"
|
||||||
);
|
/>
|
||||||
})}
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = resp.config.filter_options?.[key];
|
||||||
|
if (!options || options.length === 0) return null;
|
||||||
|
|
||||||
|
const opts = options.map(o => ({ value: o, label: o }));
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
key={key}
|
||||||
|
value={activeFilters[key] || ''}
|
||||||
|
onChange={(e) => {
|
||||||
|
setActiveFilters(prev => ({ ...prev, [key]: e.target.value }));
|
||||||
|
setPage(1);
|
||||||
|
}}
|
||||||
|
options={[{ value: '', label: `All ${label}` }, ...opts]}
|
||||||
|
className="w-36 sm:w-40 shrink-0"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
})()}
|
||||||
</div>
|
</div>
|
||||||
<div className="relative w-full sm:w-[240px] shrink-0">
|
<div className="relative w-full sm:w-[240px] shrink-0">
|
||||||
<Search size={15} className="absolute left-3 top-1/2 -translate-y-1/2 text-faint pointer-events-none" />
|
<Search size={15} className="absolute left-3 top-1/2 -translate-y-1/2 text-faint pointer-events-none" />
|
||||||
@ -206,7 +257,7 @@ export function RecordView({
|
|||||||
>
|
>
|
||||||
{fields.map((f) => (
|
{fields.map((f) => (
|
||||||
<td key={f.field_key} className="px-[18px] py-3 text-sm text-body whitespace-nowrap">
|
<td key={f.field_key} className="px-[18px] py-3 text-sm text-body whitespace-nowrap">
|
||||||
{formatValue(row[f.field_key])}
|
{formatValue(row[f.field_key], f.field_key)}
|
||||||
</td>
|
</td>
|
||||||
))}
|
))}
|
||||||
{rowActions && (
|
{rowActions && (
|
||||||
|
|||||||
@ -13,10 +13,37 @@ function isObject(v: unknown): v is Record<string, unknown> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Best-effort single-line display string for a field value. */
|
/** Best-effort single-line display string for a field value. */
|
||||||
export function formatValue(value: unknown): string {
|
export function formatValue(value: unknown, fieldKey?: string): string {
|
||||||
if (value == null || value === '') return '—';
|
if (value == null || value === '') return '—';
|
||||||
if (typeof value === 'boolean') return value ? 'Yes' : 'No';
|
if (typeof value === 'boolean') return value ? 'Yes' : 'No';
|
||||||
if (typeof value === 'number' || typeof value === 'string') return String(value);
|
if (typeof value === 'number') return String(value);
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
const isoDateRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/;
|
||||||
|
if (isoDateRegex.test(value)) {
|
||||||
|
const date = new Date(value);
|
||||||
|
if (!isNaN(date.getTime())) {
|
||||||
|
const lowerKey = fieldKey?.toLowerCase() || '';
|
||||||
|
const isTimeOnly = lowerKey.includes('time');
|
||||||
|
const isDateOnly = lowerKey.includes('date');
|
||||||
|
|
||||||
|
if (isTimeOnly && !isDateOnly) {
|
||||||
|
return date.toLocaleTimeString('en-IN', { hour: 'numeric', minute: '2-digit', hour12: true });
|
||||||
|
} else if (isDateOnly && !isTimeOnly) {
|
||||||
|
return date.toLocaleDateString('en-IN', { day: 'numeric', month: 'short', year: 'numeric' });
|
||||||
|
} else {
|
||||||
|
return date.toLocaleString('en-IN', {
|
||||||
|
day: 'numeric',
|
||||||
|
month: 'short',
|
||||||
|
year: 'numeric',
|
||||||
|
hour: 'numeric',
|
||||||
|
minute: '2-digit',
|
||||||
|
hour12: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
if (value.length === 0) return '—';
|
if (value.length === 0) return '—';
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { defineConfig } from 'vite'
|
|||||||
import react from '@vitejs/plugin-react'
|
import react from '@vitejs/plugin-react'
|
||||||
import tailwindcss from '@tailwindcss/vite'
|
import tailwindcss from '@tailwindcss/vite'
|
||||||
|
|
||||||
const base = process.env.VITE_BASE_URL ?? '/'
|
const base = process.env.VITE_BASE_URL ?? '/krishna_sales'
|
||||||
|
|
||||||
export default defineConfig(() => ({
|
export default defineConfig(() => ({
|
||||||
base,
|
base,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user