tiles and card design in calls done
This commit is contained in:
parent
e8732bf436
commit
da013c6bac
@ -1,18 +1,70 @@
|
|||||||
|
import { Store, MapPin, TrendingUp, Calendar, Clock, Info } from 'lucide-react';
|
||||||
import { formatValue } from '../../lib/format';
|
import { formatValue } from '../../lib/format';
|
||||||
import { BASE_URL, APP_ID } from '../../api/config';
|
import { BASE_URL, APP_ID } from '../../api/config';
|
||||||
|
import './card.css';
|
||||||
|
|
||||||
|
// {
|
||||||
|
// "42b7f47d-96f0-4289-a6d9-38f455ad57dd__user_id": {
|
||||||
|
// "email": "surya.c@getzino.com",
|
||||||
|
// "job_title": "",
|
||||||
|
// "name": "Surya C",
|
||||||
|
// "user_id": 29113
|
||||||
|
// },
|
||||||
|
// "current_state_id": "33d7ab1f-ddf4-4549-ac99-7d23172c4ca5",
|
||||||
|
// "current_state_name": "Productive Call",
|
||||||
|
// "date_of_visit": "2026-07-15",
|
||||||
|
// "instance_id": 6592,
|
||||||
|
// "is_productive_call": "yes",
|
||||||
|
// "select_store": {
|
||||||
|
// "area": "Dummy Area",
|
||||||
|
// "business_name": "Business Name 777",
|
||||||
|
// "complete_address": "Dummy notes for Complete Address",
|
||||||
|
// "distributor_email": "test@example.com",
|
||||||
|
// "distributor_name": "Banashree Multi Millet Flour",
|
||||||
|
// "distributor_owner_name": "Dummy Distributor Owner Name",
|
||||||
|
// "distributor_phone_number": {
|
||||||
|
// "dial_code": "+91",
|
||||||
|
// "phone": "9876543210",
|
||||||
|
// "phone_with_dial_code": "+919876543210"
|
||||||
|
// },
|
||||||
|
// "email": "test@example.com",
|
||||||
|
// "instance_id": 6118,
|
||||||
|
// "notes": "Dummy notes for Notes",
|
||||||
|
// "owner_name": "Dummy Owner Name",
|
||||||
|
// "phone_number": {
|
||||||
|
// "dial_code": "+91",
|
||||||
|
// "phone": "9876543210",
|
||||||
|
// "phone_with_dial_code": "+919876543210"
|
||||||
|
// },
|
||||||
|
// "pin_code": "100000",
|
||||||
|
// "potential": "[{\"row_kgs\": 0, \"quantity\": 20, \"product_category\": \"MAIDA SUPER VALUE\"}, {\"row_kgs\": 0, \"quantity\": 30, \"product_category\": \"SUJI\"}, {\"row_kgs\": 0, \"quantity\": 10, \"product_category\": \"FORTIFIED MAIDA\"}, {\"row_kgs\": 0, \"quantity\": 300, \"product_category\": \"ATTA REGULAR\"}]",
|
||||||
|
// "route_code": "k",
|
||||||
|
// "route_name": "Krishna",
|
||||||
|
// "store_code": "STR-00024",
|
||||||
|
// "store_image": "[{\"uuid\": \"a449f6cb-0dce-4e21-9599-ab4d3e47042b\", \"blob_path\": \"434/a449f6cb-0dce-4e21-9599-ab4d3e47042b.png\", \"mime_type\": \"image/png\", \"size_bytes\": 32174, \"original_name\": \"profile.png\"}]",
|
||||||
|
// "store_location": "{\"latitude\":12.9716,\"longitude\":77.5946}",
|
||||||
|
// "sub_route": "A"
|
||||||
|
// },
|
||||||
|
// "time_of_visit": "13:02:00",
|
||||||
|
// "upload_image": null
|
||||||
|
// }
|
||||||
|
|
||||||
export function CallCard({ row, isDetailView = false }: { row: Record<string, any>; isDetailView?: boolean }) {
|
export function CallCard({ row, isDetailView = false }: { row: Record<string, any>; isDetailView?: boolean }) {
|
||||||
// Status
|
// Status Mapping
|
||||||
const stateName = String(row.status || row.current_state_name || 'Pending');
|
const stateName = row.current_state_name;
|
||||||
const isProductive = stateName.toLowerCase().includes('productive') || stateName.toLowerCase().includes('completed') || stateName.toLowerCase().includes('success');
|
const lowerState = stateName.toLowerCase();
|
||||||
|
|
||||||
// Image
|
let statusClass = 'z-card-status--neutral';
|
||||||
let imageObj = null;
|
if (lowerState.includes('productive call')) {
|
||||||
const imgData = row.upload_image || row.store_image || row.image;
|
statusClass = 'z-card-status--success';
|
||||||
if (Array.isArray(imgData) && imgData.length > 0) {
|
} else if (lowerState.includes('close order') || lowerState.includes('completed') || lowerState.includes('success')) {
|
||||||
imageObj = imgData[0];
|
statusClass = 'z-card-status--success';
|
||||||
} else if (imgData && typeof imgData === 'object' && imgData.blob_path) {
|
} else if (lowerState.includes('no order')) {
|
||||||
imageObj = imgData;
|
statusClass = 'z-card-status--danger';
|
||||||
|
} else if (lowerState.includes('visited')) {
|
||||||
|
statusClass = 'z-card-status--primary';
|
||||||
|
} else {
|
||||||
|
statusClass = 'z-card-status--primary'; // default fallback for other active states
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract nested objects if they exist
|
// Extract nested objects if they exist
|
||||||
@ -20,14 +72,43 @@ export function CallCard({ row, isDetailView = false }: { row: Record<string, an
|
|||||||
const userKey = Object.keys(row).find(k => k.includes('user_id'));
|
const userKey = Object.keys(row).find(k => k.includes('user_id'));
|
||||||
const userObj = userKey ? row[userKey] : null;
|
const userObj = userKey ? row[userKey] : null;
|
||||||
|
|
||||||
|
// Image
|
||||||
|
let imageObj = null;
|
||||||
|
let imgData = row.upload_image || storeObj?.store_image || row.store_image || row.image;
|
||||||
|
if (typeof imgData === 'string' && imgData.startsWith('[')) {
|
||||||
|
try { imgData = JSON.parse(imgData); } catch (e) { }
|
||||||
|
}
|
||||||
|
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(storeObj?.business_name || storeObj?.distributor_name || row.store_name || row.customer_name || (typeof row.store === 'string' ? row.store : 'Unknown Store'));
|
const storeName = formatValue(storeObj?.business_name || storeObj?.distributor_name || row.store_name || row.customer_name || (typeof row.store === 'string' ? row.store : 'Unknown Store'));
|
||||||
const storeCode = formatValue(storeObj?.store_code || row.store_code || '—');
|
const storeCode = formatValue(storeObj?.store_code || row.store_code || '—');
|
||||||
|
|
||||||
// Grid Details
|
// Grid Details (Calculate from potential if missing)
|
||||||
const totalWeight = formatValue(row.total_kgs || row.total_weight || row.weight || 0);
|
let totalWeightVal = Number(row.total_kgs || row.total_weight || row.weight || 0);
|
||||||
const totalQuantity = formatValue(row.total_bags || row.total_quantity || row.quantity || 0);
|
let totalQuantityVal = Number(row.total_bags || row.total_quantity || row.quantity || 0);
|
||||||
const totalOrders = formatValue(row.order_count || row.total_orders || 0);
|
let totalOrdersVal = Number(row.order_count || row.total_orders || 0);
|
||||||
|
|
||||||
|
if (totalWeightVal === 0 && totalQuantityVal === 0 && storeObj?.potential) {
|
||||||
|
try {
|
||||||
|
const pot = typeof storeObj.potential === 'string' ? JSON.parse(storeObj.potential) : storeObj.potential;
|
||||||
|
if (Array.isArray(pot)) {
|
||||||
|
totalOrdersVal = pot.length;
|
||||||
|
pot.forEach((p: any) => {
|
||||||
|
totalWeightVal += Number(p.row_kgs || 0);
|
||||||
|
totalQuantityVal += Number(p.quantity || 0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalWeight = formatValue(totalWeightVal);
|
||||||
|
const totalQuantity = formatValue(totalQuantityVal);
|
||||||
|
const totalOrders = formatValue(totalOrdersVal);
|
||||||
|
|
||||||
// Route & Assignment
|
// Route & Assignment
|
||||||
const routeName = formatValue(storeObj?.route_name || storeObj?.route || row.route_name || row.route || '—');
|
const routeName = formatValue(storeObj?.route_name || storeObj?.route || row.route_name || row.route || '—');
|
||||||
@ -46,8 +127,9 @@ export function CallCard({ row, isDetailView = false }: { row: Record<string, an
|
|||||||
try {
|
try {
|
||||||
const dVal = String(row.date_of_visit || row.visit_date);
|
const dVal = String(row.date_of_visit || row.visit_date);
|
||||||
const d = new Date(dVal);
|
const d = new Date(dVal);
|
||||||
if (!isNaN(d.getTime())) dateStr = d.toISOString().split('T')[0];
|
if (!isNaN(d.getTime())) {
|
||||||
else dateStr = dVal.split('T')[0];
|
dateStr = d.toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' }).replace(/ /g, '-');
|
||||||
|
} else dateStr = dVal.split('T')[0];
|
||||||
} catch {
|
} catch {
|
||||||
dateStr = String(row.date_of_visit || row.visit_date).split('T')[0];
|
dateStr = String(row.date_of_visit || row.visit_date).split('T')[0];
|
||||||
}
|
}
|
||||||
@ -56,8 +138,18 @@ export function CallCard({ row, isDetailView = false }: { row: Record<string, an
|
|||||||
if (row.time_of_visit || row.visit_time) {
|
if (row.time_of_visit || row.visit_time) {
|
||||||
try {
|
try {
|
||||||
const t = String(row.time_of_visit || row.visit_time);
|
const t = String(row.time_of_visit || row.visit_time);
|
||||||
|
let timeParts = t.split(':');
|
||||||
if (t.includes('T')) {
|
if (t.includes('T')) {
|
||||||
timeStr = t.split('T')[1].replace('Z', '').split('.')[0];
|
timeParts = t.split('T')[1].replace('Z', '').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;
|
||||||
|
timeStr = `${hStr}:${m} ${ampm}`;
|
||||||
} else {
|
} else {
|
||||||
timeStr = t;
|
timeStr = t;
|
||||||
}
|
}
|
||||||
@ -66,114 +158,127 @@ export function CallCard({ row, isDetailView = false }: { row: Record<string, an
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Choose styling based on status
|
|
||||||
const statusBg = isProductive ? 'bg-emerald-50' : 'bg-amber-50';
|
|
||||||
const statusBorder = isProductive ? 'border-emerald-100' : 'border-amber-100';
|
|
||||||
const statusDot = isProductive ? 'bg-emerald-500 animate-pulse' : 'bg-amber-500';
|
|
||||||
const statusText = isProductive ? 'text-emerald-700' : 'text-amber-700';
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full bg-white rounded-2xl shadow-[0_2px_12px_-4px_rgba(0,0,0,0.08)] border border-slate-100 overflow-hidden font-sans transition-all hover:shadow-[0_8px_24px_-8px_rgba(0,0,0,0.12)]">
|
<div className="z-card">
|
||||||
{/* Header Status Bar */}
|
|
||||||
<div className={`${statusBg} px-5 py-3.5 border-b ${statusBorder} flex justify-between items-center`}>
|
{/* Top Row */}
|
||||||
<div className="flex items-center gap-2">
|
<div className="z-card-header">
|
||||||
<span className={`w-2.5 h-2.5 rounded-full ${statusDot}`}></span>
|
<div className="z-card-header-icon">
|
||||||
<span className={`text-[13px] font-bold ${statusText} tracking-wide uppercase`}>{stateName}</span>
|
<TrendingUp size={16} />
|
||||||
|
<span>{storeCode}</span>
|
||||||
|
</div>
|
||||||
|
<span className={`z-card-status ${statusClass}`}>
|
||||||
|
{stateName}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Store Name & Location */}
|
||||||
|
<div className="z-card-store-row">
|
||||||
|
<Store size={18} className="z-card-store-icon" />
|
||||||
|
<h2 className="z-card-store-name">{storeName}</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="z-card-area-row">
|
||||||
|
<MapPin size={16} className="z-card-area-icon" />
|
||||||
|
<p className="z-card-area-name">{areaName}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Divider */}
|
||||||
|
<div className="z-card-divider"></div>
|
||||||
|
|
||||||
|
{/* Date & Time */}
|
||||||
|
<div className="z-card-footer">
|
||||||
|
<div className="z-card-footer-item">
|
||||||
|
<div className="z-card-footer-label"><Calendar size={14} /> DATE</div>
|
||||||
|
<span className="z-card-footer-value">{dateStr}</span>
|
||||||
|
</div>
|
||||||
|
<div className="z-card-footer-item" style={{ textAlign: 'left', width: '96px' }}>
|
||||||
|
<div className="z-card-footer-label"><Clock size={14} /> TIME</div>
|
||||||
|
<span className="z-card-footer-value">{timeStr}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Main Content */}
|
{/* Button (List View Only) */}
|
||||||
<div className="p-5 space-y-5">
|
{!isDetailView && (
|
||||||
{/* Store Details */}
|
<div className="z-card-action-btn">
|
||||||
<div className="flex justify-between items-start gap-4">
|
<Info size={16} />
|
||||||
<div>
|
<span>See More Details</span>
|
||||||
<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>
|
)}
|
||||||
{storeCode !== '—' && (
|
|
||||||
<p className="text-[13px] text-slate-500 mt-0.5">
|
|
||||||
Code: <span className="font-mono font-medium text-slate-700">{storeCode}</span>
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{isDetailView && imageObj && imageObj.uuid && (
|
{/* Detail View Additions */}
|
||||||
<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">
|
{isDetailView && (
|
||||||
|
<div className="z-card-details">
|
||||||
|
|
||||||
|
{storeCode !== '—' && (
|
||||||
|
<p className="z-card-meta">
|
||||||
|
Code: <span className="z-card-meta-code">{storeCode}</span>
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{imageObj && imageObj.uuid && (
|
||||||
|
<div className="z-card-image-box">
|
||||||
<img
|
<img
|
||||||
src={`${BASE_URL}/app/${APP_ID}/view/files/${imageObj.uuid}/preview`}
|
src={`${BASE_URL}/app/${APP_ID}/view/files/${imageObj.uuid}/preview`}
|
||||||
alt="Store"
|
alt="Store"
|
||||||
className="w-full h-full object-cover"
|
className="z-card-image"
|
||||||
onError={(e) => {
|
onError={(e) => {
|
||||||
(e.target as HTMLImageElement).style.display = 'none';
|
(e.target as HTMLImageElement).style.display = 'none';
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Logistics Grid */}
|
{/* Logistics Grid */}
|
||||||
{isDetailView && (
|
<div className="z-card-grid">
|
||||||
<div className="grid grid-cols-2 gap-4 bg-slate-50 p-4 rounded-xl border border-slate-100/80">
|
<div>
|
||||||
<div>
|
<p className="z-card-grid-item-label">Total Weight</p>
|
||||||
<p className="text-[10px] font-bold text-slate-400 uppercase tracking-widest">Total Weight</p>
|
<p className="z-card-grid-item-val">
|
||||||
<p className="text-[17px] font-bold text-slate-800 mt-0.5 leading-none">
|
{totalWeight} <span className="z-card-grid-item-unit">Kgs</span>
|
||||||
{totalWeight} <span className="text-[12px] font-medium text-slate-500">Kgs</span>
|
</p>
|
||||||
</p>
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="z-card-grid-item-label">Total Quantity</p>
|
||||||
|
<p className="z-card-grid-item-val">
|
||||||
|
{totalQuantity} <span className="z-card-grid-item-unit">Bags</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="z-card-grid-footer">
|
||||||
|
<span className="z-card-grid-footer-label">Total Orders</span>
|
||||||
|
<span className="z-card-grid-footer-badge">
|
||||||
|
{totalOrders} {Number(totalOrders) === 1 ? 'Order' : 'Orders'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<p className="text-[10px] font-bold text-slate-400 uppercase tracking-widest">Total Quantity</p>
|
|
||||||
<p className="text-[17px] font-bold text-slate-800 mt-0.5 leading-none">
|
|
||||||
{totalQuantity} <span className="text-[12px] font-medium text-slate-500">Bags</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="col-span-2 pt-3 border-t border-slate-200/60 flex justify-between items-center">
|
|
||||||
<span className="text-[12px] font-medium text-slate-500">Total Orders</span>
|
|
||||||
<span className="bg-indigo-50 text-indigo-700 border border-indigo-100/50 text-[11px] font-bold px-2.5 py-0.5 rounded-full">
|
|
||||||
{totalOrders} {Number(totalOrders) === 1 ? 'Order' : 'Orders'}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Route & Assignment */}
|
{/* Route & Assignment */}
|
||||||
{isDetailView && (
|
<div className="z-card-route">
|
||||||
<div className="grid grid-cols-2 gap-y-4 text-sm border-t border-slate-100 pt-4">
|
<div>
|
||||||
<div>
|
<p className="z-card-grid-item-label">Route</p>
|
||||||
<p className="text-[10px] font-bold text-slate-400 uppercase tracking-widest">Route</p>
|
<p className="z-card-route-val">{routeName}</p>
|
||||||
<p className="font-semibold text-slate-700 mt-0.5 text-[13px]">{routeName}</p>
|
</div>
|
||||||
</div>
|
<div>
|
||||||
<div>
|
<p className="z-card-grid-item-label">Area</p>
|
||||||
<p className="text-[10px] font-bold text-slate-400 uppercase tracking-widest">Area</p>
|
<p className="z-card-route-val">{areaName}</p>
|
||||||
<p className="font-semibold text-slate-700 mt-0.5 text-[13px]">{areaName}</p>
|
</div>
|
||||||
</div>
|
<div className="z-card-so-box">
|
||||||
<div className="col-span-2">
|
<p className="z-card-grid-item-label">Sales Officer</p>
|
||||||
<p className="text-[10px] font-bold text-slate-400 uppercase tracking-widest">Sales Officer</p>
|
<div className="z-card-so-info">
|
||||||
<div className="flex items-center gap-2 mt-1.5">
|
<div className="z-card-so-avatar">
|
||||||
<div className="w-8 h-8 bg-slate-200 rounded-full flex items-center justify-center font-bold text-[11px] text-slate-600 uppercase shadow-inner">
|
{initials}
|
||||||
{initials}
|
</div>
|
||||||
</div>
|
<div className="z-card-so-details">
|
||||||
<div className="flex flex-col min-w-0">
|
<p className="z-card-so-name">{userName}</p>
|
||||||
<p className="font-semibold text-[13px] text-slate-800 leading-tight truncate">{userName}</p>
|
{userEmail !== '—' && (
|
||||||
{userEmail !== '—' && (
|
<p className="z-card-so-email">{userEmail}</p>
|
||||||
<p className="text-[11px] text-slate-500 font-mono truncate">{userEmail}</p>
|
)}
|
||||||
)}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Footer Timeline */}
|
|
||||||
<div className="bg-slate-50/70 px-5 py-3 border-t border-slate-100 flex justify-between text-[11px] text-slate-500 font-medium">
|
|
||||||
<div className="flex items-center gap-1.5">
|
|
||||||
<svg className="w-3.5 h-3.5 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path></svg>
|
|
||||||
<span>{dateStr}</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1.5">
|
)}
|
||||||
<svg className="w-3.5 h-3.5 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
|
|
||||||
<span>{timeStr}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
318
src/components/cards/card.css
Normal file
318
src/components/cards/card.css
Normal file
@ -0,0 +1,318 @@
|
|||||||
|
.z-card {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
background-color: var(--z-bg-neutral-100);
|
||||||
|
border-radius: var(--block-radius);
|
||||||
|
padding: var(--block-padding);
|
||||||
|
box-shadow: var(--block-shadow);
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
border: 1px solid var(--z-border-neutral-300);
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-card:hover {
|
||||||
|
box-shadow: var(--z-shadow-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-card-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-card-header-icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
color: var(--z-text-primary-300);
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: var(--z-font-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-card-status {
|
||||||
|
padding: 4px 12px;
|
||||||
|
border-radius: var(--z-border-radius-pill);
|
||||||
|
font-size: var(--z-font-size-xs);
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-card-status--success {
|
||||||
|
background-color: var(--z-bg-success-100);
|
||||||
|
color: var(--z-text-success-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-card-status--warning {
|
||||||
|
background-color: var(--z-bg-warning-100);
|
||||||
|
color: var(--z-text-warning-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-card-status--danger {
|
||||||
|
background-color: var(--z-bg-danger-100);
|
||||||
|
color: var(--z-text-danger-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-card-status--primary {
|
||||||
|
background-color: var(--z-bg-primary-100);
|
||||||
|
color: var(--z-text-primary-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-card-status--neutral {
|
||||||
|
background-color: var(--z-bg-neutral-200);
|
||||||
|
color: var(--z-text-neutral-600);
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-card-store-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-card-store-icon {
|
||||||
|
color: var(--z-text-neutral-500);
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-card-store-name {
|
||||||
|
font-size: 17px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--z-text-neutral-900);
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-card-area-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-card-area-icon {
|
||||||
|
color: var(--z-text-neutral-700);
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-card-area-name {
|
||||||
|
font-size: var(--z-font-sm);
|
||||||
|
color: var(--z-text-neutral-700);
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-card-divider {
|
||||||
|
width: 100%;
|
||||||
|
height: 1px;
|
||||||
|
background-color: var(--z-border-neutral-300);
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-card-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-card-footer-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-card-footer-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
color: var(--z-text-neutral-600);
|
||||||
|
font-size: var(--z-font-size-sm);
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-card-footer-value {
|
||||||
|
color: var(--z-text-neutral-900);
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: -0.02em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-card-action-btn {
|
||||||
|
width: 100%;
|
||||||
|
background-color: var(--z-bg-neutral-200);
|
||||||
|
border-radius: var(--z-border-radius-lg);
|
||||||
|
padding: 10px 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 6px;
|
||||||
|
color: var(--z-text-primary-400);
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: var(--z-font-sm);
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-card-action-btn:hover {
|
||||||
|
background-color: var(--z-bg-neutral-300);
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-card-details {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
padding-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-card-meta {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--z-text-neutral-500);
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-card-meta-code {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--z-text-neutral-700);
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-card-image-box {
|
||||||
|
width: 100%;
|
||||||
|
height: 192px;
|
||||||
|
border-radius: var(--z-border-radius-md);
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid var(--z-border-neutral-300);
|
||||||
|
box-shadow: var(--z-shadow-sm);
|
||||||
|
background-color: var(--z-bg-neutral-200);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-card-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-card-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 16px;
|
||||||
|
background-color: var(--z-bg-neutral-200);
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: var(--z-border-radius-lg);
|
||||||
|
border: 1px solid var(--z-border-neutral-300);
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-card-grid-item-label {
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--z-text-neutral-400);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-card-grid-item-val {
|
||||||
|
font-size: 17px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--z-text-neutral-900);
|
||||||
|
margin-top: 2px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-card-grid-item-unit {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--z-text-neutral-500);
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-card-grid-footer {
|
||||||
|
grid-column: span 2;
|
||||||
|
padding-top: 12px;
|
||||||
|
border-top: 1px solid var(--z-border-neutral-300);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-card-grid-footer-label {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--z-text-neutral-500);
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-card-grid-footer-badge {
|
||||||
|
background-color: var(--z-bg-primary-100);
|
||||||
|
color: var(--z-text-primary-400);
|
||||||
|
border: 1px solid var(--z-border-primary-100);
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
padding: 2px 10px;
|
||||||
|
border-radius: var(--z-border-radius-full);
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-card-route {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 16px;
|
||||||
|
font-size: var(--z-font-sm);
|
||||||
|
border-top: 1px solid var(--z-border-neutral-300);
|
||||||
|
padding-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-card-route-val {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--z-text-neutral-700);
|
||||||
|
margin-top: 2px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-card-so-box {
|
||||||
|
grid-column: span 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-card-so-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-card-so-avatar {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
background-color: var(--z-bg-neutral-300);
|
||||||
|
border-radius: var(--z-border-radius-full);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--z-text-neutral-600);
|
||||||
|
text-transform: uppercase;
|
||||||
|
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-card-so-details {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-card-so-name {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--z-text-neutral-900);
|
||||||
|
line-height: 1.2;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-card-so-email {
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--z-text-neutral-500);
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
@ -27,9 +27,10 @@ export interface DynamicFormProps {
|
|||||||
onCancel?: () => void;
|
onCancel?: () => void;
|
||||||
ignorePrefill?: boolean;
|
ignorePrefill?: boolean;
|
||||||
customPrefillData?: Record<string, unknown>;
|
customPrefillData?: Record<string, unknown>;
|
||||||
|
onActivityChange?: (activityName: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DynamicForm({ client, activityId: initialActivityId, instanceId: initialInstanceId, onSuccess, onCancel, ignorePrefill, customPrefillData }: DynamicFormProps) {
|
export function DynamicForm({ client, activityId: initialActivityId, instanceId: initialInstanceId, onSuccess, onCancel, ignorePrefill, customPrefillData, onActivityChange }: DynamicFormProps) {
|
||||||
const [currentActivityId, setCurrentActivityId] = useState(initialActivityId);
|
const [currentActivityId, setCurrentActivityId] = useState(initialActivityId);
|
||||||
const [currentInstanceId, setCurrentInstanceId] = useState<number | string | undefined>(initialInstanceId);
|
const [currentInstanceId, setCurrentInstanceId] = useState<number | string | undefined>(initialInstanceId);
|
||||||
const [chainQueue, setChainQueue] = useState<Array<{ activity_uid: string; activity_name: string }>>([]);
|
const [chainQueue, setChainQueue] = useState<Array<{ activity_uid: string; activity_name: string }>>([]);
|
||||||
@ -209,6 +210,7 @@ export function DynamicForm({ client, activityId: initialActivityId, instanceId:
|
|||||||
setChainQueue(pending);
|
setChainQueue(pending);
|
||||||
setCurrentActivityId(nextActivity.activity_uid);
|
setCurrentActivityId(nextActivity.activity_uid);
|
||||||
setCurrentInstanceId(res.instance_id ?? currentInstanceId);
|
setCurrentInstanceId(res.instance_id ?? currentInstanceId);
|
||||||
|
onActivityChange?.(nextActivity.activity_name);
|
||||||
} else {
|
} else {
|
||||||
onSuccess?.();
|
onSuccess?.();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,7 +24,7 @@ export function SmartGridField({
|
|||||||
const visibleColumns = columns.filter(c => {
|
const visibleColumns = columns.filter(c => {
|
||||||
const normalized = c.id.toLowerCase().replace(/[^a-z]/g, '');
|
const normalized = c.id.toLowerCase().replace(/[^a-z]/g, '');
|
||||||
// Use a blacklist so we don't accidentally hide important columns from other grids
|
// Use a blacklist so we don't accidentally hide important columns from other grids
|
||||||
return !['sku', 'rowkgs', 'brcode'].includes(normalized);
|
return !['sku', 'skucode', 'rowkgs', 'brcode'].includes(normalized);
|
||||||
});
|
});
|
||||||
|
|
||||||
const removeRow = (idx: number) => {
|
const removeRow = (idx: number) => {
|
||||||
|
|||||||
41
src/components/reusable/StatsTile.tsx
Normal file
41
src/components/reusable/StatsTile.tsx
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import type { TileItem } from "../../api/types";
|
||||||
|
import { Phone, TrendingUp, Activity, BarChart2, ShoppingCart, ShoppingBag, Scale } from 'lucide-react';
|
||||||
|
import './reusable.css';
|
||||||
|
import '../../styles/styles.css';
|
||||||
|
|
||||||
|
export interface StatsTileProps {
|
||||||
|
tile: TileItem;
|
||||||
|
idx?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function StatsTile({ tile, idx = 0 }: StatsTileProps) {
|
||||||
|
const displayLabel = (tile.key || `tile_${idx}`)
|
||||||
|
.replace(/_/g, " ")
|
||||||
|
.replace(/\b\w/g, (c) => c.toUpperCase());
|
||||||
|
|
||||||
|
const lowerKey = String(tile.key).toLowerCase();
|
||||||
|
|
||||||
|
// Try to pick a relevant icon
|
||||||
|
let Icon = BarChart2;
|
||||||
|
if (lowerKey.includes('call') || lowerKey.includes('total')) Icon = Phone;
|
||||||
|
if (lowerKey.includes('productive')) Icon = TrendingUp;
|
||||||
|
if (lowerKey.includes('order') || lowerKey.includes('cart')) Icon = ShoppingCart;
|
||||||
|
if (lowerKey.includes('bag')) Icon = ShoppingBag;
|
||||||
|
if (lowerKey.includes('kg') || lowerKey.includes('weight')) Icon = Scale;
|
||||||
|
if (lowerKey.includes('active')) Icon = Activity;
|
||||||
|
|
||||||
|
const isDanger = lowerKey.includes('no_order');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="z-stats-tile z-stats-tile--secondary">
|
||||||
|
<div className="z-stats-header">
|
||||||
|
<span className={`z-stats-label`}>{displayLabel}</span>
|
||||||
|
<Icon size={16} className={`z-stats-icon ${isDanger ? 'z-stats-icon--danger' : ''}`} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className={`z-stats-value nums ${isDanger ? 'z-stats-value--danger' : ''}`}>
|
||||||
|
{(tile.value as React.ReactNode) ?? "-"}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,45 +1,20 @@
|
|||||||
import type { TileItem } from "../../api/types";
|
import type { TileItem } from "../../api/types";
|
||||||
|
import { StatsTile } from "./StatsTile";
|
||||||
|
import './reusable.css';
|
||||||
|
import '../../styles/styles.css';
|
||||||
|
|
||||||
export interface StatsTilesProps {
|
export interface StatsTilesProps {
|
||||||
tiles?: TileItem[];
|
tiles?: TileItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const TONES = [
|
|
||||||
"bg-sunrise-500",
|
|
||||||
"bg-emerald-500",
|
|
||||||
"bg-sky-500",
|
|
||||||
"bg-amber-500",
|
|
||||||
"bg-navy-600",
|
|
||||||
"bg-ruby-500",
|
|
||||||
];
|
|
||||||
|
|
||||||
export function StatsTiles({ tiles }: StatsTilesProps) {
|
export function StatsTiles({ tiles }: StatsTilesProps) {
|
||||||
if (!tiles?.length) return null;
|
if (!tiles?.length) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-2 gap-3 sm:grid-cols-4 sm:gap-4">
|
<div className="z-stats-container hide-scrollbar">
|
||||||
{tiles.map((tile, idx) => {
|
{tiles.map((tile, idx) => (
|
||||||
const displayLabel = (tile.key || `tile_${idx}`)
|
<StatsTile key={tile.tile_uid || idx} tile={tile} idx={idx} />
|
||||||
.replace(/_/g, " ")
|
))}
|
||||||
.replace(/\b\w/g, (c) => c.toUpperCase());
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={tile.tile_uid || idx}
|
|
||||||
className="relative overflow-hidden rounded-xl border border-border-subtle bg-card p-4 shadow-sm"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className={`absolute inset-x-0 top-0 h-1 ${TONES[idx % TONES.length]}`}
|
|
||||||
/>
|
|
||||||
<p className="text-2xs font-semibold uppercase tracking-[0.12em] text-muted">
|
|
||||||
{displayLabel}
|
|
||||||
</p>
|
|
||||||
<p className="mt-2 font-numeric text-3xl font-bold leading-none tracking-tight text-strong nums">
|
|
||||||
{(tile.value as React.ReactNode) ?? "-"}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
113
src/components/reusable/reusable.css
Normal file
113
src/components/reusable/reusable.css
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
.tile {
|
||||||
|
box-shadow: var(--block-shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide scrollbar for horizontal scroll containers */
|
||||||
|
.hide-scrollbar {
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hide-scrollbar::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Horizontal Scroll Container */
|
||||||
|
.z-stats-container {
|
||||||
|
display: flex;
|
||||||
|
overflow-x: auto;
|
||||||
|
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;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Secondary Tile (White) */
|
||||||
|
.z-stats-tile--secondary {
|
||||||
|
background-color: var(--z-bg-neutral-100);
|
||||||
|
border: 1px solid var(--z-border-neutral-300);
|
||||||
|
color: var(--z-text-neutral-900);
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-stats-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-stats-label {
|
||||||
|
font-size: var(--z-font-size-xs);
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
max-width: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-stats-tile--secondary .z-stats-label {
|
||||||
|
color: var(--z-text-neutral-500);
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-stats-tile--secondary .z-stats-label.z-stats-label--danger {
|
||||||
|
color: var(--z-text-danger-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-stats-tile--secondary .z-stats-icon {
|
||||||
|
color: var(--z-text-success-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-stats-tile--secondary .z-stats-icon.z-stats-icon--danger {
|
||||||
|
color: var(--z-text-danger-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-stats-value {
|
||||||
|
font-size: 30px;
|
||||||
|
font-weight: 800;
|
||||||
|
line-height: 1.1;
|
||||||
|
font-family: var(--font-numeric);
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-stats-tile--secondary .z-stats-value {
|
||||||
|
color: var(--z-text-primary-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-stats-tile--secondary .z-stats-value.z-stats-value--danger {
|
||||||
|
color: var(--z-text-danger-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-stats-footer {
|
||||||
|
margin-top: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-stats-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
border-radius: var(--z-border-radius-pill);
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-stats-tile--secondary .z-stats-badge {
|
||||||
|
padding: 0;
|
||||||
|
color: var(--z-text-success-400);
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
@ -18,6 +18,7 @@ export function CallsView({ onRowClick, pageSize, headerActions, rowActions, ref
|
|||||||
refreshKey={refreshKey}
|
refreshKey={refreshKey}
|
||||||
sortBy="instance_id"
|
sortBy="instance_id"
|
||||||
sortDir="desc"
|
sortDir="desc"
|
||||||
|
hideChart={true}
|
||||||
renderItem={(row) => <CallCard row={row} />}
|
renderItem={(row) => <CallCard row={row} />}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { Search, Filter } from 'lucide-react';
|
import { Search, SlidersHorizontal } from 'lucide-react';
|
||||||
import { cn } from '../../lib/cn';
|
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';
|
||||||
@ -40,6 +40,8 @@ export interface RecordViewProps {
|
|||||||
sortBy?: string;
|
sortBy?: string;
|
||||||
/** Sort direction */
|
/** Sort direction */
|
||||||
sortDir?: 'asc' | 'desc';
|
sortDir?: 'asc' | 'desc';
|
||||||
|
/** If true, the analytics chart will not be rendered even if data is returned */
|
||||||
|
hideChart?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -61,6 +63,7 @@ export function RecordView({
|
|||||||
renderItem,
|
renderItem,
|
||||||
sortBy,
|
sortBy,
|
||||||
sortDir,
|
sortDir,
|
||||||
|
hideChart = false,
|
||||||
}: RecordViewProps) {
|
}: RecordViewProps) {
|
||||||
const [page, setPage] = useState(1);
|
const [page, setPage] = useState(1);
|
||||||
const [search, setSearch] = useState('');
|
const [search, setSearch] = useState('');
|
||||||
@ -139,7 +142,7 @@ export function RecordView({
|
|||||||
<div className="flex flex-col gap-5 w-full">
|
<div className="flex flex-col gap-5 w-full">
|
||||||
<StatsTiles tiles={tileValues} />
|
<StatsTiles tiles={tileValues} />
|
||||||
|
|
||||||
<AnalyticsChart data={chartData} />
|
{!hideChart && <AnalyticsChart data={chartData} />}
|
||||||
|
|
||||||
<div className="flex items-center justify-between gap-3">
|
<div className="flex items-center justify-between gap-3">
|
||||||
<h2 className="text-lg font-bold tracking-tight text-strong">{title}</h2>
|
<h2 className="text-lg font-bold tracking-tight text-strong">{title}</h2>
|
||||||
@ -164,16 +167,13 @@ export function RecordView({
|
|||||||
setShowFilters(true);
|
setShowFilters(true);
|
||||||
}}
|
}}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex items-center justify-center h-11 px-4 rounded-pill border transition-colors shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-1 focus:ring-slate-300",
|
"relative flex items-center justify-center w-11 h-11 rounded-lg transition-colors shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-1 focus:ring-[var(--z-bg-primary-400)]",
|
||||||
Object.values(activeFilters).some(Boolean)
|
"bg-[var(--z-bg-primary-400)] text-white border border-[var(--z-bg-primary-400)] hover:opacity-90"
|
||||||
? "bg-slate-800 text-white border-slate-800"
|
|
||||||
: "bg-white text-slate-700 border-border-subtle hover:bg-slate-50"
|
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Filter size={16} className={cn("mr-2", Object.values(activeFilters).some(Boolean) ? "text-slate-300" : "text-faint")} />
|
<SlidersHorizontal size={16} className="text-white" />
|
||||||
<span className="font-semibold text-[13px]">Filter</span>
|
|
||||||
{Object.values(activeFilters).filter(Boolean).length > 0 && (
|
{Object.values(activeFilters).filter(Boolean).length > 0 && (
|
||||||
<span className="ml-1.5 bg-indigo-500 text-white text-[10px] w-4 h-4 flex items-center justify-center rounded-full font-bold">
|
<span className="absolute -top-1 -right-1 bg-white text-[var(--z-text-primary-400)] text-[10px] w-4 h-4 flex items-center justify-center rounded-full font-bold shadow-sm border border-[var(--z-bg-primary-400)]">
|
||||||
{Object.values(activeFilters).filter(Boolean).length}
|
{Object.values(activeFilters).filter(Boolean).length}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@ -187,19 +187,19 @@ export function RecordView({
|
|||||||
title="Filters"
|
title="Filters"
|
||||||
width="sm"
|
width="sm"
|
||||||
actions={
|
actions={
|
||||||
Object.values(pendingFilters).some(Boolean) && (
|
Object.values(pendingFilters).some(Boolean) && (
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setPendingFilters({});
|
setPendingFilters({});
|
||||||
setActiveFilters({});
|
setActiveFilters({});
|
||||||
setPage(1);
|
setPage(1);
|
||||||
setShowFilters(false);
|
setShowFilters(false);
|
||||||
}}
|
}}
|
||||||
className="text-xs font-semibold text-ruby-600 hover:text-ruby-700 bg-ruby-50 px-3 py-1.5 rounded-md"
|
className="text-xs font-semibold text-ruby-600 hover:text-ruby-700 bg-ruby-50 px-3 py-1.5 rounded-md"
|
||||||
>
|
>
|
||||||
Clear All
|
Clear All
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className="flex flex-col gap-5">
|
<div className="flex flex-col gap-5">
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { CallsView } from '../components/rv';
|
|||||||
import { CallDetail } from '../components/dv';
|
import { CallDetail } from '../components/dv';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Button } from '../components/buttons/Button';
|
import { Button } from '../components/buttons/Button';
|
||||||
import { Plus } from 'lucide-react';
|
import { Phone, Plus } from 'lucide-react';
|
||||||
import { DynamicForm } from '../components/forms/DynamicForm';
|
import { DynamicForm } from '../components/forms/DynamicForm';
|
||||||
import { ORDER_BOOKING } from '../api/config';
|
import { ORDER_BOOKING } from '../api/config';
|
||||||
import { orderBookingClient } from '../api/clients';
|
import { orderBookingClient } from '../api/clients';
|
||||||
@ -14,6 +14,7 @@ export function CallsPage() {
|
|||||||
const instanceId = params.instanceId ? Number(params.instanceId) : undefined;
|
const instanceId = params.instanceId ? Number(params.instanceId) : undefined;
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [isCreating, setIsCreating] = useState(false);
|
const [isCreating, setIsCreating] = useState(false);
|
||||||
|
const [createTitle, setCreateTitle] = useState("Log Visit");
|
||||||
const [refreshKey, setRefreshKey] = useState(0);
|
const [refreshKey, setRefreshKey] = useState(0);
|
||||||
const [activeActivity, setActiveActivity] = useState<{ id: string; name: string } | null>(null);
|
const [activeActivity, setActiveActivity] = useState<{ id: string; name: string } | null>(null);
|
||||||
const [isFabOpen, setIsFabOpen] = useState(false);
|
const [isFabOpen, setIsFabOpen] = useState(false);
|
||||||
@ -75,17 +76,20 @@ export function CallsPage() {
|
|||||||
const id = row.instance_id as number | string | undefined;
|
const id = row.instance_id as number | string | undefined;
|
||||||
if (id != null) navigate(`/calls/${id}`);
|
if (id != null) navigate(`/calls/${id}`);
|
||||||
}}
|
}}
|
||||||
headerActions={
|
|
||||||
<Button size="sm" iconLeft={<Plus size={14} />} onClick={() => setIsCreating(true)}>
|
|
||||||
Log Visit
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* Global Floating Action Button for Add Call */}
|
||||||
|
<button
|
||||||
|
onClick={() => { setIsCreating(true); setCreateTitle("Log Visit"); }}
|
||||||
|
className="fixed bottom-24 right-6 w-[60px] h-[60px] bg-[var(--z-bg-primary-400)] rounded-3xl flex items-center justify-center shadow-[0_4px_12px_rgba(var(--z-bg-primary-400-rgb),0.4)] text-white z-40 hover:opacity-90 transition-transform hover:scale-105 active:scale-95"
|
||||||
|
>
|
||||||
|
<Phone size={20} className="text-white" />
|
||||||
|
</button>
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
open={isCreating}
|
open={isCreating}
|
||||||
onClose={() => setIsCreating(false)}
|
onClose={() => setIsCreating(false)}
|
||||||
title="Log Visit"
|
title={createTitle}
|
||||||
width="md"
|
width="md"
|
||||||
>
|
>
|
||||||
<DynamicForm
|
<DynamicForm
|
||||||
@ -96,6 +100,7 @@ export function CallsPage() {
|
|||||||
setRefreshKey(k => k + 1);
|
setRefreshKey(k => k + 1);
|
||||||
}}
|
}}
|
||||||
onCancel={() => setIsCreating(false)}
|
onCancel={() => setIsCreating(false)}
|
||||||
|
onActivityChange={(name) => setCreateTitle(name)}
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
@ -136,7 +141,7 @@ export function CallsPage() {
|
|||||||
title={activeActivity?.name}
|
title={activeActivity?.name}
|
||||||
width="md"
|
width="md"
|
||||||
>
|
>
|
||||||
{activeActivity && instanceId != null && (
|
{activeActivity && (
|
||||||
<DynamicForm
|
<DynamicForm
|
||||||
client={orderBookingClient}
|
client={orderBookingClient}
|
||||||
activityId={activeActivity.id}
|
activityId={activeActivity.id}
|
||||||
@ -148,6 +153,7 @@ export function CallsPage() {
|
|||||||
setRefreshKey(k => k + 1);
|
setRefreshKey(k => k + 1);
|
||||||
}}
|
}}
|
||||||
onCancel={() => setActiveActivity(null)}
|
onCancel={() => setActiveActivity(null)}
|
||||||
|
onActivityChange={(name) => setActiveActivity(prev => prev ? { ...prev, name } : null)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|||||||
@ -14,7 +14,9 @@ export function DailyLogsPage() {
|
|||||||
const instanceId = params.instanceId ? Number(params.instanceId) : undefined;
|
const instanceId = params.instanceId ? Number(params.instanceId) : undefined;
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [isCreating, setIsCreating] = useState(false);
|
const [isCreating, setIsCreating] = useState(false);
|
||||||
|
const [createTitle, setCreateTitle] = useState("Punch In");
|
||||||
const [punchOutInstanceId, setPunchOutInstanceId] = useState<number | string | null>(null);
|
const [punchOutInstanceId, setPunchOutInstanceId] = useState<number | string | null>(null);
|
||||||
|
const [punchOutTitle, setPunchOutTitle] = useState("Punch Out");
|
||||||
const [refreshKey, setRefreshKey] = useState(0);
|
const [refreshKey, setRefreshKey] = useState(0);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -26,17 +28,20 @@ export function DailyLogsPage() {
|
|||||||
if (id != null) navigate(`/daily/${id}`);
|
if (id != null) navigate(`/daily/${id}`);
|
||||||
}}
|
}}
|
||||||
headerActions={
|
headerActions={
|
||||||
<Button size="sm" iconLeft={<Plus size={14} />} onClick={() => setIsCreating(true)}>
|
<Button size="sm" iconLeft={<Plus size={14} />} onClick={() => { setIsCreating(true); setCreateTitle("Punch In"); }}>
|
||||||
Punch In
|
Punch In
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
onPunchOutRow={(row) => setPunchOutInstanceId(row.instance_id as string | number)}
|
onPunchOutRow={(row) => {
|
||||||
|
setPunchOutInstanceId(row.instance_id as string | number);
|
||||||
|
setPunchOutTitle("Punch Out");
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
open={isCreating}
|
open={isCreating}
|
||||||
onClose={() => setIsCreating(false)}
|
onClose={() => setIsCreating(false)}
|
||||||
title="Punch In"
|
title={createTitle}
|
||||||
width="md"
|
width="md"
|
||||||
>
|
>
|
||||||
<DynamicForm
|
<DynamicForm
|
||||||
@ -47,13 +52,14 @@ export function DailyLogsPage() {
|
|||||||
setRefreshKey(k => k + 1);
|
setRefreshKey(k => k + 1);
|
||||||
}}
|
}}
|
||||||
onCancel={() => setIsCreating(false)}
|
onCancel={() => setIsCreating(false)}
|
||||||
|
onActivityChange={(name) => setCreateTitle(name)}
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
open={punchOutInstanceId != null}
|
open={punchOutInstanceId != null}
|
||||||
onClose={() => setPunchOutInstanceId(null)}
|
onClose={() => setPunchOutInstanceId(null)}
|
||||||
title="Punch Out"
|
title={punchOutTitle}
|
||||||
width="md"
|
width="md"
|
||||||
>
|
>
|
||||||
{punchOutInstanceId != null && (
|
{punchOutInstanceId != null && (
|
||||||
@ -66,6 +72,7 @@ export function DailyLogsPage() {
|
|||||||
setRefreshKey(k => k + 1);
|
setRefreshKey(k => k + 1);
|
||||||
}}
|
}}
|
||||||
onCancel={() => setPunchOutInstanceId(null)}
|
onCancel={() => setPunchOutInstanceId(null)}
|
||||||
|
onActivityChange={(name) => setPunchOutTitle(name)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|||||||
@ -14,6 +14,7 @@ export function OrdersPage() {
|
|||||||
const instanceId = params.instanceId ? Number(params.instanceId) : undefined;
|
const instanceId = params.instanceId ? Number(params.instanceId) : undefined;
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [isCreating, setIsCreating] = useState(false);
|
const [isCreating, setIsCreating] = useState(false);
|
||||||
|
const [createTitle, setCreateTitle] = useState("Place Order");
|
||||||
const [refreshKey, setRefreshKey] = useState(0);
|
const [refreshKey, setRefreshKey] = useState(0);
|
||||||
const [activeActivity, setActiveActivity] = useState<{ id: string; name: string } | null>(null);
|
const [activeActivity, setActiveActivity] = useState<{ id: string; name: string } | null>(null);
|
||||||
|
|
||||||
@ -26,7 +27,7 @@ export function OrdersPage() {
|
|||||||
if (id != null) navigate(`/orders/${id}`);
|
if (id != null) navigate(`/orders/${id}`);
|
||||||
}}
|
}}
|
||||||
headerActions={
|
headerActions={
|
||||||
<Button size="sm" variant={'outline'} iconLeft={<Plus size={14} />} onClick={() => setIsCreating(true)}>
|
<Button size="sm" variant={'outline'} iconLeft={<Plus size={14} />} onClick={() => { setIsCreating(true); setCreateTitle("Place Order"); }}>
|
||||||
Place Order
|
Place Order
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
@ -35,7 +36,7 @@ export function OrdersPage() {
|
|||||||
<Modal
|
<Modal
|
||||||
open={isCreating}
|
open={isCreating}
|
||||||
onClose={() => setIsCreating(false)}
|
onClose={() => setIsCreating(false)}
|
||||||
title="Place Order"
|
title={createTitle}
|
||||||
width="md"
|
width="md"
|
||||||
>
|
>
|
||||||
<DynamicForm
|
<DynamicForm
|
||||||
@ -46,6 +47,7 @@ export function OrdersPage() {
|
|||||||
setRefreshKey(k => k + 1);
|
setRefreshKey(k => k + 1);
|
||||||
}}
|
}}
|
||||||
onCancel={() => setIsCreating(false)}
|
onCancel={() => setIsCreating(false)}
|
||||||
|
onActivityChange={(name) => setCreateTitle(name)}
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
@ -75,6 +77,7 @@ export function OrdersPage() {
|
|||||||
setRefreshKey(k => k + 1);
|
setRefreshKey(k => k + 1);
|
||||||
}}
|
}}
|
||||||
onCancel={() => setActiveActivity(null)}
|
onCancel={() => setActiveActivity(null)}
|
||||||
|
onActivityChange={(name) => setActiveActivity(prev => prev ? { ...prev, name } : null)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|||||||
@ -14,7 +14,9 @@ export function StoresPage() {
|
|||||||
const instanceId = params.instanceId ? Number(params.instanceId) : undefined;
|
const instanceId = params.instanceId ? Number(params.instanceId) : undefined;
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [isCreating, setIsCreating] = useState(false);
|
const [isCreating, setIsCreating] = useState(false);
|
||||||
|
const [createTitle, setCreateTitle] = useState("Create Store");
|
||||||
const [editingInstanceId, setEditingInstanceId] = useState<number | string | null>(null);
|
const [editingInstanceId, setEditingInstanceId] = useState<number | string | null>(null);
|
||||||
|
const [editTitle, setEditTitle] = useState("Edit Store");
|
||||||
const [refreshKey, setRefreshKey] = useState(0);
|
const [refreshKey, setRefreshKey] = useState(0);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -26,17 +28,20 @@ export function StoresPage() {
|
|||||||
if (id != null) navigate(`/stores/${id}`);
|
if (id != null) navigate(`/stores/${id}`);
|
||||||
}}
|
}}
|
||||||
headerActions={
|
headerActions={
|
||||||
<Button size="sm" iconLeft={<Plus size={14} />} onClick={() => setIsCreating(true)}>
|
<Button size="sm" iconLeft={<Plus size={14} />} onClick={() => { setIsCreating(true); setCreateTitle("Create Store"); }}>
|
||||||
Create Store
|
Create Store
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
onEditRow={(row) => setEditingInstanceId(row.instance_id as string | number)}
|
onEditRow={(row) => {
|
||||||
|
setEditingInstanceId(row.instance_id as string | number);
|
||||||
|
setEditTitle("Edit Store");
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
open={isCreating}
|
open={isCreating}
|
||||||
onClose={() => setIsCreating(false)}
|
onClose={() => setIsCreating(false)}
|
||||||
title="Create Store"
|
title={createTitle}
|
||||||
width="md"
|
width="md"
|
||||||
>
|
>
|
||||||
<DynamicForm
|
<DynamicForm
|
||||||
@ -47,13 +52,14 @@ export function StoresPage() {
|
|||||||
setRefreshKey(k => k + 1);
|
setRefreshKey(k => k + 1);
|
||||||
}}
|
}}
|
||||||
onCancel={() => setIsCreating(false)}
|
onCancel={() => setIsCreating(false)}
|
||||||
|
onActivityChange={(name) => setCreateTitle(name)}
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
open={editingInstanceId != null}
|
open={editingInstanceId != null}
|
||||||
onClose={() => setEditingInstanceId(null)}
|
onClose={() => setEditingInstanceId(null)}
|
||||||
title="Edit Store"
|
title={editTitle}
|
||||||
width="md"
|
width="md"
|
||||||
>
|
>
|
||||||
{editingInstanceId != null && (
|
{editingInstanceId != null && (
|
||||||
@ -66,6 +72,7 @@ export function StoresPage() {
|
|||||||
setRefreshKey(k => k + 1);
|
setRefreshKey(k => k + 1);
|
||||||
}}
|
}}
|
||||||
onCancel={() => setEditingInstanceId(null)}
|
onCancel={() => setEditingInstanceId(null)}
|
||||||
|
onActivityChange={(name) => setEditTitle(name)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|||||||
@ -192,7 +192,7 @@
|
|||||||
/* root variable */
|
/* root variable */
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--primary-color: #0058be;
|
--primary-color: #1d4ed8;
|
||||||
--secondary-color: #f9fafb;
|
--secondary-color: #f9fafb;
|
||||||
--font-color: #10182b;
|
--font-color: #10182b;
|
||||||
--font-family: "IBM Plex Sans", serif;
|
--font-family: "IBM Plex Sans", serif;
|
||||||
@ -257,7 +257,8 @@
|
|||||||
--block-padding: 20px;
|
--block-padding: 20px;
|
||||||
--block-radius: 20px;
|
--block-radius: 20px;
|
||||||
--block-border: #e5e7eb;
|
--block-border: #e5e7eb;
|
||||||
--block-shadow: 0px 4px 8px 0px rgba(228, 231, 236, 0.3);
|
/* --block-shadow: 0px 4px 8px 0px rgba(228, 231, 236, 0.3); */
|
||||||
|
--block-shadow: 0 1px 3px 0 var(--tw-shadow-color, #0000001a), 0 1px 2px -1px var(--tw-shadow-color, #0000001a);
|
||||||
|
|
||||||
/* button or input */
|
/* button or input */
|
||||||
--button-radius: 8px;
|
--button-radius: 8px;
|
||||||
@ -327,7 +328,7 @@
|
|||||||
--z-shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
|
--z-shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||||
--z-shadow-lg: 0 10px 20px rgba(0, 0, 0, 0.15);
|
--z-shadow-lg: 0 10px 20px rgba(0, 0, 0, 0.15);
|
||||||
|
|
||||||
--z-font-size-xs: 8px;
|
--z-font-size-xs: 10px;
|
||||||
--z-font-size-sm: 12px;
|
--z-font-size-sm: 12px;
|
||||||
--z-font-size-md: 14px;
|
--z-font-size-md: 14px;
|
||||||
--z-font-size-lg: 16px;
|
--z-font-size-lg: 16px;
|
||||||
@ -412,7 +413,7 @@
|
|||||||
--z-bg-warning-400: #dfb400;
|
--z-bg-warning-400: #dfb400;
|
||||||
|
|
||||||
/* Success Colors */
|
/* Success Colors */
|
||||||
--z-bg-success-100: #a6f4c5;
|
--z-bg-success-100: oklch(97.9% .021 166.113);
|
||||||
--z-bg-success-200: #6ce9a6;
|
--z-bg-success-200: #6ce9a6;
|
||||||
--z-bg-success-300: #32d584;
|
--z-bg-success-300: #32d584;
|
||||||
--z-bg-success-400: #12b76a;
|
--z-bg-success-400: #12b76a;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user