order and calls are done,fixed detail view with those table for grid
This commit is contained in:
parent
94fbcc16ee
commit
785885c383
@ -3,6 +3,7 @@ import { cn } from '../../lib/cn';
|
||||
import { formatValue } from '../../lib/format';
|
||||
import type { ZinoClient } from '../../api/client';
|
||||
import type { AuditEntry } from '../../api/types';
|
||||
import { WORKFLOWS } from '../../api/config';
|
||||
import { Card } from '../reusable/Card';
|
||||
import { Spinner } from '../reusable/Spinner';
|
||||
import { EmptyState } from '../reusable/EmptyState';
|
||||
@ -22,6 +23,23 @@ export interface DetailViewProps {
|
||||
columns?: 1 | 2 | 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to resolve a human-readable activity name from its UID.
|
||||
*/
|
||||
function getActivityName(uid: string): string | undefined {
|
||||
for (const wf of Object.values(WORKFLOWS)) {
|
||||
for (const [actName, actDef] of Object.entries(wf.activities)) {
|
||||
if (actDef.uid === uid) {
|
||||
return actName
|
||||
.split('_')
|
||||
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
||||
.join(' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic Zino detail view. Fetches `GET /app/{id}/view/audit` and
|
||||
* renders the audit trail as a labeled definition grid.
|
||||
@ -81,22 +99,80 @@ export function DetailView({ client, instanceId, title, columns = 2 }: DetailVie
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
{entries.map((entry, i) => (
|
||||
<Card key={entry.id} title={i === 0 && title ? title : `Update on ${new Date(entry.created_at).toLocaleString()}`}>
|
||||
<dl className={cn('grid gap-x-6 gap-y-4', gridCols)}>
|
||||
{Object.entries(entry.data).map(([key, value]) => (
|
||||
<div key={key} className="flex flex-col gap-1 min-w-0">
|
||||
<dt className="text-[10px] font-bold uppercase tracking-[0.06em] text-faint">
|
||||
{key.replace(/_/g, ' ')}
|
||||
</dt>
|
||||
<dd className="m-0 text-sm text-strong font-medium break-words">
|
||||
{formatValue(value)}
|
||||
</dd>
|
||||
</div>
|
||||
))}
|
||||
</dl>
|
||||
</Card>
|
||||
))}
|
||||
{entries.map((entry, i) => {
|
||||
const actName = getActivityName(entry.activity_id);
|
||||
const headerTitle = actName || (i === 0 && title ? title : 'Update');
|
||||
const updatedAt = new Date(entry.created_at).toLocaleString();
|
||||
|
||||
return (
|
||||
<Card
|
||||
key={entry.id}
|
||||
title={headerTitle}
|
||||
action={<span className="text-xs font-medium text-faint">{updatedAt}</span>}
|
||||
>
|
||||
<dl className={cn('grid gap-x-6 gap-y-4', gridCols)}>
|
||||
{Object.entries(entry.data).map(([key, value]) => {
|
||||
const isGridArray =
|
||||
Array.isArray(value) &&
|
||||
value.length > 0 &&
|
||||
typeof value[0] === 'object' &&
|
||||
value[0] !== null &&
|
||||
!('original_name' in value[0]) &&
|
||||
!('url' in value[0]);
|
||||
|
||||
if (isGridArray) {
|
||||
const rows = value as Record<string, unknown>[];
|
||||
// Use all unique keys found across all rows just in case they differ slightly
|
||||
const headers = Array.from(new Set(rows.flatMap(r => Object.keys(r))));
|
||||
|
||||
return (
|
||||
<div key={key} className="flex flex-col gap-2 min-w-0 col-span-full mt-2 mb-4">
|
||||
<dt className="text-[10px] font-bold uppercase tracking-[0.06em] text-faint">
|
||||
{key.replace(/_/g, ' ')}
|
||||
</dt>
|
||||
<dd className="m-0 text-sm text-strong font-medium overflow-x-auto rounded border border-border-subtle shadow-sm">
|
||||
<table className="min-w-full divide-y divide-border-subtle text-left bg-white">
|
||||
<thead className="bg-slate-50">
|
||||
<tr>
|
||||
{headers.map(h => (
|
||||
<th key={h} className="px-4 py-2 text-[10px] font-bold uppercase text-muted tracking-wide whitespace-nowrap">
|
||||
{h.replace(/_/g, ' ')}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-border-subtle">
|
||||
{rows.map((row, idx) => (
|
||||
<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])}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</dd>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={key} className="flex flex-col gap-1 min-w-0">
|
||||
<dt className="text-[10px] font-bold uppercase tracking-[0.06em] text-faint">
|
||||
{key.replace(/_/g, ' ')}
|
||||
</dt>
|
||||
<dd className="m-0 text-sm text-strong font-medium break-words">
|
||||
{formatValue(value)}
|
||||
</dd>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</dl>
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@ import { Button } from '../buttons/Button';
|
||||
import { Spinner } from '../reusable/Spinner';
|
||||
import {
|
||||
FileInput,
|
||||
GridInput,
|
||||
SmartGridField,
|
||||
GeolocationInput,
|
||||
PhoneInput,
|
||||
SelectField,
|
||||
@ -24,9 +24,10 @@ export interface DynamicFormProps {
|
||||
instanceId?: number | string;
|
||||
onSuccess?: () => void;
|
||||
onCancel?: () => void;
|
||||
ignorePrefill?: boolean;
|
||||
}
|
||||
|
||||
export function DynamicForm({ client, activityId: initialActivityId, instanceId: initialInstanceId, onSuccess, onCancel }: DynamicFormProps) {
|
||||
export function DynamicForm({ client, activityId: initialActivityId, instanceId: initialInstanceId, onSuccess, onCancel, ignorePrefill }: DynamicFormProps) {
|
||||
const [currentActivityId, setCurrentActivityId] = useState(initialActivityId);
|
||||
const [currentInstanceId, setCurrentInstanceId] = useState<number | string | undefined>(initialInstanceId);
|
||||
const [chainQueue, setChainQueue] = useState<Array<{ activity_uid: string; activity_name: string }>>([]);
|
||||
@ -71,15 +72,18 @@ export function DynamicForm({ client, activityId: initialActivityId, instanceId:
|
||||
res.fields.filter(f => f.data_type === 'image' || f.data_type === 'file').map(f => f.id)
|
||||
);
|
||||
|
||||
if (res.prefill_data) {
|
||||
Object.entries(res.prefill_data).forEach(([k, v]) => {
|
||||
if (!imageFieldIds.has(k)) defaultValues[k] = v;
|
||||
});
|
||||
} else if (res.data) {
|
||||
Object.entries(res.data).forEach(([k, v]) => {
|
||||
if (!imageFieldIds.has(k)) defaultValues[k] = v;
|
||||
});
|
||||
if (!ignorePrefill) {
|
||||
if (res.prefill_data) {
|
||||
Object.entries(res.prefill_data).forEach(([k, v]) => {
|
||||
if (!imageFieldIds.has(k)) defaultValues[k] = v;
|
||||
});
|
||||
} else if (res.data) {
|
||||
Object.entries(res.data).forEach(([k, v]) => {
|
||||
if (!imageFieldIds.has(k)) defaultValues[k] = v;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setValues(defaultValues);
|
||||
setLoading(false);
|
||||
}
|
||||
@ -97,6 +101,26 @@ export function DynamicForm({ client, activityId: initialActivityId, instanceId:
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [submitError, setSubmitError] = useState<string | null>(null);
|
||||
|
||||
const handleFieldChange = (fieldId: string, newVal: unknown) => {
|
||||
setValues(prev => {
|
||||
const next = { ...prev, [fieldId]: newVal };
|
||||
|
||||
// Auto-calculate order_details totals
|
||||
if (fieldId === 'order_details' && Array.isArray(newVal)) {
|
||||
let totalBags = 0;
|
||||
let totalKgs = 0;
|
||||
newVal.forEach(row => {
|
||||
totalBags += Number(row.bags) || 0;
|
||||
totalKgs += Number(row.row_kgs) || 0;
|
||||
});
|
||||
next['total_bags'] = totalBags;
|
||||
next['total_kgs'] = totalKgs;
|
||||
}
|
||||
|
||||
return next;
|
||||
});
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return <div className="p-8 flex justify-center"><Spinner label="Loading form..." /></div>;
|
||||
}
|
||||
@ -193,7 +217,7 @@ export function DynamicForm({ client, activityId: initialActivityId, instanceId:
|
||||
activityId={currentActivityId}
|
||||
fieldId={f.id}
|
||||
formData={values}
|
||||
onChange={(newVal) => setValues(prev => ({ ...prev, [f.id]: newVal }))}
|
||||
onChange={(newVal) => handleFieldChange(f.id, newVal)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -205,7 +229,7 @@ export function DynamicForm({ client, activityId: initialActivityId, instanceId:
|
||||
required={f.mandatory}
|
||||
value={(val as string) ?? ''}
|
||||
options={f.properties?.options || []}
|
||||
onChange={(newVal) => setValues(prev => ({ ...prev, [f.id]: newVal }))}
|
||||
onChange={(newVal) => handleFieldChange(f.id, newVal)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -217,7 +241,7 @@ export function DynamicForm({ client, activityId: initialActivityId, instanceId:
|
||||
required={f.mandatory}
|
||||
value={(val as string) ?? ''}
|
||||
options={f.properties?.options || []}
|
||||
onChange={(newVal) => setValues(prev => ({ ...prev, [f.id]: newVal }))}
|
||||
onChange={(newVal) => handleFieldChange(f.id, newVal)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -229,18 +253,18 @@ export function DynamicForm({ client, activityId: initialActivityId, instanceId:
|
||||
type={type}
|
||||
required={f.mandatory}
|
||||
value={val}
|
||||
onChange={(newVal) => setValues(prev => ({ ...prev, [f.id]: newVal }))}
|
||||
onChange={(newVal) => handleFieldChange(f.id, newVal)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (type.startsWith('grid')) {
|
||||
return (
|
||||
<GridInput
|
||||
<SmartGridField
|
||||
label={f.name}
|
||||
columns={f.columns || []}
|
||||
value={(val as Record<string, unknown>[]) || []}
|
||||
onChange={(newVal) => setValues(prev => ({ ...prev, [f.id]: newVal }))}
|
||||
onChange={(newVal) => handleFieldChange(f.id, newVal)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -251,7 +275,7 @@ export function DynamicForm({ client, activityId: initialActivityId, instanceId:
|
||||
label={f.name}
|
||||
required={f.mandatory}
|
||||
value={(val as { latitude: number; longitude: number }) || null}
|
||||
onChange={(newVal) => setValues(prev => ({ ...prev, [f.id]: newVal }))}
|
||||
onChange={(newVal) => handleFieldChange(f.id, newVal)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -263,7 +287,7 @@ export function DynamicForm({ client, activityId: initialActivityId, instanceId:
|
||||
label={f.name}
|
||||
required={f.mandatory}
|
||||
value={phoneStr}
|
||||
onChange={(newVal) => setValues(prev => ({ ...prev, [f.id]: newVal }))}
|
||||
onChange={(newVal) => handleFieldChange(f.id, newVal)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -274,7 +298,7 @@ export function DynamicForm({ client, activityId: initialActivityId, instanceId:
|
||||
label={f.name}
|
||||
required={f.mandatory}
|
||||
value={(val as string) ?? ''}
|
||||
onChange={(newVal) => setValues(prev => ({ ...prev, [f.id]: newVal }))}
|
||||
onChange={(newVal) => handleFieldChange(f.id, newVal)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -285,7 +309,7 @@ export function DynamicForm({ client, activityId: initialActivityId, instanceId:
|
||||
label={f.name}
|
||||
required={f.mandatory}
|
||||
value={(val as string) ?? ''}
|
||||
onChange={(newVal) => setValues(prev => ({ ...prev, [f.id]: newVal }))}
|
||||
onChange={(newVal) => handleFieldChange(f.id, newVal)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -296,7 +320,7 @@ export function DynamicForm({ client, activityId: initialActivityId, instanceId:
|
||||
label={f.name}
|
||||
required={f.mandatory}
|
||||
value={(val as string) ?? ''}
|
||||
onChange={(newVal) => setValues(prev => ({ ...prev, [f.id]: newVal }))}
|
||||
onChange={(newVal) => handleFieldChange(f.id, newVal)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -307,7 +331,7 @@ export function DynamicForm({ client, activityId: initialActivityId, instanceId:
|
||||
label={f.name}
|
||||
required={f.mandatory}
|
||||
value={(val as string) ?? ''}
|
||||
onChange={(newVal) => setValues(prev => ({ ...prev, [f.id]: newVal }))}
|
||||
onChange={(newVal) => handleFieldChange(f.id, newVal)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -318,7 +342,7 @@ export function DynamicForm({ client, activityId: initialActivityId, instanceId:
|
||||
required={f.mandatory}
|
||||
type={type}
|
||||
value={(val as string) ?? ''}
|
||||
onChange={(newVal) => setValues(prev => ({ ...prev, [f.id]: newVal }))}
|
||||
onChange={(newVal) => handleFieldChange(f.id, newVal)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
145
src/components/forms/fields/SmartGridField.tsx
Normal file
145
src/components/forms/fields/SmartGridField.tsx
Normal file
@ -0,0 +1,145 @@
|
||||
import { Button } from '../../buttons/Button';
|
||||
import { Select } from '../../reusable/Select';
|
||||
import { Input } from '../../reusable/Input';
|
||||
import type { FormScreenField } from '../../../api/types';
|
||||
|
||||
export function SmartGridField({
|
||||
label,
|
||||
columns,
|
||||
value = [],
|
||||
onChange,
|
||||
}: {
|
||||
label: string;
|
||||
columns: FormScreenField[];
|
||||
value: Record<string, unknown>[];
|
||||
onChange: (val: Record<string, unknown>[]) => void;
|
||||
}) {
|
||||
const addRow = () => onChange([...value, {}]);
|
||||
|
||||
const removeRow = (idx: number) => {
|
||||
const next = [...value];
|
||||
next.splice(idx, 1);
|
||||
onChange(next);
|
||||
};
|
||||
|
||||
const updateRow = (idx: number, fieldId: string, val: unknown) => {
|
||||
const next = [...value];
|
||||
let row = { ...next[idx], [fieldId]: val };
|
||||
|
||||
const colDef = columns.find(c => c.id === fieldId);
|
||||
|
||||
// If product category changes, clear out the rest of the row's data
|
||||
if (colDef && (colDef.id === 'product_category' || colDef.name === 'Product Category')) {
|
||||
Object.keys(row).forEach(k => {
|
||||
if (k !== fieldId) {
|
||||
row[k] = '';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Auto-fill dataset keys from _raw
|
||||
if (colDef && (colDef.data_type === 'select' || colDef.data_type === 'multiselect')) {
|
||||
const option = colDef.properties?.options?.find(o => String(o.value) === String(val));
|
||||
if (option && option._raw) {
|
||||
for (const key of Object.keys(option._raw)) {
|
||||
// Match column ID with or without underscores (e.g., brcode <-> br_code)
|
||||
const targetCol = columns.find(c => c.id === key || c.id.replace(/_/g, '') === key.replace(/_/g, ''));
|
||||
if (targetCol && targetCol.id !== fieldId) {
|
||||
row[targetCol.id] = option._raw[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-calculate row_kgs if sku and bags are present
|
||||
const skuVal = Number(row.sku) || 0;
|
||||
const bagsVal = Number(row.bags) || 0;
|
||||
row.row_kgs = skuVal * bagsVal;
|
||||
|
||||
next[idx] = row;
|
||||
onChange(next);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2 font-sans border border-border-default rounded-md p-4 bg-slate-50">
|
||||
<span className="text-sm font-semibold text-strong mb-2">{label}</span>
|
||||
{value.length === 0 ? (
|
||||
<span className="text-sm text-faint italic">No rows added.</span>
|
||||
) : (
|
||||
<div className="flex flex-col gap-4">
|
||||
{value.map((row, i) => (
|
||||
<div key={i} className="flex flex-col gap-3 p-3 bg-white border border-border-subtle rounded relative shadow-sm">
|
||||
<div className="absolute top-2 right-2">
|
||||
<button type="button" onClick={() => removeRow(i)} className="text-xs text-ruby-600 font-medium hover:underline">
|
||||
Remove
|
||||
</button>
|
||||
</div>
|
||||
<span className="text-xs font-bold text-muted uppercase tracking-wider">Row {i + 1}</span>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
{columns.map(col => {
|
||||
const val = row[col.id];
|
||||
if (col.data_type === 'select' || col.data_type === 'multiselect') {
|
||||
// Cascade filter options
|
||||
const allOpts = col.properties?.options || [];
|
||||
let filteredOpts = allOpts;
|
||||
|
||||
// Specific logic for product_name depending on product_category
|
||||
if (col.id === 'product_name' || col.name === 'Product Name') {
|
||||
const catCol = columns.find(c => c.id === 'product_category' || c.name === 'Product Category');
|
||||
if (catCol) {
|
||||
const selectedCategory = row[catCol.id] as string;
|
||||
if (selectedCategory) {
|
||||
filteredOpts = allOpts.filter(opt => {
|
||||
const labelStr = String(opt.label || opt.value || '');
|
||||
return labelStr.startsWith(selectedCategory);
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Generic _raw cascading for other fields just in case
|
||||
filteredOpts = allOpts.filter(opt => {
|
||||
if (!opt._raw) return true;
|
||||
for (const [rowKey, rowVal] of Object.entries(row)) {
|
||||
if (rowKey === col.id || rowVal == null || rowVal === '') continue;
|
||||
const rawKey = Object.keys(opt._raw).find(rk => rk === rowKey || rk.replace(/_/g, '') === rowKey.replace(/_/g, ''));
|
||||
if (rawKey && String(opt._raw[rawKey]) !== String(rowVal)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Select
|
||||
key={col.id}
|
||||
label={col.name}
|
||||
required={col.mandatory}
|
||||
value={(val as string) ?? ''}
|
||||
onChange={(e) => updateRow(i, col.id, e.target.value)}
|
||||
options={[{ value: '', label: 'Select...' }, ...filteredOpts.map((o: any) => ({ value: String(o.value), label: o.label }))]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Input
|
||||
key={col.id}
|
||||
label={col.name}
|
||||
required={col.mandatory}
|
||||
type={col.data_type === 'number' ? 'number' : col.data_type === 'email' ? 'email' : 'text'}
|
||||
value={(val as string) ?? ''}
|
||||
onChange={(e) => updateRow(i, col.id, col.data_type === 'number' ? Number(e.target.value) : e.target.value)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<Button type="button" variant="secondary" size="sm" onClick={addRow} className="mt-2 self-start">
|
||||
+ Add Row
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -10,3 +10,4 @@ export * from './DateField';
|
||||
export * from './TimeField';
|
||||
export * from './WfLookupField';
|
||||
export * from './RadioField';
|
||||
export * from './SmartGridField';
|
||||
|
||||
@ -19,11 +19,12 @@ export interface ModalProps {
|
||||
subtitle?: ReactNode;
|
||||
/** @default "md" */
|
||||
width?: ModalWidth;
|
||||
actions?: ReactNode;
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
/** Portal modal host — backdrop, Esc / click-out close, scroll-locked body. */
|
||||
export function Modal({ open, onClose, title, subtitle, width = 'md', children }: ModalProps) {
|
||||
export function Modal({ open, onClose, title, subtitle, width = 'md', actions, children }: ModalProps) {
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
@ -61,13 +62,16 @@ export function Modal({ open, onClose, title, subtitle, width = 'md', children }
|
||||
{title && <h3 className="m-0 text-md font-semibold text-strong truncate">{title}</h3>}
|
||||
{subtitle && <div className="text-xs text-faint mt-0.5">{subtitle}</div>}
|
||||
</div>
|
||||
<button
|
||||
onClick={onClose}
|
||||
aria-label="Close"
|
||||
className="shrink-0 -mr-1 -mt-1 p-1.5 rounded-md text-faint hover:text-strong hover:bg-black/5"
|
||||
>
|
||||
<X size={18} />
|
||||
</button>
|
||||
<div className="flex items-center gap-2">
|
||||
{actions && <div className="flex items-center gap-2 mr-2">{actions}</div>}
|
||||
<button
|
||||
onClick={onClose}
|
||||
aria-label="Close"
|
||||
className="shrink-0 -mr-1 -mt-1 p-1.5 rounded-md text-faint hover:text-strong hover:bg-black/5"
|
||||
>
|
||||
<X size={18} />
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
<div className="flex-1 min-h-0 overflow-y-auto p-5 scrollbar-slim">{children}</div>
|
||||
</div>
|
||||
|
||||
@ -10,10 +10,12 @@ import { ORDER_BOOKING } from '../api/config';
|
||||
import { orderBookingClient } from '../api/clients';
|
||||
|
||||
export function CallsPage() {
|
||||
const { instanceId } = useParams();
|
||||
const params = useParams();
|
||||
const instanceId = params.instanceId ? Number(params.instanceId) : undefined;
|
||||
const navigate = useNavigate();
|
||||
const [isCreating, setIsCreating] = useState(false);
|
||||
const [refreshKey, setRefreshKey] = useState(0);
|
||||
const [activeActivity, setActiveActivity] = useState<{ id: string; name: string } | null>(null);
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -52,9 +54,40 @@ export function CallsPage() {
|
||||
onClose={() => navigate(`/calls`)}
|
||||
title={instanceId != null ? `Call #${instanceId}` : undefined}
|
||||
width="lg"
|
||||
actions={
|
||||
<>
|
||||
<Button size="sm" variant="secondary" onClick={() => setActiveActivity({ id: ORDER_BOOKING.activities.POTENTIAL_MINING.uid, name: 'Potential Mining' })}>
|
||||
Potential Mining
|
||||
</Button>
|
||||
<Button size="sm" onClick={() => setActiveActivity({ id: ORDER_BOOKING.activities.PLACE_ORDER.uid, name: 'Place Order' })}>
|
||||
Place Order
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
>
|
||||
{instanceId != null && <CallDetail instanceId={instanceId} />}
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
open={activeActivity != null}
|
||||
onClose={() => setActiveActivity(null)}
|
||||
title={activeActivity?.name}
|
||||
width="md"
|
||||
>
|
||||
{activeActivity && instanceId != null && (
|
||||
<DynamicForm
|
||||
client={orderBookingClient}
|
||||
activityId={activeActivity.id}
|
||||
instanceId={instanceId}
|
||||
ignorePrefill={activeActivity.id === ORDER_BOOKING.activities.PLACE_ORDER.uid}
|
||||
onSuccess={() => {
|
||||
setActiveActivity(null);
|
||||
setRefreshKey(k => k + 1);
|
||||
}}
|
||||
onCancel={() => setActiveActivity(null)}
|
||||
/>
|
||||
)}
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -10,7 +10,8 @@ import { DAILY_REPORTS } from '../api/config';
|
||||
import { dailyReportsClient } from '../api/clients';
|
||||
|
||||
export function DailyLogsPage() {
|
||||
const { instanceId } = useParams();
|
||||
const params = useParams();
|
||||
const instanceId = params.instanceId ? Number(params.instanceId) : undefined;
|
||||
const navigate = useNavigate();
|
||||
const [isCreating, setIsCreating] = useState(false);
|
||||
const [punchOutInstanceId, setPunchOutInstanceId] = useState<number | string | null>(null);
|
||||
|
||||
@ -10,10 +10,12 @@ import { ORDER_BOOKING } from '../api/config';
|
||||
import { orderBookingClient } from '../api/clients';
|
||||
|
||||
export function OrdersPage() {
|
||||
const { instanceId } = useParams();
|
||||
const params = useParams();
|
||||
const instanceId = params.instanceId ? Number(params.instanceId) : undefined;
|
||||
const navigate = useNavigate();
|
||||
const [isCreating, setIsCreating] = useState(false);
|
||||
const [refreshKey, setRefreshKey] = useState(0);
|
||||
const [activeActivity, setActiveActivity] = useState<{ id: string; name: string } | null>(null);
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -52,9 +54,40 @@ export function OrdersPage() {
|
||||
onClose={() => navigate(`/orders`)}
|
||||
title={instanceId != null ? `Order #${instanceId}` : undefined}
|
||||
width="lg"
|
||||
actions={
|
||||
<>
|
||||
<Button size="sm" variant="secondary" onClick={() => setActiveActivity({ id: ORDER_BOOKING.activities.POTENTIAL_MINING.uid, name: 'Potential Mining' })}>
|
||||
Potential Mining
|
||||
</Button>
|
||||
<Button size="sm" onClick={() => setActiveActivity({ id: ORDER_BOOKING.activities.PLACE_ORDER.uid, name: 'Place Order' })}>
|
||||
Place Order
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
>
|
||||
{instanceId != null && <OrderDetail instanceId={instanceId} />}
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
open={activeActivity != null}
|
||||
onClose={() => setActiveActivity(null)}
|
||||
title={activeActivity?.name}
|
||||
width="md"
|
||||
>
|
||||
{activeActivity && instanceId != null && (
|
||||
<DynamicForm
|
||||
client={orderBookingClient}
|
||||
activityId={activeActivity.id}
|
||||
instanceId={instanceId}
|
||||
ignorePrefill={activeActivity.id === ORDER_BOOKING.activities.PLACE_ORDER.uid}
|
||||
onSuccess={() => {
|
||||
setActiveActivity(null);
|
||||
setRefreshKey(k => k + 1);
|
||||
}}
|
||||
onCancel={() => setActiveActivity(null)}
|
||||
/>
|
||||
)}
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -10,7 +10,8 @@ import { STORE } from '../api/config';
|
||||
import { storeClient } from '../api/clients';
|
||||
|
||||
export function StoresPage() {
|
||||
const { instanceId } = useParams();
|
||||
const params = useParams();
|
||||
const instanceId = params.instanceId ? Number(params.instanceId) : undefined;
|
||||
const navigate = useNavigate();
|
||||
const [isCreating, setIsCreating] = useState(false);
|
||||
const [editingInstanceId, setEditingInstanceId] = useState<number | string | null>(null);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user