made styling the cards stat tiles

This commit is contained in:
suryacp23 2026-07-30 15:28:48 +05:30
parent d1ef7f4838
commit f8c3658243
12 changed files with 63 additions and 126 deletions

1
.env Normal file
View File

@ -0,0 +1 @@
VITE_GOOGLE_MAPS_API_KEY=AIzaSyDMWCLgNVsigqHdgwgdV0yurVhy6n5dnb0

1
.gitignore vendored
View File

@ -25,4 +25,3 @@ dist-ssr
# vite-plugin-pwa dev output
dev-dist
.env

View File

@ -203,7 +203,7 @@ export function DailyLogCard({ row, onPunchOut, isDetailView = false }: { row: R
)}
{isPunchedIn && onPunchOut && (
<div style={{ padding: '16px 6px 0px 6px' }}>
<div>
<Button
variant="danger"
full

View File

@ -61,10 +61,8 @@ export function OrderCard({ row, fields, isDetailView = false }: { row: Record<s
const productsSection = gridVal.length > 0 ? (
<div style={{
marginTop: '16px',
backgroundColor: 'var(--tiles-card-bg)',
border: '1px solid #e2e8f0',
borderRadius: '16px',
padding: '16px'
marginLeft: '-12px',
marginRight: '-12px'
}}>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '16px' }}>
<div style={{ backgroundColor: '#e6f9ed', padding: '6px', borderRadius: '50%' }}>

View File

@ -109,7 +109,10 @@
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.z-card-footer:not(:last-child) {
margin-bottom: 16px;
}
.z-card-footer-item {

View File

@ -154,18 +154,18 @@ export function DailyLogDetail({ instanceId, onBack }: DailyLogDetailProps) {
</div>
</div>
<div className="flex flex-nowrap items-center gap-3 w-full lg:w-auto overflow-x-auto pb-2 min-w-0">
<div className="bg-primary-light-bg border border-primary-light-border rounded-2xl p-3 text-center min-w-[100px] flex-1">
<div className="text-[10px] font-bold text-primary uppercase tracking-wider mb-0.5">PROD. CALLS</div>
<div className="text-[20px] font-extrabold text-primary">{prodCalls}</div>
<div className="grid grid-cols-3 gap-2 w-full lg:w-auto">
<div className="bg-primary-light-bg border border-primary-light-border rounded-2xl p-2 text-center flex flex-col justify-center">
<div className="text-[9px] font-bold text-primary uppercase tracking-wider mb-0.5 whitespace-nowrap overflow-hidden text-ellipsis">PROD. CALLS</div>
<div className="text-[18px] font-extrabold text-primary leading-none mt-0.5">{prodCalls}</div>
</div>
<div className="bg-primary-light-bg border border-primary-light-border rounded-2xl p-3 text-center min-w-[100px] flex-1">
<div className="text-[10px] font-bold text-primary uppercase tracking-wider mb-0.5">NON PROD.</div>
<div className="text-[20px] font-extrabold text-primary">{nonProdCalls}</div>
<div className="bg-primary-light-bg border border-primary-light-border rounded-2xl p-2 text-center flex flex-col justify-center">
<div className="text-[9px] font-bold text-primary uppercase tracking-wider mb-0.5 whitespace-nowrap overflow-hidden text-ellipsis">NON PROD.</div>
<div className="text-[18px] font-extrabold text-primary leading-none mt-0.5">{nonProdCalls}</div>
</div>
<div className="bg-primary-light-bg border border-primary-light-border rounded-2xl p-3 text-center min-w-[110px] flex-1">
<div className="text-[10px] font-bold text-primary uppercase tracking-wider mb-0.5">TOTAL CALLS</div>
<div className="text-sm font-bold text-primary mt-1 whitespace-nowrap overflow-hidden text-ellipsis">
<div className="bg-primary-light-bg border border-primary-light-border rounded-2xl p-2 text-center flex flex-col justify-center">
<div className="text-[9px] font-bold text-primary uppercase tracking-wider mb-0.5 whitespace-nowrap overflow-hidden text-ellipsis">TOTAL CALLS</div>
<div className="text-[18px] font-extrabold text-primary leading-none mt-0.5">
{Number(prodCalls) + Number(nonProdCalls)}
</div>
</div>
@ -173,8 +173,8 @@ export function DailyLogDetail({ instanceId, onBack }: DailyLogDetailProps) {
</div>
<div className="border-t border-slate-100 pt-4">
<div className="flex items-center gap-8 overflow-x-auto pb-2">
<div className="flex items-center gap-3 min-w-max">
<div className="flex flex-col gap-4 pb-2">
<div className="flex items-center gap-3">
<div className="w-8 h-8 rounded-full bg-emerald-50 text-emerald-600 flex items-center justify-center text-xs shrink-0">
<Clock size={16} />
</div>
@ -187,7 +187,7 @@ export function DailyLogDetail({ instanceId, onBack }: DailyLogDetailProps) {
</div>
{checkOutTime && (
<div className="flex items-center gap-3 min-w-max">
<div className="flex items-center gap-3">
<div className="w-8 h-8 rounded-full bg-blue-50 text-blue-600 flex items-center justify-center text-xs shrink-0">
<Clock size={16} />
</div>

View File

@ -47,7 +47,7 @@ export function AnalyticsChart({ data }: AnalyticsChartProps) {
return (
<Card key={chart.chart_uid || idx} title={title} className="shadow-sm">
<div className="h-[320px] w-full mt-4">
<div className="h-[320px] w-full mt-4 [&_.recharts-wrapper]:!outline-none [&_.recharts-surface]:!outline-none [&_*]:!outline-none">
<ResponsiveContainer width="100%" height="100%">
<BarChart data={formattedRows} margin={{ top: 25, right: 10, left: -20, bottom: 0 }}>
<defs>
@ -81,6 +81,7 @@ export function AnalyticsChart({ data }: AnalyticsChartProps) {
fill={`url(#colorGradient-${idx})`}
radius={[8, 8, 0, 0]}
maxBarSize={40}
activeBar={{ stroke: '#cbd5e1', strokeWidth: 1, fill: `url(#colorGradient-${idx})` }}
>
<LabelList dataKey="value" position="top" fill="#475569" fontSize={12} fontWeight="bold" />
</Bar>

View File

@ -39,7 +39,6 @@ export function Select({
}: SelectProps) {
const [isOpen, setIsOpen] = useState(false);
const [searchQuery, setSearchQuery] = useState('');
const [dropdownStyle, setDropdownStyle] = useState<React.CSSProperties>({});
const containerRef = useRef<HTMLDivElement>(null);
const dropdownRef = useRef<HTMLDivElement>(null);
@ -56,39 +55,7 @@ export function Select({
o.label.toLowerCase().includes(searchQuery.toLowerCase())
);
const updatePosition = useCallback(() => {
if (containerRef.current) {
const rect = containerRef.current.getBoundingClientRect();
const dropdownHeight = 260; // Max height approximation
const spaceBelow = window.innerHeight - rect.bottom;
const openUpwards = spaceBelow < dropdownHeight && rect.top > dropdownHeight;
setDropdownStyle({
position: 'fixed',
left: `${rect.left}px`,
minWidth: `${rect.width}px`,
width: 'max-content',
maxWidth: 'min(92vw, 450px)',
zIndex: 999999,
...(openUpwards
? { bottom: `${window.innerHeight - rect.top + 4}px` }
: { top: `${rect.bottom + 4}px` }),
});
}
}, []);
useEffect(() => {
if (isOpen) {
updatePosition();
const handleScrollOrResize = () => updatePosition();
window.addEventListener('resize', handleScrollOrResize);
window.addEventListener('scroll', handleScrollOrResize, true);
return () => {
window.removeEventListener('resize', handleScrollOrResize);
window.removeEventListener('scroll', handleScrollOrResize, true);
};
}
}, [isOpen, updatePosition]);
useEffect(() => {
const handleClickOutside = (e: MouseEvent) => {
@ -117,7 +84,7 @@ export function Select({
};
return (
<div ref={containerRef} className={cn('flex flex-col gap-1.5 font-sans relative w-full', className)}>
<div ref={containerRef} className={cn('flex flex-col gap-1.5 font-sans relative w-full', isOpen ? 'z-50' : 'z-10', className)}>
{label && (
<label className="text-sm font-semibold text-slate-700">
{label}
@ -148,12 +115,11 @@ export function Select({
<ChevronDown size={15} className={cn("transition-transform duration-150 text-faint shrink-0 ml-1", isOpen && "rotate-180")} />
</div>
{/* Portaled Dropdown Menu Overlay (bypasses parent overflow:hidden clipping) */}
{isOpen && !disabled && createPortal(
{/* Dropdown Menu Overlay */}
{isOpen && !disabled && (
<div
ref={dropdownRef}
style={dropdownStyle}
className="bg-card border border-border-default rounded-md shadow-2xl overflow-hidden flex flex-col max-h-64 animate-in fade-in-50 duration-100"
className="absolute left-0 top-[calc(100%+4px)] w-full bg-card border border-border-default rounded-md shadow-2xl overflow-hidden flex flex-col max-h-64 animate-in fade-in-50 duration-100 z-50"
onClick={(e) => e.stopPropagation()}
>
{searchable && (
@ -178,7 +144,7 @@ export function Select({
</div>
)}
<div className="overflow-y-auto flex-1 py-1">
<div className="overflow-y-auto overflow-x-auto flex-1 py-1">
{filteredOptions.length > 0 ? (
filteredOptions.map((opt) => {
const isSelected = opt.value === currentValue;
@ -187,11 +153,11 @@ export function Select({
key={opt.value}
onClick={() => handleSelect(opt.value)}
className={cn(
"px-3 py-2 text-sm flex items-center justify-between cursor-pointer transition-colors",
"px-3 py-2 text-sm flex items-center justify-between cursor-pointer transition-colors w-max min-w-full",
isSelected ? "bg-navy-50/20 text-navy-700 font-semibold" : "hover:bg-black/5 text-foreground"
)}
>
<span className="whitespace-normal break-words leading-tight flex-1">{opt.label}</span>
<span className="whitespace-nowrap leading-tight flex-1 pr-4">{opt.label}</span>
{isSelected && <Check size={14} className="text-navy-600 shrink-0 ml-2" />}
</div>
);
@ -202,8 +168,7 @@ export function Select({
</div>
)}
</div>
</div>,
document.body
</div>
)}
{/* Hidden Native Select for Required/Form Validation */}
@ -262,7 +227,6 @@ export function MultiSelect({
}: MultiSelectProps) {
const [isOpen, setIsOpen] = useState(false);
const [searchQuery, setSearchQuery] = useState('');
const [dropdownStyle, setDropdownStyle] = useState<React.CSSProperties>({});
const [draftValues, setDraftValues] = useState<string[]>(value ?? []);
const containerRef = useRef<HTMLDivElement>(null);
@ -288,39 +252,7 @@ export function MultiSelect({
o.label.toLowerCase().includes(searchQuery.toLowerCase())
);
const updatePosition = useCallback(() => {
if (containerRef.current) {
const rect = containerRef.current.getBoundingClientRect();
const dropdownHeight = 320;
const spaceBelow = window.innerHeight - rect.bottom;
const openUpwards = spaceBelow < dropdownHeight && rect.top > dropdownHeight;
setDropdownStyle({
position: 'fixed',
left: `${rect.left}px`,
minWidth: `${rect.width}px`,
width: 'max-content',
maxWidth: 'min(92vw, 450px)',
zIndex: 999999,
...(openUpwards
? { bottom: `${window.innerHeight - rect.top + 4}px` }
: { top: `${rect.bottom + 4}px` }),
});
}
}, []);
useEffect(() => {
if (isOpen) {
updatePosition();
const handleScrollOrResize = () => updatePosition();
window.addEventListener('resize', handleScrollOrResize);
window.addEventListener('scroll', handleScrollOrResize, true);
return () => {
window.removeEventListener('resize', handleScrollOrResize);
window.removeEventListener('scroll', handleScrollOrResize, true);
};
}
}, [isOpen, updatePosition]);
useEffect(() => {
const handleClickOutside = (e: MouseEvent) => {
@ -363,7 +295,7 @@ export function MultiSelect({
: `${selectedLabels.length} selected`;
return (
<div ref={containerRef} className={cn('flex flex-col gap-1.5 font-sans relative w-full', className)}>
<div ref={containerRef} className={cn('flex flex-col gap-1.5 font-sans relative w-full', isOpen ? 'z-50' : 'z-10', className)}>
{label && (
<label className="text-xs font-bold text-slate-700">
{label}
@ -386,12 +318,11 @@ export function MultiSelect({
<ChevronDown size={15} className={cn("transition-transform duration-150 text-faint shrink-0 ml-1", isOpen && "rotate-180")} />
</div>
{/* Portaled Dropdown Menu Overlay */}
{isOpen && !disabled && createPortal(
{/* Dropdown Menu Overlay */}
{isOpen && !disabled && (
<div
ref={dropdownRef}
style={dropdownStyle}
className="bg-card border border-border-default rounded-md shadow-2xl overflow-hidden flex flex-col max-h-72 animate-in fade-in-50 duration-100 z-[999999]"
className="absolute left-0 top-[calc(100%+4px)] w-full bg-card border border-border-default rounded-md shadow-2xl overflow-hidden flex flex-col max-h-72 animate-in fade-in-50 duration-100 z-50"
onClick={(e) => e.stopPropagation()}
>
{searchable && (
@ -416,7 +347,7 @@ export function MultiSelect({
</div>
)}
<div className="overflow-y-auto flex-1 py-1 min-h-[100px]">
<div className="overflow-y-auto overflow-x-auto flex-1 py-1 min-h-[100px]">
{filteredOptions.length > 0 ? (
filteredOptions.map((opt) => {
const isSelected = draftValues.includes(opt.value);
@ -425,7 +356,7 @@ export function MultiSelect({
key={opt.value}
onClick={() => toggleOption(opt.value)}
className={cn(
"px-3 py-2 text-xs flex items-center gap-2.5 cursor-pointer transition-colors select-none",
"px-3 py-2 text-xs flex items-center gap-2.5 cursor-pointer transition-colors select-none w-max min-w-full",
isSelected ? "bg-navy-50/40 text-navy-900 font-semibold" : "hover:bg-black/5 text-strong"
)}
>
@ -433,9 +364,9 @@ export function MultiSelect({
type="checkbox"
checked={isSelected}
onChange={() => {}}
className="rounded border-slate-300 text-navy-600 focus:ring-navy-500 pointer-events-none h-3.5 w-3.5"
className="shrink-0 rounded border-slate-300 text-navy-600 focus:ring-navy-500 pointer-events-none h-3.5 w-3.5"
/>
<span className="whitespace-normal break-words leading-tight flex-1">{opt.label}</span>
<span className="whitespace-nowrap leading-tight flex-1 pr-4">{opt.label}</span>
</div>
);
})
@ -464,8 +395,7 @@ export function MultiSelect({
Apply
</button>
</div>
</div>,
document.body
</div>
)}
{(hint || error) && (

View File

@ -31,7 +31,7 @@ export function StatsTile({ tile, idx = 0 }: StatsTileProps) {
return (
<div className={`z-stats-tile z-stats-tile--${colorTheme}`}>
<div className="z-stats-icon-wrapper">
<Icon size={26} className="z-stats-icon" strokeWidth={2.5} />
<Icon size={22} className="z-stats-icon" strokeWidth={2.5} />
</div>
<div className="z-stats-content">
<span className="z-stats-label">{displayLabel}</span>

View File

@ -17,7 +17,6 @@
display: flex;
flex-direction: column;
gap: 12px;
margin-bottom: 16px;
}
/* Base Tile */
@ -25,7 +24,7 @@
display: flex;
flex-direction: row;
align-items: center;
padding: 16px 20px;
padding: 12px 16px;
background-color: #FFFBF4;
border-radius: var(--z-border-radius-lg, 12px);
box-shadow: var(--block-shadow);
@ -43,13 +42,13 @@
}
.z-stats-icon-wrapper {
width: 60px;
height: 60px;
border-radius: 14px;
width: 44px;
height: 44px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
margin-right: 16px;
margin-right: 12px;
flex-shrink: 0;
}
@ -88,7 +87,7 @@
}
.z-stats-label {
font-size: 11px;
font-size: 10px;
font-weight: 700;
color: var(--primary-color);
/* subtle gray */
@ -98,7 +97,7 @@
}
.z-stats-value {
font-size: 32px;
font-size: 24px;
font-weight: 800;
line-height: 1.1;
color: var(--primary-color);

View File

@ -187,7 +187,7 @@ export function RecordView({
}, [activeFilters]);
return (
<div className="flex flex-col gap-5 w-full max-w-full overflow-x-hidden">
<div className="flex flex-col gap-4 w-full max-w-full overflow-x-hidden">
{!hideTiles && <StatsTiles tiles={tileValues} />}
{!hideChart && <AnalyticsChart data={chartData} />}

View File

@ -76,11 +76,11 @@ export function AnalyticsPage() {
// Decide chart type based on key
if (chart.key === 'status_overview') {
return (
<div key={chart.chart_uid} className="bg-white rounded-2xl shadow-[0_2px_12px_-4px_rgba(0,0,0,0.08)] border border-slate-100 p-5 flex flex-col gap-4">
<div key={chart.chart_uid} className="bg-[#FFFBF4] rounded-2xl shadow-[0_2px_12px_-4px_rgba(0,0,0,0.08)] border border-slate-200 p-5 flex flex-col gap-4">
<div>
<h2 className="text-[15px] font-bold text-slate-800 tracking-tight">{title}</h2>
</div>
<div className="h-64 w-full">
<div className="h-64 w-full [&_.recharts-wrapper]:!outline-none [&_.recharts-surface]:!outline-none [&_*]:!outline-none">
<ResponsiveContainer width="100%" height="100%">
<PieChart>
<Pie
@ -108,11 +108,11 @@ export function AnalyticsPage() {
}
return (
<div key={chart.chart_uid} className="bg-white rounded-2xl shadow-[0_2px_12px_-4px_rgba(0,0,0,0.08)] border border-slate-100 p-5 flex flex-col gap-4">
<div key={chart.chart_uid} className="bg-[#FFFBF4] rounded-2xl shadow-[0_2px_12px_-4px_rgba(0,0,0,0.08)] border border-slate-200 p-5 flex flex-col gap-4">
<div>
<h2 className="text-[15px] font-bold text-slate-800 tracking-tight">{title}</h2>
</div>
<div className="h-64 w-full">
<div className="h-64 w-full [&_.recharts-wrapper]:!outline-none [&_.recharts-surface]:!outline-none [&_*]:!outline-none">
<ResponsiveContainer width="100%" height="100%">
<BarChart data={data} margin={{ top: 10, right: 10, left: -20, bottom: 0 }}>
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#f1f5f9" />
@ -122,7 +122,13 @@ export function AnalyticsPage() {
cursor={{ fill: '#f8fafc' }}
contentStyle={{ borderRadius: '8px', border: 'none', boxShadow: '0 4px 6px -1px rgb(0 0 0 / 0.1)' }}
/>
<Bar dataKey="value" fill={COLORS[index % COLORS.length]} radius={[4, 4, 0, 0]} barSize={40} />
<Bar
dataKey="value"
fill={COLORS[index % COLORS.length]}
radius={[4, 4, 0, 0]}
barSize={40}
activeBar={{ stroke: '#cbd5e1', strokeWidth: 1, fill: COLORS[index % COLORS.length] }}
/>
</BarChart>
</ResponsiveContainer>
</div>