order detail product card design done

This commit is contained in:
suryacp23 2026-07-20 11:01:46 +05:30
parent cd4de05a68
commit 377c851ba6
8 changed files with 55 additions and 94 deletions

View File

@ -127,7 +127,6 @@ export class ZinoClient {
try {
const p = JSON.parse(atob(this.token.split('.')[1]));
console.log('--- JWT PAYLOAD ---', p);
return {
id: String(p.user_id ?? p.sub ?? ''),
org_id: String(p.org_id ?? ''),

View File

@ -65,7 +65,7 @@ export const ORDER_BOOKING = {
recordViews: {
ORDERS: '1e424561-9a27-44f5-9b68-64d63296d837',
CALLS: 'b4d7a710-24e7-416d-885a-31fd1e727aab',
ANALYTICS: '56b21b46-6d2c-4e10-b5a3-c63d058d0afa'
ANALYTICS: 'b4d7a710-24e7-416d-885a-31fd1e727aab'
},
detailViews: {
ORDERS: '0804c6c3-6cf9-4050-94bb-fa48dd5de87d',

View File

@ -159,61 +159,49 @@ export function SmartGridField({
{value.length === 0 ? (
<span className="text-sm text-faint italic">No rows added.</span>
) : (
<div className="overflow-x-auto">
<table className="w-full text-left border-collapse bg-white border border-border-subtle shadow-sm rounded">
<thead>
<tr className="bg-sunk border-b border-border-default">
{visibleColumns.map(col => (
<th key={col.id} className="p-3 text-xs font-semibold text-muted uppercase tracking-wider whitespace-nowrap">
{col.name}
</th>
))}
<th className="p-3 text-xs font-semibold text-muted uppercase tracking-wider w-16"></th>
</tr>
</thead>
<tbody>
{value.map((row, i) => (
<tr key={i} className="border-b border-border-subtle last:border-b-0 hover:bg-slate-50/50">
{visibleColumns.map(col => (
<td key={col.id} className="p-3 text-sm text-strong whitespace-nowrap">
{(() => {
const val = row[col.id];
let displayVal = String(val ?? '-');
<div className="flex flex-col gap-3">
{value.map((row, i) => {
let productName = 'Unknown Product';
let bags = '0';
if (col.data_type === 'select' || col.data_type === 'multiselect') {
const opt = col.properties?.options?.find(o => String(o.value) === String(val));
if (opt) displayVal = opt.label;
}
visibleColumns.forEach(col => {
const isName = col.id.toLowerCase().includes('name') || col.name.toLowerCase().includes('name') || (col.id.toLowerCase().includes('category') && productName === 'Unknown Product');
const isBags = col.id.toLowerCase().includes('bags') || col.name.toLowerCase().includes('bags') || col.id.toLowerCase().includes('quantity');
if ((col.id.toLowerCase().includes('difference') || col.name?.toLowerCase().includes('difference')) && val != null && val !== '') {
const numVal = Number(val);
if (!isNaN(numVal)) {
if (numVal < 0) {
displayVal = `${Math.abs(numVal)} kgs less`;
} else if (numVal > 0) {
displayVal = `${numVal} kgs more`;
} else {
displayVal = `0 kgs`;
}
}
}
if (isName || isBags) {
const val = row[col.id];
let displayVal = String(val ?? '-');
return displayVal;
})()}
</td>
))}
<td className="p-2 align-middle text-center flex items-center justify-end gap-1">
<button type="button" onClick={() => startEdit(i)} className="text-blue-600 hover:bg-blue-50 p-1.5 rounded transition-colors" title="Edit row">
<Pencil size={16} />
</button>
<button type="button" onClick={() => removeRow(i)} className="text-ruby-600 hover:bg-ruby-50 p-1.5 rounded transition-colors" title="Remove row">
<Trash2 size={16} />
</button>
</td>
</tr>
))}
</tbody>
</table>
if (col.data_type === 'select' || col.data_type === 'multiselect') {
const opt = col.properties?.options?.find(o => String(o.value) === String(val));
if (opt) displayVal = opt.label;
}
if (isName && (productName === 'Unknown Product' || col.id.toLowerCase().includes('name'))) {
productName = displayVal;
}
if (isBags) bags = displayVal;
}
});
return (
<div key={i} className="bg-slate-50 border border-slate-200 rounded-xl p-3 shadow-sm flex items-center justify-between">
<div className="flex flex-col min-w-0 pr-4">
<span className="font-bold text-slate-800 text-[14px] truncate">{productName}</span>
<span className="font-extrabold text-indigo-600 text-[13px] mt-0.5">{bags} Bags</span>
</div>
<div className="flex items-center gap-1.5 shrink-0">
<button type="button" onClick={() => startEdit(i)} className="text-indigo-600 bg-white border border-indigo-100 hover:bg-indigo-50 p-1.5 rounded-lg transition-colors flex items-center justify-center shadow-sm" title="Edit">
<Pencil size={14} />
</button>
<button type="button" onClick={() => removeRow(i)} className="text-red-600 bg-white border border-red-100 hover:bg-red-50 p-1.5 rounded-lg transition-colors flex items-center justify-center shadow-sm" title="Remove">
<Trash2 size={14} />
</button>
</div>
</div>
);
})}
</div>
)}

View File

@ -27,7 +27,7 @@ export function StatsTile({ tile, idx = 0 }: StatsTileProps) {
const isDanger = lowerKey.includes('no_order');
return (
<div className="z-stats-tile z-stats-tile--secondary">
<div className={`z-stats-tile z-stats-tile--secondary ${isDanger ? 'z-stats-tile--danger' : ''}`}>
<div className="z-stats-header">
<span className={`z-stats-label`}>{displayLabel}</span>
<Icon size={16} className={`z-stats-icon ${isDanger ? 'z-stats-icon--danger' : ''}`} />

View File

@ -12,27 +12,24 @@
display: none;
}
/* Horizontal Scroll Container */
/* Vertical Layout Container */
.z-stats-container {
display: flex;
overflow-x: auto;
flex-direction: column;
gap: 12px;
padding-bottom: 8px;
/* For box-shadow clipping */
scroll-snap-type: x mandatory;
}
/* Base Tile */
.z-stats-tile {
flex: 0 0 auto;
width: 160px;
width: 100%;
height: 100px;
border-radius: var(--z-border-radius-lg);
padding: 16px;
display: flex;
flex-direction: column;
justify-content: space-between;
scroll-snap-align: start;
transition: transform 0.2s ease;
box-shadow: var(--block-shadow);
font-family: var(--font-sans);
@ -42,6 +39,7 @@
.z-stats-tile--secondary {
background-color: var(--z-bg-neutral-100);
border: 1px solid var(--z-border-neutral-300);
border-left: 4px solid var(--z-text-primary-400);
color: var(--z-text-neutral-900);
}
@ -93,6 +91,10 @@
color: var(--z-text-danger-400);
}
.z-stats-tile--secondary.z-stats-tile--danger {
border-left-color: var(--z-text-danger-400);
}
.z-stats-footer {
margin-top: auto;
}

View File

@ -3,6 +3,7 @@ import { orderBookingClient } from '../api/clients';
import { ORDER_BOOKING } from '../api/config';
import { Spinner } from '../components/reusable/Spinner';
import { EmptyState } from '../components/reusable/EmptyState';
import { StatsTiles } from '../components/reusable/StatsTiles';
import { Activity, Package, ShoppingBag, BarChart3, TrendingUp, Phone } from 'lucide-react';
import { formatValue } from '../lib/format';
import {
@ -63,18 +64,7 @@ export function AnalyticsPage() {
if (loading) return <div className="h-full flex items-center justify-center"><Spinner label="Loading Analytics..." /></div>;
if (error) return <EmptyState title="Analytics Error" hint={error} />;
// Tile Helper
const getTileLabelAndIcon = (key: string) => {
switch (key) {
case 'total_orders': return { label: 'Total Orders', icon: ShoppingBag, color: 'text-indigo-500', bg: 'bg-indigo-50' };
case 'total_bags': return { label: 'Total Bags', icon: Package, color: 'text-amber-500', bg: 'bg-amber-50' };
case 'total_kgs': return { label: 'Total KGs', icon: Activity, color: 'text-emerald-500', bg: 'bg-emerald-50' };
case 'total_calls': return { label: 'Total Calls', icon: Phone, color: 'text-blue-500', bg: 'bg-blue-50' };
case 'total_productive_calls': return { label: 'Productive Calls', icon: TrendingUp, color: 'text-green-500', bg: 'bg-green-50' };
case 'no_order_calls': return { label: 'No Order Calls', icon: Activity, color: 'text-red-500', bg: 'bg-red-50' };
default: return { label: key.replace(/_/g, ' '), icon: BarChart3, color: 'text-slate-500', bg: 'bg-slate-50' };
}
};
const renderChart = (chart: ChartData, index: number) => {
const data = chart.rows.map(r => ({
@ -149,24 +139,7 @@ export function AnalyticsPage() {
</div>
{/* Tiles Grid */}
<div className="grid grid-cols-2 sm:grid-cols-3 gap-4">
{tiles.map((t) => {
const { label, icon: Icon, color, bg } = getTileLabelAndIcon(t.key);
return (
<div key={t.tile_uid} className="bg-white rounded-2xl p-4 shadow-[0_2px_12px_-4px_rgba(0,0,0,0.08)] border border-slate-100 flex flex-col gap-3 transition-transform hover:-translate-y-1 hover:shadow-lg">
<div className={`w-10 h-10 rounded-xl flex items-center justify-center ${bg} ${color}`}>
<Icon size={20} strokeWidth={2.5} />
</div>
<div className="flex flex-col">
<span className="text-[10px] font-bold text-slate-400 uppercase tracking-wider line-clamp-1">{label}</span>
<span className="text-xl font-black text-slate-800 tracking-tight">
{t.key === 'total_kgs' ? Number(t.value).toLocaleString(undefined, { maximumFractionDigits: 1 }) : formatValue(t.value)}
</span>
</div>
</div>
);
})}
</div>
<StatsTiles tiles={tiles as any} />
{/* Charts Grid */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 pb-4">

View File

@ -126,7 +126,6 @@ export function CallsPage() {
<Button size="sm" onClick={(e) => {
e.stopPropagation();
e.preventDefault();
console.log("Clicked Edit Order, ID:", ORDER_BOOKING.activities.EDIT_ORDER?.uid);
setActiveActivity({ id: ORDER_BOOKING.activities.EDIT_ORDER.uid, name: 'Edit Order' });
setIsFabOpen(false);
}}>

View File

@ -31,13 +31,13 @@ export interface ScreenDef {
}
export const SCREENS: ScreenDef[] = [
{ key: 'analytics', label: 'Analytics', icon: BarChart2, View: AnalyticsPage as any },
{ key: 'sales-report', label: 'Sales Report', icon: FileText, View: DailySalesReportPage as any },
{ key: 'daily', label: 'Home', icon: Home, View: DailyLogsView, Detail: DailyLogDetail, noun: 'Daily Log' },
{ key: 'orders', label: 'Orders', icon: ShoppingCart, View: OrdersView, Detail: OrderDetail, noun: 'Order' },
{ key: 'calls', label: 'Calls', icon: Phone, View: CallsView, Detail: CallDetail, noun: 'Call' },
{ key: 'stores', label: 'Stores', icon: Store, View: StoresView, Detail: StoreDetail, noun: 'Store' },
{ key: 'all-daily', label: 'All Logs', icon: ClipboardList, View: DailyLogsView, Detail: DailyLogDetail, noun: 'Daily Log' },
{ key: 'analytics', label: 'Analytics', icon: BarChart2, View: AnalyticsPage as any },
{ key: 'sales-report', label: 'Sales Report', icon: FileText, View: DailySalesReportPage as any }
];
export function screenByKey(key: string | undefined): ScreenDef | undefined {