diff --git a/src/components/cards/CallCard.tsx b/src/components/cards/CallCard.tsx index c066ada..a174f56 100644 --- a/src/components/cards/CallCard.tsx +++ b/src/components/cards/CallCard.tsx @@ -1,5 +1,5 @@ import { formatValue } from '../../lib/format'; -import { BASE_URL } from '../../api/config'; +import { BASE_URL, APP_ID } from '../../api/config'; export function CallCard({ row }: { row: Record }) { // Status @@ -89,10 +89,10 @@ export function CallCard({ row }: { row: Record }) { )} - {imageObj && imageObj.blob_path && ( + {imageObj && imageObj.uuid && (
Store { diff --git a/src/components/cards/DailyLogCard.tsx b/src/components/cards/DailyLogCard.tsx index 9cc7fea..3a9e352 100644 --- a/src/components/cards/DailyLogCard.tsx +++ b/src/components/cards/DailyLogCard.tsx @@ -1,5 +1,6 @@ import { formatValue } from '../../lib/format'; import { FileImage } from 'lucide-react'; +import { BASE_URL, APP_ID } from '../../api/config'; export function DailyLogCard({ row, onPunchOut }: { row: Record; onPunchOut?: (row: any) => void }) { const stateName = String(row.status || row.current_state_name || 'Logged'); @@ -105,20 +106,35 @@ export function DailyLogCard({ row, onPunchOut }: { row: Record; on {hasImage && (
Verification Image -
-
-
- -
-
-

{imgName}

-

{imgSize}

-
+ {imgData.uuid ? ( +
+ {imgName} { + (e.target as HTMLImageElement).style.display = 'none'; + (e.target as HTMLImageElement).parentElement!.innerHTML = `
${imgName}
`; + }} + /> +
- - MIME: {imgMime.split('/')[1] || 'Media'} - -
+ ) : ( +
+
+
+ +
+
+

{imgName}

+

{imgSize}

+
+
+ + MIME: {imgMime.split('/')[1] || 'Media'} + +
+ )}
)} diff --git a/src/components/cards/StoreCard.tsx b/src/components/cards/StoreCard.tsx index 2b36590..2141221 100644 --- a/src/components/cards/StoreCard.tsx +++ b/src/components/cards/StoreCard.tsx @@ -1,17 +1,29 @@ import { formatValue } from '../../lib/format'; import { Edit2 } from 'lucide-react'; +import { BASE_URL, APP_ID } from '../../api/config'; -export function StoreCard({ row, fields, onEdit }: { row: Record; fields: any[]; onEdit?: (row: any) => void }) { +export function StoreCard({ row, fields, isDetailView = false, onEdit }: { row: Record; fields: any[]; isDetailView?: boolean; onEdit?: (row: any) => void }) { // Status const stateName = String(row.current_state_name || row.status || 'Active'); const isActive = stateName.toLowerCase().includes('active') || stateName.toLowerCase().includes('created') || stateName.toLowerCase().includes('approved'); + // Image + let imageObj = null; + const imgData = row.store_image || row.image || row.upload_image || row.store_photo; + if (Array.isArray(imgData) && imgData.length > 0) { + imageObj = imgData[0]; + } else if (imgData && typeof imgData === 'object' && imgData.uuid) { + imageObj = imgData; + } + // Store Details const storeName = formatValue(row.business_name || row.store_name || row.name || 'Unknown Store'); const storeCode = formatValue(row.store_code || row.code || '—'); const ownerName = formatValue(row.owner_name || row.contact_person || '—'); const phoneObj = row.phone_number as Record | null; const phone = formatValue(phoneObj?.phone_with_dial_code || phoneObj?.phone || row.phone || row.mobile || '—'); + const emailObj = row.email_address as Record | null; + const email = formatValue(emailObj?.email || row.email || row.store_email || row.business_email || '—'); // Route Info const routeName = formatValue(row.route_name || row.route || '—'); @@ -23,6 +35,8 @@ export function StoreCard({ row, fields, onEdit }: { row: Record; f const distContact = formatValue(row.distributor_owner_name || '—'); const distPhoneObj = row.distributor_phone_number as Record | null; const distPhone = formatValue(distPhoneObj?.phone_with_dial_code || distPhoneObj?.phone || row.distributor_phone || '—'); + const distEmailObj = row.distributor_email_address as Record | null; + const distEmail = formatValue(distEmailObj?.email || row.distributor_email || '—'); // Address const address = formatValue(row.complete_address || row.address || '—'); @@ -93,22 +107,37 @@ export function StoreCard({ row, fields, onEdit }: { row: Record; f {/* Main Content */}
{/* Store Identity */} -
- Store Details -

{storeName}

- - {/* Status Badge for Route/Sub-route */} - {(routeName !== '—' || subRouteName !== '—') && ( -
- - R: {routeName} ({String(routeCode).substring(0, 1)}) - {subRouteName !== '—' && ` • Sub: ${subRouteName}`} - +
+
+ Store Details +

{storeName}

+ + {/* Status Badge for Route/Sub-route */} + {(routeName !== '—' || subRouteName !== '—') && ( +
+ + R: {routeName} ({String(routeCode).substring(0, 1)}) + {subRouteName !== '—' && ` • Sub: ${subRouteName}`} + +
+ )}
- )} + {imageObj && imageObj.uuid && ( +
+ Store { + (e.target as HTMLImageElement).style.display = 'none'; + }} + /> +
+ )} +
{/* Contact Info */} - {(ownerName !== '—' || phone !== '—') && ( + {(ownerName !== '—' || phone !== '—' || email !== '—') && (
{ownerName !== '—' && (

@@ -122,24 +151,30 @@ export function StoreCard({ row, fields, onEdit }: { row: Record; f {phone}

)} + {email !== '—' && ( +

+ Email + {email} +

+ )}
)} -
{/* Fulfillment / Distributor */} - {distName !== '—' && ( + {isDetailView && distName !== '—' && (

Assigned Distributor

{distName}

{distContact !== '—' &&

Contact: {distContact}

} - {distPhone !== '—' &&

{distPhone}

} + {distPhone !== '—' &&

Phone: {distPhone}

} + {distEmail !== '—' &&

Email: {distEmail}

}
)} {/* Product Potential Breakdown */} - {potentials.length > 0 && ( + {isDetailView && potentials.length > 0 && (

Inventory Potential

@@ -158,31 +193,33 @@ export function StoreCard({ row, fields, onEdit }: { row: Record; f )} {/* Logistics & Address */} -
-
-

Address

-

{address}

- {(pin !== '—' || lat !== '—') && ( -

- {pin !== '—' && `PIN: ${pin}`} {pin !== '—' && lat !== '—' && '|'} {lat !== '—' && `Loc: ${lat}, ${lng}`} -

- )} -
-
-

Created By

-
-
- {initials} -
-
-

{userName}

- {userEmail !== '—' && ( -

{userEmail}

- )} + {isDetailView && ( +
+
+

Address

+

{address}

+ {(pin !== '—' || lat !== '—') && ( +

+ {pin !== '—' && `PIN: ${pin}`} {pin !== '—' && lat !== '—' && '|'} {lat !== '—' && `Loc: ${lat}, ${lng}`} +

+ )} +
+
+

Created By

+
+
+ {initials} +
+
+

{userName}

+ {userEmail !== '—' && ( +

{userEmail}

+ )} +
-
+ )}
{/* Footer Timestamp */} diff --git a/src/components/dv/CallDetail.tsx b/src/components/dv/CallDetail.tsx index 0fe0b75..13270f6 100644 --- a/src/components/dv/CallDetail.tsx +++ b/src/components/dv/CallDetail.tsx @@ -3,14 +3,13 @@ import { ORDER_BOOKING } from '../../api/config'; import { DetailView } from './DetailView'; import type { WiredDetailViewProps } from './OrderDetail'; -/** Call detail view (Order Booking workflow). */ -export function CallDetail({ instanceId, columns }: WiredDetailViewProps) { +export function CallDetail({ instanceId, columns = 2 }: WiredDetailViewProps) { return ( ); diff --git a/src/components/dv/DailyLogDetail.tsx b/src/components/dv/DailyLogDetail.tsx index 2de54bd..3df3c09 100644 --- a/src/components/dv/DailyLogDetail.tsx +++ b/src/components/dv/DailyLogDetail.tsx @@ -1,17 +1,21 @@ import { dailyReportsClient } from '../../api/clients'; import { DAILY_REPORTS } from '../../api/config'; -import { DetailView } from './DetailView'; import type { WiredDetailViewProps } from './OrderDetail'; +import { useDetailViewData } from './useDetailViewData'; +import { Spinner } from '../reusable/Spinner'; +import { EmptyState } from '../reusable/EmptyState'; +import { DailyLogCard } from '../cards/DailyLogCard'; + +export function DailyLogDetail({ instanceId }: WiredDetailViewProps) { + const { data, loading, error } = useDetailViewData(dailyReportsClient, DAILY_REPORTS.detailViews.DAILY_LOGS, instanceId); + + if (error) return ; + if (loading) return
; + if (!data) return ; -/** Daily Log detail view (Daily Reports workflow). */ -export function DailyLogDetail({ instanceId, columns }: WiredDetailViewProps) { return ( - +
+ +
); } diff --git a/src/components/dv/DetailView.tsx b/src/components/dv/DetailView.tsx index 732b786..903fa76 100644 --- a/src/components/dv/DetailView.tsx +++ b/src/components/dv/DetailView.tsx @@ -44,7 +44,7 @@ function getActivityName(uid: string): string | undefined { * Generic Zino detail view. Fetches `GET /app/{id}/view/audit` and * renders the audit trail as a labeled definition grid. */ -export function DetailView({ client, dvUid, instanceId, title, columns = 2 }: DetailViewProps) { +export function DetailView({ client, instanceId, title, columns = 2 }: DetailViewProps) { const [entries, setEntries] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); @@ -55,15 +55,9 @@ export function DetailView({ client, dvUid, instanceId, title, columns = 2 }: De setLoading(true); setError(null); try { - if (!dvUid) throw new Error("dvUid is required to fetch detail view data."); - const r = await client.detailView(dvUid, instanceId); + const r = await client.audit(instanceId); if (live) { - setEntries([{ - id: Number(instanceId), - activity_id: '', - data: r.data || {}, - created_at: (r as any).created_at || new Date().toISOString() - } as AuditEntry]); + setEntries(r || []); } } catch (e) { if (live) setError((e as { message?: string })?.message ?? 'Failed to load'); diff --git a/src/components/dv/OrderDetail.tsx b/src/components/dv/OrderDetail.tsx index 9d8b1ce..181d48c 100644 --- a/src/components/dv/OrderDetail.tsx +++ b/src/components/dv/OrderDetail.tsx @@ -3,18 +3,17 @@ import { ORDER_BOOKING } from '../../api/config'; import { DetailView } from './DetailView'; export interface WiredDetailViewProps { - instanceId: number | string; + instanceId: string | number; columns?: 1 | 2 | 3; } -/** Order detail view (Order Booking workflow). */ -export function OrderDetail({ instanceId, columns }: WiredDetailViewProps) { +export function OrderDetail({ instanceId, columns = 2 }: WiredDetailViewProps) { return ( ); diff --git a/src/components/dv/StoreDetail.tsx b/src/components/dv/StoreDetail.tsx index 9d2b109..486a9cc 100644 --- a/src/components/dv/StoreDetail.tsx +++ b/src/components/dv/StoreDetail.tsx @@ -1,17 +1,39 @@ import { storeClient } from '../../api/clients'; import { STORE } from '../../api/config'; -import { DetailView } from './DetailView'; import type { WiredDetailViewProps } from './OrderDetail'; +import { useDetailViewData } from './useDetailViewData'; +import { Spinner } from '../reusable/Spinner'; +import { EmptyState } from '../reusable/EmptyState'; +import { StoreCard } from '../cards/StoreCard'; +import { MapPin } from 'lucide-react'; + +export function StoreDetail({ instanceId }: WiredDetailViewProps) { + const { data, config, loading, error } = useDetailViewData(storeClient, STORE.detailViews.STORE, instanceId); + + if (error) return ; + if (loading) return
; + if (!data) return ; + + const locObj = data.store_location as Record | null; + const lat = locObj?.latitude || data.latitude; + const lng = locObj?.longitude || data.longitude; + const hasLocation = lat && lng && lat !== '—' && lng !== '—'; -/** Store detail view (Store workflow). */ -export function StoreDetail({ instanceId, columns }: WiredDetailViewProps) { return ( - +
+ + + {hasLocation && ( + + + View on Google Maps + + )} +
); } diff --git a/src/components/dv/useDetailViewData.ts b/src/components/dv/useDetailViewData.ts new file mode 100644 index 0000000..711f3ba --- /dev/null +++ b/src/components/dv/useDetailViewData.ts @@ -0,0 +1,35 @@ +import { useState, useEffect } from 'react'; +import type { ZinoClient } from '../../api/client'; + +export function useDetailViewData(client: ZinoClient, dvUid: string | undefined, instanceId: string | number) { + const [data, setData] = useState | null>(null); + const [config, setConfig] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + let live = true; + async function run() { + setLoading(true); + setError(null); + try { + if (!dvUid) throw new Error("dvUid is required to fetch detail view data."); + const r = await client.detailView(dvUid, instanceId); + if (live) { + setData(r.data || {}); + setConfig(r.config || {}); + } + } catch (e) { + if (live) setError((e as { message?: string })?.message ?? 'Failed to load'); + } finally { + if (live) setLoading(false); + } + } + run(); + return () => { + live = false; + }; + }, [client, dvUid, instanceId]); + + return { data, config, loading, error }; +}