fix:image rendering fixed
This commit is contained in:
parent
f35eb87af6
commit
5273ff589c
@ -1,10 +1,20 @@
|
|||||||
import { formatValue } from '../../lib/format';
|
import { formatValue } from '../../lib/format';
|
||||||
|
import { BASE_URL } from '../../api/config';
|
||||||
|
|
||||||
export function CallCard({ row }: { row: Record<string, any> }) {
|
export function CallCard({ row }: { row: Record<string, any> }) {
|
||||||
// Status
|
// Status
|
||||||
const stateName = String(row.status || row.current_state_name || 'Pending');
|
const stateName = String(row.status || row.current_state_name || 'Pending');
|
||||||
const isProductive = stateName.toLowerCase().includes('productive') || stateName.toLowerCase().includes('completed') || stateName.toLowerCase().includes('success');
|
const isProductive = stateName.toLowerCase().includes('productive') || stateName.toLowerCase().includes('completed') || stateName.toLowerCase().includes('success');
|
||||||
|
|
||||||
|
// Image
|
||||||
|
let imageObj = null;
|
||||||
|
const imgData = row.upload_image || row.store_image || row.image;
|
||||||
|
if (Array.isArray(imgData) && imgData.length > 0) {
|
||||||
|
imageObj = imgData[0];
|
||||||
|
} else if (imgData && typeof imgData === 'object' && imgData.blob_path) {
|
||||||
|
imageObj = imgData;
|
||||||
|
}
|
||||||
|
|
||||||
// Store Details
|
// Store Details
|
||||||
const storeName = formatValue(row.store || row.store_name || row.customer_name || 'Unknown Store');
|
const storeName = formatValue(row.store || row.store_name || row.customer_name || 'Unknown Store');
|
||||||
const storeCode = formatValue(row.store_code || '—');
|
const storeCode = formatValue(row.store_code || '—');
|
||||||
@ -68,13 +78,28 @@ export function CallCard({ row }: { row: Record<string, any> }) {
|
|||||||
{/* Main Content */}
|
{/* Main Content */}
|
||||||
<div className="p-5 space-y-5">
|
<div className="p-5 space-y-5">
|
||||||
{/* Store Details */}
|
{/* Store Details */}
|
||||||
<div>
|
<div className="flex justify-between items-start gap-4">
|
||||||
<span className="text-[10px] font-bold text-indigo-600 tracking-wider uppercase">Store Details</span>
|
<div>
|
||||||
<h2 className="text-[17px] font-bold text-slate-800 mt-0.5 leading-tight">{storeName}</h2>
|
<span className="text-[10px] font-bold text-indigo-600 tracking-wider uppercase">Store Details</span>
|
||||||
{storeCode !== '—' && (
|
<h2 className="text-[17px] font-bold text-slate-800 mt-0.5 leading-tight">{storeName}</h2>
|
||||||
<p className="text-[13px] text-slate-500 mt-0.5">
|
{storeCode !== '—' && (
|
||||||
Code: <span className="font-mono font-medium text-slate-700">{storeCode}</span>
|
<p className="text-[13px] text-slate-500 mt-0.5">
|
||||||
</p>
|
Code: <span className="font-mono font-medium text-slate-700">{storeCode}</span>
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{imageObj && imageObj.blob_path && (
|
||||||
|
<div className="w-16 h-16 shrink-0 rounded-lg overflow-hidden border border-slate-200 shadow-sm bg-slate-100 flex items-center justify-center">
|
||||||
|
<img
|
||||||
|
src={`${BASE_URL}/${imageObj.blob_path}`}
|
||||||
|
alt="Store"
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
onError={(e) => {
|
||||||
|
(e.target as HTMLImageElement).style.display = 'none';
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { cn } from '../../lib/cn';
|
|||||||
import { formatValue } from '../../lib/format';
|
import { formatValue } from '../../lib/format';
|
||||||
import type { ZinoClient } from '../../api/client';
|
import type { ZinoClient } from '../../api/client';
|
||||||
import type { AuditEntry } from '../../api/types';
|
import type { AuditEntry } from '../../api/types';
|
||||||
import { WORKFLOWS } from '../../api/config';
|
import { WORKFLOWS, APP_ID } from '../../api/config';
|
||||||
import { Card } from '../reusable/Card';
|
import { Card } from '../reusable/Card';
|
||||||
import { Spinner } from '../reusable/Spinner';
|
import { Spinner } from '../reusable/Spinner';
|
||||||
import { EmptyState } from '../reusable/EmptyState';
|
import { EmptyState } from '../reusable/EmptyState';
|
||||||
@ -44,7 +44,7 @@ function getActivityName(uid: string): string | undefined {
|
|||||||
* Generic Zino detail view. Fetches `GET /app/{id}/view/audit` and
|
* Generic Zino detail view. Fetches `GET /app/{id}/view/audit` and
|
||||||
* renders the audit trail as a labeled definition grid.
|
* renders the audit trail as a labeled definition grid.
|
||||||
*/
|
*/
|
||||||
export function DetailView({ client, instanceId, title, columns = 2 }: DetailViewProps) {
|
export function DetailView({ client, dvUid, instanceId, title, columns = 2 }: DetailViewProps) {
|
||||||
const [entries, setEntries] = useState<AuditEntry[]>([]);
|
const [entries, setEntries] = useState<AuditEntry[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
@ -55,8 +55,16 @@ export function DetailView({ client, instanceId, title, columns = 2 }: DetailVie
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
try {
|
try {
|
||||||
const r = await client.audit(instanceId);
|
if (!dvUid) throw new Error("dvUid is required to fetch detail view data.");
|
||||||
if (live) setEntries(r);
|
const r = await client.detailView(dvUid, instanceId);
|
||||||
|
if (live) {
|
||||||
|
setEntries([{
|
||||||
|
id: Number(instanceId),
|
||||||
|
activity_id: '',
|
||||||
|
data: r.data || {},
|
||||||
|
created_at: (r as any).created_at || new Date().toISOString()
|
||||||
|
} as AuditEntry]);
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (live) setError((e as { message?: string })?.message ?? 'Failed to load');
|
if (live) setError((e as { message?: string })?.message ?? 'Failed to load');
|
||||||
} finally {
|
} finally {
|
||||||
@ -158,6 +166,47 @@ export function DetailView({ client, instanceId, title, columns = 2 }: DetailVie
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isFileArray =
|
||||||
|
Array.isArray(value) &&
|
||||||
|
value.length > 0 &&
|
||||||
|
typeof value[0] === 'object' &&
|
||||||
|
value[0] !== null &&
|
||||||
|
('original_name' in value[0] || 'uuid' in value[0]);
|
||||||
|
|
||||||
|
if (isFileArray) {
|
||||||
|
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 flex flex-wrap gap-4">
|
||||||
|
{(value as any[]).map((file, idx) => {
|
||||||
|
const previewUrl = `${client.baseUrl}/app/${APP_ID}/view/files/${file.uuid}/preview`;
|
||||||
|
return (
|
||||||
|
<div key={file.uuid || idx} className="relative w-32 h-32 rounded-lg border border-border-subtle overflow-hidden bg-slate-100 flex items-center justify-center group shadow-sm">
|
||||||
|
<img
|
||||||
|
src={previewUrl}
|
||||||
|
alt={file.original_name || 'Attached File'}
|
||||||
|
className="w-full h-full object-cover transition-transform group-hover:scale-105"
|
||||||
|
onError={(e) => {
|
||||||
|
(e.target as HTMLImageElement).style.display = 'none';
|
||||||
|
(e.target as HTMLImageElement).parentElement!.innerHTML = `<span class="text-[10px] text-faint text-center px-2 break-all font-mono">${file.original_name || 'File'}</span>`;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<a
|
||||||
|
href={previewUrl}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="absolute inset-0 z-10"
|
||||||
|
></a>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={key} className="flex flex-col gap-1 min-w-0">
|
<div key={key} className="flex flex-col gap-1 min-w-0">
|
||||||
<dt className="text-[10px] font-bold uppercase tracking-[0.06em] text-faint">
|
<dt className="text-[10px] font-bold uppercase tracking-[0.06em] text-faint">
|
||||||
|
|||||||
@ -160,12 +160,12 @@ export function DynamicForm({ client, activityId: initialActivityId, instanceId:
|
|||||||
} else if ((f.data_type === 'image' || f.data_type === 'file') && Array.isArray(val) && val.length > 0 && val[0] instanceof File) {
|
} else if ((f.data_type === 'image' || f.data_type === 'file') && Array.isArray(val) && val.length > 0 && val[0] instanceof File) {
|
||||||
const uploadedFiles = [];
|
const uploadedFiles = [];
|
||||||
for (const file of val) {
|
for (const file of val) {
|
||||||
const fileMeta = await client.uploadFile(file, { activityId: currentActivityId, fieldId: f.id });
|
const fileMeta = await client.uploadFile(file, { activityId: currentActivityId, fieldId: f.id, instanceId: currentInstanceId });
|
||||||
uploadedFiles.push(fileMeta);
|
uploadedFiles.push(fileMeta);
|
||||||
}
|
}
|
||||||
payload[f.id] = uploadedFiles;
|
payload[f.id] = uploadedFiles;
|
||||||
} else if ((f.data_type === 'image' || f.data_type === 'file') && val instanceof File) {
|
} else if ((f.data_type === 'image' || f.data_type === 'file') && val instanceof File) {
|
||||||
const fileMeta = await client.uploadFile(val, { activityId: currentActivityId, fieldId: f.id });
|
const fileMeta = await client.uploadFile(val, { activityId: currentActivityId, fieldId: f.id, instanceId: currentInstanceId });
|
||||||
payload[f.id] = [fileMeta];
|
payload[f.id] = [fileMeta];
|
||||||
} else {
|
} else {
|
||||||
payload[f.id] = val;
|
payload[f.id] = val;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user