From 7f88b4a7e4ab5643678727fb1753e2215c156578 Mon Sep 17 00:00:00 2001 From: suryacp23 Date: Thu, 16 Jul 2026 16:11:47 +0530 Subject: [PATCH] card design done --- src/components/cards/DailyLogCard.tsx | 213 +++++++---------- src/components/cards/OrderCard.tsx | 146 ++++-------- src/components/cards/StoreCard.tsx | 328 ++++++++++---------------- src/components/dv/DailyLogDetail.tsx | 2 +- src/components/dv/StoreDetail.tsx | 4 +- src/components/rv/DailyLogsView.tsx | 1 + src/components/rv/StoresView.tsx | 2 +- 7 files changed, 269 insertions(+), 427 deletions(-) diff --git a/src/components/cards/DailyLogCard.tsx b/src/components/cards/DailyLogCard.tsx index 4bccaf8..d09f63d 100644 --- a/src/components/cards/DailyLogCard.tsx +++ b/src/components/cards/DailyLogCard.tsx @@ -1,158 +1,119 @@ import { formatValue } from '../../lib/format'; -import { FileImage } from 'lucide-react'; -import { BASE_URL, APP_ID } from '../../api/config'; +import { User as UserIcon, Clock, LogOut } from 'lucide-react'; +import { Button } from '../buttons/Button'; +import './card.css'; -export function DailyLogCard({ row, isDetailView = false, onPunchOut }: { row: Record; isDetailView?: boolean; onPunchOut?: (row: any) => void }) { +export function DailyLogCard({ row, onPunchOut }: { row: Record; onPunchOut?: (row: any) => void }) { const stateName = String(row.status || row.current_state_name || 'Logged'); // User details const userObj = (row.sales_officer_name || row.user_id || row.user) as Record | null; const userName = formatValue(userObj?.name || row.user_name || 'Unknown User'); - const userEmail = formatValue(userObj?.email || row.user_email || '—'); - const initials = userName !== 'Unknown User' && userName !== '—' ? String(userName).substring(0, 2).toUpperCase() : 'US'; - // Punch Details + // Date let dateStr = '—'; - let timeStr = '—'; - if (row.date) dateStr = String(row.date).split('T')[0]; else if (row.created_at) dateStr = String(row.created_at).split('T')[0]; + + try { + if (dateStr !== '—') { + const d = new Date(dateStr); + if (!isNaN(d.getTime())) { + dateStr = d.toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' }).replace(/ /g, '-'); + } + } + } catch (e) {} - if (row.time) timeStr = String(row.time).split('T')[1]?.split('.')[0] || String(row.time); - else if (row.created_at) timeStr = String(row.created_at).split('T')[1]?.split('.')[0] || '—'; + const formatTimeStr = (t: string | undefined | null) => { + if (!t) return '—'; + const str = String(t); + if (str === '—') return str; + try { + let timeParts = str.split(':'); + if (str.includes('T')) { + timeParts = str.split('T')[1].replace('Z', '').split('.')[0].split(':'); + } + if (timeParts.length >= 2) { + let h = parseInt(timeParts[0]); + const m = timeParts[1]; + const ampm = h >= 12 ? 'PM' : 'AM'; + h = h % 12; + h = h ? h : 12; + const hStr = h < 10 ? '0' + h : h; + return `${hStr}:${m} ${ampm}`; + } + return str.split('.')[0]; + } catch { + return str.split('.')[0]; + } + }; - const routeCode = formatValue(row.route_code || row.route || '—'); - const dayPlanNotes = formatValue(row.day_plan_notes || row.notes || row.plan || 'No notes provided.'); + // Checkin time + let checkInTimeRaw = row.time || row.created_at || row.punch_in_time; + let checkInTime = formatTimeStr(checkInTimeRaw); - // Store Image / Verification Media - const imageArray = row.store_image || row.image || row.verification_media; - const imgData = Array.isArray(imageArray) && imageArray.length > 0 ? imageArray[0] : null; - - let imgName = 'None attached'; - let imgSize = '—'; - let imgMime = '—'; - - if (imgData && typeof imgData === 'object') { - imgName = imgData.original_name || imgData.name || 'attachment.png'; - const sizeBytes = imgData.size_bytes || imgData.size || 0; - imgSize = sizeBytes > 0 ? `${(sizeBytes / 1024).toFixed(1)} KB` : 'Unknown size'; - imgMime = imgData.mime_type || imgData.type || 'image/jpeg'; + // Check out time + let checkOutTimeRaw = row.check_out_time || row.punch_out_time; + if (!checkOutTimeRaw && stateName.toLowerCase().includes('out') && row.time) { + checkOutTimeRaw = row.time; } + let checkOutTime = checkOutTimeRaw ? formatTimeStr(checkOutTimeRaw) : null; - const hasImage = imgName !== 'None attached'; - - // Dynamic styling based on status const isPunchedIn = stateName.toLowerCase().includes('in') || stateName.toLowerCase().includes('active'); - const statusBg = isPunchedIn ? 'bg-amber-50' : 'bg-slate-50'; - const statusBorder = isPunchedIn ? 'border-amber-100' : 'border-slate-200'; - const statusDot = isPunchedIn ? 'bg-amber-500 animate-pulse' : 'bg-slate-400'; - const statusText = isPunchedIn ? 'text-amber-800' : 'text-slate-600'; + + let statusClass = 'z-card-status--neutral'; + if (isPunchedIn) statusClass = 'z-card-status--success'; + else if (stateName.toLowerCase().includes('out')) statusClass = 'z-card-status--primary'; return ( -
- - {/* Attendance Status Header */} -
-
- - {stateName} +
+
+
+ + {userName}
+ + {stateName} +
-
+
- {/* Profile and Account Info */} -
-
- {initials} -
-
-

{userName}

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

{userEmail}

- )} -
-
- - {/* Punch Details Grid */} -
-
- Punch Date -

{dateStr}

-
-
- Punch Time -

{timeStr}

-
-
- Route Assigned - - Route: {routeCode} +
+
+
PUNCH-IN TIME
+
+ + {dateStr} + {checkInTime}
- - {/* Day Plan & Notes */} - {isDetailView && ( -
- Day Plan Summary -
-

- "{dayPlanNotes}" -

-
-
- )} - - {/* Attached Verification Media */} - {isDetailView && hasImage && ( -
- Verification Image - {imgData.uuid ? ( -
- {imgName} { - (e.target as HTMLImageElement).style.display = 'none'; - (e.target as HTMLImageElement).parentElement!.innerHTML = `
${imgName}
`; - }} - /> - -
- ) : ( -
-
-
- -
-
-

{imgName}

-

{imgSize}

-
-
- - MIME: {imgMime.split('/')[1] || 'Media'} - -
- )} + {checkOutTime && ( +
+
PUNCH-OUT TIME
+
+ + {dateStr} + {checkOutTime} + +
)} - - {/* Inline Punch Out Action */} - {onPunchOut && isPunchedIn && ( -
- -
- )} -
+ + {isPunchedIn && onPunchOut && ( +
+ +
+ )}
); } diff --git a/src/components/cards/OrderCard.tsx b/src/components/cards/OrderCard.tsx index e9e5d7c..3480138 100644 --- a/src/components/cards/OrderCard.tsx +++ b/src/components/cards/OrderCard.tsx @@ -1,102 +1,77 @@ import { formatValue } from '../../lib/format'; -import { Badge } from '../reusable/Badge'; -import { CheckCircle2Icon, Factory, UserIcon } from 'lucide-react'; +import { Store, Calendar, Package, ShoppingBag } from 'lucide-react'; +import './card.css'; export function OrderCard({ row, fields }: { row: Record; fields: any[] }) { - const orderIdStr = row.order_id; + const orderIdStr = row.order_id || '—'; // Extract store lookup object if it exists const storeObj = (row.select_store || row.store) as Record | null; - const storeVal = formatValue(storeObj?.business_name || '—'); - const routeVal = formatValue(storeObj?.route_name || '—'); - const distributorVal = formatValue(storeObj?.distributor_name || '—'); - - // Extract user/performed_by object - const userKey = Object.keys(row).find(k => k.includes('user_id') || k.includes('created_by')); - const userObj = userKey ? row[userKey] as Record : null; - const userVal = formatValue(userObj?.name || userObj?.user_name || '—'); + const storeVal = formatValue(storeObj?.business_name || row.business_name || row.store_name || '—'); // Extract date const dateKey = Object.keys(row).find(k => k.includes('date_of_order')); - const dateVal = dateKey && row[dateKey] ? formatValue(row[dateKey]) : '—'; + let dateVal = dateKey && row[dateKey] ? formatValue(row[dateKey]) : '—'; + // Try to append time if it exists + const timeKey = Object.keys(row).find(k => k.includes('time_of_order')); + if (timeKey && row[timeKey]) { + dateVal += ` ${formatValue(row[timeKey])}`; + } + const stateName = String(row.current_state_name || ''); const isProductive = stateName.toLowerCase().includes('productive') || stateName.toLowerCase().includes('closed') || stateName.toLowerCase().includes('ordered'); const productiveLabel = stateName || 'Pending'; + + let statusClass = 'z-card-status--neutral'; + if (isProductive) statusClass = 'z-card-status--success'; // Order Details Grid const gridField = fields.find(f => f.data_type === 'grid' || String(f.field_key).includes('order_details')); const gridValRaw = gridField ? row[gridField.field_key] : (row.order_details || row.order_details_2 || row.order_details_3); const gridVal = Array.isArray(gridValRaw) ? gridValRaw : []; + // Total Kgs + let totalKgsVal = Number(row.total_kgs || row.total_kgs_2 || row.total_kgs_3 || row.total_weight || row.weight || 0); + if (totalKgsVal === 0 && gridVal.length > 0) { + gridVal.forEach(p => { totalKgsVal += Number(p.row_kgs || p.kgs || p.weight || 0); }) + } + const totalKgs = formatValue(totalKgsVal); + return ( -
- - {/* Top Header Section */} -
-
-
- Order ID - {orderIdStr} -
- - {isProductive && } - {productiveLabel} - +
+
+
+ + {orderIdStr}
- -
-
- Date - {dateVal} -
-
- Store & Route - - {storeVal} ({routeVal}) - -
+ + {productiveLabel} + +
+ +
+ +

{storeVal}

+
+ +
+ +
+
+
DATE & TIME
+ {dateVal} +
+
+
TOTAL KGS
+ {totalKgs} kg
- {/* Middle Section: Distributor & User */} -
-
-
- -
-
- Distributor - {distributorVal} -
-
- -
- -
-
- Placed by - {userVal} -
-
- -
-
-
- - {/* Bottom Section: Order Items */} {gridVal.length > 0 && ( -
-
-

- Order Items - - {gridVal.length} - -

-
- -
+
+

Order Products

+
{gridVal.map((item, i) => { const getVal = (key: string) => { if (item[key] !== undefined) return item[key]; @@ -106,29 +81,12 @@ export function OrderCard({ row, fields }: { row: Record; fields: a }; const productName = formatValue(getVal('product_name') || getVal('product_category') || 'Unknown Product'); - const sku = formatValue(getVal('sku_code') || getVal('sku') || 'N/A'); const bags = formatValue(getVal('bags') || getVal('total_bags') || getVal('quantity') || 0); return ( -
-
-
- - {sku} - -
- - {productName} - -
-
- - {bags} - - - bags - -
+
+ {productName} + {bags} Bags
); })} diff --git a/src/components/cards/StoreCard.tsx b/src/components/cards/StoreCard.tsx index 4239785..2ed8c8b 100644 --- a/src/components/cards/StoreCard.tsx +++ b/src/components/cards/StoreCard.tsx @@ -1,12 +1,14 @@ import { formatValue } from '../../lib/format'; -import { Edit2 } from 'lucide-react'; +import { Store, MapPin, User as UserIcon, Edit2, Phone, Mail } from 'lucide-react'; import { BASE_URL, APP_ID } from '../../api/config'; +import { Button } from '../buttons/Button'; +import './card.css'; -export function StoreCard({ row, fields, isDetailView = false, onEdit }: { row: Record; fields: any[]; isDetailView?: boolean; onEdit?: (row: any) => void }) { +export function StoreCard({ row, isDetailView = false, onEdit }: { row: Record; 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; @@ -24,216 +26,136 @@ export function StoreCard({ row, fields, isDetailView = false, onEdit }: { row: 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 || '—'); - const subRouteName = formatValue(row.sub_route || row.sub_route_name || '—'); - const routeCode = formatValue(row.route_code || routeName.charAt(0) || 'R'); - - // Distributor Info - const distName = formatValue(row.distributor_name || '—'); - 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 || '—'); - const pin = formatValue(row.pin_code || row.pincode || '—'); + // Location const locObj = row.store_location as Record | null; - const lat = formatValue(locObj?.latitude || row.latitude || '—'); - const lng = formatValue(locObj?.longitude || row.longitude || '—'); + const latRaw = locObj?.latitude || row.latitude; + const lngRaw = locObj?.longitude || row.longitude; + const lat = formatValue(latRaw || '—'); + const lng = formatValue(lngRaw || '—'); + const hasLocation = lat !== '—' && lng !== '—' && latRaw !== undefined && lngRaw !== undefined; + const areaName = formatValue(row.area || row.area_name || '—'); - // User - const userKey = Object.keys(row).find(k => k.includes('user_id') || k.includes('created_by') || k.includes('sales_officer')); - const userObj = userKey ? (typeof row[userKey] === 'object' ? row[userKey] : null) : null; - const userName = formatValue(userObj?.name || row.sales_officer || row.user_name || '—'); - const userEmail = formatValue(userObj?.email || row.user_email || '—'); - const initials = userName !== '—' ? String(userName).substring(0, 2).toUpperCase() : 'SO'; - - // Date - let dateStr = '—'; - const dateKey = Object.keys(row).find(k => k.includes('created_at')); - const rawDate = dateKey ? row[dateKey] : (row.created_at || row.date || row.registration_date); - if (rawDate) { - try { - const d = new Date(rawDate as string); - if (!isNaN(d.getTime())) dateStr = d.toISOString().split('T')[0]; - else dateStr = String(rawDate).split('T')[0]; - } catch { - dateStr = String(rawDate).split('T')[0]; - } - } - - // Potential (grid) - const potentialKey = Object.keys(row).find(k => k.includes('potential') || k.includes('inventory')); - let potentials: any[] = []; - if (potentialKey && Array.isArray(row[potentialKey])) { - potentials = row[potentialKey]; - } else { - // try to find any grid - const gridField = fields.find(f => f.data_type === 'grid'); - if (gridField && Array.isArray(row[gridField.field_key])) { - potentials = row[gridField.field_key]; - } - } - - const statusBg = isActive ? 'bg-blue-50' : 'bg-slate-50'; - const statusBorder = isActive ? 'border-blue-100' : 'border-slate-200'; - const statusDot = isActive ? 'bg-blue-500 animate-pulse' : 'bg-slate-400'; - const statusText = isActive ? 'text-blue-700' : 'text-slate-600'; + let statusClass = 'z-card-status--neutral'; + if (isActive) statusClass = 'z-card-status--success'; return ( -
- {/* Header Status Bar */} -
-
- - {storeCode} +
+
+
+ + {storeCode}
- -
- - {/* 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}`} - -
- )} -
- {isDetailView && imageObj && imageObj.uuid && ( -
- Store { - (e.target as HTMLImageElement).style.display = 'none'; - }} - /> -
- )} -
- - {/* Contact Info */} - {isDetailView && (ownerName !== '—' || phone !== '—' || email !== '—') && ( -
- {ownerName !== '—' && ( -

- Owner - {ownerName} -

- )} - {phone !== '—' && ( -

- Phone - {phone} -

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

- Email - {email} -

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

Assigned Distributor

-
-

{distName}

- {distContact !== '—' &&

Contact: {distContact}

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

Phone: {distPhone}

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

Email: {distEmail}

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

Inventory Potential

-
- {potentials.map((item, idx) => { - const prodName = formatValue(item.product_name || item.product_category || item.product || `Item ${idx+1}`); - const qty = formatValue(item.quantity || item.qty || item.potential || item.bags || 0); - return ( -
- {prodName} - {qty} Qty -
- ); - })} -
-
- )} - - {/* Logistics & Address */} - {isDetailView && ( -
-
-

Address

-

{address}

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

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

- )} -
-
-

Created By

-
-
- {initials} -
-
-

{userName}

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

{userEmail}

- )} -
-
-
-
- )} -
- - {/* Footer Timestamp */} -
-
- - Registered: {dateStr} -
- {areaName !== '—' && ( - - Area: {areaName} + {hasLocation ? ( + e.stopPropagation()} + title="Open in Google Maps" + > + Google Maps + + ) : ( + + {stateName} )}
+ +
+ +
+

+ {storeName} +

+ +
+ {ownerName !== '—' && ( + <> +
+ + Owner +
+
{ownerName}
+ + )} + {areaName !== '—' && ( + <> +
+ + Area +
+
{areaName}
+ + )} +
+
+ + {!isDetailView && ( +
+ +
+ )} + + {isDetailView && ( +
+ {imageObj && imageObj.uuid && ( +
+ Store { + (e.target as HTMLImageElement).style.display = 'none'; + }} + /> +
+ )} + +
+
+
+ +
+
+ Phone + {phone} +
+
+ +
+
+ +
+
+ Email + {email} +
+
+
+
+ )}
); } diff --git a/src/components/dv/DailyLogDetail.tsx b/src/components/dv/DailyLogDetail.tsx index 73338e5..3df3c09 100644 --- a/src/components/dv/DailyLogDetail.tsx +++ b/src/components/dv/DailyLogDetail.tsx @@ -15,7 +15,7 @@ export function DailyLogDetail({ instanceId }: WiredDetailViewProps) { return (
- +
); } diff --git a/src/components/dv/StoreDetail.tsx b/src/components/dv/StoreDetail.tsx index 486a9cc..b278e0b 100644 --- a/src/components/dv/StoreDetail.tsx +++ b/src/components/dv/StoreDetail.tsx @@ -8,7 +8,7 @@ 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); + const { data, loading, error } = useDetailViewData(storeClient, STORE.detailViews.STORE, instanceId); if (error) return ; if (loading) return
; @@ -21,7 +21,7 @@ export function StoreDetail({ instanceId }: WiredDetailViewProps) { return (