tile design done
This commit is contained in:
parent
d30fd56d4f
commit
1894b1f9a5
@ -65,7 +65,8 @@ export const ORDER_BOOKING = {
|
|||||||
recordViews: {
|
recordViews: {
|
||||||
ORDERS: '1e424561-9a27-44f5-9b68-64d63296d837',
|
ORDERS: '1e424561-9a27-44f5-9b68-64d63296d837',
|
||||||
CALLS: 'b4d7a710-24e7-416d-885a-31fd1e727aab',
|
CALLS: 'b4d7a710-24e7-416d-885a-31fd1e727aab',
|
||||||
ANALYTICS: 'b4d7a710-24e7-416d-885a-31fd1e727aab'
|
ANALYTICS: 'b4d7a710-24e7-416d-885a-31fd1e727aab',
|
||||||
|
|
||||||
},
|
},
|
||||||
detailViews: {
|
detailViews: {
|
||||||
ORDERS: '0804c6c3-6cf9-4050-94bb-fa48dd5de87d',
|
ORDERS: '0804c6c3-6cf9-4050-94bb-fa48dd5de87d',
|
||||||
|
|||||||
@ -330,6 +330,13 @@ export function DynamicForm({ client, activityId: initialActivityId, instanceId:
|
|||||||
const val = values[f.id];
|
const val = values[f.id];
|
||||||
const isDisabled = schema.field_defaults?.[f.id]?.disabled || f.properties?.disabled;
|
const isDisabled = schema.field_defaults?.[f.id]?.disabled || f.properties?.disabled;
|
||||||
|
|
||||||
|
const isPlaceOrder = currentActivityId === ORDER_BOOKING.activities.PLACE_ORDER.uid;
|
||||||
|
const isOrderId = f.name.toLowerCase() === 'order id' || f.id.toLowerCase().includes('order_id') || f.mapped_workflow_field === 'order_id';
|
||||||
|
|
||||||
|
if (isPlaceOrder && isOrderId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const renderField = () => {
|
const renderField = () => {
|
||||||
if (type === 'wf_lookup') {
|
if (type === 'wf_lookup') {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -29,9 +29,10 @@ export function FileInput({
|
|||||||
const files = Array.isArray(value) ? value : (value instanceof File ? [value] : []);
|
const files = Array.isArray(value) ? value : (value instanceof File ? [value] : []);
|
||||||
|
|
||||||
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
if (e.target.files) {
|
if (e.target.files && e.target.files.length > 0) {
|
||||||
const newFiles = Array.from(e.target.files);
|
const newFiles = Array.from(e.target.files);
|
||||||
onChange([...files, ...newFiles]);
|
// Since we only want 1 file, replace the existing one instead of appending
|
||||||
|
onChange(newFiles);
|
||||||
}
|
}
|
||||||
// reset input so the same file can be selected again if needed
|
// reset input so the same file can be selected again if needed
|
||||||
e.target.value = '';
|
e.target.value = '';
|
||||||
@ -53,7 +54,6 @@ export function FileInput({
|
|||||||
<div className="flex flex-col gap-3">
|
<div className="flex flex-col gap-3">
|
||||||
<input
|
<input
|
||||||
type="file"
|
type="file"
|
||||||
multiple
|
|
||||||
required={required && files.length === 0}
|
required={required && files.length === 0}
|
||||||
accept={type === 'image' ? 'image/*' : undefined}
|
accept={type === 'image' ? 'image/*' : undefined}
|
||||||
onChange={handleFileChange}
|
onChange={handleFileChange}
|
||||||
@ -63,7 +63,7 @@ export function FileInput({
|
|||||||
{files.length > 0 && (
|
{files.length > 0 && (
|
||||||
<div className="flex flex-wrap gap-3">
|
<div className="flex flex-wrap gap-3">
|
||||||
{files.map((file, i) => (
|
{files.map((file, i) => (
|
||||||
<div key={i} className="relative w-24 h-24 rounded-md overflow-hidden border border-border-default shadow-sm bg-slate-50 flex-shrink-0 group">
|
<div key={i} className="relative w-24 h-24 rounded-md overflow-hidden border border-border-default shadow-sm bg-slate-50 flex-shrink-0">
|
||||||
{type === 'image' ? (
|
{type === 'image' ? (
|
||||||
<ImagePreview file={file} />
|
<ImagePreview file={file} />
|
||||||
) : (
|
) : (
|
||||||
@ -74,7 +74,7 @@ export function FileInput({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => removeFile(i)}
|
onClick={() => removeFile(i)}
|
||||||
className="absolute top-1 right-1 bg-ruby-600/90 hover:bg-ruby-700 text-white rounded-full w-6 h-6 flex items-center justify-center text-xs opacity-0 group-hover:opacity-100 transition-opacity"
|
className="absolute top-1 right-1 bg-ruby-600/90 hover:bg-ruby-700 text-white rounded-full w-6 h-6 flex items-center justify-center text-xs shadow-md"
|
||||||
title="Remove file"
|
title="Remove file"
|
||||||
>
|
>
|
||||||
✕
|
✕
|
||||||
|
|||||||
@ -152,6 +152,8 @@ export function SmartGridField({
|
|||||||
setEditingIdx(null);
|
setEditingIdx(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isPotentialMining = label.toLowerCase().includes('potential');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-2 font-sans border border-border-default rounded-md p-4 bg-slate-50">
|
<div className="flex flex-col gap-2 font-sans border border-border-default rounded-md p-4 bg-slate-50">
|
||||||
<span className="text-sm font-semibold text-strong mb-2">{label}</span>
|
<span className="text-sm font-semibold text-strong mb-2">{label}</span>
|
||||||
@ -163,12 +165,16 @@ export function SmartGridField({
|
|||||||
{value.map((row, i) => {
|
{value.map((row, i) => {
|
||||||
let productName = 'Unknown Product';
|
let productName = 'Unknown Product';
|
||||||
let bags = '0';
|
let bags = '0';
|
||||||
|
let hasBagsCol = false;
|
||||||
|
let diffVal: number | null = null;
|
||||||
|
let reasonStr = '';
|
||||||
|
|
||||||
visibleColumns.forEach(col => {
|
visibleColumns.forEach(col => {
|
||||||
const isName = col.id.toLowerCase().includes('name') || col.name.toLowerCase().includes('name') || (col.id.toLowerCase().includes('category') && productName === 'Unknown Product');
|
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');
|
const isBags = col.id.toLowerCase().includes('bags') || col.name.toLowerCase().includes('bags') || col.id.toLowerCase().includes('quantity');
|
||||||
|
const isDiff = col.id.toLowerCase().includes('diff') || col.name.toLowerCase().includes('diff');
|
||||||
|
const isReason = col.id.toLowerCase().includes('reason') || col.name.toLowerCase().includes('reason');
|
||||||
|
|
||||||
if (isName || isBags) {
|
|
||||||
const val = row[col.id];
|
const val = row[col.id];
|
||||||
let displayVal = String(val ?? '-');
|
let displayVal = String(val ?? '-');
|
||||||
|
|
||||||
@ -180,15 +186,41 @@ export function SmartGridField({
|
|||||||
if (isName && (productName === 'Unknown Product' || col.id.toLowerCase().includes('name'))) {
|
if (isName && (productName === 'Unknown Product' || col.id.toLowerCase().includes('name'))) {
|
||||||
productName = displayVal;
|
productName = displayVal;
|
||||||
}
|
}
|
||||||
if (isBags) bags = displayVal;
|
if (isBags) {
|
||||||
|
bags = displayVal;
|
||||||
|
hasBagsCol = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPotentialMining && isDiff && val != null && val !== '') {
|
||||||
|
diffVal = Number(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPotentialMining && isReason && val && displayVal !== 'Select...' && displayVal !== '-') {
|
||||||
|
reasonStr = displayVal;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={i} className="bg-slate-50 border border-slate-200 rounded-xl p-3 shadow-sm flex items-center justify-between">
|
<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">
|
<div className="flex flex-col min-w-0 pr-4 gap-1.5">
|
||||||
<span className="font-bold text-slate-800 text-[14px] truncate">{productName}</span>
|
<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 className="flex flex-wrap items-center gap-2">
|
||||||
|
{(!isPotentialMining || hasBagsCol) && (
|
||||||
|
<span className="font-extrabold text-indigo-600 text-[13px]">{bags} Bags</span>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isPotentialMining && diffVal !== null && !isNaN(diffVal) && diffVal !== 0 && (
|
||||||
|
<span className={`text-[11px] font-bold px-2 py-0.5 rounded-md ${diffVal > 0 ? 'bg-emerald-100 text-emerald-700' : 'bg-red-100 text-red-700'}`}>
|
||||||
|
{Math.abs(diffVal)} {diffVal > 0 ? 'Surplus' : 'Less'}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isPotentialMining && reasonStr && (
|
||||||
|
<span className="text-[11px] font-medium bg-slate-200 text-slate-700 px-2 py-0.5 rounded-md truncate max-w-[120px]" title={reasonStr}>
|
||||||
|
{reasonStr}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-1.5 shrink-0">
|
<div className="flex items-center gap-1.5 shrink-0">
|
||||||
|
|||||||
@ -6,7 +6,8 @@ import {
|
|||||||
YAxis,
|
YAxis,
|
||||||
CartesianGrid,
|
CartesianGrid,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
ResponsiveContainer
|
ResponsiveContainer,
|
||||||
|
LabelList
|
||||||
} from 'recharts';
|
} from 'recharts';
|
||||||
|
|
||||||
export interface ChartRow {
|
export interface ChartRow {
|
||||||
@ -28,7 +29,6 @@ export function AnalyticsChart({ data }: AnalyticsChartProps) {
|
|||||||
if (!data || !Array.isArray(data) || data.length === 0) return null;
|
if (!data || !Array.isArray(data) || data.length === 0) return null;
|
||||||
|
|
||||||
const charts = data as ChartConfig[];
|
const charts = data as ChartConfig[];
|
||||||
const colors = ['#3B82F6', '#10B981', '#F59E0B', '#8B5CF6', '#EC4899', '#14B8A6'];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 w-full">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 w-full">
|
||||||
@ -46,10 +46,16 @@ export function AnalyticsChart({ data }: AnalyticsChartProps) {
|
|||||||
if (formattedRows.length === 0) return null;
|
if (formattedRows.length === 0) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card key={chart.chart_uid || idx} title={title} className="shadow-sm border-t-4 border-t-indigo-500">
|
<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">
|
||||||
<ResponsiveContainer width="100%" height="100%">
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
<BarChart data={formattedRows} margin={{ top: 10, right: 10, left: -20, bottom: 0 }}>
|
<BarChart data={formattedRows} margin={{ top: 25, right: 10, left: -20, bottom: 0 }}>
|
||||||
|
<defs>
|
||||||
|
<linearGradient id={`colorGradient-${idx}`} x1="0" y1="0" x2="0" y2="1">
|
||||||
|
<stop offset="0%" stopColor="var(--primary-color)" stopOpacity={1} />
|
||||||
|
<stop offset="100%" stopColor="var(--primary-color-300)" stopOpacity={1} />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#E2E8F0" />
|
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#E2E8F0" />
|
||||||
<XAxis
|
<XAxis
|
||||||
dataKey="dimension"
|
dataKey="dimension"
|
||||||
@ -72,10 +78,12 @@ export function AnalyticsChart({ data }: AnalyticsChartProps) {
|
|||||||
<Bar
|
<Bar
|
||||||
dataKey="value"
|
dataKey="value"
|
||||||
name="Value"
|
name="Value"
|
||||||
fill={colors[idx % colors.length]}
|
fill={`url(#colorGradient-${idx})`}
|
||||||
radius={[4, 4, 0, 0]}
|
radius={[8, 8, 0, 0]}
|
||||||
maxBarSize={40}
|
maxBarSize={40}
|
||||||
/>
|
>
|
||||||
|
<LabelList dataKey="value" position="top" fill="#475569" fontSize={12} fontWeight="bold" />
|
||||||
|
</Bar>
|
||||||
</BarChart>
|
</BarChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -28,7 +28,7 @@ export function Input({
|
|||||||
return (
|
return (
|
||||||
<label className={cn('flex flex-col gap-1.5 font-sans', disabled && 'cursor-not-allowed', className)}>
|
<label className={cn('flex flex-col gap-1.5 font-sans', disabled && 'cursor-not-allowed', className)}>
|
||||||
{label && (
|
{label && (
|
||||||
<span className="text-sm font-medium text-muted">
|
<span className="text-sm font-semibold text-slate-700">
|
||||||
{label}
|
{label}
|
||||||
{required && <span className="text-ruby-600"> *</span>}
|
{required && <span className="text-ruby-600"> *</span>}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@ -53,7 +53,7 @@ export function Modal({ open, onClose, title, subtitle, width = 'md', actions, c
|
|||||||
aria-modal="true"
|
aria-modal="true"
|
||||||
className={cn(
|
className={cn(
|
||||||
'relative w-full bg-card rounded-t-2xl sm:rounded-lg border border-border-subtle shadow-lg sm:my-auto',
|
'relative w-full bg-card rounded-t-2xl sm:rounded-lg border border-border-subtle shadow-lg sm:my-auto',
|
||||||
'flex flex-col max-h-[92vh] sm:max-h-[85vh]',
|
'flex flex-col max-h-[70vh]',
|
||||||
WIDTH[width],
|
WIDTH[width],
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -21,7 +21,7 @@ export function Select({ label, hint, options = [], required, className, ...rest
|
|||||||
return (
|
return (
|
||||||
<label className={cn('flex flex-col gap-1.5 font-sans', className)}>
|
<label className={cn('flex flex-col gap-1.5 font-sans', className)}>
|
||||||
{label && (
|
{label && (
|
||||||
<span className="text-sm font-medium text-muted">
|
<span className="text-sm font-semibold text-slate-700">
|
||||||
{label}
|
{label}
|
||||||
{required && <span className="text-ruby-600"> *</span>}
|
{required && <span className="text-ruby-600"> *</span>}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { TileItem } from "../../api/types";
|
import type { TileItem } from "../../api/types";
|
||||||
import { Phone, TrendingUp, Activity, BarChart2, ShoppingCart, ShoppingBag, Scale } from 'lucide-react';
|
import { Phone, TrendingUp, Activity, BarChart2, ShoppingCart, ShoppingBag, Scale, Store, Map } from 'lucide-react';
|
||||||
import './reusable.css';
|
import './reusable.css';
|
||||||
import '../../styles/styles.css';
|
import '../../styles/styles.css';
|
||||||
|
|
||||||
@ -15,27 +15,30 @@ export function StatsTile({ tile, idx = 0 }: StatsTileProps) {
|
|||||||
|
|
||||||
const lowerKey = String(tile.key).toLowerCase();
|
const lowerKey = String(tile.key).toLowerCase();
|
||||||
|
|
||||||
// Try to pick a relevant icon
|
// Try to pick a relevant icon and color theme
|
||||||
let Icon = BarChart2;
|
let Icon = BarChart2;
|
||||||
if (lowerKey.includes('call') || lowerKey.includes('total')) Icon = Phone;
|
let colorTheme = 'blue';
|
||||||
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');
|
if (lowerKey.includes('call') || lowerKey.includes('total')) { Icon = Phone; colorTheme = 'blue'; }
|
||||||
|
if (lowerKey.includes('productive')) { Icon = TrendingUp; colorTheme = 'green'; }
|
||||||
|
if (lowerKey.includes('order') || lowerKey.includes('cart')) { Icon = ShoppingCart; colorTheme = 'blue'; }
|
||||||
|
if (lowerKey.includes('bag')) { Icon = ShoppingBag; colorTheme = 'green'; }
|
||||||
|
if (lowerKey.includes('kg') || lowerKey.includes('weight')) { Icon = Scale; colorTheme = 'purple'; }
|
||||||
|
if (lowerKey.includes('active')) { Icon = Activity; colorTheme = 'blue'; }
|
||||||
|
if (lowerKey.includes('store')) { Icon = Store; colorTheme = 'blue'; }
|
||||||
|
if (lowerKey.includes('area') || lowerKey.includes('region')) { Icon = Map; colorTheme = 'purple'; }
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`z-stats-tile z-stats-tile--secondary ${isDanger ? 'z-stats-tile--danger' : ''}`}>
|
<div className={`z-stats-tile z-stats-tile--${colorTheme}`}>
|
||||||
<div className="z-stats-header">
|
<div className="z-stats-icon-wrapper">
|
||||||
<span className={`z-stats-label`}>{displayLabel}</span>
|
<Icon size={26} className="z-stats-icon" strokeWidth={2.5} />
|
||||||
<Icon size={16} className={`z-stats-icon ${isDanger ? 'z-stats-icon--danger' : ''}`} />
|
|
||||||
</div>
|
</div>
|
||||||
|
<div className="z-stats-content">
|
||||||
<p className={`z-stats-value nums ${isDanger ? 'z-stats-value--danger' : ''}`}>
|
<span className="z-stats-label">{displayLabel}</span>
|
||||||
|
<p className="z-stats-value">
|
||||||
{(tile.value as React.ReactNode) ?? "-"}
|
{(tile.value as React.ReactNode) ?? "-"}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,7 +20,7 @@ export function Textarea({
|
|||||||
return (
|
return (
|
||||||
<label className={cn('flex flex-col gap-1.5 font-sans', disabled && 'cursor-not-allowed', className)}>
|
<label className={cn('flex flex-col gap-1.5 font-sans', disabled && 'cursor-not-allowed', className)}>
|
||||||
{label && (
|
{label && (
|
||||||
<span className="text-sm font-medium text-muted">
|
<span className="text-sm font-semibold text-slate-700">
|
||||||
{label}
|
{label}
|
||||||
{required && <span className="text-ruby-600"> *</span>}
|
{required && <span className="text-ruby-600"> *</span>}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@ -17,99 +17,86 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
padding-bottom: 8px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Base Tile */
|
/* Base Tile */
|
||||||
.z-stats-tile {
|
.z-stats-tile {
|
||||||
flex: 0 0 auto;
|
display: flex;
|
||||||
width: 100%;
|
flex-direction: row;
|
||||||
height: 100px;
|
align-items: center;
|
||||||
border-radius: var(--z-border-radius-lg);
|
padding: 16px 20px;
|
||||||
padding: 16px;
|
background: var(--z-bg-neutral-200);
|
||||||
|
border-radius: var(--z-border-radius-lg, 12px);
|
||||||
|
box-shadow: var(--block-shadow, 0 7px 7px rgba(0, 0, 0, 0.1));
|
||||||
|
border: 1px solid var(--z-neutral-500);
|
||||||
|
font-family: var(--font-sans, system-ui, -apple-system, sans-serif);
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-stats-tile:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-stats-icon-wrapper {
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
border-radius: 14px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-right: 16px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Color Themes */
|
||||||
|
.z-stats-tile--blue .z-stats-icon-wrapper {
|
||||||
|
background-color: #e0ebff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-stats-tile--blue .z-stats-icon {
|
||||||
|
color: var(--z-text-primary-400, #0055ff);
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-stats-tile--green .z-stats-icon-wrapper {
|
||||||
|
background-color: #dcfce7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-stats-tile--green .z-stats-icon {
|
||||||
|
color: #00b050;
|
||||||
|
/* adjusted for the green from the image */
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-stats-tile--purple .z-stats-icon-wrapper {
|
||||||
|
background-color: #f3e8ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-stats-tile--purple .z-stats-icon {
|
||||||
|
color: #6a35ff;
|
||||||
|
/* adjusted for purple from image */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Content */
|
||||||
|
.z-stats-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
justify-content: center;
|
||||||
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);
|
|
||||||
border-left: 4px solid var(--z-text-primary-400);
|
|
||||||
color: var(--z-text-neutral-900);
|
|
||||||
}
|
|
||||||
|
|
||||||
.z-stats-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.z-stats-label {
|
.z-stats-label {
|
||||||
font-size: var(--z-font-size-xs);
|
font-size: 11px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
color: #8892a0;
|
||||||
|
/* subtle gray */
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
letter-spacing: 0.05em;
|
letter-spacing: 0.04em;
|
||||||
white-space: nowrap;
|
margin-bottom: 2px;
|
||||||
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 {
|
.z-stats-value {
|
||||||
font-size: 30px;
|
font-size: 32px;
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
line-height: 1.1;
|
line-height: 1.1;
|
||||||
font-family: var(--font-numeric);
|
color: var(--z-text-primary-400, #0055ff);
|
||||||
}
|
font-family: var(--font-numeric, inherit);
|
||||||
|
letter-spacing: -0.02em;
|
||||||
.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-tile--secondary.z-stats-tile--danger {
|
|
||||||
border-left-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;
|
|
||||||
}
|
}
|
||||||
@ -27,7 +27,7 @@ export function ConsoleLayout() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-[100dvh] overflow-hidden bg-app flex flex-col">
|
<div className="h-[100dvh] overflow-hidden bg-app flex flex-col">
|
||||||
<header className="print:hidden sticky top-0 z-20 shrink-0 flex items-center justify-between gap-3 px-4 h-14 pt-[env(safe-area-inset-top)] top-navbar">
|
<header className="print:hidden sticky top-0 z-20 shrink-0 flex items-center justify-between gap-3 px-4 h-14 pt-[env(safe-area-inset-top)] top-navbar shadow-md">
|
||||||
<div className="flex items-center gap-3 flex-1">
|
<div className="flex items-center gap-3 flex-1">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@ -156,9 +156,9 @@
|
|||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sunrise focus ring on inputs/selects. */
|
/* Primary focus ring on inputs/selects. */
|
||||||
.focus-ring:focus-within {
|
.focus-ring:focus-within {
|
||||||
border-color: var(--sunrise-500);
|
border-color: var(--primary-color);
|
||||||
box-shadow: var(--shadow-focus);
|
box-shadow: var(--shadow-focus);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -193,6 +193,7 @@
|
|||||||
|
|
||||||
:root {
|
:root {
|
||||||
--primary-color: #1d4ed8;
|
--primary-color: #1d4ed8;
|
||||||
|
--primary-color-300: #93c5fd;
|
||||||
--secondary-color: #f9fafb;
|
--secondary-color: #f9fafb;
|
||||||
--font-color: #10182b;
|
--font-color: #10182b;
|
||||||
--font-family: "IBM Plex Sans", serif;
|
--font-family: "IBM Plex Sans", serif;
|
||||||
|
|||||||
@ -97,7 +97,7 @@
|
|||||||
--action-primary-hover: var(--sunrise-600);
|
--action-primary-hover: var(--sunrise-600);
|
||||||
--action-navy: var(--navy-900);
|
--action-navy: var(--navy-900);
|
||||||
--action-navy-hover: var(--navy-800);
|
--action-navy-hover: var(--navy-800);
|
||||||
--focus-ring: rgba(242,107,58,0.45);
|
--focus-ring: rgba(29,78,216,0.3); /* Matches primary-color #1d4ed8 with opacity */
|
||||||
|
|
||||||
/* Status */
|
/* Status */
|
||||||
--status-success: var(--emerald-600);
|
--status-success: var(--emerald-600);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user