order view calls view pages done
This commit is contained in:
parent
48c6a1cab9
commit
b5e84a34f7
@ -54,8 +54,8 @@ export const ORDER_BOOKING = {
|
||||
},
|
||||
},
|
||||
recordViews: {
|
||||
ORDERS: '1e424561-9a27-44f5-9b68-64d63296d837',
|
||||
CALLS: 'b4d7a710-24e7-416d-885a-31fd1e727aab',
|
||||
ORDERS: '63714ac2-c9fb-40e2-abba-d4081a70b768',
|
||||
CALLS: '56b21b46-6d2c-4e10-b5a3-c63d058d0afa',
|
||||
},
|
||||
detailViews: {
|
||||
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">
|
||||
{headers.map(h => (
|
||||
<td key={h} className="px-4 py-2 whitespace-nowrap text-sm text-strong">
|
||||
{formatValue(row[h])}
|
||||
{formatValue(row[h], h)}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
@ -164,7 +164,7 @@ export function DetailView({ client, instanceId, title, columns = 2 }: DetailVie
|
||||
{key.replace(/_/g, ' ')}
|
||||
</dt>
|
||||
<dd className="m-0 text-sm text-strong font-medium break-words">
|
||||
{formatValue(value)}
|
||||
{formatValue(value, key)}
|
||||
</dd>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -15,6 +15,9 @@ export function CallsView({ onRowClick, pageSize, headerActions, rowActions, ref
|
||||
headerActions={headerActions}
|
||||
rowActions={rowActions}
|
||||
refreshKey={refreshKey}
|
||||
sortBy="instance_id"
|
||||
sortDir="desc"
|
||||
omitColumns={['instance_id']}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@ -22,6 +22,9 @@ export function OrdersView({ onRowClick, pageSize, headerActions, rowActions, re
|
||||
headerActions={headerActions}
|
||||
rowActions={rowActions}
|
||||
refreshKey={refreshKey}
|
||||
sortBy="created_at"
|
||||
sortDir="desc"
|
||||
omitColumns={['created_at', 'instance_id']}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@ -21,6 +21,8 @@ export interface RecordViewProps {
|
||||
title?: string;
|
||||
/** Restrict/order visible columns by field_key. Defaults to all fields. */
|
||||
columns?: string[];
|
||||
/** Columns to hide. */
|
||||
omitColumns?: string[];
|
||||
/** Rows per page. @default 25 */
|
||||
pageSize?: number;
|
||||
/** Click handler — receives the raw row + index. */
|
||||
@ -33,6 +35,10 @@ export interface RecordViewProps {
|
||||
rowActions?: (row: Record<string, unknown>) => React.ReactNode;
|
||||
/** Pass a new value to trigger a refresh. */
|
||||
refreshKey?: number;
|
||||
/** Field to sort by */
|
||||
sortBy?: string;
|
||||
/** Sort direction */
|
||||
sortDir?: 'asc' | 'desc';
|
||||
}
|
||||
|
||||
/**
|
||||
@ -51,6 +57,9 @@ export function RecordView({
|
||||
headerActions,
|
||||
rowActions,
|
||||
refreshKey,
|
||||
sortBy,
|
||||
sortDir,
|
||||
omitColumns,
|
||||
}: RecordViewProps) {
|
||||
const [page, setPage] = useState(1);
|
||||
const [search, setSearch] = useState('');
|
||||
@ -89,7 +98,14 @@ export function RecordView({
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
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);
|
||||
if (live) setResp(r);
|
||||
} catch (e) {
|
||||
@ -102,16 +118,23 @@ export function RecordView({
|
||||
return () => {
|
||||
live = false;
|
||||
};
|
||||
}, [client, rvUid, page, pageSize, debounced, filtersParam, refreshKey]);
|
||||
}, [client, rvUid, page, pageSize, debounced, filtersParam, refreshKey, sortBy, sortDir]);
|
||||
|
||||
const fields: RecordViewField[] = useMemo(() => {
|
||||
const all = resp?.config.fields ?? [];
|
||||
if (!columns) return all;
|
||||
let result = all;
|
||||
if (columns) {
|
||||
const byKey = new Map(all.map((f) => [f.field_key, f]));
|
||||
return columns
|
||||
result = columns
|
||||
.map((k) => byKey.get(k) ?? ({ field_key: k, output_label: k, data_type: 'string', is_filter: false, is_search: false } as RecordViewField))
|
||||
.filter(Boolean);
|
||||
}, [resp, columns]);
|
||||
}
|
||||
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 total = resp?.pagination?.total_count ?? rows.length;
|
||||
@ -133,12 +156,39 @@ 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 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 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';
|
||||
|
||||
if (isDate) {
|
||||
return (
|
||||
<input
|
||||
key={key}
|
||||
type="date"
|
||||
title={`Filter by ${label}`}
|
||||
value={activeFilters[key] || ''}
|
||||
onChange={(e) => {
|
||||
setActiveFilters(prev => ({ ...prev, [key]: e.target.value }));
|
||||
setPage(1);
|
||||
}}
|
||||
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 (
|
||||
@ -150,10 +200,11 @@ export function RecordView({
|
||||
setPage(1);
|
||||
}}
|
||||
options={[{ value: '', label: `All ${label}` }, ...opts]}
|
||||
className="w-40 shrink-0"
|
||||
className="w-36 sm:w-40 shrink-0"
|
||||
/>
|
||||
);
|
||||
})}
|
||||
});
|
||||
})()}
|
||||
</div>
|
||||
<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" />
|
||||
@ -206,7 +257,7 @@ export function RecordView({
|
||||
>
|
||||
{fields.map((f) => (
|
||||
<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>
|
||||
))}
|
||||
{rowActions && (
|
||||
|
||||
@ -13,10 +13,37 @@ function isObject(v: unknown): v is Record<string, unknown> {
|
||||
}
|
||||
|
||||
/** 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 (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 (value.length === 0) return '—';
|
||||
|
||||
@ -2,7 +2,7 @@ import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
import tailwindcss from '@tailwindcss/vite'
|
||||
|
||||
const base = process.env.VITE_BASE_URL ?? '/'
|
||||
const base = process.env.VITE_BASE_URL ?? '/krishna_sales'
|
||||
|
||||
export default defineConfig(() => ({
|
||||
base,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user