m13
This commit is contained in:
parent
60917e85c0
commit
26fe2eb52e
34 changed files with 2170 additions and 1544 deletions
1040
DESIGN_SYSTEM.md
1040
DESIGN_SYSTEM.md
File diff suppressed because it is too large
Load diff
|
|
@ -5,8 +5,9 @@
|
|||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════════════
|
||||
OBSIDIAN LUXE - Premium Dark-First CRM Design System
|
||||
A sophisticated aesthetic with vivid accents and glass-morphism effects
|
||||
BOTTLECRM DESIGN SYSTEM - Orange Primary
|
||||
A modern CRM design system with warm orange accents and professional styling
|
||||
Based on DESIGN_SYSTEM.md v1.2
|
||||
═══════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
:root {
|
||||
|
|
@ -14,121 +15,249 @@
|
|||
--radius: 0.625rem;
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────────────────
|
||||
LIGHT MODE - Clean, airy with subtle warmth
|
||||
BRAND COLORS
|
||||
───────────────────────────────────────────────────────────────────────── */
|
||||
--color-brand-primary: #EA580C;
|
||||
--color-brand-dark: #404346;
|
||||
--color-brand-white: #FFFFFF;
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────────────────
|
||||
LIGHT MODE - Clean, professional with warm orange accents
|
||||
───────────────────────────────────────────────────────────────────────── */
|
||||
|
||||
/* Core surfaces */
|
||||
--background: oklch(0.985 0.002 250);
|
||||
--background: oklch(0.985 0.002 60);
|
||||
--foreground: oklch(0.15 0.02 260);
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.15 0.02 260);
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.15 0.02 260);
|
||||
|
||||
/* Primary - Deep teal */
|
||||
--primary: oklch(0.55 0.15 195);
|
||||
/* Primary - Orange */
|
||||
--primary: oklch(0.58 0.2 30);
|
||||
--primary-foreground: oklch(0.98 0 0);
|
||||
|
||||
/* Secondary */
|
||||
--secondary: oklch(0.96 0.01 250);
|
||||
--secondary: oklch(0.96 0.01 60);
|
||||
--secondary-foreground: oklch(0.25 0.02 260);
|
||||
|
||||
/* Muted */
|
||||
--muted: oklch(0.96 0.005 250);
|
||||
--muted: oklch(0.96 0.005 60);
|
||||
--muted-foreground: oklch(0.45 0.02 260);
|
||||
|
||||
/* Accent - Warm amber */
|
||||
--accent: oklch(0.96 0.02 80);
|
||||
--accent-foreground: oklch(0.25 0.05 80);
|
||||
/* Accent - Light orange tint */
|
||||
--accent: oklch(0.98 0.02 60);
|
||||
--accent-foreground: oklch(0.25 0.05 30);
|
||||
|
||||
/* Destructive */
|
||||
--destructive: oklch(0.55 0.22 25);
|
||||
|
||||
/* Borders & inputs */
|
||||
--border: oklch(0.91 0.005 250);
|
||||
--input: oklch(0.91 0.005 250);
|
||||
--ring: oklch(0.55 0.15 195);
|
||||
--border: oklch(0.91 0.005 60);
|
||||
--input: oklch(0.91 0.005 60);
|
||||
--ring: oklch(0.58 0.2 30);
|
||||
|
||||
/* Chart colors - Vibrant palette */
|
||||
--chart-1: oklch(0.65 0.18 195);
|
||||
--chart-2: oklch(0.7 0.16 145);
|
||||
--chart-3: oklch(0.75 0.15 80);
|
||||
--chart-4: oklch(0.6 0.2 280);
|
||||
--chart-5: oklch(0.65 0.2 350);
|
||||
/* Chart colors - Sequential (Orange-based) */
|
||||
--chart-1: #EA580C;
|
||||
--chart-2: #F97316;
|
||||
--chart-3: #FB923C;
|
||||
--chart-4: #FDBA74;
|
||||
--chart-5: #FED7AA;
|
||||
|
||||
/* Sidebar - HubSpot Style Light */
|
||||
/* Sidebar */
|
||||
--sidebar: oklch(0.995 0 0);
|
||||
--sidebar-foreground: oklch(0.25 0.02 250);
|
||||
--sidebar-primary: oklch(0.62 0.2 25);
|
||||
--sidebar-foreground: oklch(0.25 0.02 60);
|
||||
--sidebar-primary: #EA580C;
|
||||
--sidebar-primary-foreground: oklch(0.995 0 0);
|
||||
--sidebar-accent: oklch(0.97 0.005 250);
|
||||
--sidebar-accent-foreground: oklch(0.2 0.02 250);
|
||||
--sidebar-border: oklch(0.94 0.005 250);
|
||||
--sidebar-ring: oklch(0.62 0.2 25);
|
||||
|
||||
/* HubSpot Brand Colors */
|
||||
--hubspot-orange: #ff7a59;
|
||||
--hubspot-orange-hover: #ff5c35;
|
||||
--hubspot-orange-light: #fff1ee;
|
||||
--hubspot-navy: #2d3e50;
|
||||
--hubspot-navy-light: #516f90;
|
||||
--hubspot-teal: #00bda5;
|
||||
--hubspot-teal-light: #e5f8f6;
|
||||
--sidebar-accent: oklch(0.97 0.01 60);
|
||||
--sidebar-accent-foreground: oklch(0.2 0.02 60);
|
||||
--sidebar-border: oklch(0.94 0.005 60);
|
||||
--sidebar-ring: #EA580C;
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────────────────
|
||||
SEMANTIC LAYERS
|
||||
PRIMARY COLOR SCALE
|
||||
───────────────────────────────────────────────────────────────────────── */
|
||||
|
||||
/* Background Layers */
|
||||
--bg-base: #fafbfc;
|
||||
--bg-subtle: #f4f6f8;
|
||||
--bg-muted: #eef1f4;
|
||||
--bg-emphasis: #e4e8ec;
|
||||
|
||||
/* Border Colors */
|
||||
--border-default: #e2e6eb;
|
||||
--border-strong: #c9d0d8;
|
||||
--border-subtle: #f0f2f5;
|
||||
|
||||
/* Text Colors */
|
||||
--text-primary: #0f172a;
|
||||
--text-secondary: #475569;
|
||||
--text-tertiary: #94a3b8;
|
||||
--text-inverse: #ffffff;
|
||||
|
||||
/* Accent Colors - Teal/Cyan primary */
|
||||
--accent-primary: #0891b2;
|
||||
--accent-primary-hover: #0e7490;
|
||||
--accent-primary-subtle: #ecfeff;
|
||||
|
||||
/* Secondary Accent - Warm Amber */
|
||||
--accent-secondary: #f59e0b;
|
||||
--accent-secondary-hover: #d97706;
|
||||
--accent-secondary-subtle: #fffbeb;
|
||||
|
||||
/* Status Colors */
|
||||
--status-success: #059669;
|
||||
--status-success-bg: #ecfdf5;
|
||||
--status-warning: #d97706;
|
||||
--status-warning-bg: #fffbeb;
|
||||
--status-danger: #dc2626;
|
||||
--status-danger-bg: #fef2f2;
|
||||
--status-info: #0891b2;
|
||||
--status-info-bg: #ecfeff;
|
||||
|
||||
/* Stage Colors (Pipeline) - Refined palette */
|
||||
--stage-prospecting: #64748b;
|
||||
--stage-qualified: #0891b2;
|
||||
--stage-proposal: #8b5cf6;
|
||||
--stage-negotiation: #f59e0b;
|
||||
--stage-won: #059669;
|
||||
--stage-lost: #dc2626;
|
||||
--color-primary-default: #EA580C;
|
||||
--color-primary-hover: #C2410C;
|
||||
--color-primary-active: #9A3412;
|
||||
--color-primary-light: #FFF7ED;
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────────────────
|
||||
SPACING & LAYOUT
|
||||
SEMANTIC COLORS
|
||||
───────────────────────────────────────────────────────────────────────── */
|
||||
|
||||
/* Negative/Error */
|
||||
--color-negative-default: #DC3545;
|
||||
--color-negative-hover: #C82333;
|
||||
--color-negative-light: #FDEAEA;
|
||||
|
||||
/* Warning */
|
||||
--color-warning-default: #FFC107;
|
||||
--color-warning-dark: #E0A800;
|
||||
--color-warning-light: #FFF8E1;
|
||||
|
||||
/* Success */
|
||||
--color-success-default: #28A745;
|
||||
--color-success-light: #E8F5E9;
|
||||
|
||||
/* Info */
|
||||
--color-info-default: #17A2B8;
|
||||
--color-info-light: #E3F2FD;
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────────────────
|
||||
GREY SCALE
|
||||
───────────────────────────────────────────────────────────────────────── */
|
||||
--color-grey-50: #FAFAFA;
|
||||
--color-grey-100: #F5F5F5;
|
||||
--color-grey-200: #EEEEEE;
|
||||
--color-grey-300: #E0E0E0;
|
||||
--color-grey-400: #BDBDBD;
|
||||
--color-grey-500: #9E9E9E;
|
||||
--color-grey-600: #757575;
|
||||
--color-grey-700: #616161;
|
||||
--color-grey-800: #424242;
|
||||
--color-grey-900: #212121;
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────────────────
|
||||
SURFACE COLORS
|
||||
───────────────────────────────────────────────────────────────────────── */
|
||||
--surface-default: #FFFFFF;
|
||||
--surface-raised: #FFFFFF;
|
||||
--surface-sunken: #F5F5F5;
|
||||
--surface-overlay: #FFFFFF;
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────────────────
|
||||
TEXT COLORS
|
||||
───────────────────────────────────────────────────────────────────────── */
|
||||
--text-primary: #212121;
|
||||
--text-secondary: #616161;
|
||||
--text-tertiary: #9E9E9E;
|
||||
--text-disabled: #BDBDBD;
|
||||
--text-inverse: #FFFFFF;
|
||||
--text-link: #EA580C;
|
||||
--text-error: #DC3545;
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────────────────
|
||||
BORDER COLORS
|
||||
───────────────────────────────────────────────────────────────────────── */
|
||||
--border-default: #E0E0E0;
|
||||
--border-strong: #BDBDBD;
|
||||
--border-focus: #EA580C;
|
||||
--border-error: #DC3545;
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────────────────
|
||||
PIPELINE STAGE COLORS
|
||||
───────────────────────────────────────────────────────────────────────── */
|
||||
--stage-new: #EA580C;
|
||||
--stage-contacted: #F97316;
|
||||
--stage-qualified: #3B82F6;
|
||||
--stage-proposal: #8B5CF6;
|
||||
--stage-negotiation: #F59E0B;
|
||||
--stage-won: #22C55E;
|
||||
--stage-lost: #EF4444;
|
||||
|
||||
/* Pipeline Stage Backgrounds */
|
||||
--stage-new-bg: #FFF7ED;
|
||||
--stage-contacted-bg: #FFEDD5;
|
||||
--stage-qualified-bg: #EFF6FF;
|
||||
--stage-proposal-bg: #F5F3FF;
|
||||
--stage-negotiation-bg: #FFFBEB;
|
||||
--stage-won-bg: #F0FDF4;
|
||||
--stage-lost-bg: #FEF2F2;
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────────────────
|
||||
ACTIVITY TYPE COLORS
|
||||
───────────────────────────────────────────────────────────────────────── */
|
||||
--activity-call: #22C55E;
|
||||
--activity-email: #3B82F6;
|
||||
--activity-meeting: #8B5CF6;
|
||||
--activity-task: #F59E0B;
|
||||
--activity-note: #6B7280;
|
||||
--activity-deadline: #EF4444;
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────────────────
|
||||
LEAD TEMPERATURE
|
||||
───────────────────────────────────────────────────────────────────────── */
|
||||
--lead-hot: #EF4444;
|
||||
--lead-warm: #F59E0B;
|
||||
--lead-cold: #6B7280;
|
||||
|
||||
/* Lead Temperature Backgrounds */
|
||||
--lead-hot-bg: #FEF2F2;
|
||||
--lead-warm-bg: #FFFBEB;
|
||||
--lead-cold-bg: #F3F4F6;
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────────────────
|
||||
PRIORITY LEVELS
|
||||
───────────────────────────────────────────────────────────────────────── */
|
||||
--priority-urgent: #DC2626;
|
||||
--priority-high: #F97316;
|
||||
--priority-medium: #FBBF24;
|
||||
--priority-low: #22C55E;
|
||||
--priority-none: #9CA3AF;
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────────────────
|
||||
DEAL PROBABILITY
|
||||
───────────────────────────────────────────────────────────────────────── */
|
||||
--probability-high: #22C55E;
|
||||
--probability-mid: #F59E0B;
|
||||
--probability-low: #EF4444;
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────────────────
|
||||
TASK STATUS
|
||||
───────────────────────────────────────────────────────────────────────── */
|
||||
--task-overdue: #EF4444;
|
||||
--task-due-today: #F59E0B;
|
||||
--task-upcoming: #3B82F6;
|
||||
--task-completed: #22C55E;
|
||||
--task-cancelled: #9CA3AF;
|
||||
|
||||
/* Task Status Backgrounds */
|
||||
--task-overdue-bg: #FEF2F2;
|
||||
--task-due-today-bg: #FFFBEB;
|
||||
--task-upcoming-bg: #EFF6FF;
|
||||
--task-completed-bg: #F0FDF4;
|
||||
--task-cancelled-bg: #F3F4F6;
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────────────────
|
||||
CHARTS - CATEGORICAL
|
||||
───────────────────────────────────────────────────────────────────────── */
|
||||
--cat-1: #EA580C;
|
||||
--cat-2: #3B82F6;
|
||||
--cat-3: #22C55E;
|
||||
--cat-4: #8B5CF6;
|
||||
--cat-5: #EC4899;
|
||||
--cat-6: #14B8A6;
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────────────────
|
||||
PROGRESS BAR
|
||||
───────────────────────────────────────────────────────────────────────── */
|
||||
--progress-default: #EA580C;
|
||||
--progress-success: #22C55E;
|
||||
--progress-warning: #F59E0B;
|
||||
--progress-danger: #EF4444;
|
||||
--progress-bg: #E5E7EB;
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────────────────
|
||||
SPACING (8px Grid System)
|
||||
───────────────────────────────────────────────────────────────────────── */
|
||||
--space-0: 0px;
|
||||
--space-25: 2px;
|
||||
--space-50: 4px;
|
||||
--space-100: 8px;
|
||||
--space-150: 12px;
|
||||
--space-200: 16px;
|
||||
--space-250: 20px;
|
||||
--space-300: 24px;
|
||||
--space-400: 32px;
|
||||
--space-500: 40px;
|
||||
--space-600: 48px;
|
||||
--space-700: 56px;
|
||||
--space-800: 64px;
|
||||
--space-900: 72px;
|
||||
--space-1000: 80px;
|
||||
|
||||
/* Legacy spacing aliases */
|
||||
--space-1: 4px;
|
||||
--space-2: 8px;
|
||||
--space-3: 12px;
|
||||
|
|
@ -139,39 +268,80 @@
|
|||
--space-10: 40px;
|
||||
--space-12: 48px;
|
||||
|
||||
/* Border Radius - Slightly larger for premium feel */
|
||||
--radius-sm: 6px;
|
||||
--radius-md: 8px;
|
||||
--radius-lg: 12px;
|
||||
--radius-xl: 16px;
|
||||
--radius-2xl: 20px;
|
||||
/* ─────────────────────────────────────────────────────────────────────────
|
||||
BORDER RADIUS
|
||||
───────────────────────────────────────────────────────────────────────── */
|
||||
--radius-none: 0px;
|
||||
--radius-xs: 2px;
|
||||
--radius-sm: 4px;
|
||||
--radius-md: 6px;
|
||||
--radius-lg: 8px;
|
||||
--radius-xl: 12px;
|
||||
--radius-2xl: 16px;
|
||||
--radius-full: 9999px;
|
||||
|
||||
/* Z-Index Layers */
|
||||
/* ─────────────────────────────────────────────────────────────────────────
|
||||
SHADOWS
|
||||
───────────────────────────────────────────────────────────────────────── */
|
||||
--shadow-none: none;
|
||||
--shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.04);
|
||||
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.06), 0 1px 2px rgba(0, 0, 0, 0.04);
|
||||
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.07), 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
--shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.08), 0 4px 6px rgba(0, 0, 0, 0.05);
|
||||
--shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.10), 0 10px 10px rgba(0, 0, 0, 0.04);
|
||||
|
||||
/* Focus Ring */
|
||||
--focus-ring: 0 0 0 2px var(--surface-default), 0 0 0 4px var(--color-primary-default);
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────────────────
|
||||
ICON SIZES
|
||||
───────────────────────────────────────────────────────────────────────── */
|
||||
--icon-xs: 12px;
|
||||
--icon-sm: 16px;
|
||||
--icon-md: 20px;
|
||||
--icon-lg: 24px;
|
||||
--icon-xl: 32px;
|
||||
--icon-2xl: 48px;
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────────────────
|
||||
Z-INDEX LAYERS
|
||||
───────────────────────────────────────────────────────────────────────── */
|
||||
--z-base: 0;
|
||||
--z-raised: 10;
|
||||
--z-dropdown: 100;
|
||||
--z-sticky: 200;
|
||||
--z-modal: 300;
|
||||
--z-overlay: 400;
|
||||
--z-toast: 500;
|
||||
--z-sidebar: 10;
|
||||
--z-drawer-backdrop: 40;
|
||||
--z-drawer: 50;
|
||||
--z-dropdown: 60;
|
||||
--z-modal: 70;
|
||||
--z-toast: 80;
|
||||
--z-tooltip: 90;
|
||||
|
||||
/* Animation Timing - Refined easing */
|
||||
/* ─────────────────────────────────────────────────────────────────────────
|
||||
TRANSITIONS
|
||||
───────────────────────────────────────────────────────────────────────── */
|
||||
--transition-fast: 100ms ease;
|
||||
--transition-normal: 150ms ease;
|
||||
--transition-slow: 300ms ease;
|
||||
|
||||
/* Animation timing functions */
|
||||
--ease-out: cubic-bezier(0.16, 1, 0.3, 1);
|
||||
--ease-in: cubic-bezier(0.7, 0, 0.84, 0);
|
||||
--ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
|
||||
--ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
--ease-bounce: cubic-bezier(0.68, -0.6, 0.32, 1.6);
|
||||
|
||||
/* Animation Durations */
|
||||
/* Animation durations */
|
||||
--duration-instant: 0ms;
|
||||
--duration-fast: 120ms;
|
||||
--duration-normal: 200ms;
|
||||
--duration-slow: 350ms;
|
||||
--duration-slower: 500ms;
|
||||
|
||||
/* Layout Dimensions */
|
||||
/* ─────────────────────────────────────────────────────────────────────────
|
||||
LAYOUT DIMENSIONS
|
||||
───────────────────────────────────────────────────────────────────────── */
|
||||
--sidebar-width: 260px;
|
||||
--sidebar-width-collapsed: 72px;
|
||||
--drawer-width: 520px;
|
||||
|
|
@ -179,116 +349,213 @@
|
|||
--content-padding: 28px;
|
||||
--content-max-width: 1400px;
|
||||
|
||||
/* Typography */
|
||||
--font-sans: 'Plus Jakarta Sans', 'DM Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
--font-body: 'DM Sans', 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
--font-mono: 'JetBrains Mono', 'Fira Code', monospace;
|
||||
/* Container Widths */
|
||||
--container-sm: 640px;
|
||||
--container-md: 768px;
|
||||
--container-lg: 1024px;
|
||||
--container-xl: 1280px;
|
||||
|
||||
/* Breakpoints */
|
||||
--breakpoint-xs: 0px;
|
||||
--breakpoint-sm: 480px;
|
||||
--breakpoint-md: 768px;
|
||||
--breakpoint-lg: 1024px;
|
||||
--breakpoint-xl: 1280px;
|
||||
--breakpoint-2xl: 1440px;
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────────────────
|
||||
TYPOGRAPHY
|
||||
───────────────────────────────────────────────────────────────────────── */
|
||||
--font-family-sans: "Geist", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
--font-family-mono: "Geist Mono", "SF Mono", "Fira Code", Consolas, monospace;
|
||||
--font-sans: var(--font-family-sans);
|
||||
--font-body: var(--font-family-sans);
|
||||
--font-mono: var(--font-family-mono);
|
||||
|
||||
--font-weight-regular: 400;
|
||||
--font-weight-medium: 500;
|
||||
--font-weight-semibold: 600;
|
||||
--font-weight-bold: 700;
|
||||
|
||||
/* Glass morphism properties */
|
||||
--glass-bg: rgba(255, 255, 255, 0.8);
|
||||
--glass-border: rgba(255, 255, 255, 0.3);
|
||||
--glass-blur: 12px;
|
||||
|
||||
/* Legacy semantic aliases */
|
||||
--bg-base: #FAFAFA;
|
||||
--bg-subtle: #F5F5F5;
|
||||
--bg-muted: #EEEEEE;
|
||||
--bg-emphasis: #E0E0E0;
|
||||
--accent-primary: #EA580C;
|
||||
--accent-primary-hover: #C2410C;
|
||||
--accent-primary-subtle: #FFF7ED;
|
||||
--accent-secondary: #F59E0B;
|
||||
--accent-secondary-hover: #D97706;
|
||||
--accent-secondary-subtle: #FFFBEB;
|
||||
--status-success: #28A745;
|
||||
--status-success-bg: #E8F5E9;
|
||||
--status-warning: #FFC107;
|
||||
--status-warning-bg: #FFF8E1;
|
||||
--status-danger: #DC3545;
|
||||
--status-danger-bg: #FDEAEA;
|
||||
--status-info: #17A2B8;
|
||||
--status-info-bg: #E3F2FD;
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════════════
|
||||
DARK MODE - Obsidian Luxe
|
||||
Deep, rich darks with vivid accent colors and subtle glow effects
|
||||
DARK MODE
|
||||
Deep, rich darks with vivid orange accent and subtle glow effects
|
||||
═══════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
.dark {
|
||||
/* Core surfaces - Deep obsidian with subtle blue undertone */
|
||||
--background: oklch(0.12 0.015 260);
|
||||
--foreground: oklch(0.95 0.005 250);
|
||||
--card: oklch(0.16 0.015 260);
|
||||
--card-foreground: oklch(0.95 0.005 250);
|
||||
--popover: oklch(0.18 0.018 260);
|
||||
--popover-foreground: oklch(0.95 0.005 250);
|
||||
/* Core surfaces - Deep with subtle warmth */
|
||||
--background: oklch(0.12 0.01 30);
|
||||
--foreground: oklch(0.95 0.005 60);
|
||||
--card: oklch(0.16 0.01 30);
|
||||
--card-foreground: oklch(0.95 0.005 60);
|
||||
--popover: oklch(0.18 0.012 30);
|
||||
--popover-foreground: oklch(0.95 0.005 60);
|
||||
|
||||
/* Primary - Vivid cyan */
|
||||
--primary: oklch(0.75 0.16 195);
|
||||
--primary-foreground: oklch(0.12 0.015 260);
|
||||
/* Primary - Vivid orange */
|
||||
--primary: oklch(0.72 0.18 35);
|
||||
--primary-foreground: oklch(0.12 0.01 30);
|
||||
|
||||
/* Secondary */
|
||||
--secondary: oklch(0.22 0.015 260);
|
||||
--secondary-foreground: oklch(0.92 0.005 250);
|
||||
--secondary: oklch(0.22 0.01 30);
|
||||
--secondary-foreground: oklch(0.92 0.005 60);
|
||||
|
||||
/* Muted */
|
||||
--muted: oklch(0.22 0.012 260);
|
||||
--muted-foreground: oklch(0.65 0.01 260);
|
||||
--muted: oklch(0.22 0.008 30);
|
||||
--muted-foreground: oklch(0.65 0.01 60);
|
||||
|
||||
/* Accent - Amber glow */
|
||||
--accent: oklch(0.25 0.04 80);
|
||||
--accent-foreground: oklch(0.85 0.12 80);
|
||||
/* Accent - Warm orange glow */
|
||||
--accent: oklch(0.25 0.04 35);
|
||||
--accent-foreground: oklch(0.85 0.12 35);
|
||||
|
||||
/* Destructive */
|
||||
--destructive: oklch(0.65 0.2 25);
|
||||
|
||||
/* Borders & inputs - Subtle with glow potential */
|
||||
--border: oklch(0.28 0.015 260);
|
||||
--input: oklch(0.24 0.015 260);
|
||||
--ring: oklch(0.7 0.14 195);
|
||||
/* Borders & inputs */
|
||||
--border: oklch(0.28 0.01 30);
|
||||
--input: oklch(0.24 0.01 30);
|
||||
--ring: oklch(0.7 0.16 35);
|
||||
|
||||
/* Chart colors - Vibrant neon-ish */
|
||||
--chart-1: oklch(0.75 0.18 195);
|
||||
--chart-2: oklch(0.72 0.18 155);
|
||||
--chart-3: oklch(0.78 0.15 85);
|
||||
--chart-4: oklch(0.7 0.2 290);
|
||||
--chart-5: oklch(0.72 0.2 10);
|
||||
/* Chart colors - Vibrant */
|
||||
--chart-1: #FB923C;
|
||||
--chart-2: #FDBA74;
|
||||
--chart-3: #FED7AA;
|
||||
--chart-4: #FFEDD5;
|
||||
--chart-5: #FFF7ED;
|
||||
|
||||
/* Sidebar - HubSpot Style Dark */
|
||||
--sidebar: oklch(0.16 0.015 250);
|
||||
--sidebar-foreground: oklch(0.92 0.005 250);
|
||||
--sidebar-primary: oklch(0.7 0.18 25);
|
||||
--sidebar-primary-foreground: oklch(0.12 0.015 260);
|
||||
--sidebar-accent: oklch(0.22 0.02 250);
|
||||
--sidebar-accent-foreground: oklch(0.92 0.005 250);
|
||||
--sidebar-border: oklch(0.28 0.015 250);
|
||||
--sidebar-ring: oklch(0.7 0.18 25);
|
||||
/* Sidebar */
|
||||
--sidebar: oklch(0.16 0.01 30);
|
||||
--sidebar-foreground: oklch(0.92 0.005 60);
|
||||
--sidebar-primary: #FB923C;
|
||||
--sidebar-primary-foreground: oklch(0.12 0.01 30);
|
||||
--sidebar-accent: oklch(0.22 0.015 30);
|
||||
--sidebar-accent-foreground: oklch(0.92 0.005 60);
|
||||
--sidebar-border: oklch(0.28 0.01 30);
|
||||
--sidebar-ring: #FB923C;
|
||||
|
||||
/* Background Layers - Obsidian depth */
|
||||
/* Primary color scale - Dark mode */
|
||||
--color-primary-default: #FB923C;
|
||||
--color-primary-hover: #FDBA74;
|
||||
--color-primary-active: #F97316;
|
||||
--color-primary-light: #431407;
|
||||
|
||||
/* Surface Colors - Dark mode */
|
||||
--surface-default: #1E1E1E;
|
||||
--surface-raised: #252525;
|
||||
--surface-sunken: #141414;
|
||||
--surface-overlay: #2D2D2D;
|
||||
|
||||
/* Text Colors - Dark mode */
|
||||
--text-primary: #FFFFFF;
|
||||
--text-secondary: #B0B0B0;
|
||||
--text-tertiary: #757575;
|
||||
--text-disabled: #525252;
|
||||
--text-inverse: #212121;
|
||||
--text-link: #FB923C;
|
||||
--text-error: #F87171;
|
||||
|
||||
/* Border Colors - Dark mode */
|
||||
--border-default: #3D3D3D;
|
||||
--border-strong: #525252;
|
||||
--border-focus: #FB923C;
|
||||
--border-error: #F87171;
|
||||
|
||||
/* Background Layers - Dark */
|
||||
--bg-base: #0c0d12;
|
||||
--bg-subtle: #121318;
|
||||
--bg-muted: #181a21;
|
||||
--bg-emphasis: #1f222b;
|
||||
|
||||
/* Border Colors */
|
||||
--border-default: #262a36;
|
||||
--border-strong: #363b4a;
|
||||
--border-subtle: #1c1f28;
|
||||
|
||||
/* Text Colors */
|
||||
--text-primary: #f1f5f9;
|
||||
--text-secondary: #94a3b8;
|
||||
--text-tertiary: #64748b;
|
||||
--text-inverse: #0c0d12;
|
||||
|
||||
/* Accent Colors - Cyan glow */
|
||||
--accent-primary: #22d3ee;
|
||||
--accent-primary-hover: #06b6d4;
|
||||
--accent-primary-subtle: rgba(34, 211, 238, 0.1);
|
||||
|
||||
/* Secondary Accent - Warm Amber */
|
||||
--accent-secondary: #fbbf24;
|
||||
--accent-secondary-hover: #f59e0b;
|
||||
/* Accent Colors - Orange glow */
|
||||
--accent-primary: #FB923C;
|
||||
--accent-primary-hover: #FDBA74;
|
||||
--accent-primary-subtle: rgba(251, 146, 60, 0.1);
|
||||
--accent-secondary: #FBBF24;
|
||||
--accent-secondary-hover: #F59E0B;
|
||||
--accent-secondary-subtle: rgba(251, 191, 36, 0.1);
|
||||
|
||||
/* Status Colors - Vivid in dark */
|
||||
--status-success: #34d399;
|
||||
--status-success: #34D399;
|
||||
--status-success-bg: rgba(52, 211, 153, 0.12);
|
||||
--status-warning: #fbbf24;
|
||||
--status-warning: #FBBF24;
|
||||
--status-warning-bg: rgba(251, 191, 36, 0.12);
|
||||
--status-danger: #f87171;
|
||||
--status-danger: #F87171;
|
||||
--status-danger-bg: rgba(248, 113, 113, 0.12);
|
||||
--status-info: #22d3ee;
|
||||
--status-info-bg: rgba(34, 211, 238, 0.12);
|
||||
--status-info: #38BDF8;
|
||||
--status-info-bg: rgba(56, 189, 248, 0.12);
|
||||
|
||||
/* Stage Colors - Neon variants */
|
||||
--stage-prospecting: #94a3b8;
|
||||
--stage-qualified: #22d3ee;
|
||||
--stage-proposal: #a78bfa;
|
||||
--stage-negotiation: #fbbf24;
|
||||
--stage-won: #34d399;
|
||||
--stage-lost: #f87171;
|
||||
/* Semantic colors dark mode */
|
||||
--color-negative-default: #F87171;
|
||||
--color-negative-hover: #FCA5A5;
|
||||
--color-negative-light: rgba(248, 113, 113, 0.15);
|
||||
--color-warning-default: #FBBF24;
|
||||
--color-warning-dark: #FCD34D;
|
||||
--color-warning-light: rgba(251, 191, 36, 0.15);
|
||||
--color-success-default: #34D399;
|
||||
--color-success-light: rgba(52, 211, 153, 0.15);
|
||||
--color-info-default: #38BDF8;
|
||||
--color-info-light: rgba(56, 189, 248, 0.15);
|
||||
|
||||
/* Grey Scale - Dark mode */
|
||||
--color-grey-50: #18181B;
|
||||
--color-grey-100: #27272A;
|
||||
--color-grey-200: #3F3F46;
|
||||
--color-grey-300: #52525B;
|
||||
--color-grey-400: #71717A;
|
||||
--color-grey-500: #A1A1AA;
|
||||
--color-grey-600: #D4D4D8;
|
||||
--color-grey-700: #E4E4E7;
|
||||
--color-grey-800: #F4F4F5;
|
||||
--color-grey-900: #FAFAFA;
|
||||
|
||||
/* Stage Colors - Lighter variants for dark mode */
|
||||
--stage-new-bg: rgba(234, 88, 12, 0.15);
|
||||
--stage-contacted-bg: rgba(249, 115, 22, 0.15);
|
||||
--stage-qualified-bg: rgba(59, 130, 246, 0.15);
|
||||
--stage-proposal-bg: rgba(139, 92, 246, 0.15);
|
||||
--stage-negotiation-bg: rgba(245, 158, 11, 0.15);
|
||||
--stage-won-bg: rgba(34, 197, 94, 0.15);
|
||||
--stage-lost-bg: rgba(239, 68, 68, 0.15);
|
||||
|
||||
/* Lead Temperature Backgrounds - Dark mode */
|
||||
--lead-hot-bg: rgba(239, 68, 68, 0.15);
|
||||
--lead-warm-bg: rgba(245, 158, 11, 0.15);
|
||||
--lead-cold-bg: rgba(107, 114, 128, 0.15);
|
||||
|
||||
/* Task Status Backgrounds - Dark mode */
|
||||
--task-overdue-bg: rgba(239, 68, 68, 0.15);
|
||||
--task-due-today-bg: rgba(245, 158, 11, 0.15);
|
||||
--task-upcoming-bg: rgba(59, 130, 246, 0.15);
|
||||
--task-completed-bg: rgba(34, 197, 94, 0.15);
|
||||
--task-cancelled-bg: rgba(156, 163, 175, 0.15);
|
||||
|
||||
/* Progress - Dark mode */
|
||||
--progress-bg: #3F3F46;
|
||||
|
||||
/* Glass morphism - Dark variant */
|
||||
--glass-bg: rgba(18, 19, 24, 0.8);
|
||||
|
|
@ -301,10 +568,10 @@
|
|||
═══════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
@theme inline {
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
--radius-sm: var(--radius-sm);
|
||||
--radius-md: var(--radius-md);
|
||||
--radius-lg: var(--radius-lg);
|
||||
--radius-xl: var(--radius-xl);
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-card: var(--card);
|
||||
|
|
@ -382,7 +649,7 @@
|
|||
background-clip: content-box;
|
||||
}
|
||||
|
||||
/* Selection styling */
|
||||
/* Selection styling - Orange */
|
||||
::selection {
|
||||
background: var(--accent-primary);
|
||||
color: var(--text-inverse);
|
||||
|
|
@ -402,7 +669,7 @@
|
|||
border: 1px solid var(--glass-border);
|
||||
}
|
||||
|
||||
/* Glow effects */
|
||||
/* Glow effects - Orange accent */
|
||||
.glow-sm {
|
||||
box-shadow: 0 0 15px -3px var(--accent-primary);
|
||||
}
|
||||
|
|
@ -442,7 +709,7 @@
|
|||
opacity: 0.04;
|
||||
}
|
||||
|
||||
/* Gradient mesh background */
|
||||
/* Gradient mesh background - Orange themed */
|
||||
.gradient-mesh {
|
||||
background:
|
||||
radial-gradient(at 40% 20%, var(--accent-primary-subtle) 0px, transparent 50%),
|
||||
|
|
@ -555,6 +822,41 @@
|
|||
border-color: var(--accent-primary);
|
||||
box-shadow: 0 0 30px -10px var(--accent-primary);
|
||||
}
|
||||
|
||||
/* Pipeline stage badge utilities */
|
||||
.badge-stage-new { background: var(--stage-new-bg); color: var(--stage-new); }
|
||||
.badge-stage-contacted { background: var(--stage-contacted-bg); color: var(--stage-contacted); }
|
||||
.badge-stage-qualified { background: var(--stage-qualified-bg); color: var(--stage-qualified); }
|
||||
.badge-stage-proposal { background: var(--stage-proposal-bg); color: var(--stage-proposal); }
|
||||
.badge-stage-negotiation { background: var(--stage-negotiation-bg); color: var(--stage-negotiation); }
|
||||
.badge-stage-won { background: var(--stage-won-bg); color: var(--stage-won); }
|
||||
.badge-stage-lost { background: var(--stage-lost-bg); color: var(--stage-lost); }
|
||||
|
||||
/* Lead temperature badge utilities */
|
||||
.badge-lead-hot { background: var(--lead-hot-bg); color: var(--lead-hot); }
|
||||
.badge-lead-warm { background: var(--lead-warm-bg); color: var(--lead-warm); }
|
||||
.badge-lead-cold { background: var(--lead-cold-bg); color: var(--lead-cold); }
|
||||
|
||||
/* Priority badge utilities */
|
||||
.badge-priority-urgent { background: rgba(220, 38, 38, 0.1); color: var(--priority-urgent); }
|
||||
.badge-priority-high { background: rgba(249, 115, 22, 0.1); color: var(--priority-high); }
|
||||
.badge-priority-medium { background: rgba(251, 191, 36, 0.1); color: var(--priority-medium); }
|
||||
.badge-priority-low { background: rgba(34, 197, 94, 0.1); color: var(--priority-low); }
|
||||
|
||||
/* Task status badge utilities */
|
||||
.badge-task-overdue { background: var(--task-overdue-bg); color: var(--task-overdue); }
|
||||
.badge-task-due-today { background: var(--task-due-today-bg); color: var(--task-due-today); }
|
||||
.badge-task-upcoming { background: var(--task-upcoming-bg); color: var(--task-upcoming); }
|
||||
.badge-task-completed { background: var(--task-completed-bg); color: var(--task-completed); }
|
||||
.badge-task-cancelled { background: var(--task-cancelled-bg); color: var(--task-cancelled); }
|
||||
|
||||
/* Activity type badge utilities */
|
||||
.badge-activity-call { background: rgba(34, 197, 94, 0.1); color: var(--activity-call); }
|
||||
.badge-activity-email { background: rgba(59, 130, 246, 0.1); color: var(--activity-email); }
|
||||
.badge-activity-meeting { background: rgba(139, 92, 246, 0.1); color: var(--activity-meeting); }
|
||||
.badge-activity-task { background: rgba(245, 158, 11, 0.1); color: var(--activity-task); }
|
||||
.badge-activity-note { background: rgba(107, 114, 128, 0.1); color: var(--activity-note); }
|
||||
.badge-activity-deadline { background: rgba(239, 68, 68, 0.1); color: var(--activity-deadline); }
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════════════
|
||||
|
|
@ -585,7 +887,7 @@ input, textarea, select {
|
|||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════════════
|
||||
HUBSPOT-STYLE SIDEBAR ENHANCEMENTS
|
||||
SIDEBAR ENHANCEMENTS
|
||||
═══════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
/* Sidebar base styling */
|
||||
|
|
@ -593,12 +895,12 @@ input, textarea, select {
|
|||
transition: width 0.2s ease, transform 0.2s ease;
|
||||
}
|
||||
|
||||
/* HubSpot-style menu button refinements */
|
||||
/* Menu button refinements */
|
||||
[data-slot="sidebar-menu-button"] {
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
/* HubSpot-style active state with left accent bar */
|
||||
/* Active state with left accent bar */
|
||||
[data-slot="sidebar-menu-button"][data-active="true"] {
|
||||
position: relative;
|
||||
}
|
||||
|
|
@ -612,7 +914,7 @@ input, textarea, select {
|
|||
transform: none;
|
||||
}
|
||||
|
||||
/* Group label styling - HubSpot uppercase sections */
|
||||
/* Group label styling - uppercase sections */
|
||||
[data-slot="sidebar-group-label"] {
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
|
|
@ -629,9 +931,9 @@ input, textarea, select {
|
|||
margin-top: 4px;
|
||||
}
|
||||
|
||||
/* Rail hover effect */
|
||||
/* Rail hover effect - Orange accent */
|
||||
[data-slot="sidebar-rail"]:hover {
|
||||
background: linear-gradient(90deg, transparent, rgba(255, 122, 89, 0.1));
|
||||
background: linear-gradient(90deg, transparent, rgba(234, 88, 12, 0.1));
|
||||
}
|
||||
|
||||
/* Collapsed state icon centering */
|
||||
|
|
@ -643,3 +945,35 @@ input, textarea, select {
|
|||
[data-slot="sidebar-footer"] {
|
||||
background: linear-gradient(180deg, transparent, var(--sidebar) 8px);
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════════════
|
||||
KANBAN BOARD STYLES
|
||||
═══════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
/* Kanban column styles using pipeline colors */
|
||||
.kanban-column-new { border-top: 3px solid var(--stage-new); background: var(--stage-new-bg); }
|
||||
.kanban-column-contacted { border-top: 3px solid var(--stage-contacted); background: var(--stage-contacted-bg); }
|
||||
.kanban-column-qualified { border-top: 3px solid var(--stage-qualified); background: var(--stage-qualified-bg); }
|
||||
.kanban-column-proposal { border-top: 3px solid var(--stage-proposal); background: var(--stage-proposal-bg); }
|
||||
.kanban-column-negotiation { border-top: 3px solid var(--stage-negotiation); background: var(--stage-negotiation-bg); }
|
||||
.kanban-column-won { border-top: 3px solid var(--stage-won); background: var(--stage-won-bg); }
|
||||
.kanban-column-lost { border-top: 3px solid var(--stage-lost); background: var(--stage-lost-bg); }
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════════════
|
||||
DATA TABLE STYLES
|
||||
═══════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
/* Table row hover - subtle orange tint */
|
||||
.data-table-row:hover {
|
||||
background: var(--color-primary-light);
|
||||
}
|
||||
|
||||
.dark .data-table-row:hover {
|
||||
background: rgba(251, 146, 60, 0.05);
|
||||
}
|
||||
|
||||
/* Selected row */
|
||||
.data-table-row-selected {
|
||||
background: var(--color-primary-light);
|
||||
border-left: 3px solid var(--color-primary-default);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<!-- Google Fonts - Plus Jakarta Sans, DM Sans, JetBrains Mono -->
|
||||
<!-- Google Fonts - Geist (via Vercel CDN) -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;0,800;1,400;1,500&family=DM+Sans:ital,wght@0,400;0,500;0,600;0,700;1,400&family=JetBrains+Mono:wght@400;500;600&display=swap"
|
||||
href="https://fonts.googleapis.com/css2?family=Geist:wght@100..900&family=Geist+Mono:wght@100..900&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<!-- Google tag (gtag.js) -->
|
||||
|
|
|
|||
|
|
@ -35,43 +35,44 @@
|
|||
|
||||
let showAll = $state(false);
|
||||
|
||||
// Using design system activity tokens
|
||||
const actionConfig = /** @type {Record<string, { icon: typeof Activity, bg: string, text: string }>} */ ({
|
||||
CREATE: {
|
||||
icon: Plus,
|
||||
bg: 'bg-emerald-500/10 dark:bg-emerald-500/20',
|
||||
text: 'text-emerald-600 dark:text-emerald-400'
|
||||
bg: 'bg-[var(--color-success-light)] dark:bg-[var(--color-success-default)]/15',
|
||||
text: 'text-[var(--color-success-default)]'
|
||||
},
|
||||
UPDATE: {
|
||||
icon: Pencil,
|
||||
bg: 'bg-cyan-500/10 dark:bg-cyan-500/20',
|
||||
text: 'text-cyan-600 dark:text-cyan-400'
|
||||
bg: 'bg-[var(--color-primary-light)] dark:bg-[var(--color-primary-default)]/15',
|
||||
text: 'text-[var(--color-primary-default)]'
|
||||
},
|
||||
DELETE: {
|
||||
icon: Trash2,
|
||||
bg: 'bg-rose-500/10 dark:bg-rose-500/20',
|
||||
text: 'text-rose-600 dark:text-rose-400'
|
||||
bg: 'bg-[var(--color-negative-light)] dark:bg-[var(--color-negative-default)]/15',
|
||||
text: 'text-[var(--color-negative-default)]'
|
||||
},
|
||||
VIEW: {
|
||||
icon: Eye,
|
||||
bg: 'bg-slate-500/10 dark:bg-slate-500/20',
|
||||
text: 'text-slate-600 dark:text-slate-400'
|
||||
bg: 'bg-[var(--surface-sunken)]',
|
||||
text: 'text-[var(--text-secondary)]'
|
||||
},
|
||||
COMMENT: {
|
||||
icon: MessageSquare,
|
||||
bg: 'bg-violet-500/10 dark:bg-violet-500/20',
|
||||
text: 'text-violet-600 dark:text-violet-400'
|
||||
bg: 'bg-[var(--activity-note)]/10 dark:bg-[var(--activity-note)]/15',
|
||||
text: 'text-[var(--activity-note)]'
|
||||
},
|
||||
ASSIGN: {
|
||||
icon: UserPlus,
|
||||
bg: 'bg-amber-500/10 dark:bg-amber-500/20',
|
||||
text: 'text-amber-600 dark:text-amber-400'
|
||||
bg: 'bg-[var(--activity-meeting)]/10 dark:bg-[var(--activity-meeting)]/15',
|
||||
text: 'text-[var(--activity-meeting)]'
|
||||
}
|
||||
});
|
||||
|
||||
const defaultConfig = {
|
||||
icon: Activity,
|
||||
bg: 'bg-muted',
|
||||
text: 'text-muted-foreground'
|
||||
bg: 'bg-[var(--surface-sunken)]',
|
||||
text: 'text-[var(--text-tertiary)]'
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -130,14 +131,14 @@
|
|||
const groupedActivities = $derived(groupByDate(displayedActivities));
|
||||
</script>
|
||||
|
||||
<div class="overflow-hidden rounded-xl border border-border/50 bg-card/80 backdrop-blur-sm dark:bg-card/50">
|
||||
<!-- Header -->
|
||||
<div class="flex items-center justify-between border-b border-border/50 px-5 py-4">
|
||||
<div class="overflow-hidden rounded-[var(--radius-lg)] border border-[var(--border-default)] bg-[var(--surface-raised)] dark:bg-[var(--surface-raised)]/80 dark:backdrop-blur-sm">
|
||||
<!-- Header - Orange accent -->
|
||||
<div class="flex items-center justify-between border-b border-[var(--border-default)]/50 px-5 py-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="flex size-8 items-center justify-center rounded-lg bg-gradient-to-br from-violet-500/10 to-purple-500/10 dark:from-violet-500/20 dark:to-purple-500/20">
|
||||
<Activity class="size-4 text-violet-600 dark:text-violet-400" />
|
||||
<div class="flex size-8 items-center justify-center rounded-[var(--radius-md)] bg-[var(--color-primary-light)] dark:bg-[var(--color-primary-default)]/15">
|
||||
<Activity class="size-4 text-[var(--color-primary-default)]" />
|
||||
</div>
|
||||
<h3 class="text-foreground text-sm font-semibold tracking-tight">Recent Activity</h3>
|
||||
<h3 class="text-[var(--text-primary)] text-sm font-semibold tracking-tight">Recent Activity</h3>
|
||||
</div>
|
||||
<Button variant="ghost" size="sm" href="/activities" class="gap-1 text-xs font-medium">
|
||||
View all
|
||||
|
|
@ -149,17 +150,17 @@
|
|||
<div class="p-5">
|
||||
{#if activities.length === 0}
|
||||
<div class="flex flex-col items-center justify-center py-10 text-center">
|
||||
<div class="mb-3 flex size-12 items-center justify-center rounded-xl bg-muted/50">
|
||||
<Clock class="text-muted-foreground/50 size-6" />
|
||||
<div class="mb-3 flex size-12 items-center justify-center rounded-[var(--radius-lg)] bg-[var(--surface-sunken)]">
|
||||
<Clock class="size-6 text-[var(--text-tertiary)]" />
|
||||
</div>
|
||||
<p class="text-muted-foreground text-sm font-medium">No recent activity</p>
|
||||
<p class="text-muted-foreground/70 text-xs">Actions will appear here</p>
|
||||
<p class="text-[var(--text-secondary)] text-sm font-medium">No recent activity</p>
|
||||
<p class="text-[var(--text-tertiary)] text-xs">Actions will appear here</p>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="space-y-5">
|
||||
{#each groupedActivities as group}
|
||||
<div>
|
||||
<p class="text-muted-foreground mb-3 text-[10px] font-semibold uppercase tracking-widest">
|
||||
<p class="text-[var(--text-tertiary)] mb-3 text-[10px] font-semibold uppercase tracking-widest">
|
||||
{group.category}
|
||||
</p>
|
||||
<div class="space-y-2">
|
||||
|
|
@ -167,25 +168,25 @@
|
|||
{@const config = actionConfig[activity.action || ''] || defaultConfig}
|
||||
{@const Icon = config.icon}
|
||||
<div
|
||||
class="group -mx-2 flex items-start gap-3 rounded-lg px-2 py-2 transition-all duration-200 hover:bg-muted/30"
|
||||
class="group -mx-2 flex items-start gap-3 rounded-[var(--radius-md)] px-2 py-2 transition-all duration-200 hover:bg-[var(--color-primary-light)] dark:hover:bg-[var(--color-primary-default)]/5"
|
||||
>
|
||||
<!-- Icon -->
|
||||
<div
|
||||
class="flex size-7 shrink-0 items-center justify-center rounded-lg transition-transform duration-200 group-hover:scale-105 {config.bg}"
|
||||
class="flex size-7 shrink-0 items-center justify-center rounded-[var(--radius-md)] transition-transform duration-200 group-hover:scale-105 {config.bg}"
|
||||
>
|
||||
<Icon class="size-3.5 {config.text}" />
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="text-foreground text-sm leading-snug">
|
||||
<p class="text-[var(--text-primary)] text-sm leading-snug">
|
||||
{activity.description ||
|
||||
activity.message ||
|
||||
`${activity.action} ${activity.entityType}: ${activity.entityName}`}
|
||||
</p>
|
||||
<div class="text-muted-foreground mt-1 flex items-center gap-1.5 text-xs">
|
||||
<div class="mt-1 flex items-center gap-1.5 text-xs text-[var(--text-secondary)]">
|
||||
<span class="font-medium">{activity.user?.name || 'System'}</span>
|
||||
<span class="text-muted-foreground/50">·</span>
|
||||
<span class="text-[var(--text-tertiary)]">·</span>
|
||||
<span>{formatTime(activity.timestamp || activity.createdAt)}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@
|
|||
{
|
||||
href: '/tasks?filter=overdue',
|
||||
icon: AlertCircle,
|
||||
iconClass: 'text-rose-500 dark:text-rose-400',
|
||||
bgClass: 'bg-rose-500/10 dark:bg-rose-500/15',
|
||||
iconClass: 'text-[var(--task-overdue)]',
|
||||
bgClass: 'bg-[var(--task-overdue-bg)] dark:bg-[var(--task-overdue)]/15',
|
||||
count: overdueCount,
|
||||
label: 'Overdue',
|
||||
show: overdueCount > 0
|
||||
|
|
@ -25,8 +25,8 @@
|
|||
{
|
||||
href: '/tasks?filter=today',
|
||||
icon: Calendar,
|
||||
iconClass: 'text-amber-500 dark:text-amber-400',
|
||||
bgClass: 'bg-amber-500/10 dark:bg-amber-500/15',
|
||||
iconClass: 'text-[var(--task-due-today)]',
|
||||
bgClass: 'bg-[var(--task-due-today-bg)] dark:bg-[var(--task-due-today)]/15',
|
||||
count: todayCount,
|
||||
label: 'Due Today',
|
||||
show: true
|
||||
|
|
@ -34,8 +34,8 @@
|
|||
{
|
||||
href: '/leads?filter=followup_today',
|
||||
icon: Phone,
|
||||
iconClass: 'text-cyan-500 dark:text-cyan-400',
|
||||
bgClass: 'bg-cyan-500/10 dark:bg-cyan-500/15',
|
||||
iconClass: 'text-[var(--activity-call)]',
|
||||
bgClass: 'bg-[var(--color-success-light)] dark:bg-[var(--activity-call)]/15',
|
||||
count: followupsCount,
|
||||
label: 'Follow-ups',
|
||||
show: true
|
||||
|
|
@ -43,8 +43,8 @@
|
|||
{
|
||||
href: '/leads?rating=HOT',
|
||||
icon: Flame,
|
||||
iconClass: 'text-orange-500 dark:text-orange-400',
|
||||
bgClass: 'bg-orange-500/10 dark:bg-orange-500/15',
|
||||
iconClass: 'text-[var(--lead-hot)]',
|
||||
bgClass: 'bg-[var(--lead-hot-bg)] dark:bg-[var(--lead-hot)]/15',
|
||||
count: hotLeadsCount,
|
||||
label: 'Hot Leads',
|
||||
show: hotLeadsCount > 0
|
||||
|
|
@ -57,41 +57,41 @@
|
|||
|
||||
{#if visibleItems.length > 0}
|
||||
<div
|
||||
class="relative overflow-hidden rounded-xl border px-4 py-3 backdrop-blur-sm transition-all duration-300 sm:px-6
|
||||
class="relative overflow-hidden rounded-[var(--radius-lg)] border px-4 py-3 backdrop-blur-sm transition-all duration-300 sm:px-6
|
||||
{hasUrgentItems
|
||||
? 'border-rose-300/50 bg-rose-50/50 dark:border-rose-500/20 dark:bg-rose-950/20'
|
||||
: 'border-border/50 bg-card/50 dark:bg-card/30'}"
|
||||
? 'border-[var(--task-overdue)]/30 bg-[var(--task-overdue-bg)] dark:border-[var(--task-overdue)]/20 dark:bg-[var(--task-overdue)]/10'
|
||||
: 'border-[var(--border-default)] bg-[var(--surface-raised)]/50 dark:bg-[var(--surface-raised)]/30'}"
|
||||
>
|
||||
<!-- Subtle gradient overlay -->
|
||||
<div class="pointer-events-none absolute inset-0 bg-gradient-to-r from-transparent via-white/5 to-transparent dark:via-white/[0.02]"></div>
|
||||
|
||||
<div class="relative flex items-center gap-3 sm:gap-5">
|
||||
<!-- Label with icon -->
|
||||
<!-- Label with icon - Orange accent -->
|
||||
<div class="hidden items-center gap-2 sm:flex">
|
||||
<div class="flex size-7 items-center justify-center rounded-lg bg-gradient-to-br from-cyan-500/10 to-teal-500/10 dark:from-cyan-500/20 dark:to-teal-500/20">
|
||||
<Zap class="size-4 text-cyan-600 dark:text-cyan-400" />
|
||||
<div class="flex size-7 items-center justify-center rounded-[var(--radius-md)] bg-[var(--color-primary-light)] dark:bg-[var(--color-primary-default)]/15">
|
||||
<Zap class="size-4 text-[var(--color-primary-default)]" />
|
||||
</div>
|
||||
<span class="text-muted-foreground text-xs font-semibold uppercase tracking-wider">
|
||||
<span class="text-[var(--text-secondary)] text-xs font-semibold uppercase tracking-wider">
|
||||
Today's Focus
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Divider -->
|
||||
<div class="hidden h-6 w-px bg-border/50 sm:block"></div>
|
||||
<div class="hidden h-6 w-px bg-[var(--border-default)]/50 sm:block"></div>
|
||||
|
||||
<!-- Items -->
|
||||
<div class="flex flex-1 items-center gap-1 overflow-x-auto sm:gap-2">
|
||||
{#each visibleItems as item}
|
||||
<a
|
||||
href={item.href}
|
||||
class="group flex items-center gap-2 rounded-lg px-3 py-2 transition-all duration-200 hover:bg-white/50 dark:hover:bg-white/5 sm:gap-2.5 sm:px-4"
|
||||
class="group flex items-center gap-2 rounded-[var(--radius-md)] px-3 py-2 transition-all duration-200 hover:bg-white/50 dark:hover:bg-white/5 sm:gap-2.5 sm:px-4"
|
||||
>
|
||||
<div class="flex size-8 items-center justify-center rounded-lg transition-transform duration-200 group-hover:scale-110 {item.bgClass}">
|
||||
<div class="flex size-8 items-center justify-center rounded-[var(--radius-md)] transition-transform duration-200 group-hover:scale-110 {item.bgClass}">
|
||||
<item.icon class="size-4 {item.iconClass}" />
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-foreground text-base font-bold tabular-nums">{item.count}</span>
|
||||
<span class="text-muted-foreground hidden text-xs font-medium sm:inline">{item.label}</span>
|
||||
<span class="text-[var(--text-primary)] text-base font-bold tabular-nums">{item.count}</span>
|
||||
<span class="text-[var(--text-secondary)] hidden text-xs font-medium sm:inline">{item.label}</span>
|
||||
</div>
|
||||
</a>
|
||||
{/each}
|
||||
|
|
|
|||
|
|
@ -22,24 +22,25 @@
|
|||
/** @type {Props} */
|
||||
let { leads = [] } = $props();
|
||||
|
||||
// Using design system lead temperature tokens
|
||||
const ratingConfig = /** @type {Record<string, { bg: string, text: string, border: string, iconColor: string }>} */ ({
|
||||
HOT: {
|
||||
bg: 'bg-orange-500/10 dark:bg-orange-500/15',
|
||||
text: 'text-orange-600 dark:text-orange-400',
|
||||
border: 'border-orange-500/30 dark:border-orange-400/20',
|
||||
iconColor: 'text-orange-500 dark:text-orange-400'
|
||||
bg: 'bg-[var(--lead-hot-bg)] dark:bg-[var(--lead-hot)]/15',
|
||||
text: 'text-[var(--lead-hot)]',
|
||||
border: 'border-[var(--lead-hot)]/30',
|
||||
iconColor: 'text-[var(--lead-hot)]'
|
||||
},
|
||||
WARM: {
|
||||
bg: 'bg-amber-500/10 dark:bg-amber-500/15',
|
||||
text: 'text-amber-600 dark:text-amber-400',
|
||||
border: 'border-amber-500/30 dark:border-amber-400/20',
|
||||
iconColor: 'text-amber-500 dark:text-amber-400'
|
||||
bg: 'bg-[var(--lead-warm-bg)] dark:bg-[var(--lead-warm)]/15',
|
||||
text: 'text-[var(--lead-warm)]',
|
||||
border: 'border-[var(--lead-warm)]/30',
|
||||
iconColor: 'text-[var(--lead-warm)]'
|
||||
},
|
||||
COLD: {
|
||||
bg: 'bg-cyan-500/10 dark:bg-cyan-500/15',
|
||||
text: 'text-cyan-600 dark:text-cyan-400',
|
||||
border: 'border-cyan-500/30 dark:border-cyan-400/20',
|
||||
iconColor: 'text-cyan-500 dark:text-cyan-400'
|
||||
bg: 'bg-[var(--lead-cold-bg)] dark:bg-[var(--lead-cold)]/15',
|
||||
text: 'text-[var(--lead-cold)]',
|
||||
border: 'border-[var(--lead-cold)]/30',
|
||||
iconColor: 'text-[var(--lead-cold)]'
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -89,14 +90,14 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<div class="flex h-full flex-col overflow-hidden rounded-xl border border-border/50 bg-card/80 backdrop-blur-sm dark:bg-card/50">
|
||||
<!-- Header -->
|
||||
<div class="flex items-center justify-between border-b border-border/50 px-5 py-4">
|
||||
<div class="flex h-full flex-col overflow-hidden rounded-[var(--radius-lg)] border border-[var(--border-default)] bg-[var(--surface-raised)] dark:bg-[var(--surface-raised)]/80 dark:backdrop-blur-sm">
|
||||
<!-- Header - Orange accent for hot leads -->
|
||||
<div class="flex items-center justify-between border-b border-[var(--border-default)]/50 px-5 py-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="flex size-8 items-center justify-center rounded-lg bg-gradient-to-br from-orange-500/10 to-red-500/10 dark:from-orange-500/20 dark:to-red-500/20">
|
||||
<Flame class="size-4 text-orange-500 dark:text-orange-400" />
|
||||
<div class="flex size-8 items-center justify-center rounded-[var(--radius-md)] bg-[var(--lead-hot-bg)] dark:bg-[var(--lead-hot)]/15">
|
||||
<Flame class="size-4 text-[var(--lead-hot)]" />
|
||||
</div>
|
||||
<h3 class="text-foreground text-sm font-semibold tracking-tight">Hot Leads</h3>
|
||||
<h3 class="text-[var(--text-primary)] text-sm font-semibold tracking-tight">Hot Leads</h3>
|
||||
</div>
|
||||
<Button variant="ghost" size="sm" href="/leads?rating=HOT" class="gap-1 text-xs font-medium">
|
||||
View all
|
||||
|
|
@ -108,26 +109,26 @@
|
|||
<div class="flex-1 overflow-auto">
|
||||
{#if leads.length === 0}
|
||||
<div class="flex h-full flex-col items-center justify-center py-10 text-center">
|
||||
<div class="mb-3 flex size-12 items-center justify-center rounded-xl bg-muted/50">
|
||||
<Sparkles class="text-muted-foreground/50 size-6" />
|
||||
<div class="mb-3 flex size-12 items-center justify-center rounded-[var(--radius-lg)] bg-[var(--surface-sunken)]">
|
||||
<Sparkles class="size-6 text-[var(--text-tertiary)]" />
|
||||
</div>
|
||||
<p class="text-muted-foreground text-sm font-medium">No hot leads</p>
|
||||
<p class="text-muted-foreground/70 text-xs">Mark leads as "Hot" to see them here</p>
|
||||
<p class="text-[var(--text-secondary)] text-sm font-medium">No hot leads</p>
|
||||
<p class="text-[var(--text-tertiary)] text-xs">Mark leads as "Hot" to see them here</p>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="divide-y divide-border/30">
|
||||
<div class="divide-y divide-[var(--border-default)]/30">
|
||||
{#each leads as lead (lead.id)}
|
||||
{@const config = ratingConfig[lead.rating || 'HOT'] || ratingConfig.HOT}
|
||||
<a
|
||||
href="/leads?view={lead.id}"
|
||||
class="group flex items-center gap-3 px-5 py-3 transition-all duration-200 hover:bg-muted/30"
|
||||
class="group flex items-center gap-3 px-5 py-3 transition-all duration-200 hover:bg-[var(--color-primary-light)] dark:hover:bg-[var(--color-primary-default)]/5"
|
||||
>
|
||||
<!-- Lead info -->
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="text-foreground truncate text-sm font-medium transition-colors group-hover:text-primary">
|
||||
<p class="text-[var(--text-primary)] truncate text-sm font-medium transition-colors group-hover:text-[var(--color-primary-default)]">
|
||||
{getLeadName(lead)}
|
||||
</p>
|
||||
<p class="text-muted-foreground mt-0.5 truncate text-xs">
|
||||
<p class="text-[var(--text-secondary)] mt-0.5 truncate text-xs">
|
||||
{lead.company || 'No company'}
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -143,10 +144,10 @@
|
|||
<!-- Follow-up date -->
|
||||
{#if lead.next_follow_up}
|
||||
<div class="flex flex-shrink-0 items-center gap-1.5">
|
||||
<Calendar class="text-muted-foreground size-3.5" />
|
||||
<Calendar class="size-3.5 text-[var(--text-tertiary)]" />
|
||||
<span
|
||||
class="text-xs font-medium tabular-nums
|
||||
{isOverdue(lead.next_follow_up) ? 'text-rose-500 dark:text-rose-400' : 'text-muted-foreground'}"
|
||||
{isOverdue(lead.next_follow_up) ? 'text-[var(--task-overdue)]' : 'text-[var(--text-secondary)]'}"
|
||||
>
|
||||
{formatFollowUp(lead.next_follow_up)}
|
||||
</span>
|
||||
|
|
@ -158,7 +159,7 @@
|
|||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="size-7 rounded-lg hover:bg-cyan-500/10 hover:text-cyan-600 dark:hover:bg-cyan-500/20 dark:hover:text-cyan-400"
|
||||
class="size-7 rounded-[var(--radius-md)] hover:bg-[var(--activity-call)]/10 hover:text-[var(--activity-call)]"
|
||||
title="Call"
|
||||
>
|
||||
<Phone class="size-3.5" />
|
||||
|
|
@ -166,7 +167,7 @@
|
|||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="size-7 rounded-lg hover:bg-violet-500/10 hover:text-violet-600 dark:hover:bg-violet-500/20 dark:hover:text-violet-400"
|
||||
class="size-7 rounded-[var(--radius-md)] hover:bg-[var(--activity-email)]/10 hover:text-[var(--activity-email)]"
|
||||
title="Email"
|
||||
>
|
||||
<Mail class="size-3.5" />
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
* @property {import('svelte').Snippet} [icon] - Icon snippet to display
|
||||
* @property {number} [trend] - Percentage change (positive or negative)
|
||||
* @property {string} [trendLabel] - Label for trend (e.g., "vs last month")
|
||||
* @property {'cyan' | 'violet' | 'emerald' | 'amber' | 'rose'} [accentColor] - Accent color theme
|
||||
* @property {'orange' | 'cyan' | 'violet' | 'emerald' | 'amber' | 'rose' | 'blue'} [accentColor] - Accent color theme
|
||||
* @property {string} [class] - Additional classes
|
||||
*/
|
||||
|
||||
|
|
@ -22,7 +22,7 @@
|
|||
icon,
|
||||
trend,
|
||||
trendLabel,
|
||||
accentColor = 'cyan',
|
||||
accentColor = 'orange',
|
||||
class: className,
|
||||
...restProps
|
||||
} = $props();
|
||||
|
|
@ -30,8 +30,14 @@
|
|||
const hasTrend = $derived(trend !== undefined && trend !== null);
|
||||
const isPositive = $derived(trend !== undefined && trend >= 0);
|
||||
|
||||
// Color configurations for different accent colors
|
||||
// Color configurations using design system tokens
|
||||
const colorConfig = {
|
||||
orange: {
|
||||
iconBg: 'bg-[var(--color-primary-light)] dark:bg-[var(--color-primary-default)]/15',
|
||||
iconColor: 'text-[var(--color-primary-default)] dark:text-[var(--color-primary-default)]',
|
||||
borderGlow: 'hover:border-[var(--color-primary-default)]/30',
|
||||
shadowGlow: 'dark:hover:shadow-[var(--color-primary-default)]/10'
|
||||
},
|
||||
cyan: {
|
||||
iconBg: 'bg-cyan-500/10 dark:bg-cyan-500/20',
|
||||
iconColor: 'text-cyan-600 dark:text-cyan-400',
|
||||
|
|
@ -39,39 +45,45 @@
|
|||
shadowGlow: 'dark:hover:shadow-cyan-500/10'
|
||||
},
|
||||
violet: {
|
||||
iconBg: 'bg-violet-500/10 dark:bg-violet-500/20',
|
||||
iconColor: 'text-violet-600 dark:text-violet-400',
|
||||
borderGlow: 'hover:border-violet-500/30 dark:hover:border-violet-400/30',
|
||||
shadowGlow: 'dark:hover:shadow-violet-500/10'
|
||||
iconBg: 'bg-[var(--stage-proposal-bg)] dark:bg-[var(--stage-proposal)]/15',
|
||||
iconColor: 'text-[var(--stage-proposal)] dark:text-[var(--stage-proposal)]',
|
||||
borderGlow: 'hover:border-[var(--stage-proposal)]/30',
|
||||
shadowGlow: 'dark:hover:shadow-[var(--stage-proposal)]/10'
|
||||
},
|
||||
blue: {
|
||||
iconBg: 'bg-[var(--stage-qualified-bg)] dark:bg-[var(--stage-qualified)]/15',
|
||||
iconColor: 'text-[var(--stage-qualified)] dark:text-[var(--stage-qualified)]',
|
||||
borderGlow: 'hover:border-[var(--stage-qualified)]/30',
|
||||
shadowGlow: 'dark:hover:shadow-[var(--stage-qualified)]/10'
|
||||
},
|
||||
emerald: {
|
||||
iconBg: 'bg-emerald-500/10 dark:bg-emerald-500/20',
|
||||
iconColor: 'text-emerald-600 dark:text-emerald-400',
|
||||
borderGlow: 'hover:border-emerald-500/30 dark:hover:border-emerald-400/30',
|
||||
shadowGlow: 'dark:hover:shadow-emerald-500/10'
|
||||
iconBg: 'bg-[var(--color-success-light)] dark:bg-[var(--stage-won)]/15',
|
||||
iconColor: 'text-[var(--stage-won)] dark:text-[var(--stage-won)]',
|
||||
borderGlow: 'hover:border-[var(--stage-won)]/30',
|
||||
shadowGlow: 'dark:hover:shadow-[var(--stage-won)]/10'
|
||||
},
|
||||
amber: {
|
||||
iconBg: 'bg-amber-500/10 dark:bg-amber-500/20',
|
||||
iconColor: 'text-amber-600 dark:text-amber-400',
|
||||
borderGlow: 'hover:border-amber-500/30 dark:hover:border-amber-400/30',
|
||||
shadowGlow: 'dark:hover:shadow-amber-500/10'
|
||||
iconBg: 'bg-[var(--stage-negotiation-bg)] dark:bg-[var(--stage-negotiation)]/15',
|
||||
iconColor: 'text-[var(--stage-negotiation)] dark:text-[var(--stage-negotiation)]',
|
||||
borderGlow: 'hover:border-[var(--stage-negotiation)]/30',
|
||||
shadowGlow: 'dark:hover:shadow-[var(--stage-negotiation)]/10'
|
||||
},
|
||||
rose: {
|
||||
iconBg: 'bg-rose-500/10 dark:bg-rose-500/20',
|
||||
iconColor: 'text-rose-600 dark:text-rose-400',
|
||||
borderGlow: 'hover:border-rose-500/30 dark:hover:border-rose-400/30',
|
||||
shadowGlow: 'dark:hover:shadow-rose-500/10'
|
||||
iconBg: 'bg-[var(--stage-lost-bg)] dark:bg-[var(--stage-lost)]/15',
|
||||
iconColor: 'text-[var(--stage-lost)] dark:text-[var(--stage-lost)]',
|
||||
borderGlow: 'hover:border-[var(--stage-lost)]/30',
|
||||
shadowGlow: 'dark:hover:shadow-[var(--stage-lost)]/10'
|
||||
}
|
||||
};
|
||||
|
||||
const colors = $derived(colorConfig[accentColor] || colorConfig.cyan);
|
||||
const colors = $derived(colorConfig[accentColor] || colorConfig.orange);
|
||||
</script>
|
||||
|
||||
<div
|
||||
class={cn(
|
||||
'group relative overflow-hidden rounded-xl border border-border/50 bg-card/80 p-5 backdrop-blur-sm transition-all duration-300',
|
||||
'hover:-translate-y-0.5 hover:shadow-lg',
|
||||
'dark:bg-card/50 dark:hover:shadow-xl',
|
||||
'group relative overflow-hidden rounded-[var(--radius-lg)] border border-[var(--border-default)] bg-[var(--surface-raised)] p-5 transition-all duration-300',
|
||||
'hover:-translate-y-0.5 hover:shadow-[var(--shadow-md)]',
|
||||
'dark:border-[var(--border-default)] dark:bg-[var(--surface-raised)]/80 dark:backdrop-blur-sm dark:hover:shadow-lg',
|
||||
colors.borderGlow,
|
||||
colors.shadowGlow,
|
||||
className
|
||||
|
|
@ -80,46 +92,46 @@
|
|||
>
|
||||
<!-- Subtle gradient overlay on hover -->
|
||||
<div class="pointer-events-none absolute inset-0 opacity-0 transition-opacity duration-300 group-hover:opacity-100">
|
||||
<div class="absolute inset-0 bg-gradient-to-br from-white/5 via-transparent to-transparent dark:from-white/[0.02]"></div>
|
||||
<div class="absolute inset-0 bg-gradient-to-br from-[var(--color-primary-default)]/5 via-transparent to-transparent dark:from-[var(--color-primary-default)]/[0.03]"></div>
|
||||
</div>
|
||||
|
||||
<div class="relative flex items-start justify-between gap-4">
|
||||
<div class="flex min-w-0 flex-col gap-1.5">
|
||||
<!-- Label -->
|
||||
<p class="text-muted-foreground text-xs font-semibold uppercase tracking-wider">
|
||||
<p class="text-[var(--text-secondary)] text-xs font-semibold uppercase tracking-wider">
|
||||
{label}
|
||||
</p>
|
||||
|
||||
<!-- Value -->
|
||||
<p class="text-foreground truncate text-2xl font-bold tabular-nums tracking-tight lg:text-3xl">
|
||||
<p class="text-[var(--text-primary)] truncate text-2xl font-bold tabular-nums tracking-tight lg:text-3xl">
|
||||
{value}
|
||||
</p>
|
||||
|
||||
<!-- Subtitle -->
|
||||
{#if subtitle}
|
||||
<p class="text-muted-foreground/70 text-[10px] font-medium">{subtitle}</p>
|
||||
<p class="text-[var(--text-tertiary)] text-[10px] font-medium">{subtitle}</p>
|
||||
{/if}
|
||||
|
||||
<!-- Trend indicator -->
|
||||
{#if hasTrend}
|
||||
<div class="mt-1 flex items-center gap-1.5">
|
||||
{#if isPositive}
|
||||
<div class="flex items-center gap-1 rounded-full bg-emerald-500/10 px-2 py-0.5 dark:bg-emerald-500/20">
|
||||
<TrendingUp class="size-3 text-emerald-600 dark:text-emerald-400" />
|
||||
<span class="text-xs font-semibold text-emerald-600 dark:text-emerald-400">
|
||||
<div class="flex items-center gap-1 rounded-full bg-[var(--color-success-light)] px-2 py-0.5 dark:bg-[var(--stage-won)]/15">
|
||||
<TrendingUp class="size-3 text-[var(--stage-won)]" />
|
||||
<span class="text-xs font-semibold text-[var(--stage-won)]">
|
||||
+{trend}%
|
||||
</span>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex items-center gap-1 rounded-full bg-rose-500/10 px-2 py-0.5 dark:bg-rose-500/20">
|
||||
<TrendingDown class="size-3 text-rose-600 dark:text-rose-400" />
|
||||
<span class="text-xs font-semibold text-rose-600 dark:text-rose-400">
|
||||
<div class="flex items-center gap-1 rounded-full bg-[var(--stage-lost-bg)] px-2 py-0.5 dark:bg-[var(--stage-lost)]/15">
|
||||
<TrendingDown class="size-3 text-[var(--stage-lost)]" />
|
||||
<span class="text-xs font-semibold text-[var(--stage-lost)]">
|
||||
{trend}%
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
{#if trendLabel}
|
||||
<span class="text-muted-foreground text-[10px]">{trendLabel}</span>
|
||||
<span class="text-[var(--text-tertiary)] text-[10px]">{trendLabel}</span>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
|
@ -129,7 +141,7 @@
|
|||
{#if icon}
|
||||
<div
|
||||
class={cn(
|
||||
'flex size-11 flex-shrink-0 items-center justify-center rounded-xl transition-transform duration-300 group-hover:scale-105',
|
||||
'flex size-11 flex-shrink-0 items-center justify-center rounded-[var(--radius-lg)] transition-transform duration-300 group-hover:scale-105',
|
||||
colors.iconBg
|
||||
)}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -19,55 +19,56 @@
|
|||
/** @type {Props} */
|
||||
let { pipelineData = {}, currency = 'USD' } = $props();
|
||||
|
||||
// Pipeline stages using design system tokens
|
||||
const stages = [
|
||||
{
|
||||
id: 'PROSPECTING',
|
||||
color: 'bg-slate-500',
|
||||
textColor: 'text-slate-600 dark:text-slate-400',
|
||||
bgColor: 'bg-slate-500/10 dark:bg-slate-500/15',
|
||||
borderColor: 'border-slate-500/30 dark:border-slate-400/20',
|
||||
glowColor: 'hover:shadow-slate-500/10'
|
||||
color: 'bg-[var(--stage-new)]',
|
||||
textColor: 'text-[var(--stage-new)]',
|
||||
bgColor: 'bg-[var(--stage-new-bg)] dark:bg-[var(--stage-new)]/15',
|
||||
borderColor: 'border-[var(--stage-new)]/30',
|
||||
glowColor: 'hover:shadow-[var(--stage-new)]/10'
|
||||
},
|
||||
{
|
||||
id: 'QUALIFICATION',
|
||||
color: 'bg-cyan-500',
|
||||
textColor: 'text-cyan-600 dark:text-cyan-400',
|
||||
bgColor: 'bg-cyan-500/10 dark:bg-cyan-500/15',
|
||||
borderColor: 'border-cyan-500/30 dark:border-cyan-400/20',
|
||||
glowColor: 'hover:shadow-cyan-500/10 dark:hover:shadow-cyan-400/20'
|
||||
color: 'bg-[var(--stage-qualified)]',
|
||||
textColor: 'text-[var(--stage-qualified)]',
|
||||
bgColor: 'bg-[var(--stage-qualified-bg)] dark:bg-[var(--stage-qualified)]/15',
|
||||
borderColor: 'border-[var(--stage-qualified)]/30',
|
||||
glowColor: 'hover:shadow-[var(--stage-qualified)]/10 dark:hover:shadow-[var(--stage-qualified)]/20'
|
||||
},
|
||||
{
|
||||
id: 'PROPOSAL',
|
||||
color: 'bg-violet-500',
|
||||
textColor: 'text-violet-600 dark:text-violet-400',
|
||||
bgColor: 'bg-violet-500/10 dark:bg-violet-500/15',
|
||||
borderColor: 'border-violet-500/30 dark:border-violet-400/20',
|
||||
glowColor: 'hover:shadow-violet-500/10 dark:hover:shadow-violet-400/20'
|
||||
color: 'bg-[var(--stage-proposal)]',
|
||||
textColor: 'text-[var(--stage-proposal)]',
|
||||
bgColor: 'bg-[var(--stage-proposal-bg)] dark:bg-[var(--stage-proposal)]/15',
|
||||
borderColor: 'border-[var(--stage-proposal)]/30',
|
||||
glowColor: 'hover:shadow-[var(--stage-proposal)]/10 dark:hover:shadow-[var(--stage-proposal)]/20'
|
||||
},
|
||||
{
|
||||
id: 'NEGOTIATION',
|
||||
color: 'bg-amber-500',
|
||||
textColor: 'text-amber-600 dark:text-amber-400',
|
||||
bgColor: 'bg-amber-500/10 dark:bg-amber-500/15',
|
||||
borderColor: 'border-amber-500/30 dark:border-amber-400/20',
|
||||
glowColor: 'hover:shadow-amber-500/10 dark:hover:shadow-amber-400/20'
|
||||
color: 'bg-[var(--stage-negotiation)]',
|
||||
textColor: 'text-[var(--stage-negotiation)]',
|
||||
bgColor: 'bg-[var(--stage-negotiation-bg)] dark:bg-[var(--stage-negotiation)]/15',
|
||||
borderColor: 'border-[var(--stage-negotiation)]/30',
|
||||
glowColor: 'hover:shadow-[var(--stage-negotiation)]/10 dark:hover:shadow-[var(--stage-negotiation)]/20'
|
||||
},
|
||||
{
|
||||
id: 'CLOSED_WON',
|
||||
color: 'bg-emerald-500',
|
||||
textColor: 'text-emerald-600 dark:text-emerald-400',
|
||||
bgColor: 'bg-emerald-500/10 dark:bg-emerald-500/15',
|
||||
borderColor: 'border-emerald-500/30 dark:border-emerald-400/20',
|
||||
glowColor: 'hover:shadow-emerald-500/10 dark:hover:shadow-emerald-400/20',
|
||||
color: 'bg-[var(--stage-won)]',
|
||||
textColor: 'text-[var(--stage-won)]',
|
||||
bgColor: 'bg-[var(--stage-won-bg)] dark:bg-[var(--stage-won)]/15',
|
||||
borderColor: 'border-[var(--stage-won)]/30',
|
||||
glowColor: 'hover:shadow-[var(--stage-won)]/10 dark:hover:shadow-[var(--stage-won)]/20',
|
||||
icon: Trophy
|
||||
},
|
||||
{
|
||||
id: 'CLOSED_LOST',
|
||||
color: 'bg-rose-500',
|
||||
textColor: 'text-rose-600 dark:text-rose-400',
|
||||
bgColor: 'bg-rose-500/10 dark:bg-rose-500/15',
|
||||
borderColor: 'border-rose-500/30 dark:border-rose-400/20',
|
||||
glowColor: 'hover:shadow-rose-500/10',
|
||||
color: 'bg-[var(--stage-lost)]',
|
||||
textColor: 'text-[var(--stage-lost)]',
|
||||
bgColor: 'bg-[var(--stage-lost-bg)] dark:bg-[var(--stage-lost)]/15',
|
||||
borderColor: 'border-[var(--stage-lost)]/30',
|
||||
glowColor: 'hover:shadow-[var(--stage-lost)]/10',
|
||||
icon: XCircle
|
||||
}
|
||||
];
|
||||
|
|
@ -79,7 +80,7 @@
|
|||
{@const data = pipelineData[stage.id] || { count: 0, value: 0, label: stage.id }}
|
||||
<a
|
||||
href="/opportunities?stage={stage.id}"
|
||||
class="group relative min-w-[130px] flex-1 overflow-hidden rounded-xl border px-4 py-3.5 transition-all duration-300 hover:-translate-y-0.5 hover:shadow-lg {stage.borderColor} {stage.bgColor} {stage.glowColor}"
|
||||
class="group relative min-w-[130px] flex-1 overflow-hidden rounded-[var(--radius-lg)] border px-4 py-3.5 transition-all duration-300 hover:-translate-y-0.5 hover:shadow-lg {stage.borderColor} {stage.bgColor} {stage.glowColor}"
|
||||
>
|
||||
<!-- Gradient overlay on hover -->
|
||||
<div class="pointer-events-none absolute inset-0 bg-gradient-to-br from-white/10 via-transparent to-transparent opacity-0 transition-opacity duration-300 group-hover:opacity-100 dark:from-white/5"></div>
|
||||
|
|
@ -89,7 +90,7 @@
|
|||
<div class="flex items-center justify-between gap-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="size-2.5 rounded-full {stage.color} shadow-sm"></div>
|
||||
<span class="text-foreground text-xs font-semibold tracking-tight">
|
||||
<span class="text-[var(--text-primary)] text-xs font-semibold tracking-tight">
|
||||
{data.label || stage.id.replace('_', ' ')}
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -106,7 +107,7 @@
|
|||
{#if stage.icon}
|
||||
<stage.icon class="size-4 {stage.textColor}" />
|
||||
{/if}
|
||||
<p class="text-foreground text-lg font-bold tabular-nums tracking-tight">
|
||||
<p class="text-[var(--text-primary)] text-lg font-bold tabular-nums tracking-tight">
|
||||
{formatCurrency(data.value, currency, true)}
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -116,16 +117,16 @@
|
|||
<!-- Connector arrows between active stages -->
|
||||
{#if index < 4}
|
||||
<div class="flex-shrink-0 px-1">
|
||||
<ChevronRight class="text-muted-foreground/30 size-5 transition-colors group-hover:text-muted-foreground/50" />
|
||||
<ChevronRight class="size-5 text-[var(--text-tertiary)]/30 transition-colors group-hover:text-[var(--text-tertiary)]/50" />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Divider before closed stages -->
|
||||
{#if index === 3}
|
||||
<div class="flex flex-shrink-0 flex-col items-center gap-1 px-2">
|
||||
<div class="bg-border/50 h-4 w-px"></div>
|
||||
<span class="text-muted-foreground/50 text-[8px] font-medium uppercase tracking-widest">Closed</span>
|
||||
<div class="bg-border/50 h-4 w-px"></div>
|
||||
<div class="h-4 w-px bg-[var(--border-default)]/50"></div>
|
||||
<span class="text-[var(--text-tertiary)]/50 text-[8px] font-medium uppercase tracking-widest">Closed</span>
|
||||
<div class="h-4 w-px bg-[var(--border-default)]/50"></div>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
|
|
|
|||
|
|
@ -26,42 +26,43 @@
|
|||
/** @type {Props} */
|
||||
let { opportunities = [] } = $props();
|
||||
|
||||
// Using design system stage tokens
|
||||
const stageConfig = /** @type {Record<string, { label: string, bg: string, text: string, border: string }>} */ ({
|
||||
PROSPECTING: {
|
||||
label: 'Prospecting',
|
||||
bg: 'bg-slate-500/10 dark:bg-slate-500/15',
|
||||
text: 'text-slate-600 dark:text-slate-400',
|
||||
border: 'border-slate-500/30'
|
||||
bg: 'bg-[var(--stage-new-bg)] dark:bg-[var(--stage-new)]/15',
|
||||
text: 'text-[var(--stage-new)]',
|
||||
border: 'border-[var(--stage-new)]/30'
|
||||
},
|
||||
QUALIFICATION: {
|
||||
label: 'Qualification',
|
||||
bg: 'bg-cyan-500/10 dark:bg-cyan-500/15',
|
||||
text: 'text-cyan-600 dark:text-cyan-400',
|
||||
border: 'border-cyan-500/30'
|
||||
bg: 'bg-[var(--stage-qualified-bg)] dark:bg-[var(--stage-qualified)]/15',
|
||||
text: 'text-[var(--stage-qualified)]',
|
||||
border: 'border-[var(--stage-qualified)]/30'
|
||||
},
|
||||
PROPOSAL: {
|
||||
label: 'Proposal',
|
||||
bg: 'bg-violet-500/10 dark:bg-violet-500/15',
|
||||
text: 'text-violet-600 dark:text-violet-400',
|
||||
border: 'border-violet-500/30'
|
||||
bg: 'bg-[var(--stage-proposal-bg)] dark:bg-[var(--stage-proposal)]/15',
|
||||
text: 'text-[var(--stage-proposal)]',
|
||||
border: 'border-[var(--stage-proposal)]/30'
|
||||
},
|
||||
NEGOTIATION: {
|
||||
label: 'Negotiation',
|
||||
bg: 'bg-amber-500/10 dark:bg-amber-500/15',
|
||||
text: 'text-amber-600 dark:text-amber-400',
|
||||
border: 'border-amber-500/30'
|
||||
bg: 'bg-[var(--stage-negotiation-bg)] dark:bg-[var(--stage-negotiation)]/15',
|
||||
text: 'text-[var(--stage-negotiation)]',
|
||||
border: 'border-[var(--stage-negotiation)]/30'
|
||||
},
|
||||
CLOSED_WON: {
|
||||
label: 'Won',
|
||||
bg: 'bg-emerald-500/10 dark:bg-emerald-500/15',
|
||||
text: 'text-emerald-600 dark:text-emerald-400',
|
||||
border: 'border-emerald-500/30'
|
||||
bg: 'bg-[var(--stage-won-bg)] dark:bg-[var(--stage-won)]/15',
|
||||
text: 'text-[var(--stage-won)]',
|
||||
border: 'border-[var(--stage-won)]/30'
|
||||
},
|
||||
CLOSED_LOST: {
|
||||
label: 'Lost',
|
||||
bg: 'bg-rose-500/10 dark:bg-rose-500/15',
|
||||
text: 'text-rose-600 dark:text-rose-400',
|
||||
border: 'border-rose-500/30'
|
||||
bg: 'bg-[var(--stage-lost-bg)] dark:bg-[var(--stage-lost)]/15',
|
||||
text: 'text-[var(--stage-lost)]',
|
||||
border: 'border-[var(--stage-lost)]/30'
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -97,14 +98,14 @@
|
|||
);
|
||||
</script>
|
||||
|
||||
<div class="flex h-full flex-col overflow-hidden rounded-xl border border-border/50 bg-card/80 backdrop-blur-sm dark:bg-card/50">
|
||||
<!-- Header -->
|
||||
<div class="flex items-center justify-between border-b border-border/50 px-5 py-4">
|
||||
<div class="flex h-full flex-col overflow-hidden rounded-[var(--radius-lg)] border border-[var(--border-default)] bg-[var(--surface-raised)] dark:bg-[var(--surface-raised)]/80 dark:backdrop-blur-sm">
|
||||
<!-- Header - Green/Success accent for opportunities -->
|
||||
<div class="flex items-center justify-between border-b border-[var(--border-default)]/50 px-5 py-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="flex size-8 items-center justify-center rounded-lg bg-gradient-to-br from-emerald-500/10 to-teal-500/10 dark:from-emerald-500/20 dark:to-teal-500/20">
|
||||
<Sparkles class="size-4 text-emerald-600 dark:text-emerald-400" />
|
||||
<div class="flex size-8 items-center justify-center rounded-[var(--radius-md)] bg-[var(--color-success-light)] dark:bg-[var(--color-success-default)]/15">
|
||||
<Sparkles class="size-4 text-[var(--color-success-default)]" />
|
||||
</div>
|
||||
<h3 class="text-foreground text-sm font-semibold tracking-tight">My Opportunities</h3>
|
||||
<h3 class="text-[var(--text-primary)] text-sm font-semibold tracking-tight">My Opportunities</h3>
|
||||
</div>
|
||||
<Button variant="ghost" size="sm" href="/opportunities" class="gap-1 text-xs font-medium">
|
||||
View all
|
||||
|
|
@ -116,32 +117,32 @@
|
|||
<div class="flex-1 overflow-auto">
|
||||
{#if openOpportunities.length === 0}
|
||||
<div class="flex h-full flex-col items-center justify-center py-10 text-center">
|
||||
<div class="mb-3 flex size-12 items-center justify-center rounded-xl bg-muted/50">
|
||||
<Sparkles class="text-muted-foreground/50 size-6" />
|
||||
<div class="mb-3 flex size-12 items-center justify-center rounded-[var(--radius-lg)] bg-[var(--surface-sunken)]">
|
||||
<Sparkles class="size-6 text-[var(--text-tertiary)]" />
|
||||
</div>
|
||||
<p class="text-muted-foreground text-sm font-medium">No open opportunities</p>
|
||||
<p class="text-muted-foreground/70 text-xs">Create one to start tracking</p>
|
||||
<p class="text-[var(--text-secondary)] text-sm font-medium">No open opportunities</p>
|
||||
<p class="text-[var(--text-tertiary)] text-xs">Create one to start tracking</p>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="divide-y divide-border/30">
|
||||
<div class="divide-y divide-[var(--border-default)]/30">
|
||||
{#each openOpportunities.slice(0, 5) as opp (opp.id)}
|
||||
{@const daysUntilClose = getDaysUntilClose(opp.closed_on)}
|
||||
{@const config = stageConfig[opp.stage] || stageConfig.PROSPECTING}
|
||||
<a
|
||||
href="/opportunities?view={opp.id}"
|
||||
class="group block px-5 py-3.5 transition-all duration-200 hover:bg-muted/30"
|
||||
class="group block px-5 py-3.5 transition-all duration-200 hover:bg-[var(--color-primary-light)] dark:hover:bg-[var(--color-primary-default)]/5"
|
||||
>
|
||||
<!-- Top row: Name and Amount -->
|
||||
<div class="mb-2.5 flex items-start justify-between gap-3">
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="text-foreground truncate text-sm font-medium transition-colors group-hover:text-primary">
|
||||
<p class="text-[var(--text-primary)] truncate text-sm font-medium transition-colors group-hover:text-[var(--color-primary-default)]">
|
||||
{opp.name}
|
||||
</p>
|
||||
<p class="text-muted-foreground mt-0.5 truncate text-xs">
|
||||
<p class="text-[var(--text-secondary)] mt-0.5 truncate text-xs">
|
||||
{opp.account?.name || 'No account'}
|
||||
</p>
|
||||
</div>
|
||||
<span class="text-foreground flex-shrink-0 text-base font-bold tabular-nums tracking-tight">
|
||||
<span class="text-[var(--text-primary)] flex-shrink-0 text-base font-bold tabular-nums tracking-tight">
|
||||
{formatCurrency(opp.amount, opp.currency || 'USD')}
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -158,7 +159,7 @@
|
|||
<!-- Probability progress -->
|
||||
<div class="flex flex-1 items-center gap-2">
|
||||
<Progress value={opp.probability || 0} class="h-1.5 flex-1" />
|
||||
<span class="text-muted-foreground w-8 text-right text-xs font-medium tabular-nums">
|
||||
<span class="text-[var(--text-secondary)] w-8 text-right text-xs font-medium tabular-nums">
|
||||
{opp.probability || 0}%
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -166,14 +167,14 @@
|
|||
<!-- Close date -->
|
||||
{#if daysUntilClose !== null}
|
||||
<div class="flex flex-shrink-0 items-center gap-1.5">
|
||||
<Calendar class="text-muted-foreground size-3.5" />
|
||||
<Calendar class="size-3.5 text-[var(--text-tertiary)]" />
|
||||
<span
|
||||
class="text-xs font-medium tabular-nums
|
||||
{daysUntilClose < 0
|
||||
? 'text-rose-500 dark:text-rose-400'
|
||||
? 'text-[var(--task-overdue)]'
|
||||
: daysUntilClose <= 7
|
||||
? 'text-amber-500 dark:text-amber-400'
|
||||
: 'text-muted-foreground'}"
|
||||
? 'text-[var(--task-due-today)]'
|
||||
: 'text-[var(--text-secondary)]'}"
|
||||
>
|
||||
{formatDaysUntilClose(daysUntilClose)}
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -18,31 +18,31 @@
|
|||
/** @type {Props} */
|
||||
let { pipelineData = {}, currency = 'USD' } = $props();
|
||||
|
||||
// Only show open stages in the chart (not closed)
|
||||
// Only show open stages in the chart (not closed) - using design system tokens
|
||||
const stages = [
|
||||
{
|
||||
id: 'PROSPECTING',
|
||||
color: 'bg-slate-500',
|
||||
bgLight: 'bg-slate-500/20',
|
||||
text: 'text-slate-600 dark:text-slate-400'
|
||||
color: 'bg-[var(--stage-new)]',
|
||||
bgLight: 'bg-[var(--stage-new)]/20',
|
||||
text: 'text-[var(--stage-new)]'
|
||||
},
|
||||
{
|
||||
id: 'QUALIFICATION',
|
||||
color: 'bg-cyan-500',
|
||||
bgLight: 'bg-cyan-500/20',
|
||||
text: 'text-cyan-600 dark:text-cyan-400'
|
||||
color: 'bg-[var(--stage-qualified)]',
|
||||
bgLight: 'bg-[var(--stage-qualified)]/20',
|
||||
text: 'text-[var(--stage-qualified)]'
|
||||
},
|
||||
{
|
||||
id: 'PROPOSAL',
|
||||
color: 'bg-violet-500',
|
||||
bgLight: 'bg-violet-500/20',
|
||||
text: 'text-violet-600 dark:text-violet-400'
|
||||
color: 'bg-[var(--stage-proposal)]',
|
||||
bgLight: 'bg-[var(--stage-proposal)]/20',
|
||||
text: 'text-[var(--stage-proposal)]'
|
||||
},
|
||||
{
|
||||
id: 'NEGOTIATION',
|
||||
color: 'bg-amber-500',
|
||||
bgLight: 'bg-amber-500/20',
|
||||
text: 'text-amber-600 dark:text-amber-400'
|
||||
color: 'bg-[var(--stage-negotiation)]',
|
||||
bgLight: 'bg-[var(--stage-negotiation)]/20',
|
||||
text: 'text-[var(--stage-negotiation)]'
|
||||
}
|
||||
];
|
||||
|
||||
|
|
@ -50,13 +50,13 @@
|
|||
const totalPipeline = $derived(stages.reduce((sum, s) => sum + (pipelineData[s.id]?.value || 0), 0));
|
||||
</script>
|
||||
|
||||
<div class="overflow-hidden rounded-xl border border-border/50 bg-card/80 p-6 backdrop-blur-sm dark:bg-card/50">
|
||||
<!-- Header -->
|
||||
<div class="overflow-hidden rounded-[var(--radius-lg)] border border-[var(--border-default)] bg-[var(--surface-raised)] p-6 dark:bg-[var(--surface-raised)]/80 dark:backdrop-blur-sm">
|
||||
<!-- Header - Orange accent -->
|
||||
<div class="mb-6 flex items-center gap-3">
|
||||
<div class="flex size-8 items-center justify-center rounded-lg bg-gradient-to-br from-cyan-500/10 to-violet-500/10 dark:from-cyan-500/20 dark:to-violet-500/20">
|
||||
<BarChart3 class="size-4 text-cyan-600 dark:text-cyan-400" />
|
||||
<div class="flex size-8 items-center justify-center rounded-[var(--radius-md)] bg-[var(--color-primary-light)] dark:bg-[var(--color-primary-default)]/15">
|
||||
<BarChart3 class="size-4 text-[var(--color-primary-default)]" />
|
||||
</div>
|
||||
<h3 class="text-foreground text-sm font-semibold tracking-tight">Pipeline by Stage</h3>
|
||||
<h3 class="text-[var(--text-primary)] text-sm font-semibold tracking-tight">Pipeline by Stage</h3>
|
||||
</div>
|
||||
|
||||
<!-- Bar chart -->
|
||||
|
|
@ -69,9 +69,9 @@
|
|||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="size-2.5 rounded-full {stage.color}"></div>
|
||||
<span class="text-muted-foreground text-xs font-medium">{data.label || stage.id}</span>
|
||||
<span class="text-[var(--text-secondary)] text-xs font-medium">{data.label || stage.id}</span>
|
||||
</div>
|
||||
<span class="text-foreground text-sm font-bold tabular-nums tracking-tight">
|
||||
<span class="text-[var(--text-primary)] text-sm font-bold tabular-nums tracking-tight">
|
||||
{formatCurrency(data.value, currency, true)}
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -90,9 +90,9 @@
|
|||
</div>
|
||||
|
||||
<!-- Summary -->
|
||||
<div class="mt-6 flex items-center justify-between border-t border-border/50 pt-4">
|
||||
<span class="text-muted-foreground text-xs font-medium">Total Open Pipeline</span>
|
||||
<span class="text-foreground text-lg font-bold tabular-nums tracking-tight">
|
||||
<div class="mt-6 flex items-center justify-between border-t border-[var(--border-default)]/50 pt-4">
|
||||
<span class="text-[var(--text-secondary)] text-xs font-medium">Total Open Pipeline</span>
|
||||
<span class="text-[var(--text-primary)] text-lg font-bold tabular-nums tracking-tight">
|
||||
{formatCurrency(totalPipeline, currency, true)}
|
||||
</span>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -78,29 +78,36 @@
|
|||
tasks.filter((t) => t.status !== 'Completed' && isOverdue(t.dueDate))
|
||||
);
|
||||
|
||||
// Using design system priority tokens
|
||||
const priorityConfig = /** @type {Record<string, { bg: string, text: string, border: string }>} */ ({
|
||||
Urgent: {
|
||||
bg: 'bg-[var(--priority-urgent-bg)] dark:bg-[var(--priority-urgent)]/15',
|
||||
text: 'text-[var(--priority-urgent)]',
|
||||
border: 'border-[var(--priority-urgent)]/30'
|
||||
},
|
||||
High: {
|
||||
bg: 'bg-[var(--priority-high-bg)] dark:bg-[var(--priority-high)]/15',
|
||||
text: 'text-[var(--priority-high)]',
|
||||
border: 'border-[var(--priority-high)]/30'
|
||||
},
|
||||
Medium: {
|
||||
bg: 'bg-[var(--priority-medium-bg)] dark:bg-[var(--priority-medium)]/15',
|
||||
text: 'text-[var(--priority-medium)]',
|
||||
border: 'border-[var(--priority-medium)]/30'
|
||||
},
|
||||
Low: {
|
||||
bg: 'bg-[var(--priority-low-bg)] dark:bg-[var(--priority-low)]/15',
|
||||
text: 'text-[var(--priority-low)]',
|
||||
border: 'border-[var(--priority-low)]/30'
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Get priority config
|
||||
* @param {string} priority
|
||||
*/
|
||||
function getPriorityConfig(priority) {
|
||||
const configs = /** @type {Record<string, { bg: string, text: string, border: string }>} */ ({
|
||||
High: {
|
||||
bg: 'bg-rose-500/10 dark:bg-rose-500/15',
|
||||
text: 'text-rose-600 dark:text-rose-400',
|
||||
border: 'border-rose-500/30 dark:border-rose-400/20'
|
||||
},
|
||||
Medium: {
|
||||
bg: 'bg-amber-500/10 dark:bg-amber-500/15',
|
||||
text: 'text-amber-600 dark:text-amber-400',
|
||||
border: 'border-amber-500/30 dark:border-amber-400/20'
|
||||
},
|
||||
Low: {
|
||||
bg: 'bg-slate-500/10 dark:bg-slate-500/15',
|
||||
text: 'text-slate-600 dark:text-slate-400',
|
||||
border: 'border-slate-500/30 dark:border-slate-400/20'
|
||||
}
|
||||
});
|
||||
return configs[priority] || configs['Medium'];
|
||||
return priorityConfig[priority] || priorityConfig['Medium'];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -127,14 +134,14 @@
|
|||
];
|
||||
</script>
|
||||
|
||||
<div class="flex h-full flex-col overflow-hidden rounded-xl border border-border/50 bg-card/80 backdrop-blur-sm dark:bg-card/50">
|
||||
<!-- Header -->
|
||||
<div class="flex items-center justify-between border-b border-border/50 px-5 py-4">
|
||||
<div class="flex h-full flex-col overflow-hidden rounded-[var(--radius-lg)] border border-[var(--border-default)] bg-[var(--surface-raised)] dark:bg-[var(--surface-raised)]/80 dark:backdrop-blur-sm">
|
||||
<!-- Header - Orange accent -->
|
||||
<div class="flex items-center justify-between border-b border-[var(--border-default)]/50 px-5 py-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="flex size-8 items-center justify-center rounded-lg bg-gradient-to-br from-cyan-500/10 to-teal-500/10 dark:from-cyan-500/20 dark:to-teal-500/20">
|
||||
<ListTodo class="size-4 text-cyan-600 dark:text-cyan-400" />
|
||||
<div class="flex size-8 items-center justify-center rounded-[var(--radius-md)] bg-[var(--color-primary-light)] dark:bg-[var(--color-primary-default)]/15">
|
||||
<ListTodo class="size-4 text-[var(--color-primary-default)]" />
|
||||
</div>
|
||||
<h3 class="text-foreground text-sm font-semibold tracking-tight">My Tasks</h3>
|
||||
<h3 class="text-[var(--text-primary)] text-sm font-semibold tracking-tight">My Tasks</h3>
|
||||
</div>
|
||||
<Button variant="ghost" size="sm" href="/tasks" class="gap-1 text-xs font-medium">
|
||||
View all
|
||||
|
|
@ -143,18 +150,18 @@
|
|||
</div>
|
||||
|
||||
<!-- Filter tabs -->
|
||||
<div class="flex gap-1 border-b border-border/50 px-4 py-2">
|
||||
<div class="flex gap-1 border-b border-[var(--border-default)]/50 px-4 py-2">
|
||||
{#each filterButtons as btn}
|
||||
<Button
|
||||
variant={filter === btn.id ? 'secondary' : 'ghost'}
|
||||
size="sm"
|
||||
class="h-7 px-3 text-xs font-medium transition-all duration-200
|
||||
{filter === btn.id ? 'bg-secondary shadow-sm' : 'hover:bg-secondary/50'}"
|
||||
{filter === btn.id ? 'bg-[var(--surface-sunken)] shadow-sm' : 'hover:bg-[var(--surface-sunken)]/50'}"
|
||||
onclick={() => (filter = btn.id)}
|
||||
>
|
||||
{btn.label}
|
||||
{#if btn.id === 'overdue' && overdueTasks.length > 0}
|
||||
<Badge class="ml-1.5 h-4 min-w-4 bg-rose-500 px-1 text-[10px] font-bold text-white">
|
||||
<Badge class="ml-1.5 h-4 min-w-4 bg-[var(--task-overdue)] px-1 text-[10px] font-bold text-white">
|
||||
{overdueTasks.length}
|
||||
</Badge>
|
||||
{/if}
|
||||
|
|
@ -166,24 +173,24 @@
|
|||
<div class="flex-1 overflow-auto">
|
||||
{#if filteredTasks().length === 0}
|
||||
<div class="flex h-full flex-col items-center justify-center py-10 text-center">
|
||||
<div class="mb-3 flex size-12 items-center justify-center rounded-xl bg-muted/50">
|
||||
<ListTodo class="text-muted-foreground/50 size-6" />
|
||||
<div class="mb-3 flex size-12 items-center justify-center rounded-[var(--radius-lg)] bg-[var(--surface-sunken)]">
|
||||
<ListTodo class="size-6 text-[var(--text-tertiary)]" />
|
||||
</div>
|
||||
<p class="text-muted-foreground text-sm font-medium">No tasks found</p>
|
||||
<p class="text-muted-foreground/70 text-xs">All caught up!</p>
|
||||
<p class="text-[var(--text-secondary)] text-sm font-medium">No tasks found</p>
|
||||
<p class="text-[var(--text-tertiary)] text-xs">All caught up!</p>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="divide-y divide-border/30">
|
||||
<div class="divide-y divide-[var(--border-default)]/30">
|
||||
{#each filteredTasks() as task (task.id)}
|
||||
{@const config = getPriorityConfig(task.priority)}
|
||||
<a
|
||||
href="/tasks?view={task.id}"
|
||||
class="group flex items-center gap-3 px-5 py-3 transition-all duration-200 hover:bg-muted/30"
|
||||
class="group flex items-center gap-3 px-5 py-3 transition-all duration-200 hover:bg-[var(--color-primary-light)] dark:hover:bg-[var(--color-primary-default)]/5"
|
||||
>
|
||||
<!-- Status icon -->
|
||||
<button class="text-muted-foreground flex-shrink-0 transition-colors hover:text-foreground">
|
||||
<button class="flex-shrink-0 text-[var(--text-tertiary)] transition-colors hover:text-[var(--text-primary)]">
|
||||
{#if task.status === 'Completed'}
|
||||
<CheckCircle2 class="size-5 text-emerald-500 dark:text-emerald-400" />
|
||||
<CheckCircle2 class="size-5 text-[var(--task-completed)]" />
|
||||
{:else}
|
||||
<Circle class="size-5" />
|
||||
{/if}
|
||||
|
|
@ -192,12 +199,12 @@
|
|||
<!-- Task details -->
|
||||
<div class="min-w-0 flex-1">
|
||||
<p
|
||||
class="text-foreground truncate text-sm font-medium transition-colors group-hover:text-primary
|
||||
{task.status === 'Completed' ? 'text-muted-foreground line-through' : ''}"
|
||||
class="text-[var(--text-primary)] truncate text-sm font-medium transition-colors group-hover:text-[var(--color-primary-default)]
|
||||
{task.status === 'Completed' ? 'text-[var(--text-tertiary)] line-through' : ''}"
|
||||
>
|
||||
{task.subject}
|
||||
</p>
|
||||
<p class="text-muted-foreground mt-0.5 truncate text-xs">{task.status}</p>
|
||||
<p class="text-[var(--text-secondary)] mt-0.5 truncate text-xs">{task.status}</p>
|
||||
</div>
|
||||
|
||||
<!-- Priority badge -->
|
||||
|
|
@ -212,7 +219,7 @@
|
|||
{#if task.dueDate}
|
||||
<span
|
||||
class="flex-shrink-0 text-xs font-medium tabular-nums
|
||||
{isOverdue(task.dueDate) ? 'text-rose-500 dark:text-rose-400' : 'text-muted-foreground'}"
|
||||
{isOverdue(task.dueDate) ? 'text-[var(--task-overdue)]' : 'text-[var(--text-secondary)]'}"
|
||||
>
|
||||
{formatDate(task.dueDate)}
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -35,8 +35,6 @@
|
|||
BarChart3,
|
||||
Tag,
|
||||
Sparkles,
|
||||
Search,
|
||||
Star,
|
||||
Home
|
||||
} from '@lucide/svelte';
|
||||
|
||||
|
|
@ -171,7 +169,7 @@
|
|||
},
|
||||
{
|
||||
href: '/accounts',
|
||||
label: 'Companies',
|
||||
label: 'Accounts',
|
||||
icon: Building,
|
||||
type: 'link',
|
||||
preload: 'off'
|
||||
|
|
@ -248,7 +246,10 @@
|
|||
};
|
||||
</script>
|
||||
|
||||
<Sidebar.Root collapsible="icon" class="hubspot-sidebar border-r border-sidebar-border/60 bg-sidebar">
|
||||
<Sidebar.Root
|
||||
collapsible="icon"
|
||||
class="hubspot-sidebar border-sidebar-border/60 bg-sidebar border-r"
|
||||
>
|
||||
<!-- Header with Logo - HubSpot Style -->
|
||||
<Sidebar.Header class="px-3 py-4">
|
||||
<Sidebar.Menu>
|
||||
|
|
@ -258,13 +259,16 @@
|
|||
<a href="/" {...props} class="flex items-center gap-3">
|
||||
<!-- Logo Container -->
|
||||
<div
|
||||
class="flex size-9 items-center justify-center rounded-lg bg-gradient-to-br from-[#ff7a59] to-[#ff5c35] shadow-md shadow-[#ff7a59]/20 transition-transform duration-200 group-hover:scale-105"
|
||||
class="flex size-9 items-center justify-center transition-transform duration-200 group-hover:scale-105"
|
||||
>
|
||||
<img src={imgLogo} alt="Logo" class="size-5" />
|
||||
<img src={imgLogo} alt="Logo" class="size-8" />
|
||||
</div>
|
||||
<div class="grid flex-1 text-left leading-tight group-data-[collapsible=icon]:hidden">
|
||||
<span class="truncate text-[15px] font-semibold tracking-tight text-sidebar-foreground">{org_name}</span>
|
||||
<span class="truncate text-[11px] font-medium text-sidebar-foreground/50">
|
||||
<span
|
||||
class="text-sidebar-foreground truncate text-[15px] font-semibold tracking-tight"
|
||||
>{org_name}</span
|
||||
>
|
||||
<span class="text-sidebar-foreground/50 truncate text-[11px] font-medium">
|
||||
CRM Platform
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -275,21 +279,12 @@
|
|||
</Sidebar.Menu>
|
||||
</Sidebar.Header>
|
||||
|
||||
<!-- Quick Search Bar - HubSpot Style -->
|
||||
<div class="mx-3 mb-3 group-data-[collapsible=icon]:mx-2 group-data-[collapsible=icon]:hidden">
|
||||
<button
|
||||
class="flex h-9 w-full items-center gap-2 rounded-lg border border-sidebar-border/60 bg-sidebar-accent/30 px-3 text-sm text-sidebar-foreground/60 transition-all hover:border-sidebar-border hover:bg-sidebar-accent/50"
|
||||
>
|
||||
<Search class="size-4" />
|
||||
<span class="flex-1 text-left">Search...</span>
|
||||
<kbd class="hidden rounded bg-sidebar-accent px-1.5 py-0.5 text-[10px] font-medium text-sidebar-foreground/50 sm:inline-block">⌘K</kbd>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<Sidebar.Content class="px-2">
|
||||
<!-- CRM Section -->
|
||||
<Sidebar.Group>
|
||||
<Sidebar.GroupLabel class="mb-1 px-3 text-[10px] font-bold uppercase tracking-widest text-sidebar-foreground/40 group-data-[collapsible=icon]:hidden">
|
||||
<Sidebar.GroupLabel
|
||||
class="text-sidebar-foreground/40 mb-1 px-3 text-[10px] font-bold tracking-widest uppercase group-data-[collapsible=icon]:hidden"
|
||||
>
|
||||
CRM
|
||||
</Sidebar.GroupLabel>
|
||||
<Sidebar.GroupContent>
|
||||
|
|
@ -299,29 +294,30 @@
|
|||
<Sidebar.MenuButton
|
||||
isActive={currentPath === item.href}
|
||||
tooltipContent={item.label}
|
||||
class="hubspot-menu-item group/item relative h-9 rounded-lg px-3 transition-all duration-150
|
||||
class="nav-item group/item relative h-9 rounded-lg px-2.5 transition-all duration-200
|
||||
{currentPath === item.href
|
||||
? 'bg-[#fff1ee] text-[#ff5c35] dark:bg-[#ff7a59]/10 dark:text-[#ff7a59]'
|
||||
: 'text-sidebar-foreground/80 hover:bg-sidebar-accent hover:text-sidebar-foreground'}"
|
||||
? 'nav-item-active bg-[var(--color-primary-default)]/[0.08] text-[var(--color-primary-default)] shadow-[inset_0_1px_2px_rgba(0,0,0,0.04)] dark:bg-[var(--color-primary-default)]/15 dark:shadow-none'
|
||||
: 'text-sidebar-foreground/70 hover:bg-sidebar-accent/60 hover:text-sidebar-foreground'}"
|
||||
>
|
||||
{#snippet child({ props })}
|
||||
<a
|
||||
href={item.href}
|
||||
{...props}
|
||||
data-sveltekit-preload-data={item.preload || 'hover'}
|
||||
class="flex items-center gap-3"
|
||||
class="flex items-center gap-2.5"
|
||||
>
|
||||
<!-- Active indicator bar - HubSpot style left border -->
|
||||
{#if currentPath === item.href}
|
||||
<div class="absolute left-0 top-1/2 h-5 w-[3px] -translate-y-1/2 rounded-r-full bg-[#ff7a59]"></div>
|
||||
{/if}
|
||||
<div class="flex size-5 items-center justify-center">
|
||||
<div
|
||||
class="flex size-6 items-center justify-center rounded-md transition-all duration-200
|
||||
{currentPath === item.href
|
||||
? 'bg-[var(--color-primary-default)] text-white shadow-sm shadow-[var(--color-primary-default)]/25'
|
||||
: 'text-current group-hover/item:bg-sidebar-accent'}"
|
||||
>
|
||||
<item.icon
|
||||
class="size-[18px] transition-colors duration-150"
|
||||
strokeWidth={currentPath === item.href ? 2.25 : 1.75}
|
||||
class="size-[15px] transition-transform duration-200 {currentPath === item.href ? 'scale-105' : 'group-hover/item:scale-105'}"
|
||||
strokeWidth={2}
|
||||
/>
|
||||
</div>
|
||||
<span class="text-[13px] font-medium">{item.label}</span>
|
||||
<span class="text-[13px] {currentPath === item.href ? 'font-semibold' : 'font-medium'}">{item.label}</span>
|
||||
</a>
|
||||
{/snippet}
|
||||
</Sidebar.MenuButton>
|
||||
|
|
@ -333,7 +329,9 @@
|
|||
|
||||
<!-- Sales Section -->
|
||||
<Sidebar.Group class="mt-4">
|
||||
<Sidebar.GroupLabel class="mb-1 px-3 text-[10px] font-bold uppercase tracking-widest text-sidebar-foreground/40 group-data-[collapsible=icon]:hidden">
|
||||
<Sidebar.GroupLabel
|
||||
class="text-sidebar-foreground/40 mb-1 px-3 text-[10px] font-bold tracking-widest uppercase group-data-[collapsible=icon]:hidden"
|
||||
>
|
||||
Sales
|
||||
</Sidebar.GroupLabel>
|
||||
<Sidebar.GroupContent>
|
||||
|
|
@ -354,40 +352,48 @@
|
|||
{...props}
|
||||
isActive={hasActiveChild(item.children ?? [])}
|
||||
tooltipContent={item.label}
|
||||
class="hubspot-menu-item group/item h-9 rounded-lg px-3 transition-all duration-150
|
||||
class="nav-item group/item h-9 rounded-lg px-2.5 transition-all duration-200
|
||||
{hasActiveChild(item.children ?? [])
|
||||
? 'text-[#ff5c35] dark:text-[#ff7a59]'
|
||||
: 'text-sidebar-foreground/80 hover:bg-sidebar-accent hover:text-sidebar-foreground'}"
|
||||
? 'text-[var(--color-primary-default)]'
|
||||
: 'text-sidebar-foreground/70 hover:bg-sidebar-accent/60 hover:text-sidebar-foreground'}"
|
||||
>
|
||||
<div class="flex w-full items-center gap-3">
|
||||
<div class="flex size-5 items-center justify-center">
|
||||
<item.icon class="size-[18px]" strokeWidth={1.75} />
|
||||
<div class="flex w-full items-center gap-2.5">
|
||||
<div
|
||||
class="flex size-6 items-center justify-center rounded-md transition-all duration-200
|
||||
{hasActiveChild(item.children ?? [])
|
||||
? 'bg-[var(--color-primary-default)]/10 text-[var(--color-primary-default)]'
|
||||
: 'text-current group-hover/item:bg-sidebar-accent'}"
|
||||
>
|
||||
<item.icon class="size-[15px]" strokeWidth={2} />
|
||||
</div>
|
||||
<span class="text-[13px] font-medium">{item.label}</span>
|
||||
<span class="text-[13px] {hasActiveChild(item.children ?? []) ? 'font-semibold' : 'font-medium'}">{item.label}</span>
|
||||
<ChevronDown
|
||||
class="ml-auto size-4 text-sidebar-foreground/40 transition-transform duration-200 group-data-[state=open]/collapsible:rotate-180"
|
||||
class="text-sidebar-foreground/40 ml-auto size-4 transition-transform duration-200 group-data-[state=open]/collapsible:rotate-180"
|
||||
/>
|
||||
</div>
|
||||
</Sidebar.MenuButton>
|
||||
{/snippet}
|
||||
</Collapsible.Trigger>
|
||||
<Collapsible.Content>
|
||||
<Sidebar.MenuSub class="hubspot-submenu mt-1 border-l-2 border-sidebar-border/50 ml-[18px] pl-4 py-1">
|
||||
<Sidebar.MenuSub
|
||||
class="mt-1.5 ml-[11px] space-y-0.5 border-l border-sidebar-border/40 py-1 pl-3"
|
||||
>
|
||||
{#each item.children as navChild}
|
||||
<Sidebar.MenuSubItem>
|
||||
<Sidebar.MenuSubButton
|
||||
isActive={currentPath === navChild.href}
|
||||
class="hubspot-submenu-item group/subitem relative h-8 rounded-md px-2 transition-all duration-150
|
||||
class="nav-subitem group/subitem relative h-7 rounded-md px-2 transition-all duration-200
|
||||
{currentPath === navChild.href
|
||||
? 'bg-[#fff1ee] text-[#ff5c35] dark:bg-[#ff7a59]/10 dark:text-[#ff7a59]'
|
||||
: 'text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-foreground'}"
|
||||
? 'bg-[var(--color-primary-default)]/[0.08] text-[var(--color-primary-default)] dark:bg-[var(--color-primary-default)]/15'
|
||||
: 'text-sidebar-foreground/60 hover:bg-sidebar-accent/50 hover:text-sidebar-foreground'}"
|
||||
>
|
||||
{#snippet child({ props })}
|
||||
<a href={navChild.href} {...props} class="flex items-center gap-2.5">
|
||||
<div class="flex size-4 items-center justify-center">
|
||||
<navChild.icon class="size-3.5" strokeWidth={1.75} />
|
||||
</div>
|
||||
<span class="text-[12px] font-medium">{navChild.label}</span>
|
||||
<a href={navChild.href} {...props} class="flex items-center gap-2">
|
||||
<navChild.icon
|
||||
class="size-3.5 transition-colors duration-200"
|
||||
strokeWidth={currentPath === navChild.href ? 2.25 : 1.75}
|
||||
/>
|
||||
<span class="text-[12px] {currentPath === navChild.href ? 'font-semibold' : 'font-medium'}">{navChild.label}</span>
|
||||
</a>
|
||||
{/snippet}
|
||||
</Sidebar.MenuSubButton>
|
||||
|
|
@ -405,7 +411,9 @@
|
|||
|
||||
<!-- Support Section -->
|
||||
<Sidebar.Group class="mt-4">
|
||||
<Sidebar.GroupLabel class="mb-1 px-3 text-[10px] font-bold uppercase tracking-widest text-sidebar-foreground/40 group-data-[collapsible=icon]:hidden">
|
||||
<Sidebar.GroupLabel
|
||||
class="text-sidebar-foreground/40 mb-1 px-3 text-[10px] font-bold tracking-widest uppercase group-data-[collapsible=icon]:hidden"
|
||||
>
|
||||
Support
|
||||
</Sidebar.GroupLabel>
|
||||
<Sidebar.GroupContent>
|
||||
|
|
@ -415,28 +423,30 @@
|
|||
<Sidebar.MenuButton
|
||||
isActive={currentPath === item.href}
|
||||
tooltipContent={item.label}
|
||||
class="hubspot-menu-item group/item relative h-9 rounded-lg px-3 transition-all duration-150
|
||||
class="nav-item group/item relative h-9 rounded-lg px-2.5 transition-all duration-200
|
||||
{currentPath === item.href
|
||||
? 'bg-[#fff1ee] text-[#ff5c35] dark:bg-[#ff7a59]/10 dark:text-[#ff7a59]'
|
||||
: 'text-sidebar-foreground/80 hover:bg-sidebar-accent hover:text-sidebar-foreground'}"
|
||||
? 'nav-item-active bg-[var(--color-primary-default)]/[0.08] text-[var(--color-primary-default)] shadow-[inset_0_1px_2px_rgba(0,0,0,0.04)] dark:bg-[var(--color-primary-default)]/15 dark:shadow-none'
|
||||
: 'text-sidebar-foreground/70 hover:bg-sidebar-accent/60 hover:text-sidebar-foreground'}"
|
||||
>
|
||||
{#snippet child({ props })}
|
||||
<a
|
||||
href={item.href}
|
||||
{...props}
|
||||
data-sveltekit-preload-data={item.preload || 'hover'}
|
||||
class="flex items-center gap-3"
|
||||
class="flex items-center gap-2.5"
|
||||
>
|
||||
{#if currentPath === item.href}
|
||||
<div class="absolute left-0 top-1/2 h-5 w-[3px] -translate-y-1/2 rounded-r-full bg-[#ff7a59]"></div>
|
||||
{/if}
|
||||
<div class="flex size-5 items-center justify-center">
|
||||
<div
|
||||
class="flex size-6 items-center justify-center rounded-md transition-all duration-200
|
||||
{currentPath === item.href
|
||||
? 'bg-[var(--color-primary-default)] text-white shadow-sm shadow-[var(--color-primary-default)]/25'
|
||||
: 'text-current group-hover/item:bg-sidebar-accent'}"
|
||||
>
|
||||
<item.icon
|
||||
class="size-[18px] transition-colors duration-150"
|
||||
strokeWidth={currentPath === item.href ? 2.25 : 1.75}
|
||||
class="size-[15px] transition-transform duration-200 {currentPath === item.href ? 'scale-105' : 'group-hover/item:scale-105'}"
|
||||
strokeWidth={2}
|
||||
/>
|
||||
</div>
|
||||
<span class="text-[13px] font-medium">{item.label}</span>
|
||||
<span class="text-[13px] {currentPath === item.href ? 'font-semibold' : 'font-medium'}">{item.label}</span>
|
||||
</a>
|
||||
{/snippet}
|
||||
</Sidebar.MenuButton>
|
||||
|
|
@ -447,14 +457,14 @@
|
|||
</Sidebar.Group>
|
||||
</Sidebar.Content>
|
||||
|
||||
<Sidebar.Footer class="border-t border-sidebar-border/60 px-2 py-3">
|
||||
<Sidebar.Footer class="border-sidebar-border/60 border-t px-2 py-3">
|
||||
<Sidebar.Menu class="space-y-1">
|
||||
<!-- Collapse/Expand Toggle - HubSpot Style -->
|
||||
<Sidebar.MenuItem class="group-data-[collapsible=icon]:hidden">
|
||||
<Sidebar.MenuButton
|
||||
onclick={() => sidebar.toggle()}
|
||||
tooltipContent="Collapse sidebar"
|
||||
class="h-8 rounded-lg px-3 text-sidebar-foreground/60 transition-all duration-150 hover:bg-sidebar-accent hover:text-sidebar-foreground"
|
||||
class="text-sidebar-foreground/60 hover:bg-sidebar-accent hover:text-sidebar-foreground h-8 rounded-lg px-3 transition-all duration-150"
|
||||
>
|
||||
{#snippet child({ props })}
|
||||
<button {...props} class="flex w-full items-center gap-3">
|
||||
|
|
@ -470,7 +480,7 @@
|
|||
<Sidebar.MenuButton
|
||||
onclick={() => sidebar.toggle()}
|
||||
tooltipContent="Expand sidebar"
|
||||
class="flex h-8 w-full items-center justify-center rounded-lg text-sidebar-foreground/60 transition-all duration-150 hover:bg-sidebar-accent hover:text-sidebar-foreground"
|
||||
class="text-sidebar-foreground/60 hover:bg-sidebar-accent hover:text-sidebar-foreground flex h-8 w-full items-center justify-center rounded-lg transition-all duration-150"
|
||||
>
|
||||
{#snippet child({ props })}
|
||||
<button {...props} class="flex items-center justify-center">
|
||||
|
|
@ -480,63 +490,6 @@
|
|||
</Sidebar.MenuButton>
|
||||
</Sidebar.MenuItem>
|
||||
|
||||
<!-- Theme Switcher -->
|
||||
<Sidebar.MenuItem>
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
{#snippet child({ props })}
|
||||
<Sidebar.MenuButton
|
||||
{...props}
|
||||
tooltipContent="Theme"
|
||||
class="h-8 rounded-lg px-3 text-sidebar-foreground/60 transition-all duration-150 hover:bg-sidebar-accent hover:text-sidebar-foreground"
|
||||
>
|
||||
<div class="flex size-4 items-center justify-center">
|
||||
{#if theme === 'dark'}
|
||||
<Moon class="size-4 text-[#ff7a59]" strokeWidth={1.75} />
|
||||
{:else if theme === 'light'}
|
||||
<Sun class="size-4 text-amber-500" strokeWidth={1.75} />
|
||||
{:else}
|
||||
<Monitor class="size-4" strokeWidth={1.75} />
|
||||
{/if}
|
||||
</div>
|
||||
<span class="text-[12px] font-medium">Theme</span>
|
||||
<span
|
||||
class="ml-auto rounded-md bg-sidebar-accent px-1.5 py-0.5 text-[10px] font-medium capitalize text-sidebar-foreground/50"
|
||||
>
|
||||
{theme}
|
||||
</span>
|
||||
</Sidebar.MenuButton>
|
||||
{/snippet}
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content side="top" align="start" class="w-44">
|
||||
<DropdownMenu.Item inset={false} onclick={() => setTheme('light')} class="gap-2.5">
|
||||
<Sun class="size-4 text-amber-500" />
|
||||
<span>Light</span>
|
||||
{#if theme === 'light'}
|
||||
<div class="ml-auto size-2 rounded-full bg-[#ff7a59]"></div>
|
||||
{/if}
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item inset={false} onclick={() => setTheme('dark')} class="gap-2.5">
|
||||
<Moon class="size-4 text-[#ff7a59]" />
|
||||
<span>Dark</span>
|
||||
{#if theme === 'dark'}
|
||||
<div class="ml-auto size-2 rounded-full bg-[#ff7a59]"></div>
|
||||
{/if}
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item inset={false} onclick={() => setTheme('system')} class="gap-2.5">
|
||||
<Monitor class="size-4 text-muted-foreground" />
|
||||
<span>System</span>
|
||||
{#if theme === 'system'}
|
||||
<div class="ml-auto size-2 rounded-full bg-[#ff7a59]"></div>
|
||||
{/if}
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
</Sidebar.MenuItem>
|
||||
|
||||
<!-- Divider -->
|
||||
<div class="mx-3 my-2 h-px bg-sidebar-border/50 group-data-[collapsible=icon]:hidden"></div>
|
||||
|
||||
<!-- User Menu - HubSpot Style -->
|
||||
<Sidebar.MenuItem>
|
||||
<DropdownMenu.Root>
|
||||
|
|
@ -545,30 +498,36 @@
|
|||
<Sidebar.MenuButton
|
||||
size="lg"
|
||||
{...props}
|
||||
class="group/user h-12 rounded-lg px-2 transition-all duration-150 data-[state=open]:bg-sidebar-accent hover:bg-sidebar-accent"
|
||||
class="group/user data-[state=open]:bg-sidebar-accent hover:bg-sidebar-accent h-12 rounded-lg px-2 transition-all duration-150"
|
||||
>
|
||||
<!-- Avatar with status -->
|
||||
<div class="relative">
|
||||
{#if user.profilePhoto && !profileImageError}
|
||||
<img
|
||||
class="size-8 rounded-full object-cover ring-2 ring-sidebar-border/50 transition-all duration-150 group-hover/user:ring-[#ff7a59]/30"
|
||||
class="ring-sidebar-border/50 size-8 rounded-full object-cover ring-2 transition-all duration-150 group-hover/user:ring-[#ff7a59]/30"
|
||||
src={user.profilePhoto}
|
||||
alt="User avatar"
|
||||
onerror={() => profileImageError = true}
|
||||
onerror={() => (profileImageError = true)}
|
||||
/>
|
||||
{:else}
|
||||
<div class="flex size-8 items-center justify-center rounded-full bg-gradient-to-br from-[#ff7a59] to-[#ff5c35] text-xs font-semibold text-white ring-2 ring-sidebar-border/50 transition-all duration-150 group-hover/user:ring-[#ff7a59]/30">
|
||||
<div
|
||||
class="ring-sidebar-border/50 flex size-8 items-center justify-center rounded-full bg-gradient-to-br from-[#ff7a59] to-[#ff5c35] text-xs font-semibold text-white ring-2 transition-all duration-150 group-hover/user:ring-[#ff7a59]/30"
|
||||
>
|
||||
{getInitials(user.name)}
|
||||
</div>
|
||||
{/if}
|
||||
<!-- Online status indicator -->
|
||||
<div class="absolute -bottom-0.5 -right-0.5 size-2.5 rounded-full border-2 border-sidebar bg-emerald-500"></div>
|
||||
<div
|
||||
class="border-sidebar absolute -right-0.5 -bottom-0.5 size-2.5 rounded-full border-2 bg-emerald-500"
|
||||
></div>
|
||||
</div>
|
||||
<div class="grid flex-1 text-left leading-tight">
|
||||
<span class="truncate text-[13px] font-semibold text-sidebar-foreground">{user.name}</span>
|
||||
<span class="truncate text-[11px] text-sidebar-foreground/50">{user.email}</span>
|
||||
<span class="text-sidebar-foreground truncate text-[13px] font-semibold"
|
||||
>{user.name}</span
|
||||
>
|
||||
<span class="text-sidebar-foreground/50 truncate text-[11px]">{user.email}</span>
|
||||
</div>
|
||||
<ChevronsUpDown class="ml-auto size-4 text-sidebar-foreground/40" />
|
||||
<ChevronsUpDown class="text-sidebar-foreground/40 ml-auto size-4" />
|
||||
</Sidebar.MenuButton>
|
||||
{/snippet}
|
||||
</DropdownMenu.Trigger>
|
||||
|
|
@ -577,12 +536,18 @@
|
|||
align="start"
|
||||
class="w-[--bits-dropdown-menu-anchor-width] min-w-56"
|
||||
>
|
||||
<DropdownMenu.Label class="text-[10px] font-bold uppercase tracking-wider text-muted-foreground">
|
||||
<DropdownMenu.Label
|
||||
class="text-muted-foreground text-[10px] font-bold tracking-wider uppercase"
|
||||
>
|
||||
My Account
|
||||
</DropdownMenu.Label>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Group>
|
||||
<DropdownMenu.Item inset={false} onclick={() => navigateTo('/profile')} class="gap-2.5">
|
||||
<DropdownMenu.Item
|
||||
inset={false}
|
||||
onclick={() => navigateTo('/profile')}
|
||||
class="gap-2.5"
|
||||
>
|
||||
<User class="size-4" />
|
||||
<span>Profile</span>
|
||||
</DropdownMenu.Item>
|
||||
|
|
@ -602,14 +567,47 @@
|
|||
<Settings class="size-4" />
|
||||
<span>Settings</span>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item inset={false} onclick={() => navigateTo('/settings/tags')} class="gap-2.5">
|
||||
<DropdownMenu.Item
|
||||
inset={false}
|
||||
onclick={() => navigateTo('/settings/tags')}
|
||||
class="gap-2.5"
|
||||
>
|
||||
<Tag class="size-4" />
|
||||
<span>Tags</span>
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Group>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Label
|
||||
class="text-muted-foreground text-[10px] font-bold tracking-wider uppercase"
|
||||
>
|
||||
Theme
|
||||
</DropdownMenu.Label>
|
||||
<DropdownMenu.Group class="flex gap-1 px-2 py-1.5">
|
||||
<button
|
||||
onclick={() => setTheme('light')}
|
||||
class="flex flex-1 flex-col items-center gap-1 rounded-md px-2 py-2 transition-colors {theme === 'light' ? 'bg-[#ff7a59]/10 text-[#ff7a59]' : 'hover:bg-sidebar-accent text-muted-foreground hover:text-foreground'}"
|
||||
>
|
||||
<Sun class="size-4" />
|
||||
<span class="text-[10px] font-medium">Light</span>
|
||||
</button>
|
||||
<button
|
||||
onclick={() => setTheme('dark')}
|
||||
class="flex flex-1 flex-col items-center gap-1 rounded-md px-2 py-2 transition-colors {theme === 'dark' ? 'bg-[#ff7a59]/10 text-[#ff7a59]' : 'hover:bg-sidebar-accent text-muted-foreground hover:text-foreground'}"
|
||||
>
|
||||
<Moon class="size-4" />
|
||||
<span class="text-[10px] font-medium">Dark</span>
|
||||
</button>
|
||||
<button
|
||||
onclick={() => setTheme('system')}
|
||||
class="flex flex-1 flex-col items-center gap-1 rounded-md px-2 py-2 transition-colors {theme === 'system' ? 'bg-[#ff7a59]/10 text-[#ff7a59]' : 'hover:bg-sidebar-accent text-muted-foreground hover:text-foreground'}"
|
||||
>
|
||||
<Monitor class="size-4" />
|
||||
<span class="text-[10px] font-medium">System</span>
|
||||
</button>
|
||||
</DropdownMenu.Group>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item
|
||||
class="gap-2.5 text-destructive focus:bg-destructive/10 focus:text-destructive"
|
||||
class="text-destructive focus:bg-destructive/10 focus:text-destructive gap-2.5"
|
||||
inset={false}
|
||||
onclick={() => navigateTo('/logout')}
|
||||
>
|
||||
|
|
@ -626,46 +624,17 @@
|
|||
</Sidebar.Root>
|
||||
|
||||
<style>
|
||||
/* HubSpot-style sidebar refinements */
|
||||
/* Refined sidebar typography */
|
||||
:global(.hubspot-sidebar) {
|
||||
font-family: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
font-family:
|
||||
'Geist',
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
'Segoe UI',
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
:global(.hubspot-sidebar [data-slot="sidebar-menu-button"]) {
|
||||
:global(.hubspot-sidebar [data-slot='sidebar-menu-button']) {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Smooth hover transition for menu items */
|
||||
:global(.hubspot-menu-item) {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Active state animation */
|
||||
:global(.hubspot-menu-item::before) {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 0;
|
||||
height: 20px;
|
||||
background: #ff7a59;
|
||||
border-radius: 0 4px 4px 0;
|
||||
transition: width 0.15s ease;
|
||||
}
|
||||
|
||||
/* Submenu refinements */
|
||||
:global(.hubspot-submenu) {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
:global(.hubspot-submenu::before) {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 2px;
|
||||
background: linear-gradient(180deg, transparent, var(--sidebar-border), transparent);
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
*/
|
||||
export function getOptionStyle(value, options) {
|
||||
const option = options.find((o) => o.value === value);
|
||||
return option?.color ?? 'bg-gray-100 text-gray-600';
|
||||
return option?.color ?? 'bg-[var(--surface-sunken)] text-[var(--text-secondary)]';
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -119,231 +119,239 @@ export function getVisibleColumnCount(columns) {
|
|||
};
|
||||
}
|
||||
|
||||
// Common status options for CRM entities
|
||||
// Common status options for CRM entities - Using design system tokens
|
||||
// Must match Django LEAD_STATUS: assigned, in process, converted, recycled, closed
|
||||
export const leadStatusOptions = [
|
||||
{
|
||||
value: 'ASSIGNED',
|
||||
label: 'Assigned',
|
||||
color: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400'
|
||||
color: 'bg-[var(--stage-new-bg)] text-[var(--stage-new)] dark:bg-[var(--stage-new)]/15'
|
||||
},
|
||||
{
|
||||
value: 'IN_PROCESS',
|
||||
label: 'In Process',
|
||||
color: 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400'
|
||||
color: 'bg-[var(--stage-contacted-bg)] text-[var(--stage-contacted)] dark:bg-[var(--stage-contacted)]/15'
|
||||
},
|
||||
{
|
||||
value: 'CONVERTED',
|
||||
label: 'Converted',
|
||||
color: 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400'
|
||||
color: 'bg-[var(--stage-won-bg)] text-[var(--stage-won)] dark:bg-[var(--stage-won)]/15'
|
||||
},
|
||||
{
|
||||
value: 'RECYCLED',
|
||||
label: 'Recycled',
|
||||
color: 'bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400'
|
||||
color: 'bg-[var(--color-primary-light)] text-[var(--color-primary-default)] dark:bg-[var(--color-primary-default)]/15'
|
||||
},
|
||||
{
|
||||
value: 'CLOSED',
|
||||
label: 'Closed',
|
||||
color: 'bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400'
|
||||
color: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)]'
|
||||
}
|
||||
];
|
||||
|
||||
// Lead temperature ratings - using design system tokens
|
||||
export const leadRatingOptions = [
|
||||
{
|
||||
value: 'HOT',
|
||||
label: 'Hot',
|
||||
color: 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400'
|
||||
color: 'bg-[var(--lead-hot-bg)] text-[var(--lead-hot)] dark:bg-[var(--lead-hot)]/15'
|
||||
},
|
||||
{
|
||||
value: 'WARM',
|
||||
label: 'Warm',
|
||||
color: 'bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400'
|
||||
color: 'bg-[var(--lead-warm-bg)] text-[var(--lead-warm)] dark:bg-[var(--lead-warm)]/15'
|
||||
},
|
||||
{
|
||||
value: 'COLD',
|
||||
label: 'Cold',
|
||||
color: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400'
|
||||
color: 'bg-[var(--lead-cold-bg)] text-[var(--lead-cold)] dark:bg-[var(--lead-cold)]/15'
|
||||
}
|
||||
];
|
||||
|
||||
// Priority options - using design system tokens
|
||||
export const priorityOptions = [
|
||||
{
|
||||
value: 'URGENT',
|
||||
label: 'Urgent',
|
||||
color: 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400'
|
||||
color: 'bg-[var(--priority-urgent-bg)] text-[var(--priority-urgent)] dark:bg-[var(--priority-urgent)]/15'
|
||||
},
|
||||
{
|
||||
value: 'HIGH',
|
||||
label: 'High',
|
||||
color: 'bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400'
|
||||
color: 'bg-[var(--priority-high-bg)] text-[var(--priority-high)] dark:bg-[var(--priority-high)]/15'
|
||||
},
|
||||
{
|
||||
value: 'NORMAL',
|
||||
label: 'Normal',
|
||||
color: 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400'
|
||||
color: 'bg-[var(--priority-medium-bg)] text-[var(--priority-medium)] dark:bg-[var(--priority-medium)]/15'
|
||||
},
|
||||
{
|
||||
value: 'MEDIUM',
|
||||
label: 'Medium',
|
||||
color: 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400'
|
||||
color: 'bg-[var(--priority-medium-bg)] text-[var(--priority-medium)] dark:bg-[var(--priority-medium)]/15'
|
||||
},
|
||||
{
|
||||
value: 'LOW',
|
||||
label: 'Low',
|
||||
color: 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400'
|
||||
color: 'bg-[var(--priority-low-bg)] text-[var(--priority-low)] dark:bg-[var(--priority-low)]/15'
|
||||
}
|
||||
];
|
||||
|
||||
// Task status options - using design system tokens
|
||||
export const taskStatusOptions = [
|
||||
{
|
||||
value: 'NOT STARTED',
|
||||
label: 'Not Started',
|
||||
color: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400'
|
||||
color: 'bg-[var(--task-upcoming-bg)] text-[var(--task-upcoming)] dark:bg-[var(--task-upcoming)]/15'
|
||||
},
|
||||
{
|
||||
value: 'IN PROGRESS',
|
||||
label: 'In Progress',
|
||||
color: 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400'
|
||||
color: 'bg-[var(--task-due-today-bg)] text-[var(--task-due-today)] dark:bg-[var(--task-due-today)]/15'
|
||||
},
|
||||
{
|
||||
value: 'PENDING',
|
||||
label: 'Pending',
|
||||
color: 'bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400'
|
||||
color: 'bg-[var(--color-primary-light)] text-[var(--color-primary-default)] dark:bg-[var(--color-primary-default)]/15'
|
||||
},
|
||||
{
|
||||
value: 'COMPLETED',
|
||||
label: 'Completed',
|
||||
color: 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400'
|
||||
color: 'bg-[var(--task-completed-bg)] text-[var(--task-completed)] dark:bg-[var(--task-completed)]/15'
|
||||
},
|
||||
{
|
||||
value: 'DEFERRED',
|
||||
label: 'Deferred',
|
||||
color: 'bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400'
|
||||
color: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)]'
|
||||
}
|
||||
];
|
||||
|
||||
// Case status options - using design system tokens
|
||||
export const caseStatusOptions = [
|
||||
{
|
||||
value: 'New',
|
||||
label: 'New',
|
||||
color: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400'
|
||||
color: 'bg-[var(--stage-new-bg)] text-[var(--stage-new)] dark:bg-[var(--stage-new)]/15'
|
||||
},
|
||||
{
|
||||
value: 'Assigned',
|
||||
label: 'Assigned',
|
||||
color: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400'
|
||||
color: 'bg-[var(--stage-qualified-bg)] text-[var(--stage-qualified)] dark:bg-[var(--stage-qualified)]/15'
|
||||
},
|
||||
{
|
||||
value: 'Pending',
|
||||
label: 'Pending',
|
||||
color: 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400'
|
||||
color: 'bg-[var(--stage-negotiation-bg)] text-[var(--stage-negotiation)] dark:bg-[var(--stage-negotiation)]/15'
|
||||
},
|
||||
{
|
||||
value: 'Closed',
|
||||
label: 'Closed',
|
||||
color: 'bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400'
|
||||
color: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)]'
|
||||
},
|
||||
{
|
||||
value: 'Rejected',
|
||||
label: 'Rejected',
|
||||
color: 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400'
|
||||
color: 'bg-[var(--stage-lost-bg)] text-[var(--stage-lost)] dark:bg-[var(--stage-lost)]/15'
|
||||
},
|
||||
{
|
||||
value: 'Duplicate',
|
||||
label: 'Duplicate',
|
||||
color: 'bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400'
|
||||
color: 'bg-[var(--color-primary-light)] text-[var(--color-primary-default)] dark:bg-[var(--color-primary-default)]/15'
|
||||
}
|
||||
];
|
||||
|
||||
// Case type options - using design system tokens
|
||||
export const caseTypeOptions = [
|
||||
{
|
||||
value: 'Question',
|
||||
label: 'Question',
|
||||
color: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400'
|
||||
color: 'bg-[var(--stage-qualified-bg)] text-[var(--stage-qualified)] dark:bg-[var(--stage-qualified)]/15'
|
||||
},
|
||||
{
|
||||
value: 'Incident',
|
||||
label: 'Incident',
|
||||
color: 'bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400'
|
||||
color: 'bg-[var(--color-primary-light)] text-[var(--color-primary-default)] dark:bg-[var(--color-primary-default)]/15'
|
||||
},
|
||||
{
|
||||
value: 'Problem',
|
||||
label: 'Problem',
|
||||
color: 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400'
|
||||
color: 'bg-[var(--stage-lost-bg)] text-[var(--stage-lost)] dark:bg-[var(--stage-lost)]/15'
|
||||
}
|
||||
];
|
||||
|
||||
// Case priority options - using design system tokens
|
||||
export const casePriorityOptions = [
|
||||
{
|
||||
value: 'Low',
|
||||
label: 'Low',
|
||||
color: 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400'
|
||||
color: 'bg-[var(--priority-low-bg)] text-[var(--priority-low)] dark:bg-[var(--priority-low)]/15'
|
||||
},
|
||||
{
|
||||
value: 'Normal',
|
||||
label: 'Normal',
|
||||
color: 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400'
|
||||
color: 'bg-[var(--priority-medium-bg)] text-[var(--priority-medium)] dark:bg-[var(--priority-medium)]/15'
|
||||
},
|
||||
{
|
||||
value: 'High',
|
||||
label: 'High',
|
||||
color: 'bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400'
|
||||
color: 'bg-[var(--priority-high-bg)] text-[var(--priority-high)] dark:bg-[var(--priority-high)]/15'
|
||||
},
|
||||
{
|
||||
value: 'Urgent',
|
||||
label: 'Urgent',
|
||||
color: 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400'
|
||||
color: 'bg-[var(--priority-urgent-bg)] text-[var(--priority-urgent)] dark:bg-[var(--priority-urgent)]/15'
|
||||
}
|
||||
];
|
||||
|
||||
// Account status options - using design system tokens
|
||||
export const accountStatusOptions = [
|
||||
{
|
||||
value: 'ACTIVE',
|
||||
label: 'Active',
|
||||
color: 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400'
|
||||
color: 'bg-[var(--color-success-light)] text-[var(--color-success-default)] dark:bg-[var(--color-success-default)]/15'
|
||||
},
|
||||
{
|
||||
value: 'INACTIVE',
|
||||
label: 'Inactive',
|
||||
color: 'bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400'
|
||||
color: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)]'
|
||||
},
|
||||
{
|
||||
value: 'PENDING',
|
||||
label: 'Pending',
|
||||
color: 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400'
|
||||
color: 'bg-[var(--stage-negotiation-bg)] text-[var(--stage-negotiation)] dark:bg-[var(--stage-negotiation)]/15'
|
||||
}
|
||||
];
|
||||
|
||||
// Opportunity stage options - using design system tokens
|
||||
export const opportunityStageOptions = [
|
||||
{
|
||||
value: 'PROSPECTING',
|
||||
label: 'Prospecting',
|
||||
color: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400'
|
||||
color: 'bg-[var(--stage-new-bg)] text-[var(--stage-new)] dark:bg-[var(--stage-new)]/15'
|
||||
},
|
||||
{
|
||||
value: 'QUALIFICATION',
|
||||
label: 'Qualification',
|
||||
color: 'bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-400'
|
||||
color: 'bg-[var(--stage-qualified-bg)] text-[var(--stage-qualified)] dark:bg-[var(--stage-qualified)]/15'
|
||||
},
|
||||
{
|
||||
value: 'PROPOSAL',
|
||||
label: 'Proposal',
|
||||
color: 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400'
|
||||
color: 'bg-[var(--stage-proposal-bg)] text-[var(--stage-proposal)] dark:bg-[var(--stage-proposal)]/15'
|
||||
},
|
||||
{
|
||||
value: 'NEGOTIATION',
|
||||
label: 'Negotiation',
|
||||
color: 'bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400'
|
||||
color: 'bg-[var(--stage-negotiation-bg)] text-[var(--stage-negotiation)] dark:bg-[var(--stage-negotiation)]/15'
|
||||
},
|
||||
{
|
||||
value: 'CLOSED_WON',
|
||||
label: 'Closed Won',
|
||||
color: 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400'
|
||||
color: 'bg-[var(--stage-won-bg)] text-[var(--stage-won)] dark:bg-[var(--stage-won)]/15'
|
||||
},
|
||||
{
|
||||
value: 'CLOSED_LOST',
|
||||
label: 'Closed Lost',
|
||||
color: 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400'
|
||||
color: 'bg-[var(--stage-lost-bg)] text-[var(--stage-lost)] dark:bg-[var(--stage-lost)]/15'
|
||||
}
|
||||
];
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { DollarSign, TrendingUp, Target, Percent, AlertCircle, Zap, Sparkles } from '@lucide/svelte';
|
||||
import { DollarSign, TrendingUp, Target, Percent, AlertCircle, Sparkles } from '@lucide/svelte';
|
||||
import {
|
||||
KPICard,
|
||||
FocusBar,
|
||||
|
|
@ -46,26 +46,26 @@
|
|||
</svelte:head>
|
||||
|
||||
<div class="relative min-h-screen">
|
||||
<!-- Subtle gradient mesh background -->
|
||||
<!-- Subtle gradient mesh background with orange tint -->
|
||||
<div class="gradient-mesh noise-overlay pointer-events-none fixed inset-0 -z-10 opacity-50 dark:opacity-30"></div>
|
||||
|
||||
<div class="space-y-8 p-6 md:p-8">
|
||||
<!-- Header Section with gradient text -->
|
||||
<!-- Header Section with orange gradient accent -->
|
||||
<header class="animate-in-up">
|
||||
<div class="flex flex-col items-start justify-between gap-4 sm:flex-row sm:items-end">
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="flex size-10 items-center justify-center rounded-xl bg-gradient-to-br from-cyan-500 to-teal-600 shadow-lg dark:shadow-cyan-500/20">
|
||||
<div class="flex size-10 items-center justify-center rounded-[var(--radius-lg)] bg-gradient-to-br from-[var(--color-primary-default)] to-[var(--stage-contacted)] shadow-lg dark:shadow-[var(--color-primary-default)]/20">
|
||||
<Sparkles class="size-5 text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-muted-foreground text-sm font-medium">{getGreeting()}</p>
|
||||
<h1 class="text-foreground text-2xl font-bold tracking-tight md:text-3xl">
|
||||
<p class="text-[var(--text-secondary)] text-sm font-medium">{getGreeting()}</p>
|
||||
<h1 class="text-[var(--text-primary)] text-2xl font-bold tracking-tight md:text-3xl">
|
||||
Dashboard
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-muted-foreground max-w-md text-sm">
|
||||
<p class="text-[var(--text-secondary)] max-w-md text-sm">
|
||||
Here's what's happening with your CRM today. Stay on top of your pipeline and close more deals.
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -74,14 +74,14 @@
|
|||
|
||||
{#if data.error}
|
||||
<div
|
||||
class="animate-in-up stagger-1 flex items-center gap-4 rounded-xl border border-red-200/50 bg-red-50/50 p-5 backdrop-blur-sm dark:border-red-800/30 dark:bg-red-900/10"
|
||||
class="animate-in-up stagger-1 flex items-center gap-4 rounded-[var(--radius-lg)] border border-[var(--color-negative-default)]/20 bg-[var(--color-negative-light)] p-5 backdrop-blur-sm dark:border-[var(--color-negative-default)]/30 dark:bg-[var(--color-negative-default)]/10"
|
||||
>
|
||||
<div class="flex size-10 items-center justify-center rounded-lg bg-red-100 dark:bg-red-900/30">
|
||||
<AlertCircle class="size-5 text-red-500 dark:text-red-400" />
|
||||
<div class="flex size-10 items-center justify-center rounded-[var(--radius-md)] bg-[var(--color-negative-light)] dark:bg-[var(--color-negative-default)]/20">
|
||||
<AlertCircle class="size-5 text-[var(--color-negative-default)]" />
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm font-medium text-red-700 dark:text-red-300">Error loading dashboard</p>
|
||||
<p class="text-xs text-red-600/80 dark:text-red-400/80">{data.error}</p>
|
||||
<p class="text-sm font-medium text-[var(--color-negative-default)]">Error loading dashboard</p>
|
||||
<p class="text-xs text-[var(--color-negative-default)]/80">{data.error}</p>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
|
|
@ -96,15 +96,15 @@
|
|||
</div>
|
||||
|
||||
<!-- Pipeline Overview - Full Width with glass effect -->
|
||||
<div class="animate-in-up stagger-2 rounded-2xl border border-border/50 bg-card/80 p-6 shadow-sm backdrop-blur-sm dark:bg-card/50 dark:shadow-lg dark:shadow-black/10">
|
||||
<div class="animate-in-up stagger-2 rounded-[var(--radius-xl)] border border-[var(--border-default)] bg-[var(--surface-raised)] p-6 shadow-[var(--shadow-sm)] dark:bg-[var(--surface-raised)]/80 dark:backdrop-blur-sm dark:shadow-lg dark:shadow-black/10">
|
||||
<div class="mb-5 flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="flex size-9 items-center justify-center rounded-lg bg-gradient-to-br from-violet-500/10 to-purple-500/10 dark:from-violet-500/20 dark:to-purple-500/20">
|
||||
<TrendingUp class="size-5 text-violet-600 dark:text-violet-400" />
|
||||
<div class="flex size-9 items-center justify-center rounded-[var(--radius-md)] bg-[var(--color-primary-light)] dark:bg-[var(--color-primary-default)]/15">
|
||||
<TrendingUp class="size-5 text-[var(--color-primary-default)]" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="text-foreground text-base font-semibold tracking-tight">Sales Pipeline</h2>
|
||||
<p class="text-muted-foreground text-xs">{currencyNote}</p>
|
||||
<h2 class="text-[var(--text-primary)] text-base font-semibold tracking-tight">Sales Pipeline</h2>
|
||||
<p class="text-[var(--text-tertiary)] text-xs">{currencyNote}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -117,7 +117,7 @@
|
|||
label="Pipeline Value"
|
||||
value={formatCurrency(revenueMetrics.pipeline_value || 0, orgCurrency, true)}
|
||||
subtitle={currencyNote}
|
||||
accentColor="cyan"
|
||||
accentColor="orange"
|
||||
>
|
||||
{#snippet icon()}
|
||||
<DollarSign class="size-5" />
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@
|
|||
const currencyOptions = CURRENCY_CODES.filter((c) => c.value).map((c) => ({
|
||||
value: c.value,
|
||||
label: c.label,
|
||||
color: 'bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300'
|
||||
color: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)]'
|
||||
}));
|
||||
|
||||
// Base drawer columns (using $derived for dynamic currency symbol)
|
||||
|
|
@ -748,14 +748,14 @@
|
|||
onclick={() => (statusChipFilter = 'ALL')}
|
||||
class="inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-sm font-medium transition-colors {statusChipFilter ===
|
||||
'ALL'
|
||||
? 'bg-gray-900 text-white dark:bg-white dark:text-gray-900'
|
||||
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700'}"
|
||||
? 'bg-[var(--color-primary-default)] text-white'
|
||||
: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)] hover:bg-[var(--surface-raised)]'}"
|
||||
>
|
||||
All
|
||||
<span
|
||||
class="rounded-full px-1.5 py-0.5 text-xs {statusChipFilter === 'ALL'
|
||||
? 'bg-gray-700 text-gray-200 dark:bg-gray-200 dark:text-gray-700'
|
||||
: 'bg-gray-200 text-gray-500 dark:bg-gray-700 dark:text-gray-500'}"
|
||||
? 'bg-[var(--color-primary-dark)] text-white/90'
|
||||
: 'bg-[var(--border-default)] text-[var(--text-tertiary)]'}"
|
||||
>
|
||||
{accounts.length}
|
||||
</span>
|
||||
|
|
@ -765,14 +765,14 @@
|
|||
onclick={() => (statusChipFilter = 'active')}
|
||||
class="inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-sm font-medium transition-colors {statusChipFilter ===
|
||||
'active'
|
||||
? 'bg-emerald-600 text-white dark:bg-emerald-500'
|
||||
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700'}"
|
||||
? 'bg-[var(--color-success-default)] text-white'
|
||||
: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)] hover:bg-[var(--surface-raised)]'}"
|
||||
>
|
||||
Active
|
||||
<span
|
||||
class="rounded-full px-1.5 py-0.5 text-xs {statusChipFilter === 'active'
|
||||
? 'bg-emerald-700 text-emerald-100 dark:bg-emerald-600'
|
||||
: 'bg-gray-200 text-gray-500 dark:bg-gray-700 dark:text-gray-500'}"
|
||||
? 'bg-[var(--color-success-dark)] text-white/90'
|
||||
: 'bg-[var(--border-default)] text-[var(--text-tertiary)]'}"
|
||||
>
|
||||
{activeCount}
|
||||
</span>
|
||||
|
|
@ -782,14 +782,14 @@
|
|||
onclick={() => (statusChipFilter = 'closed')}
|
||||
class="inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-sm font-medium transition-colors {statusChipFilter ===
|
||||
'closed'
|
||||
? 'bg-gray-600 text-white dark:bg-gray-500'
|
||||
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700'}"
|
||||
? 'bg-[var(--text-secondary)] text-white'
|
||||
: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)] hover:bg-[var(--surface-raised)]'}"
|
||||
>
|
||||
Closed
|
||||
<span
|
||||
class="rounded-full px-1.5 py-0.5 text-xs {statusChipFilter === 'closed'
|
||||
? 'bg-gray-700 text-gray-200 dark:bg-gray-600'
|
||||
: 'bg-gray-200 text-gray-500 dark:bg-gray-700 dark:text-gray-500'}"
|
||||
? 'bg-[var(--text-tertiary)] text-white/90'
|
||||
: 'bg-[var(--border-default)] text-[var(--text-tertiary)]'}"
|
||||
>
|
||||
{closedCount}
|
||||
</span>
|
||||
|
|
@ -809,7 +809,7 @@
|
|||
Filters
|
||||
{#if activeFiltersCount > 0}
|
||||
<span
|
||||
class="rounded-full bg-blue-100 px-1.5 py-0.5 text-xs font-medium text-blue-700 dark:bg-blue-900/30 dark:text-blue-400"
|
||||
class="rounded-full bg-[var(--color-primary-light)] px-1.5 py-0.5 text-xs font-medium text-[var(--color-primary-default)]"
|
||||
>
|
||||
{activeFiltersCount}
|
||||
</span>
|
||||
|
|
@ -825,7 +825,7 @@
|
|||
Columns
|
||||
{#if visibleColumnCount < totalColumnCount}
|
||||
<span
|
||||
class="rounded-full bg-blue-100 px-1.5 py-0.5 text-xs font-medium text-blue-700 dark:bg-blue-900/30 dark:text-blue-400"
|
||||
class="rounded-full bg-[var(--color-primary-light)] px-1.5 py-0.5 text-xs font-medium text-[var(--color-primary-default)]"
|
||||
>
|
||||
{visibleColumnCount}/{totalColumnCount}
|
||||
</span>
|
||||
|
|
@ -892,8 +892,11 @@
|
|||
<!-- Accounts Table -->
|
||||
{#if filteredAccounts.length === 0}
|
||||
<div class="flex flex-col items-center justify-center py-16 text-center">
|
||||
<Building2 class="text-muted-foreground/50 mb-4 h-12 w-12" />
|
||||
<h3 class="text-foreground text-lg font-medium">No accounts found</h3>
|
||||
<div class="mb-4 flex size-16 items-center justify-center rounded-[var(--radius-xl)] bg-[var(--surface-sunken)]">
|
||||
<Building2 class="size-8 text-[var(--text-tertiary)]" />
|
||||
</div>
|
||||
<h3 class="text-[var(--text-primary)] text-lg font-medium">No accounts found</h3>
|
||||
<p class="text-[var(--text-secondary)] mt-1 text-sm">Try adjusting your filters or create a new account</p>
|
||||
</div>
|
||||
{:else}
|
||||
<!-- Desktop Table using CrmTable -->
|
||||
|
|
@ -906,42 +909,44 @@
|
|||
>
|
||||
{#snippet emptyState()}
|
||||
<div class="flex flex-col items-center justify-center py-16 text-center">
|
||||
<Building2 class="text-muted-foreground/50 mb-4 h-12 w-12" />
|
||||
<h3 class="text-foreground text-lg font-medium">No accounts found</h3>
|
||||
<div class="mb-4 flex size-16 items-center justify-center rounded-[var(--radius-xl)] bg-[var(--surface-sunken)]">
|
||||
<Building2 class="size-8 text-[var(--text-tertiary)]" />
|
||||
</div>
|
||||
<h3 class="text-[var(--text-primary)] text-lg font-medium">No accounts found</h3>
|
||||
</div>
|
||||
{/snippet}
|
||||
</CrmTable>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Card View -->
|
||||
<div class="divide-y md:hidden dark:divide-gray-800">
|
||||
<div class="divide-y divide-[var(--border-default)] md:hidden">
|
||||
{#each filteredAccounts as account (account.id)}
|
||||
<button
|
||||
type="button"
|
||||
class="flex w-full items-start gap-4 p-4 text-left transition-colors hover:bg-gray-50 dark:hover:bg-gray-800 {!account.isActive
|
||||
class="flex w-full items-start gap-4 p-4 text-left transition-colors hover:bg-[var(--surface-sunken)] {!account.isActive
|
||||
? 'opacity-60'
|
||||
: ''}"
|
||||
onclick={() => openAccount(account)}
|
||||
>
|
||||
<div
|
||||
class="flex h-12 w-12 shrink-0 items-center justify-center rounded-lg bg-gradient-to-br from-blue-500 to-blue-600 text-sm font-medium text-white"
|
||||
class="flex size-12 shrink-0 items-center justify-center rounded-[var(--radius-lg)] bg-[var(--color-primary-default)] text-sm font-medium text-white"
|
||||
>
|
||||
{getAccountInitials(account)}
|
||||
</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="flex items-start justify-between gap-2">
|
||||
<div>
|
||||
<p class="font-medium text-gray-900 dark:text-gray-100">{account.name}</p>
|
||||
<p class="font-medium text-[var(--text-primary)]">{account.name}</p>
|
||||
<div class="mt-1 flex items-center gap-1.5">
|
||||
{#if account.isActive !== false}
|
||||
<span
|
||||
class="inline-flex items-center rounded-full bg-emerald-100 px-2.5 py-1 text-xs font-medium text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400"
|
||||
class="inline-flex items-center rounded-full bg-[var(--color-success-light)] px-2.5 py-1 text-xs font-medium text-[var(--color-success-default)] dark:bg-[var(--color-success-default)]/15"
|
||||
>
|
||||
Active
|
||||
</span>
|
||||
{:else}
|
||||
<span
|
||||
class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-1 text-xs font-medium text-gray-600 dark:bg-gray-800 dark:text-gray-400"
|
||||
class="inline-flex items-center rounded-full bg-[var(--surface-sunken)] px-2.5 py-1 text-xs font-medium text-[var(--text-secondary)]"
|
||||
>
|
||||
Closed
|
||||
</span>
|
||||
|
|
@ -950,21 +955,21 @@
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mt-2 flex flex-wrap items-center gap-3 text-sm text-gray-500 dark:text-gray-400"
|
||||
class="mt-2 flex flex-wrap items-center gap-3 text-sm text-[var(--text-secondary)]"
|
||||
>
|
||||
{#if account.industry}
|
||||
<span>{account.industry}</span>
|
||||
{/if}
|
||||
<div class="flex items-center gap-1">
|
||||
<Users class="h-3.5 w-3.5 text-gray-400 dark:text-gray-500" />
|
||||
<Users class="size-3.5 text-[var(--text-tertiary)]" />
|
||||
<span>{account.contactCount || 0}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1">
|
||||
<Target class="h-3.5 w-3.5 text-gray-400 dark:text-gray-500" />
|
||||
<Target class="size-3.5 text-[var(--text-tertiary)]" />
|
||||
<span>{account.opportunityCount || 0}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1">
|
||||
<Calendar class="h-3.5 w-3.5 text-gray-400 dark:text-gray-500" />
|
||||
<Calendar class="size-3.5 text-[var(--text-tertiary)]" />
|
||||
<span>{formatRelativeDate(account.createdAt)}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1003,13 +1008,13 @@
|
|||
<!-- Closed account warning -->
|
||||
{#if isClosed && drawerMode !== 'create'}
|
||||
<div
|
||||
class="mb-4 rounded-lg border border-red-200 bg-red-50 p-3 dark:border-red-800 dark:bg-red-900/30"
|
||||
class="mb-4 rounded-[var(--radius-lg)] border border-[var(--color-negative-light)] bg-[var(--color-negative-light)] p-3 dark:border-[var(--color-negative-default)]/30 dark:bg-[var(--color-negative-default)]/10"
|
||||
>
|
||||
<div class="flex gap-2">
|
||||
<AlertTriangle class="mt-0.5 h-4 w-4 shrink-0 text-red-500 dark:text-red-400" />
|
||||
<AlertTriangle class="mt-0.5 size-4 shrink-0 text-[var(--color-negative-default)]" />
|
||||
<div>
|
||||
<p class="text-sm font-medium text-red-700 dark:text-red-400">This account is closed</p>
|
||||
<p class="mt-0.5 text-xs text-red-600 dark:text-red-500">
|
||||
<p class="text-sm font-medium text-[var(--color-negative-default)]">This account is closed</p>
|
||||
<p class="mt-0.5 text-xs text-[var(--color-negative-default)]/80">
|
||||
Reopen the account to make changes
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -1021,46 +1026,46 @@
|
|||
{#if drawerMode !== 'create' && selectedAccount}
|
||||
<div class="mb-4">
|
||||
<p
|
||||
class="mb-2 text-xs font-medium tracking-wider text-gray-500 uppercase dark:text-gray-400"
|
||||
class="mb-2 text-xs font-medium tracking-wider text-[var(--text-tertiary)] uppercase"
|
||||
>
|
||||
Related
|
||||
</p>
|
||||
<div class="grid grid-cols-4 gap-2">
|
||||
<div class="rounded-lg bg-gray-50 p-2 text-center dark:bg-gray-800">
|
||||
<div class="flex items-center justify-center gap-1 text-gray-400 dark:text-gray-500">
|
||||
<Users class="h-3.5 w-3.5" />
|
||||
<div class="rounded-[var(--radius-lg)] bg-[var(--surface-sunken)] p-2 text-center">
|
||||
<div class="flex items-center justify-center gap-1 text-[var(--text-tertiary)]">
|
||||
<Users class="size-3.5" />
|
||||
</div>
|
||||
<p class="mt-0.5 text-lg font-semibold text-gray-900 dark:text-gray-100">
|
||||
<p class="mt-0.5 text-lg font-semibold text-[var(--text-primary)]">
|
||||
{selectedAccount.contactCount || 0}
|
||||
</p>
|
||||
<p class="text-[10px] text-gray-500 dark:text-gray-400">Contacts</p>
|
||||
<p class="text-[10px] text-[var(--text-secondary)]">Contacts</p>
|
||||
</div>
|
||||
<div class="rounded-lg bg-gray-50 p-2 text-center dark:bg-gray-800">
|
||||
<div class="flex items-center justify-center gap-1 text-gray-400 dark:text-gray-500">
|
||||
<Target class="h-3.5 w-3.5" />
|
||||
<div class="rounded-[var(--radius-lg)] bg-[var(--surface-sunken)] p-2 text-center">
|
||||
<div class="flex items-center justify-center gap-1 text-[var(--text-tertiary)]">
|
||||
<Target class="size-3.5" />
|
||||
</div>
|
||||
<p class="mt-0.5 text-lg font-semibold text-gray-900 dark:text-gray-100">
|
||||
<p class="mt-0.5 text-lg font-semibold text-[var(--text-primary)]">
|
||||
{selectedAccount.opportunityCount || 0}
|
||||
</p>
|
||||
<p class="text-[10px] text-gray-500 dark:text-gray-400">Opportunities</p>
|
||||
<p class="text-[10px] text-[var(--text-secondary)]">Opportunities</p>
|
||||
</div>
|
||||
<div class="rounded-lg bg-gray-50 p-2 text-center dark:bg-gray-800">
|
||||
<div class="flex items-center justify-center gap-1 text-gray-400 dark:text-gray-500">
|
||||
<AlertTriangle class="h-3.5 w-3.5" />
|
||||
<div class="rounded-[var(--radius-lg)] bg-[var(--surface-sunken)] p-2 text-center">
|
||||
<div class="flex items-center justify-center gap-1 text-[var(--text-tertiary)]">
|
||||
<AlertTriangle class="size-3.5" />
|
||||
</div>
|
||||
<p class="mt-0.5 text-lg font-semibold text-gray-900 dark:text-gray-100">
|
||||
<p class="mt-0.5 text-lg font-semibold text-[var(--text-primary)]">
|
||||
{selectedAccount.caseCount || 0}
|
||||
</p>
|
||||
<p class="text-[10px] text-gray-500 dark:text-gray-400">Cases</p>
|
||||
<p class="text-[10px] text-[var(--text-secondary)]">Cases</p>
|
||||
</div>
|
||||
<div class="rounded-lg bg-gray-50 p-2 text-center dark:bg-gray-800">
|
||||
<div class="flex items-center justify-center gap-1 text-gray-400 dark:text-gray-500">
|
||||
<CheckSquare class="h-3.5 w-3.5" />
|
||||
<div class="rounded-[var(--radius-lg)] bg-[var(--surface-sunken)] p-2 text-center">
|
||||
<div class="flex items-center justify-center gap-1 text-[var(--text-tertiary)]">
|
||||
<CheckSquare class="size-3.5" />
|
||||
</div>
|
||||
<p class="mt-0.5 text-lg font-semibold text-gray-900 dark:text-gray-100">
|
||||
<p class="mt-0.5 text-lg font-semibold text-[var(--text-primary)]">
|
||||
{selectedAccount.taskCount || 0}
|
||||
</p>
|
||||
<p class="text-[10px] text-gray-500 dark:text-gray-400">Tasks</p>
|
||||
<p class="text-[10px] text-[var(--text-secondary)]">Tasks</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1069,7 +1074,7 @@
|
|||
{#if !isClosed}
|
||||
<div class="mb-4">
|
||||
<p
|
||||
class="mb-2 text-xs font-medium tracking-wider text-gray-500 uppercase dark:text-gray-400"
|
||||
class="mb-2 text-xs font-medium tracking-wider text-[var(--text-tertiary)] uppercase"
|
||||
>
|
||||
Quick Actions
|
||||
</p>
|
||||
|
|
@ -1099,20 +1104,20 @@
|
|||
<!-- Metadata -->
|
||||
<div>
|
||||
<p
|
||||
class="mb-2 text-xs font-medium tracking-wider text-gray-500 uppercase dark:text-gray-400"
|
||||
class="mb-2 text-xs font-medium tracking-wider text-[var(--text-tertiary)] uppercase"
|
||||
>
|
||||
Details
|
||||
</p>
|
||||
<div class="grid grid-cols-2 gap-3 text-sm">
|
||||
<div>
|
||||
<p class="text-xs text-gray-400 dark:text-gray-500">Owner</p>
|
||||
<p class="font-medium text-gray-900 dark:text-gray-100">
|
||||
<p class="text-xs text-[var(--text-tertiary)]">Owner</p>
|
||||
<p class="font-medium text-[var(--text-primary)]">
|
||||
{selectedAccount.owner?.name || 'Unassigned'}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs text-gray-400 dark:text-gray-500">Created</p>
|
||||
<p class="font-medium text-gray-900 dark:text-gray-100">
|
||||
<p class="text-xs text-[var(--text-tertiary)]">Created</p>
|
||||
<p class="font-medium text-[var(--text-primary)]">
|
||||
{formatRelativeDate(selectedAccount.createdAt)}
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -1120,7 +1125,7 @@
|
|||
</div>
|
||||
|
||||
<!-- Comments Section -->
|
||||
<div class="mt-6 border-t pt-4 dark:border-gray-700">
|
||||
<div class="mt-6 border-t border-[var(--border-default)] pt-4">
|
||||
<CommentSection
|
||||
entityId={selectedAccount.id}
|
||||
entityType="accounts"
|
||||
|
|
@ -1142,22 +1147,22 @@
|
|||
<Button variant="outline" onclick={closeDrawer} disabled={isSubmitting}>Cancel</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
class="text-green-600 hover:text-green-700 dark:text-green-400 dark:hover:text-green-300"
|
||||
class="text-[var(--color-success-default)] hover:text-[var(--color-success-dark)]"
|
||||
onclick={handleReopen}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
<Unlock class="mr-1.5 h-4 w-4" />
|
||||
<Unlock class="mr-1.5 size-4" />
|
||||
Reopen Account
|
||||
</Button>
|
||||
{:else}
|
||||
<Button variant="outline" onclick={closeDrawer} disabled={isSubmitting}>Cancel</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
class="text-orange-600 hover:text-orange-700 dark:text-orange-400 dark:hover:text-orange-300"
|
||||
class="text-[var(--color-primary-default)] hover:text-[var(--color-primary-dark)]"
|
||||
onclick={handleClose}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
<Lock class="mr-1.5 h-4 w-4" />
|
||||
<Lock class="mr-1.5 size-4" />
|
||||
Close Account
|
||||
</Button>
|
||||
<Button onclick={handleDrawerUpdate} disabled={isSubmitting || !drawerFormData.name?.trim()}>
|
||||
|
|
|
|||
|
|
@ -253,11 +253,11 @@
|
|||
|
||||
// Account options for drawer select
|
||||
const accountOptions = $derived([
|
||||
{ value: '', label: 'None', color: 'bg-gray-100 text-gray-600' },
|
||||
{ value: '', label: 'None', color: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)]' },
|
||||
...accounts.map((/** @type {any} */ a) => ({
|
||||
value: a.id,
|
||||
label: a.name,
|
||||
color: 'bg-blue-100 text-blue-700'
|
||||
color: 'bg-[var(--color-primary-light)] text-[var(--color-primary-default)]'
|
||||
}))
|
||||
]);
|
||||
|
||||
|
|
@ -801,14 +801,14 @@
|
|||
onclick={() => (statusChipFilter = 'ALL')}
|
||||
class="inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-sm font-medium transition-colors {statusChipFilter ===
|
||||
'ALL'
|
||||
? 'bg-gray-900 text-white dark:bg-white dark:text-gray-900'
|
||||
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700'}"
|
||||
? 'bg-[var(--color-primary-default)] text-white'
|
||||
: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)] hover:bg-[var(--surface-raised)]'}"
|
||||
>
|
||||
All
|
||||
<span
|
||||
class="rounded-full px-1.5 py-0.5 text-xs {statusChipFilter === 'ALL'
|
||||
? 'bg-gray-700 text-gray-200 dark:bg-gray-200 dark:text-gray-700'
|
||||
: 'bg-gray-200 text-gray-500 dark:bg-gray-700 dark:text-gray-500'}"
|
||||
? 'bg-[var(--color-primary-dark)] text-white/90'
|
||||
: 'bg-[var(--border-default)] text-[var(--text-tertiary)]'}"
|
||||
>
|
||||
{casesData.length}
|
||||
</span>
|
||||
|
|
@ -818,14 +818,14 @@
|
|||
onclick={() => (statusChipFilter = 'open')}
|
||||
class="inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-sm font-medium transition-colors {statusChipFilter ===
|
||||
'open'
|
||||
? 'bg-blue-600 text-white dark:bg-blue-500'
|
||||
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700'}"
|
||||
? 'bg-[var(--stage-qualified)] text-white'
|
||||
: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)] hover:bg-[var(--surface-raised)]'}"
|
||||
>
|
||||
Open
|
||||
<span
|
||||
class="rounded-full px-1.5 py-0.5 text-xs {statusChipFilter === 'open'
|
||||
? 'bg-blue-700 text-blue-100 dark:bg-blue-600'
|
||||
: 'bg-gray-200 text-gray-500 dark:bg-gray-700 dark:text-gray-500'}"
|
||||
? 'bg-black/20 text-white/90'
|
||||
: 'bg-[var(--border-default)] text-[var(--text-tertiary)]'}"
|
||||
>
|
||||
{openCount}
|
||||
</span>
|
||||
|
|
@ -835,14 +835,14 @@
|
|||
onclick={() => (statusChipFilter = 'closed')}
|
||||
class="inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-sm font-medium transition-colors {statusChipFilter ===
|
||||
'closed'
|
||||
? 'bg-gray-600 text-white dark:bg-gray-500'
|
||||
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700'}"
|
||||
? 'bg-[var(--text-secondary)] text-white'
|
||||
: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)] hover:bg-[var(--surface-raised)]'}"
|
||||
>
|
||||
Closed
|
||||
<span
|
||||
class="rounded-full px-1.5 py-0.5 text-xs {statusChipFilter === 'closed'
|
||||
? 'bg-gray-700 text-gray-200 dark:bg-gray-600'
|
||||
: 'bg-gray-200 text-gray-500 dark:bg-gray-700 dark:text-gray-500'}"
|
||||
? 'bg-[var(--text-tertiary)] text-white/90'
|
||||
: 'bg-[var(--border-default)] text-[var(--text-tertiary)]'}"
|
||||
>
|
||||
{closedCount}
|
||||
</span>
|
||||
|
|
@ -886,7 +886,7 @@
|
|||
Filters
|
||||
{#if activeFiltersCount > 0}
|
||||
<span
|
||||
class="rounded-full bg-blue-100 px-1.5 py-0.5 text-xs font-medium text-blue-700 dark:bg-blue-900/30 dark:text-blue-400"
|
||||
class="rounded-full bg-[var(--color-primary-light)] px-1.5 py-0.5 text-xs font-medium text-[var(--color-primary-default)]"
|
||||
>
|
||||
{activeFiltersCount}
|
||||
</span>
|
||||
|
|
@ -901,7 +901,7 @@
|
|||
Columns
|
||||
{#if columnCounts.visible < columnCounts.total}
|
||||
<span
|
||||
class="rounded-full bg-blue-100 px-1.5 py-0.5 text-xs font-medium text-blue-700 dark:bg-blue-900/30 dark:text-blue-400"
|
||||
class="rounded-full bg-[var(--color-primary-light)] px-1.5 py-0.5 text-xs font-medium text-[var(--color-primary-default)]"
|
||||
>
|
||||
{columnCounts.visible}/{columnCounts.total}
|
||||
</span>
|
||||
|
|
@ -983,8 +983,11 @@
|
|||
>
|
||||
{#snippet emptyState()}
|
||||
<div class="flex flex-col items-center justify-center py-16 text-center">
|
||||
<Briefcase class="text-muted-foreground/50 mb-4 h-12 w-12" />
|
||||
<h3 class="text-foreground text-lg font-medium">No cases found</h3>
|
||||
<div class="mb-4 flex size-16 items-center justify-center rounded-[var(--radius-xl)] bg-[var(--surface-sunken)]">
|
||||
<Briefcase class="size-8 text-[var(--text-tertiary)]" />
|
||||
</div>
|
||||
<h3 class="text-[var(--text-primary)] text-lg font-medium">No cases found</h3>
|
||||
<p class="text-[var(--text-secondary)] mt-1 text-sm">Try adjusting your filters or create a new case</p>
|
||||
</div>
|
||||
{/snippet}
|
||||
</CrmTable>
|
||||
|
|
|
|||
|
|
@ -738,7 +738,7 @@
|
|||
Filters
|
||||
{#if activeFiltersCount > 0}
|
||||
<span
|
||||
class="rounded-full bg-blue-100 px-1.5 py-0.5 text-xs font-medium text-blue-700 dark:bg-blue-900/30 dark:text-blue-400"
|
||||
class="rounded-full bg-[var(--color-primary-light)] px-1.5 py-0.5 text-xs font-medium text-[var(--color-primary-default)] dark:bg-[var(--color-primary-default)]/15"
|
||||
>
|
||||
{activeFiltersCount}
|
||||
</span>
|
||||
|
|
@ -754,7 +754,7 @@
|
|||
Columns
|
||||
{#if visibleColumns.length < columns.length}
|
||||
<span
|
||||
class="rounded-full bg-blue-100 px-1.5 py-0.5 text-xs font-medium text-blue-700 dark:bg-blue-900/30 dark:text-blue-400"
|
||||
class="rounded-full bg-[var(--color-primary-light)] px-1.5 py-0.5 text-xs font-medium text-[var(--color-primary-default)] dark:bg-[var(--color-primary-default)]/15"
|
||||
>
|
||||
{visibleColumns.length}/{columns.length}
|
||||
</span>
|
||||
|
|
@ -819,8 +819,11 @@
|
|||
<!-- Table -->
|
||||
{#if filteredContacts.length === 0}
|
||||
<div class="flex flex-col items-center justify-center py-16 text-center">
|
||||
<User class="text-muted-foreground/50 mb-4 h-12 w-12" />
|
||||
<h3 class="text-foreground text-lg font-medium">No contacts found</h3>
|
||||
<div class="mb-4 flex size-16 items-center justify-center rounded-[var(--radius-xl)] bg-[var(--surface-sunken)]">
|
||||
<User class="size-8 text-[var(--text-tertiary)]" />
|
||||
</div>
|
||||
<h3 class="text-lg font-medium text-[var(--text-primary)]">No contacts found</h3>
|
||||
<p class="mt-1 text-sm text-[var(--text-secondary)]">Create a new contact to get started</p>
|
||||
</div>
|
||||
{:else}
|
||||
<CrmTable
|
||||
|
|
@ -832,8 +835,10 @@
|
|||
>
|
||||
{#snippet emptyState()}
|
||||
<div class="flex flex-col items-center justify-center py-16 text-center">
|
||||
<User class="text-muted-foreground/50 mb-4 h-12 w-12" />
|
||||
<h3 class="text-foreground text-lg font-medium">No contacts found</h3>
|
||||
<div class="mb-4 flex size-12 items-center justify-center rounded-[var(--radius-lg)] bg-[var(--surface-sunken)]">
|
||||
<User class="size-6 text-[var(--text-tertiary)]" />
|
||||
</div>
|
||||
<h3 class="text-lg font-medium text-[var(--text-primary)]">No contacts found</h3>
|
||||
</div>
|
||||
{/snippet}
|
||||
</CrmTable>
|
||||
|
|
@ -870,20 +875,20 @@
|
|||
{#if drawerMode !== 'create' && selectedContact}
|
||||
<div>
|
||||
<p
|
||||
class="mb-2 text-xs font-medium tracking-wider text-gray-500 uppercase dark:text-gray-400"
|
||||
class="mb-2 text-xs font-medium tracking-wider text-[var(--text-tertiary)] uppercase"
|
||||
>
|
||||
Details
|
||||
</p>
|
||||
<div class="grid grid-cols-2 gap-3 text-sm">
|
||||
<div>
|
||||
<p class="text-xs text-gray-400 dark:text-gray-500">Owner</p>
|
||||
<p class="font-medium text-gray-900 dark:text-gray-100">
|
||||
<p class="text-xs text-[var(--text-tertiary)]">Owner</p>
|
||||
<p class="font-medium text-[var(--text-primary)]">
|
||||
{selectedContact.owner?.name || 'Unassigned'}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs text-gray-400 dark:text-gray-500">Created</p>
|
||||
<p class="font-medium text-gray-900 dark:text-gray-100">
|
||||
<p class="text-xs text-[var(--text-tertiary)]">Created</p>
|
||||
<p class="font-medium text-[var(--text-primary)]">
|
||||
{formatRelativeDate(selectedContact.createdAt)}
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -891,7 +896,7 @@
|
|||
</div>
|
||||
|
||||
<!-- Comments Section -->
|
||||
<div class="mt-6 border-t pt-4 dark:border-gray-700">
|
||||
<div class="mt-6 border-t border-[var(--border-default)] pt-4">
|
||||
<CommentSection
|
||||
entityId={selectedContact.id}
|
||||
entityType="contacts"
|
||||
|
|
|
|||
|
|
@ -14,42 +14,42 @@
|
|||
/** @type {{ data: import('./$types').PageData }} */
|
||||
let { data } = $props();
|
||||
|
||||
// Invoice status options
|
||||
// Invoice status options - using design system tokens
|
||||
const INVOICE_STATUSES = [
|
||||
{
|
||||
value: 'Draft',
|
||||
label: 'Draft',
|
||||
color: 'bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300'
|
||||
color: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)]'
|
||||
},
|
||||
{
|
||||
value: 'Sent',
|
||||
label: 'Sent',
|
||||
color: 'bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300'
|
||||
color: 'bg-[var(--stage-contacted-bg)] text-[var(--stage-contacted)] dark:bg-[var(--stage-contacted)]/15'
|
||||
},
|
||||
{
|
||||
value: 'Viewed',
|
||||
label: 'Viewed',
|
||||
color: 'bg-indigo-100 text-indigo-700 dark:bg-indigo-900 dark:text-indigo-300'
|
||||
color: 'bg-[var(--stage-qualified-bg)] text-[var(--stage-qualified)] dark:bg-[var(--stage-qualified)]/15'
|
||||
},
|
||||
{
|
||||
value: 'Partially_Paid',
|
||||
label: 'Partially Paid',
|
||||
color: 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900 dark:text-yellow-300'
|
||||
color: 'bg-[var(--stage-negotiation-bg)] text-[var(--stage-negotiation)] dark:bg-[var(--stage-negotiation)]/15'
|
||||
},
|
||||
{
|
||||
value: 'Paid',
|
||||
label: 'Paid',
|
||||
color: 'bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-300'
|
||||
color: 'bg-[var(--color-success-light)] text-[var(--color-success-default)] dark:bg-[var(--color-success-default)]/15'
|
||||
},
|
||||
{
|
||||
value: 'Overdue',
|
||||
label: 'Overdue',
|
||||
color: 'bg-red-100 text-red-700 dark:bg-red-900 dark:text-red-300'
|
||||
color: 'bg-[var(--color-negative-light)] text-[var(--color-negative-default)] dark:bg-[var(--color-negative-default)]/15'
|
||||
},
|
||||
{
|
||||
value: 'Cancelled',
|
||||
label: 'Cancelled',
|
||||
color: 'bg-gray-200 text-gray-500 dark:bg-gray-700 dark:text-gray-400'
|
||||
color: 'bg-[var(--surface-sunken)] text-[var(--text-tertiary)]'
|
||||
}
|
||||
];
|
||||
|
||||
|
|
@ -309,14 +309,14 @@
|
|||
onclick={() => (statusChipFilter = chip.key)}
|
||||
class="inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-sm font-medium transition-colors {statusChipFilter ===
|
||||
chip.key
|
||||
? 'bg-gray-900 text-white dark:bg-white dark:text-gray-900'
|
||||
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700'}"
|
||||
? 'bg-[var(--color-primary-default)] text-white'
|
||||
: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)] hover:bg-[var(--surface-raised)]'}"
|
||||
>
|
||||
{chip.label}
|
||||
<span
|
||||
class="rounded-full px-1.5 py-0.5 text-xs {statusChipFilter === chip.key
|
||||
? 'bg-gray-700 text-gray-200 dark:bg-gray-200 dark:text-gray-700'
|
||||
: 'bg-gray-200 text-gray-500 dark:bg-gray-700 dark:text-gray-500'}"
|
||||
? 'bg-[var(--color-primary-dark)] text-white/90'
|
||||
: 'bg-[var(--border-default)] text-[var(--text-tertiary)]'}"
|
||||
>
|
||||
{chipCounts[chip.key] || 0}
|
||||
</span>
|
||||
|
|
@ -336,7 +336,7 @@
|
|||
<Filter class="size-4" />
|
||||
Filters
|
||||
{#if activeFiltersCount > 0}
|
||||
<span class="bg-primary text-primary-foreground rounded-full px-2 py-0.5 text-xs">
|
||||
<span class="rounded-full bg-[var(--color-primary-light)] px-2 py-0.5 text-xs text-[var(--color-primary-default)]">
|
||||
{activeFiltersCount}
|
||||
</span>
|
||||
{/if}
|
||||
|
|
@ -416,9 +416,11 @@
|
|||
<CrmTable data={invoices} {columns} bind:visibleColumns onRowClick={handleRowClick}>
|
||||
{#snippet emptyState()}
|
||||
<div class="flex flex-col items-center justify-center py-16 text-center">
|
||||
<span class="mb-4 text-6xl">📄</span>
|
||||
<h3 class="text-foreground text-lg font-medium">No invoices yet</h3>
|
||||
<p class="text-muted-foreground mb-4 text-sm">Create your first invoice to get started</p>
|
||||
<div class="mb-4 flex size-16 items-center justify-center rounded-[var(--radius-xl)] bg-[var(--surface-sunken)]">
|
||||
<span class="text-4xl">📄</span>
|
||||
</div>
|
||||
<h3 class="text-[var(--text-primary)] text-lg font-medium">No invoices yet</h3>
|
||||
<p class="text-[var(--text-secondary)] mb-4 text-sm">Create your first invoice to get started</p>
|
||||
<Button onclick={createNewInvoice} class="gap-2">
|
||||
<Plus class="size-4" />
|
||||
Create Invoice
|
||||
|
|
@ -439,7 +441,7 @@
|
|||
{:else if column.key === 'totalAmount' || column.key === 'amountDue'}
|
||||
<span
|
||||
class={column.key === 'amountDue' && parseFloat(row.amountDue || 0) > 0
|
||||
? 'font-medium text-orange-600 dark:text-orange-400'
|
||||
? 'font-medium text-[var(--color-primary-default)]'
|
||||
: ''}
|
||||
>
|
||||
{formatCurrency(row[column.key], row.currency)}
|
||||
|
|
|
|||
|
|
@ -53,16 +53,16 @@
|
|||
{ value: 'CUSTOM', label: 'Custom' }
|
||||
];
|
||||
|
||||
// Status colors
|
||||
// Status colors - using design system tokens
|
||||
const STATUS_COLORS = {
|
||||
Draft: 'bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300',
|
||||
Sent: 'bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300',
|
||||
Viewed: 'bg-indigo-100 text-indigo-700 dark:bg-indigo-900 dark:text-indigo-300',
|
||||
Partially_Paid: 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900 dark:text-yellow-300',
|
||||
Paid: 'bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-300',
|
||||
Overdue: 'bg-red-100 text-red-700 dark:bg-red-900 dark:text-red-300',
|
||||
Pending: 'bg-orange-100 text-orange-700 dark:bg-orange-900 dark:text-orange-300',
|
||||
Cancelled: 'bg-gray-200 text-gray-500 dark:bg-gray-700 dark:text-gray-400'
|
||||
Draft: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)]',
|
||||
Sent: 'bg-[var(--stage-contacted-bg)] text-[var(--stage-contacted)] dark:bg-[var(--stage-contacted)]/15',
|
||||
Viewed: 'bg-[var(--stage-qualified-bg)] text-[var(--stage-qualified)] dark:bg-[var(--stage-qualified)]/15',
|
||||
Partially_Paid: 'bg-[var(--stage-negotiation-bg)] text-[var(--stage-negotiation)] dark:bg-[var(--stage-negotiation)]/15',
|
||||
Paid: 'bg-[var(--color-success-light)] text-[var(--color-success-default)] dark:bg-[var(--color-success-default)]/15',
|
||||
Overdue: 'bg-[var(--color-negative-light)] text-[var(--color-negative-default)] dark:bg-[var(--color-negative-default)]/15',
|
||||
Pending: 'bg-[var(--color-primary-light)] text-[var(--color-primary-default)] dark:bg-[var(--color-primary-default)]/15',
|
||||
Cancelled: 'bg-[var(--surface-sunken)] text-[var(--text-tertiary)]'
|
||||
};
|
||||
|
||||
// Status options for dropdown
|
||||
|
|
@ -782,7 +782,7 @@
|
|||
</div>
|
||||
<div class="flex justify-between font-medium">
|
||||
<span>Amount Due</span>
|
||||
<span class="text-orange-600"
|
||||
<span class="text-[var(--color-primary-default)]"
|
||||
>{formatCurrency(Number(invoice.amountDue), invoice.currency)}</span
|
||||
>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -17,14 +17,14 @@
|
|||
/** @type {{ data: import('./$types').PageData }} */
|
||||
let { data } = $props();
|
||||
|
||||
// Estimate status options
|
||||
// Estimate status options - using design system tokens
|
||||
const ESTIMATE_STATUSES = [
|
||||
{ value: 'Draft', label: 'Draft', color: 'gray' },
|
||||
{ value: 'Sent', label: 'Sent', color: 'blue' },
|
||||
{ value: 'Viewed', label: 'Viewed', color: 'indigo' },
|
||||
{ value: 'Accepted', label: 'Accepted', color: 'green' },
|
||||
{ value: 'Declined', label: 'Declined', color: 'red' },
|
||||
{ value: 'Expired', label: 'Expired', color: 'orange' }
|
||||
{ value: 'Draft', label: 'Draft', color: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)]' },
|
||||
{ value: 'Sent', label: 'Sent', color: 'bg-[var(--stage-contacted-bg)] text-[var(--stage-contacted)] dark:bg-[var(--stage-contacted)]/15' },
|
||||
{ value: 'Viewed', label: 'Viewed', color: 'bg-[var(--stage-qualified-bg)] text-[var(--stage-qualified)] dark:bg-[var(--stage-qualified)]/15' },
|
||||
{ value: 'Accepted', label: 'Accepted', color: 'bg-[var(--color-success-light)] text-[var(--color-success-default)] dark:bg-[var(--color-success-default)]/15' },
|
||||
{ value: 'Declined', label: 'Declined', color: 'bg-[var(--color-negative-light)] text-[var(--color-negative-default)] dark:bg-[var(--color-negative-default)]/15' },
|
||||
{ value: 'Expired', label: 'Expired', color: 'bg-[var(--color-primary-light)] text-[var(--color-primary-default)] dark:bg-[var(--color-primary-default)]/15' }
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
@ -451,17 +451,10 @@
|
|||
window.open(`/api/invoices/estimates/${selectedEstimate.id}/pdf/`, '_blank');
|
||||
}
|
||||
|
||||
// Status badge color
|
||||
// Status badge color - using design system tokens
|
||||
function getStatusColor(status) {
|
||||
const colors = {
|
||||
Draft: 'bg-gray-100 text-gray-700',
|
||||
Sent: 'bg-blue-100 text-blue-700',
|
||||
Viewed: 'bg-indigo-100 text-indigo-700',
|
||||
Accepted: 'bg-green-100 text-green-700',
|
||||
Declined: 'bg-red-100 text-red-700',
|
||||
Expired: 'bg-orange-100 text-orange-700'
|
||||
};
|
||||
return colors[status] || 'bg-gray-100 text-gray-700';
|
||||
const statusObj = ESTIMATE_STATUSES.find((s) => s.value === status);
|
||||
return statusObj?.color || 'bg-[var(--surface-sunken)] text-[var(--text-secondary)]';
|
||||
}
|
||||
|
||||
// Check if estimate is expired
|
||||
|
|
@ -685,14 +678,14 @@
|
|||
onclick={() => (statusChipFilter = chip.key)}
|
||||
class="inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-sm font-medium transition-colors {statusChipFilter ===
|
||||
chip.key
|
||||
? 'bg-gray-900 text-white dark:bg-white dark:text-gray-900'
|
||||
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700'}"
|
||||
? 'bg-[var(--color-primary-default)] text-white'
|
||||
: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)] hover:bg-[var(--surface-raised)]'}"
|
||||
>
|
||||
{chip.label}
|
||||
<span
|
||||
class="rounded-full px-1.5 py-0.5 text-xs {statusChipFilter === chip.key
|
||||
? 'bg-gray-700 text-gray-200 dark:bg-gray-200 dark:text-gray-700'
|
||||
: 'bg-gray-200 text-gray-500 dark:bg-gray-700 dark:text-gray-500'}"
|
||||
? 'bg-[var(--color-primary-dark)] text-white/90'
|
||||
: 'bg-[var(--border-default)] text-[var(--text-tertiary)]'}"
|
||||
>
|
||||
{chipCounts[chip.key] || 0}
|
||||
</span>
|
||||
|
|
@ -743,7 +736,7 @@
|
|||
</svg>
|
||||
Filters
|
||||
{#if activeFiltersCount() > 0}
|
||||
<span class="bg-primary text-primary-foreground rounded-full px-2 py-0.5 text-xs">
|
||||
<span class="rounded-full bg-[var(--color-primary-light)] px-2 py-0.5 text-xs text-[var(--color-primary-default)]">
|
||||
{activeFiltersCount()}
|
||||
</span>
|
||||
{/if}
|
||||
|
|
@ -849,9 +842,11 @@
|
|||
<CrmTable data={estimates} {columns} bind:visibleColumns onRowClick={handleRowClick}>
|
||||
{#snippet emptyState()}
|
||||
<div class="flex flex-col items-center justify-center py-16 text-center">
|
||||
<span class="mb-4 text-4xl">📋</span>
|
||||
<h3 class="text-foreground text-lg font-medium">No estimates yet</h3>
|
||||
<p class="text-muted-foreground text-sm">Create your first estimate to get started</p>
|
||||
<div class="mb-4 flex size-16 items-center justify-center rounded-[var(--radius-xl)] bg-[var(--surface-sunken)]">
|
||||
<span class="text-4xl">📋</span>
|
||||
</div>
|
||||
<h3 class="text-[var(--text-primary)] text-lg font-medium">No estimates yet</h3>
|
||||
<p class="text-[var(--text-secondary)] text-sm">Create your first estimate to get started</p>
|
||||
</div>
|
||||
{/snippet}
|
||||
{#snippet cellContent(row, column)}
|
||||
|
|
|
|||
|
|
@ -411,7 +411,7 @@
|
|||
</svg>
|
||||
Filters
|
||||
{#if activeFiltersCount() > 0}
|
||||
<span class="bg-primary text-primary-foreground rounded-full px-2 py-0.5 text-xs">
|
||||
<span class="rounded-full bg-[var(--color-primary-light)] px-2 py-0.5 text-xs text-[var(--color-primary-default)]">
|
||||
{activeFiltersCount()}
|
||||
</span>
|
||||
{/if}
|
||||
|
|
@ -509,9 +509,11 @@
|
|||
<CrmTable data={products} {columns} bind:visibleColumns onRowClick={handleRowClick}>
|
||||
{#snippet emptyState()}
|
||||
<div class="flex flex-col items-center justify-center py-16 text-center">
|
||||
<span class="mb-4 text-4xl">📦</span>
|
||||
<h3 class="text-foreground text-lg font-medium">No products yet</h3>
|
||||
<p class="text-muted-foreground text-sm">Create your first product to use in invoices</p>
|
||||
<div class="mb-4 flex size-16 items-center justify-center rounded-[var(--radius-xl)] bg-[var(--surface-sunken)]">
|
||||
<span class="text-4xl">📦</span>
|
||||
</div>
|
||||
<h3 class="text-[var(--text-primary)] text-lg font-medium">No products yet</h3>
|
||||
<p class="text-[var(--text-secondary)] text-sm">Create your first product to use in invoices</p>
|
||||
</div>
|
||||
{/snippet}
|
||||
{#snippet cellContent(row, column)}
|
||||
|
|
|
|||
|
|
@ -17,15 +17,15 @@
|
|||
/** @type {{ data: import('./$types').PageData }} */
|
||||
let { data } = $props();
|
||||
|
||||
// Frequency options
|
||||
// Frequency options - using design system tokens
|
||||
const FREQUENCIES = [
|
||||
{ value: 'WEEKLY', label: 'Weekly', color: 'blue' },
|
||||
{ value: 'BIWEEKLY', label: 'Bi-weekly', color: 'cyan' },
|
||||
{ value: 'MONTHLY', label: 'Monthly', color: 'green' },
|
||||
{ value: 'QUARTERLY', label: 'Quarterly', color: 'yellow' },
|
||||
{ value: 'SEMI_ANNUALLY', label: 'Semi-annually', color: 'orange' },
|
||||
{ value: 'YEARLY', label: 'Yearly', color: 'purple' },
|
||||
{ value: 'CUSTOM', label: 'Custom', color: 'gray' }
|
||||
{ value: 'WEEKLY', label: 'Weekly', color: 'bg-[var(--stage-contacted-bg)] text-[var(--stage-contacted)]' },
|
||||
{ value: 'BIWEEKLY', label: 'Bi-weekly', color: 'bg-[var(--stage-qualified-bg)] text-[var(--stage-qualified)]' },
|
||||
{ value: 'MONTHLY', label: 'Monthly', color: 'bg-[var(--color-success-light)] text-[var(--color-success-default)]' },
|
||||
{ value: 'QUARTERLY', label: 'Quarterly', color: 'bg-[var(--stage-negotiation-bg)] text-[var(--stage-negotiation)]' },
|
||||
{ value: 'SEMI_ANNUALLY', label: 'Semi-annually', color: 'bg-[var(--color-primary-light)] text-[var(--color-primary-default)]' },
|
||||
{ value: 'YEARLY', label: 'Yearly', color: 'bg-[var(--stage-proposal-bg)] text-[var(--stage-proposal)]' },
|
||||
{ value: 'CUSTOM', label: 'Custom', color: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)]' }
|
||||
];
|
||||
|
||||
// Status options
|
||||
|
|
@ -592,7 +592,7 @@
|
|||
</svg>
|
||||
Filters
|
||||
{#if activeFiltersCount() > 0}
|
||||
<span class="bg-primary text-primary-foreground rounded-full px-2 py-0.5 text-xs">
|
||||
<span class="rounded-full bg-[var(--color-primary-light)] px-2 py-0.5 text-xs text-[var(--color-primary-default)]">
|
||||
{activeFiltersCount()}
|
||||
</span>
|
||||
{/if}
|
||||
|
|
@ -688,9 +688,11 @@
|
|||
<CrmTable data={recurringInvoices} {columns} bind:visibleColumns onRowClick={handleRowClick}>
|
||||
{#snippet emptyState()}
|
||||
<div class="flex flex-col items-center justify-center py-16 text-center">
|
||||
<span class="mb-4 text-4xl">🔄</span>
|
||||
<h3 class="text-foreground text-lg font-medium">No recurring invoices yet</h3>
|
||||
<p class="text-muted-foreground text-sm">Create your first recurring invoice template</p>
|
||||
<div class="mb-4 flex size-16 items-center justify-center rounded-[var(--radius-xl)] bg-[var(--surface-sunken)]">
|
||||
<span class="text-4xl">🔄</span>
|
||||
</div>
|
||||
<h3 class="text-[var(--text-primary)] text-lg font-medium">No recurring invoices yet</h3>
|
||||
<p class="text-[var(--text-secondary)] text-sm">Create your first recurring invoice template</p>
|
||||
</div>
|
||||
{/snippet}
|
||||
{#snippet cellContent(row, column)}
|
||||
|
|
|
|||
|
|
@ -33,17 +33,18 @@
|
|||
await goto(url.toString(), { replaceState: true, invalidateAll: true });
|
||||
}
|
||||
|
||||
// Status colors - using design system tokens
|
||||
function getStatusColor(status) {
|
||||
const colors = {
|
||||
Draft: 'bg-gray-500',
|
||||
Sent: 'bg-blue-500',
|
||||
Viewed: 'bg-indigo-500',
|
||||
Partially_Paid: 'bg-yellow-500',
|
||||
Paid: 'bg-green-500',
|
||||
Overdue: 'bg-red-500',
|
||||
Cancelled: 'bg-gray-400'
|
||||
Draft: 'bg-[var(--text-tertiary)]',
|
||||
Sent: 'bg-[var(--stage-contacted)]',
|
||||
Viewed: 'bg-[var(--stage-qualified)]',
|
||||
Partially_Paid: 'bg-[var(--stage-negotiation)]',
|
||||
Paid: 'bg-[var(--color-success-default)]',
|
||||
Overdue: 'bg-[var(--color-negative-default)]',
|
||||
Cancelled: 'bg-[var(--text-tertiary)]'
|
||||
};
|
||||
return colors[status] || 'bg-gray-500';
|
||||
return colors[status] || 'bg-[var(--text-tertiary)]';
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
@ -74,15 +75,15 @@
|
|||
<!-- Summary Cards -->
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-4">
|
||||
<!-- Total Invoiced -->
|
||||
<div class="rounded-xl border bg-white p-6 shadow-sm">
|
||||
<div class="rounded-xl border border-[var(--border-default)] bg-[var(--surface-default)] p-6 shadow-sm">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm text-gray-500">Total Invoiced</p>
|
||||
<p class="mt-1 text-2xl font-bold text-gray-900">
|
||||
<p class="text-sm text-[var(--text-secondary)]">Total Invoiced</p>
|
||||
<p class="mt-1 text-2xl font-bold text-[var(--text-primary)]">
|
||||
{formatCurrency(Number(dashboard.summary?.total_invoiced || 0), 'USD')}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex h-12 w-12 items-center justify-center rounded-full bg-blue-100">
|
||||
<div class="flex h-12 w-12 items-center justify-center rounded-full bg-[var(--color-primary-light)]">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
|
|
@ -93,7 +94,7 @@
|
|||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="text-blue-600"
|
||||
class="text-[var(--color-primary-default)]"
|
||||
>
|
||||
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
|
||||
<polyline points="14 2 14 8 20 8" />
|
||||
|
|
@ -105,15 +106,15 @@
|
|||
</div>
|
||||
|
||||
<!-- Total Paid -->
|
||||
<div class="rounded-xl border bg-white p-6 shadow-sm">
|
||||
<div class="rounded-xl border border-[var(--border-default)] bg-[var(--surface-default)] p-6 shadow-sm">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm text-gray-500">Total Collected</p>
|
||||
<p class="mt-1 text-2xl font-bold text-green-600">
|
||||
<p class="text-sm text-[var(--text-secondary)]">Total Collected</p>
|
||||
<p class="mt-1 text-2xl font-bold text-[var(--color-success-default)]">
|
||||
{formatCurrency(Number(dashboard.summary?.total_paid || 0), 'USD')}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex h-12 w-12 items-center justify-center rounded-full bg-green-100">
|
||||
<div class="flex h-12 w-12 items-center justify-center rounded-full bg-[var(--color-success-light)]">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
|
|
@ -124,7 +125,7 @@
|
|||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="text-green-600"
|
||||
class="text-[var(--color-success-default)]"
|
||||
>
|
||||
<line x1="12" y1="1" x2="12" y2="23" />
|
||||
<path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6" />
|
||||
|
|
@ -134,15 +135,15 @@
|
|||
</div>
|
||||
|
||||
<!-- Outstanding -->
|
||||
<div class="rounded-xl border bg-white p-6 shadow-sm">
|
||||
<div class="rounded-xl border border-[var(--border-default)] bg-[var(--surface-default)] p-6 shadow-sm">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm text-gray-500">Outstanding</p>
|
||||
<p class="mt-1 text-2xl font-bold text-orange-600">
|
||||
<p class="text-sm text-[var(--text-secondary)]">Outstanding</p>
|
||||
<p class="mt-1 text-2xl font-bold text-[var(--color-primary-default)]">
|
||||
{formatCurrency(Number(dashboard.summary?.total_due || 0), 'USD')}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex h-12 w-12 items-center justify-center rounded-full bg-orange-100">
|
||||
<div class="flex h-12 w-12 items-center justify-center rounded-full bg-[var(--color-primary-light)]">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
|
|
@ -153,7 +154,7 @@
|
|||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="text-orange-600"
|
||||
class="text-[var(--color-primary-default)]"
|
||||
>
|
||||
<circle cx="12" cy="12" r="10" />
|
||||
<polyline points="12 6 12 12 16 14" />
|
||||
|
|
@ -163,15 +164,15 @@
|
|||
</div>
|
||||
|
||||
<!-- Overdue -->
|
||||
<div class="rounded-xl border bg-white p-6 shadow-sm">
|
||||
<div class="rounded-xl border border-[var(--border-default)] bg-[var(--surface-default)] p-6 shadow-sm">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm text-gray-500">Overdue ({dashboard.overdue?.count || 0})</p>
|
||||
<p class="mt-1 text-2xl font-bold text-red-600">
|
||||
<p class="text-sm text-[var(--text-secondary)]">Overdue ({dashboard.overdue?.count || 0})</p>
|
||||
<p class="mt-1 text-2xl font-bold text-[var(--color-negative-default)]">
|
||||
{formatCurrency(Number(dashboard.overdue?.amount || 0), 'USD')}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex h-12 w-12 items-center justify-center rounded-full bg-red-100">
|
||||
<div class="flex h-12 w-12 items-center justify-center rounded-full bg-[var(--color-negative-light)]">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
|
|
@ -182,7 +183,7 @@
|
|||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="text-red-600"
|
||||
class="text-[var(--color-negative-default)]"
|
||||
>
|
||||
<path
|
||||
d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"
|
||||
|
|
@ -198,26 +199,26 @@
|
|||
<!-- Recent Activity & Status -->
|
||||
<div class="grid grid-cols-1 gap-6 lg:grid-cols-2">
|
||||
<!-- Recent Activity -->
|
||||
<div class="rounded-xl border bg-white p-6 shadow-sm">
|
||||
<h3 class="mb-4 text-lg font-semibold text-gray-900">Last 30 Days</h3>
|
||||
<div class="rounded-xl border border-[var(--border-default)] bg-[var(--surface-default)] p-6 shadow-sm">
|
||||
<h3 class="mb-4 text-lg font-semibold text-[var(--text-primary)]">Last 30 Days</h3>
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-gray-600">Revenue Collected</span>
|
||||
<span class="font-medium text-green-600">
|
||||
<span class="text-[var(--text-secondary)]">Revenue Collected</span>
|
||||
<span class="font-medium text-[var(--color-success-default)]">
|
||||
{formatCurrency(Number(dashboard.recent_activity?.revenue_30d || 0), 'USD')}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-gray-600">Invoices Created</span>
|
||||
<span class="font-medium">{dashboard.recent_activity?.invoices_created_30d || 0}</span>
|
||||
<span class="text-[var(--text-secondary)]">Invoices Created</span>
|
||||
<span class="font-medium text-[var(--text-primary)]">{dashboard.recent_activity?.invoices_created_30d || 0}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-gray-600">Invoices Paid</span>
|
||||
<span class="font-medium">{dashboard.recent_activity?.invoices_paid_30d || 0}</span>
|
||||
<span class="text-[var(--text-secondary)]">Invoices Paid</span>
|
||||
<span class="font-medium text-[var(--text-primary)]">{dashboard.recent_activity?.invoices_paid_30d || 0}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-gray-600">Total Invoiced</span>
|
||||
<span class="font-medium">
|
||||
<span class="text-[var(--text-secondary)]">Total Invoiced</span>
|
||||
<span class="font-medium text-[var(--text-primary)]">
|
||||
{formatCurrency(Number(dashboard.recent_activity?.invoiced_30d || 0), 'USD')}
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -225,14 +226,14 @@
|
|||
</div>
|
||||
|
||||
<!-- Status Breakdown -->
|
||||
<div class="rounded-xl border bg-white p-6 shadow-sm">
|
||||
<h3 class="mb-4 text-lg font-semibold text-gray-900">Invoice Status</h3>
|
||||
<div class="rounded-xl border border-[var(--border-default)] bg-[var(--surface-default)] p-6 shadow-sm">
|
||||
<h3 class="mb-4 text-lg font-semibold text-[var(--text-primary)]">Invoice Status</h3>
|
||||
<div class="space-y-3">
|
||||
{#each Object.entries(dashboard.status_counts || {}) as [status, count]}
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="h-3 w-3 rounded-full {getStatusColor(status)}"></div>
|
||||
<span class="flex-1 text-gray-600">{status.replace('_', ' ')}</span>
|
||||
<span class="font-medium">{count}</span>
|
||||
<span class="flex-1 text-[var(--text-secondary)]">{status.replace('_', ' ')}</span>
|
||||
<span class="font-medium text-[var(--text-primary)]">{count}</span>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
|
@ -240,9 +241,9 @@
|
|||
</div>
|
||||
|
||||
<!-- Revenue Chart Section -->
|
||||
<div class="rounded-xl border bg-white p-6 shadow-sm">
|
||||
<div class="rounded-xl border border-[var(--border-default)] bg-[var(--surface-default)] p-6 shadow-sm">
|
||||
<div class="mb-6 flex flex-col items-start justify-between gap-4 sm:flex-row sm:items-center">
|
||||
<h3 class="text-lg font-semibold text-gray-900">Revenue Over Time</h3>
|
||||
<h3 class="text-lg font-semibold text-[var(--text-primary)]">Revenue Over Time</h3>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<input
|
||||
type="date"
|
||||
|
|
@ -252,7 +253,7 @@
|
|||
...filters,
|
||||
startDate: /** @type {HTMLInputElement} */ (e.target).value
|
||||
})}
|
||||
class="rounded-lg border px-3 py-1.5 text-sm"
|
||||
class="rounded-lg border border-[var(--border-default)] bg-[var(--surface-default)] px-3 py-1.5 text-sm text-[var(--text-primary)]"
|
||||
/>
|
||||
<input
|
||||
type="date"
|
||||
|
|
@ -262,7 +263,7 @@
|
|||
...filters,
|
||||
endDate: /** @type {HTMLInputElement} */ (e.target).value
|
||||
})}
|
||||
class="rounded-lg border px-3 py-1.5 text-sm"
|
||||
class="rounded-lg border border-[var(--border-default)] bg-[var(--surface-default)] px-3 py-1.5 text-sm text-[var(--text-primary)]"
|
||||
/>
|
||||
<select
|
||||
value={filters.groupBy}
|
||||
|
|
@ -271,7 +272,7 @@
|
|||
...filters,
|
||||
groupBy: /** @type {HTMLSelectElement} */ (e.target).value
|
||||
})}
|
||||
class="rounded-lg border px-3 py-1.5 text-sm"
|
||||
class="rounded-lg border border-[var(--border-default)] bg-[var(--surface-default)] px-3 py-1.5 text-sm text-[var(--text-primary)]"
|
||||
>
|
||||
{#each GROUP_BY_OPTIONS as option}
|
||||
<option value={option.value}>{option.label}</option>
|
||||
|
|
@ -284,28 +285,28 @@
|
|||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr class="border-b">
|
||||
<th class="py-2 text-left font-medium text-gray-500">Period</th>
|
||||
<th class="py-2 text-right font-medium text-gray-500">Invoices</th>
|
||||
<th class="py-2 text-right font-medium text-gray-500">Revenue</th>
|
||||
<tr class="border-b border-[var(--border-default)]">
|
||||
<th class="py-2 text-left font-medium text-[var(--text-secondary)]">Period</th>
|
||||
<th class="py-2 text-right font-medium text-[var(--text-secondary)]">Invoices</th>
|
||||
<th class="py-2 text-right font-medium text-[var(--text-secondary)]">Revenue</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each revenue.data as item}
|
||||
<tr class="border-b last:border-b-0">
|
||||
<td class="py-3 text-gray-700">{formatDate(item.period)}</td>
|
||||
<td class="py-3 text-right text-gray-600">{item.count}</td>
|
||||
<td class="py-3 text-right font-medium text-green-600">
|
||||
<tr class="border-b border-[var(--border-default)] last:border-b-0">
|
||||
<td class="py-3 text-[var(--text-primary)]">{formatDate(item.period)}</td>
|
||||
<td class="py-3 text-right text-[var(--text-secondary)]">{item.count}</td>
|
||||
<td class="py-3 text-right font-medium text-[var(--color-success-default)]">
|
||||
{formatCurrency(Number(item.revenue), 'USD')}
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr class="border-t bg-gray-50">
|
||||
<td class="py-3 font-medium">Total</td>
|
||||
<td class="py-3 text-right font-medium">{revenue.total?.count || 0}</td>
|
||||
<td class="py-3 text-right font-bold text-green-600">
|
||||
<tr class="border-t border-[var(--border-default)] bg-[var(--surface-sunken)]">
|
||||
<td class="py-3 font-medium text-[var(--text-primary)]">Total</td>
|
||||
<td class="py-3 text-right font-medium text-[var(--text-primary)]">{revenue.total?.count || 0}</td>
|
||||
<td class="py-3 text-right font-bold text-[var(--color-success-default)]">
|
||||
{formatCurrency(Number(revenue.total?.revenue || 0), 'USD')}
|
||||
</td>
|
||||
</tr>
|
||||
|
|
@ -313,88 +314,88 @@
|
|||
</table>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="py-8 text-center text-gray-500">No revenue data for the selected period</div>
|
||||
<div class="py-8 text-center text-[var(--text-secondary)]">No revenue data for the selected period</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Aging Report -->
|
||||
<div class="rounded-xl border bg-white p-6 shadow-sm">
|
||||
<h3 class="mb-6 text-lg font-semibold text-gray-900">Accounts Receivable Aging</h3>
|
||||
<div class="rounded-xl border border-[var(--border-default)] bg-[var(--surface-default)] p-6 shadow-sm">
|
||||
<h3 class="mb-6 text-lg font-semibold text-[var(--text-primary)]">Accounts Receivable Aging</h3>
|
||||
|
||||
<div class="mb-6 grid grid-cols-1 gap-4 md:grid-cols-5">
|
||||
<!-- Current -->
|
||||
<div class="rounded-lg bg-green-50 p-4">
|
||||
<p class="text-sm font-medium text-green-700">Current</p>
|
||||
<p class="mt-1 text-xl font-bold text-green-800">
|
||||
<div class="rounded-lg bg-[var(--color-success-light)] p-4">
|
||||
<p class="text-sm font-medium text-[var(--color-success-default)]">Current</p>
|
||||
<p class="mt-1 text-xl font-bold text-[var(--color-success-default)]">
|
||||
{formatCurrency(Number(aging.current?.amount || 0), 'USD')}
|
||||
</p>
|
||||
<p class="text-sm text-green-600">{aging.current?.count || 0} invoices</p>
|
||||
<p class="text-sm text-[var(--color-success-default)]/80">{aging.current?.count || 0} invoices</p>
|
||||
</div>
|
||||
|
||||
<!-- 1-30 Days -->
|
||||
<div class="rounded-lg bg-yellow-50 p-4">
|
||||
<p class="text-sm font-medium text-yellow-700">1-30 Days</p>
|
||||
<p class="mt-1 text-xl font-bold text-yellow-800">
|
||||
<div class="rounded-lg bg-[var(--stage-negotiation-bg)] p-4">
|
||||
<p class="text-sm font-medium text-[var(--stage-negotiation)]">1-30 Days</p>
|
||||
<p class="mt-1 text-xl font-bold text-[var(--stage-negotiation)]">
|
||||
{formatCurrency(Number(aging['1_30_days']?.amount || 0), 'USD')}
|
||||
</p>
|
||||
<p class="text-sm text-yellow-600">{aging['1_30_days']?.count || 0} invoices</p>
|
||||
<p class="text-sm text-[var(--stage-negotiation)]/80">{aging['1_30_days']?.count || 0} invoices</p>
|
||||
</div>
|
||||
|
||||
<!-- 31-60 Days -->
|
||||
<div class="rounded-lg bg-orange-50 p-4">
|
||||
<p class="text-sm font-medium text-orange-700">31-60 Days</p>
|
||||
<p class="mt-1 text-xl font-bold text-orange-800">
|
||||
<div class="rounded-lg bg-[var(--color-primary-light)] p-4">
|
||||
<p class="text-sm font-medium text-[var(--color-primary-default)]">31-60 Days</p>
|
||||
<p class="mt-1 text-xl font-bold text-[var(--color-primary-default)]">
|
||||
{formatCurrency(Number(aging['31_60_days']?.amount || 0), 'USD')}
|
||||
</p>
|
||||
<p class="text-sm text-orange-600">{aging['31_60_days']?.count || 0} invoices</p>
|
||||
<p class="text-sm text-[var(--color-primary-default)]/80">{aging['31_60_days']?.count || 0} invoices</p>
|
||||
</div>
|
||||
|
||||
<!-- 61-90 Days -->
|
||||
<div class="rounded-lg bg-red-50 p-4">
|
||||
<p class="text-sm font-medium text-red-700">61-90 Days</p>
|
||||
<p class="mt-1 text-xl font-bold text-red-800">
|
||||
<div class="rounded-lg bg-[var(--color-negative-light)] p-4">
|
||||
<p class="text-sm font-medium text-[var(--color-negative-default)]">61-90 Days</p>
|
||||
<p class="mt-1 text-xl font-bold text-[var(--color-negative-default)]">
|
||||
{formatCurrency(Number(aging['61_90_days']?.amount || 0), 'USD')}
|
||||
</p>
|
||||
<p class="text-sm text-red-600">{aging['61_90_days']?.count || 0} invoices</p>
|
||||
<p class="text-sm text-[var(--color-negative-default)]/80">{aging['61_90_days']?.count || 0} invoices</p>
|
||||
</div>
|
||||
|
||||
<!-- Over 90 Days -->
|
||||
<div class="rounded-lg bg-red-100 p-4">
|
||||
<p class="text-sm font-medium text-red-800">Over 90 Days</p>
|
||||
<p class="mt-1 text-xl font-bold text-red-900">
|
||||
<div class="rounded-lg bg-[var(--color-negative-default)]/20 p-4">
|
||||
<p class="text-sm font-medium text-[var(--color-negative-default)]">Over 90 Days</p>
|
||||
<p class="mt-1 text-xl font-bold text-[var(--color-negative-default)]">
|
||||
{formatCurrency(Number(aging['over_90_days']?.amount || 0), 'USD')}
|
||||
</p>
|
||||
<p class="text-sm text-red-700">{aging['over_90_days']?.count || 0} invoices</p>
|
||||
<p class="text-sm text-[var(--color-negative-default)]/80">{aging['over_90_days']?.count || 0} invoices</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Total Outstanding -->
|
||||
<div class="flex items-center justify-between border-t pt-4">
|
||||
<span class="font-medium text-gray-600">Total Outstanding</span>
|
||||
<div class="flex items-center justify-between border-t border-[var(--border-default)] pt-4">
|
||||
<span class="font-medium text-[var(--text-secondary)]">Total Outstanding</span>
|
||||
<div class="text-right">
|
||||
<p class="text-2xl font-bold text-gray-900">
|
||||
<p class="text-2xl font-bold text-[var(--text-primary)]">
|
||||
{formatCurrency(Number(aging.total?.amount || 0), 'USD')}
|
||||
</p>
|
||||
<p class="text-sm text-gray-500">{aging.total?.count || 0} invoices</p>
|
||||
<p class="text-sm text-[var(--text-secondary)]">{aging.total?.count || 0} invoices</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Estimates Summary -->
|
||||
<div class="rounded-xl border bg-white p-6 shadow-sm">
|
||||
<h3 class="mb-4 text-lg font-semibold text-gray-900">Estimates Overview</h3>
|
||||
<div class="rounded-xl border border-[var(--border-default)] bg-[var(--surface-default)] p-6 shadow-sm">
|
||||
<h3 class="mb-4 text-lg font-semibold text-[var(--text-primary)]">Estimates Overview</h3>
|
||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-3">
|
||||
<div class="rounded-lg bg-blue-50 p-4 text-center">
|
||||
<p class="text-3xl font-bold text-blue-600">{dashboard.estimates?.pending || 0}</p>
|
||||
<p class="mt-1 text-sm text-blue-700">Pending</p>
|
||||
<div class="rounded-lg bg-[var(--color-primary-light)] p-4 text-center">
|
||||
<p class="text-3xl font-bold text-[var(--color-primary-default)]">{dashboard.estimates?.pending || 0}</p>
|
||||
<p class="mt-1 text-sm text-[var(--color-primary-default)]">Pending</p>
|
||||
</div>
|
||||
<div class="rounded-lg bg-green-50 p-4 text-center">
|
||||
<p class="text-3xl font-bold text-green-600">{dashboard.estimates?.accepted || 0}</p>
|
||||
<p class="mt-1 text-sm text-green-700">Accepted</p>
|
||||
<div class="rounded-lg bg-[var(--color-success-light)] p-4 text-center">
|
||||
<p class="text-3xl font-bold text-[var(--color-success-default)]">{dashboard.estimates?.accepted || 0}</p>
|
||||
<p class="mt-1 text-sm text-[var(--color-success-default)]">Accepted</p>
|
||||
</div>
|
||||
<div class="rounded-lg bg-red-50 p-4 text-center">
|
||||
<p class="text-3xl font-bold text-red-600">{dashboard.estimates?.declined || 0}</p>
|
||||
<p class="mt-1 text-sm text-red-700">Declined</p>
|
||||
<div class="rounded-lg bg-[var(--color-negative-light)] p-4 text-center">
|
||||
<p class="text-3xl font-bold text-[var(--color-negative-default)]">{dashboard.estimates?.declined || 0}</p>
|
||||
<p class="mt-1 text-sm text-[var(--color-negative-default)]">Declined</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -355,7 +355,7 @@
|
|||
</svg>
|
||||
Filters
|
||||
{#if activeFiltersCount() > 0}
|
||||
<span class="bg-primary text-primary-foreground rounded-full px-2 py-0.5 text-xs">
|
||||
<span class="rounded-full bg-[var(--color-primary-light)] px-2 py-0.5 text-xs text-[var(--color-primary-default)]">
|
||||
{activeFiltersCount()}
|
||||
</span>
|
||||
{/if}
|
||||
|
|
@ -400,26 +400,28 @@
|
|||
<CrmTable data={templates} {columns} bind:visibleColumns onRowClick={handleRowClick}>
|
||||
{#snippet emptyState()}
|
||||
<div class="flex flex-col items-center justify-center py-16 text-center">
|
||||
<span class="mb-4 text-4xl">🎨</span>
|
||||
<h3 class="text-foreground text-lg font-medium">No templates yet</h3>
|
||||
<p class="text-muted-foreground text-sm">Create your first invoice template</p>
|
||||
<div class="mb-4 flex size-16 items-center justify-center rounded-[var(--radius-xl)] bg-[var(--surface-sunken)]">
|
||||
<span class="text-4xl">🎨</span>
|
||||
</div>
|
||||
<h3 class="text-[var(--text-primary)] text-lg font-medium">No templates yet</h3>
|
||||
<p class="text-[var(--text-secondary)] text-sm">Create your first invoice template</p>
|
||||
</div>
|
||||
{/snippet}
|
||||
{#snippet cellContent(row, column)}
|
||||
{#if column.key === 'isDefault'}
|
||||
{#if row.isDefault}
|
||||
<span
|
||||
class="inline-flex items-center rounded-full bg-blue-100 px-2 py-0.5 text-xs font-medium text-blue-700"
|
||||
class="inline-flex items-center rounded-full bg-[var(--color-primary-light)] px-2 py-0.5 text-xs font-medium text-[var(--color-primary-default)]"
|
||||
>
|
||||
Default
|
||||
</span>
|
||||
{:else}
|
||||
<span class="text-gray-400">-</span>
|
||||
<span class="text-[var(--text-tertiary)]">-</span>
|
||||
{/if}
|
||||
{:else if column.key === 'primaryColor'}
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="h-5 w-5 rounded border" style="background-color: {row.primaryColor}"></div>
|
||||
<span class="text-xs text-gray-500">{row.primaryColor}</span>
|
||||
<div class="h-5 w-5 rounded border border-[var(--border-default)]" style="background-color: {row.primaryColor}"></div>
|
||||
<span class="text-xs text-[var(--text-secondary)]">{row.primaryColor}</span>
|
||||
</div>
|
||||
{:else}
|
||||
{row[column.key] || '-'}
|
||||
|
|
@ -479,7 +481,7 @@
|
|||
></div>
|
||||
</div>
|
||||
{#if selectedTemplate.footerText}
|
||||
<p class="text-xs text-gray-500 italic">{selectedTemplate.footerText}</p>
|
||||
<p class="text-xs text-[var(--text-secondary)] italic">{selectedTemplate.footerText}</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -184,79 +184,79 @@
|
|||
];
|
||||
let visibleColumns = $state([...DEFAULT_VISIBLE_COLUMNS]);
|
||||
|
||||
// Source options for leads
|
||||
// Source options for leads - using design system tokens
|
||||
const sourceOptions = [
|
||||
{
|
||||
value: 'call',
|
||||
label: 'Call',
|
||||
color: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400'
|
||||
color: 'bg-[var(--activity-call)]/10 text-[var(--activity-call)] dark:bg-[var(--activity-call)]/15'
|
||||
},
|
||||
{
|
||||
value: 'email',
|
||||
label: 'Email',
|
||||
color: 'bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-400'
|
||||
color: 'bg-[var(--activity-email)]/10 text-[var(--activity-email)] dark:bg-[var(--activity-email)]/15'
|
||||
},
|
||||
{
|
||||
value: 'existing customer',
|
||||
label: 'Existing Customer',
|
||||
color: 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400'
|
||||
color: 'bg-[var(--color-success-light)] text-[var(--color-success-default)] dark:bg-[var(--color-success-default)]/15'
|
||||
},
|
||||
{
|
||||
value: 'partner',
|
||||
label: 'Partner',
|
||||
color: 'bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400'
|
||||
color: 'bg-[var(--color-primary-light)] text-[var(--color-primary-default)] dark:bg-[var(--color-primary-default)]/15'
|
||||
},
|
||||
{
|
||||
value: 'public relations',
|
||||
label: 'Public Relations',
|
||||
color: 'bg-pink-100 text-pink-700 dark:bg-pink-900/30 dark:text-pink-400'
|
||||
color: 'bg-[var(--activity-meeting)]/10 text-[var(--activity-meeting)] dark:bg-[var(--activity-meeting)]/15'
|
||||
},
|
||||
{
|
||||
value: 'campaign',
|
||||
label: 'Campaign',
|
||||
color: 'bg-cyan-100 text-cyan-700 dark:bg-cyan-900/30 dark:text-cyan-400'
|
||||
color: 'bg-[var(--stage-qualified)]/10 text-[var(--stage-qualified)] dark:bg-[var(--stage-qualified)]/15'
|
||||
},
|
||||
{
|
||||
value: 'other',
|
||||
label: 'Other',
|
||||
color: 'bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300'
|
||||
color: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)] dark:bg-[var(--surface-sunken)]'
|
||||
}
|
||||
];
|
||||
|
||||
// Salutation options
|
||||
// Salutation options - using design system tokens
|
||||
const salutationOptions = [
|
||||
{
|
||||
value: 'Mr',
|
||||
label: 'Mr',
|
||||
color: 'bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300'
|
||||
color: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)]'
|
||||
},
|
||||
{
|
||||
value: 'Mrs',
|
||||
label: 'Mrs',
|
||||
color: 'bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300'
|
||||
color: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)]'
|
||||
},
|
||||
{
|
||||
value: 'Ms',
|
||||
label: 'Ms',
|
||||
color: 'bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300'
|
||||
color: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)]'
|
||||
},
|
||||
{
|
||||
value: 'Dr',
|
||||
label: 'Dr',
|
||||
color: 'bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300'
|
||||
color: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)]'
|
||||
},
|
||||
{
|
||||
value: 'Prof',
|
||||
label: 'Prof',
|
||||
color: 'bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300'
|
||||
color: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)]'
|
||||
}
|
||||
];
|
||||
|
||||
// Currency options for select
|
||||
// Currency options for select - using design system tokens
|
||||
const currencyOptions = CURRENCY_CODES.filter((c) => c.value).map((c) => ({
|
||||
value: c.value,
|
||||
label: c.label,
|
||||
color: 'bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300'
|
||||
color: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)]'
|
||||
}));
|
||||
|
||||
// Full drawer columns for NotionDrawer (all lead fields)
|
||||
|
|
@ -1443,23 +1443,23 @@
|
|||
<!-- View Toggle -->
|
||||
<ViewToggle view={viewMode} onchange={setViewMode} />
|
||||
|
||||
<div class="bg-border mx-1 h-6 w-px"></div>
|
||||
<div class="mx-1 h-6 w-px bg-[var(--border-default)]"></div>
|
||||
|
||||
<!-- Status Filter Chips -->
|
||||
<!-- Status Filter Chips - Using design system tokens -->
|
||||
<div class="flex gap-1">
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => (statusChipFilter = 'ALL')}
|
||||
class="inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-sm font-medium transition-colors {statusChipFilter ===
|
||||
'ALL'
|
||||
? 'bg-gray-900 text-white dark:bg-white dark:text-gray-900'
|
||||
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700'}"
|
||||
? 'bg-[var(--text-primary)] text-[var(--surface-default)]'
|
||||
: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)] hover:bg-[var(--surface-raised)]'}"
|
||||
>
|
||||
All
|
||||
<span
|
||||
class="rounded-full px-1.5 py-0.5 text-xs {statusChipFilter === 'ALL'
|
||||
? 'bg-gray-700 text-gray-200 dark:bg-gray-200 dark:text-gray-700'
|
||||
: 'bg-gray-200 text-gray-500 dark:bg-gray-700 dark:text-gray-500'}"
|
||||
? 'bg-[var(--text-secondary)] text-[var(--surface-default)]'
|
||||
: 'bg-[var(--border-default)] text-[var(--text-tertiary)]'}"
|
||||
>
|
||||
{totalLeadCount}
|
||||
</span>
|
||||
|
|
@ -1469,14 +1469,14 @@
|
|||
onclick={() => (statusChipFilter = 'open')}
|
||||
class="inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-sm font-medium transition-colors {statusChipFilter ===
|
||||
'open'
|
||||
? 'bg-blue-600 text-white dark:bg-blue-500'
|
||||
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700'}"
|
||||
? 'bg-[var(--color-primary-default)] text-white'
|
||||
: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)] hover:bg-[var(--surface-raised)]'}"
|
||||
>
|
||||
Open
|
||||
<span
|
||||
class="rounded-full px-1.5 py-0.5 text-xs {statusChipFilter === 'open'
|
||||
? 'bg-blue-700 text-blue-100 dark:bg-blue-600'
|
||||
: 'bg-gray-200 text-gray-500 dark:bg-gray-700 dark:text-gray-500'}"
|
||||
? 'bg-[var(--color-primary-hover)] text-white/90'
|
||||
: 'bg-[var(--border-default)] text-[var(--text-tertiary)]'}"
|
||||
>
|
||||
{openCount}
|
||||
</span>
|
||||
|
|
@ -1486,21 +1486,21 @@
|
|||
onclick={() => (statusChipFilter = 'lost')}
|
||||
class="inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-sm font-medium transition-colors {statusChipFilter ===
|
||||
'lost'
|
||||
? 'bg-red-600 text-white dark:bg-red-500'
|
||||
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700'}"
|
||||
? 'bg-[var(--stage-lost)] text-white'
|
||||
: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)] hover:bg-[var(--surface-raised)]'}"
|
||||
>
|
||||
Lost
|
||||
<span
|
||||
class="rounded-full px-1.5 py-0.5 text-xs {statusChipFilter === 'lost'
|
||||
? 'bg-red-700 text-red-100 dark:bg-red-600'
|
||||
: 'bg-gray-200 text-gray-500 dark:bg-gray-700 dark:text-gray-500'}"
|
||||
? 'bg-[var(--stage-lost)]/80 text-white/90'
|
||||
: 'bg-[var(--border-default)] text-[var(--text-tertiary)]'}"
|
||||
>
|
||||
{lostCount}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="bg-border mx-1 h-6 w-px"></div>
|
||||
<div class="mx-1 h-6 w-px bg-[var(--border-default)]"></div>
|
||||
|
||||
<!-- Filter Toggle Button -->
|
||||
<Button
|
||||
|
|
@ -1513,7 +1513,7 @@
|
|||
Filters
|
||||
{#if activeFiltersCount > 0}
|
||||
<span
|
||||
class="rounded-full bg-blue-100 px-1.5 py-0.5 text-xs font-medium text-blue-700 dark:bg-blue-900/30 dark:text-blue-400"
|
||||
class="rounded-full bg-[var(--color-primary-light)] px-1.5 py-0.5 text-xs font-medium text-[var(--color-primary-default)] dark:bg-[var(--color-primary-default)]/15"
|
||||
>
|
||||
{activeFiltersCount}
|
||||
</span>
|
||||
|
|
@ -1530,7 +1530,7 @@
|
|||
Columns
|
||||
{#if visibleColumns.length < columns.length}
|
||||
<span
|
||||
class="rounded-full bg-blue-100 px-1.5 py-0.5 text-xs font-medium text-blue-700 dark:bg-blue-900/30 dark:text-blue-400"
|
||||
class="rounded-full bg-[var(--color-primary-light)] px-1.5 py-0.5 text-xs font-medium text-[var(--color-primary-default)] dark:bg-[var(--color-primary-default)]/15"
|
||||
>
|
||||
{visibleColumns.length}/{columns.length}
|
||||
</span>
|
||||
|
|
@ -1620,8 +1620,11 @@
|
|||
<!-- Table View -->
|
||||
{#if filteredLeads.length === 0}
|
||||
<div class="flex flex-col items-center justify-center py-16 text-center">
|
||||
<User class="mb-4 h-12 w-12 text-gray-300 dark:text-gray-600" />
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100">No leads found</h3>
|
||||
<div class="mb-4 flex size-16 items-center justify-center rounded-[var(--radius-xl)] bg-[var(--surface-sunken)]">
|
||||
<User class="size-8 text-[var(--text-tertiary)]" />
|
||||
</div>
|
||||
<h3 class="text-lg font-medium text-[var(--text-primary)]">No leads found</h3>
|
||||
<p class="mt-1 text-sm text-[var(--text-secondary)]">Create a new lead to get started</p>
|
||||
</div>
|
||||
{:else}
|
||||
<!-- Desktop Table using CrmTable -->
|
||||
|
|
@ -1635,29 +1638,31 @@
|
|||
>
|
||||
{#snippet emptyState()}
|
||||
<div class="flex flex-col items-center justify-center py-16 text-center">
|
||||
<User class="mb-4 h-12 w-12 text-gray-300 dark:text-gray-600" />
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100">No leads found</h3>
|
||||
<div class="mb-4 flex size-12 items-center justify-center rounded-[var(--radius-lg)] bg-[var(--surface-sunken)]">
|
||||
<User class="size-6 text-[var(--text-tertiary)]" />
|
||||
</div>
|
||||
<h3 class="text-lg font-medium text-[var(--text-primary)]">No leads found</h3>
|
||||
</div>
|
||||
{/snippet}
|
||||
</CrmTable>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Card View -->
|
||||
<div class="divide-y md:hidden dark:divide-gray-800">
|
||||
<div class="divide-y divide-[var(--border-default)]/50 md:hidden">
|
||||
{#each filteredLeads as lead (lead.id)}
|
||||
<button
|
||||
type="button"
|
||||
class="flex w-full items-start gap-3 px-4 py-3 text-left transition-colors hover:bg-gray-50/30 dark:hover:bg-gray-800/30"
|
||||
class="flex w-full items-start gap-3 px-4 py-3 text-left transition-colors hover:bg-[var(--color-primary-light)] dark:hover:bg-[var(--color-primary-default)]/5"
|
||||
onclick={() => openLead(lead)}
|
||||
>
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="flex items-start justify-between gap-2">
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-900 dark:text-gray-100">
|
||||
<p class="text-sm font-medium text-[var(--text-primary)]">
|
||||
{getFullName(lead)}
|
||||
</p>
|
||||
{#if lead.company}
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
<p class="text-sm text-[var(--text-secondary)]">
|
||||
{typeof lead.company === 'object' ? lead.company.name : lead.company}
|
||||
</p>
|
||||
{/if}
|
||||
|
|
@ -1672,7 +1677,7 @@
|
|||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="mt-2 flex flex-wrap items-center gap-3 text-xs text-gray-500 dark:text-gray-400"
|
||||
class="mt-2 flex flex-wrap items-center gap-3 text-xs text-[var(--text-secondary)]"
|
||||
>
|
||||
{#if lead.rating}
|
||||
<span
|
||||
|
|
@ -1694,7 +1699,7 @@
|
|||
<button
|
||||
type="button"
|
||||
onclick={openCreate}
|
||||
class="flex w-full items-center gap-2 px-4 py-3 text-sm text-gray-500 transition-colors hover:bg-gray-50 hover:text-gray-700 dark:hover:bg-gray-800 dark:hover:text-gray-300"
|
||||
class="flex w-full items-center gap-2 px-4 py-3 text-sm text-[var(--text-secondary)] transition-colors hover:bg-[var(--surface-raised)] hover:text-[var(--text-primary)]"
|
||||
>
|
||||
<Plus class="h-4 w-4" />
|
||||
New
|
||||
|
|
|
|||
|
|
@ -60,59 +60,59 @@
|
|||
let accountName = $state('');
|
||||
let accountIdFromUrl = $state('');
|
||||
|
||||
// Stage options with colors
|
||||
// Stage options with colors - using design system tokens
|
||||
const stageOptions = [
|
||||
{
|
||||
value: 'PROSPECTING',
|
||||
label: 'Prospecting',
|
||||
color: 'bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300'
|
||||
color: 'bg-[var(--stage-new-bg)] text-[var(--stage-new)] dark:bg-[var(--stage-new)]/15'
|
||||
},
|
||||
{
|
||||
value: 'QUALIFICATION',
|
||||
label: 'Qualification',
|
||||
color: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400'
|
||||
color: 'bg-[var(--stage-qualified-bg)] text-[var(--stage-qualified)] dark:bg-[var(--stage-qualified)]/15'
|
||||
},
|
||||
{
|
||||
value: 'PROPOSAL',
|
||||
label: 'Proposal',
|
||||
color: 'bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-400'
|
||||
color: 'bg-[var(--stage-proposal-bg)] text-[var(--stage-proposal)] dark:bg-[var(--stage-proposal)]/15'
|
||||
},
|
||||
{
|
||||
value: 'NEGOTIATION',
|
||||
label: 'Negotiation',
|
||||
color: 'bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400'
|
||||
color: 'bg-[var(--stage-negotiation-bg)] text-[var(--stage-negotiation)] dark:bg-[var(--stage-negotiation)]/15'
|
||||
},
|
||||
{
|
||||
value: 'CLOSED_WON',
|
||||
label: 'Won',
|
||||
color: 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400'
|
||||
color: 'bg-[var(--stage-won-bg)] text-[var(--stage-won)] dark:bg-[var(--stage-won)]/15'
|
||||
},
|
||||
{
|
||||
value: 'CLOSED_LOST',
|
||||
label: 'Lost',
|
||||
color: 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400'
|
||||
color: 'bg-[var(--stage-lost-bg)] text-[var(--stage-lost)] dark:bg-[var(--stage-lost)]/15'
|
||||
}
|
||||
];
|
||||
|
||||
// Type options
|
||||
// Type options - using design system tokens
|
||||
const typeOptions = OPPORTUNITY_TYPES.map((t) => ({
|
||||
value: t.value,
|
||||
label: t.label,
|
||||
color: 'bg-slate-100 text-slate-700 dark:bg-slate-800 dark:text-slate-300'
|
||||
color: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)]'
|
||||
}));
|
||||
|
||||
// Source options
|
||||
// Source options - using design system tokens
|
||||
const sourceOptions = OPPORTUNITY_SOURCES.map((s) => ({
|
||||
value: s.value,
|
||||
label: s.label,
|
||||
color: 'bg-indigo-100 text-indigo-700 dark:bg-indigo-900/30 dark:text-indigo-400'
|
||||
color: 'bg-[var(--color-primary-light)] text-[var(--color-primary-default)] dark:bg-[var(--color-primary-default)]/15'
|
||||
}));
|
||||
|
||||
// Currency options
|
||||
// Currency options - using design system tokens
|
||||
const currencyOptions = CURRENCY_CODES.filter((c) => c.value).map((c) => ({
|
||||
value: c.value,
|
||||
label: c.label,
|
||||
color: 'bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400'
|
||||
color: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)]'
|
||||
}));
|
||||
|
||||
/**
|
||||
|
|
@ -1098,14 +1098,14 @@
|
|||
onclick={() => (statusChipFilter = 'ALL')}
|
||||
class="inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-sm font-medium transition-colors {statusChipFilter ===
|
||||
'ALL'
|
||||
? 'bg-gray-900 text-white dark:bg-white dark:text-gray-900'
|
||||
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700'}"
|
||||
? 'bg-[var(--color-primary-default)] text-white'
|
||||
: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)] hover:bg-[var(--surface-raised)]'}"
|
||||
>
|
||||
All
|
||||
<span
|
||||
class="rounded-full px-1.5 py-0.5 text-xs {statusChipFilter === 'ALL'
|
||||
? 'bg-gray-700 text-gray-200 dark:bg-gray-200 dark:text-gray-700'
|
||||
: 'bg-gray-200 text-gray-500 dark:bg-gray-700 dark:text-gray-500'}"
|
||||
? 'bg-[var(--color-primary-dark)] text-white/90'
|
||||
: 'bg-[var(--border-default)] text-[var(--text-tertiary)]'}"
|
||||
>
|
||||
{opportunities.length}
|
||||
</span>
|
||||
|
|
@ -1115,14 +1115,14 @@
|
|||
onclick={() => (statusChipFilter = 'open')}
|
||||
class="inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-sm font-medium transition-colors {statusChipFilter ===
|
||||
'open'
|
||||
? 'bg-blue-600 text-white dark:bg-blue-500'
|
||||
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700'}"
|
||||
? 'bg-[var(--stage-qualified)] text-white'
|
||||
: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)] hover:bg-[var(--surface-raised)]'}"
|
||||
>
|
||||
Open
|
||||
<span
|
||||
class="rounded-full px-1.5 py-0.5 text-xs {statusChipFilter === 'open'
|
||||
? 'bg-blue-700 text-blue-100 dark:bg-blue-600'
|
||||
: 'bg-gray-200 text-gray-500 dark:bg-gray-700 dark:text-gray-500'}"
|
||||
? 'bg-black/20 text-white/90'
|
||||
: 'bg-[var(--border-default)] text-[var(--text-tertiary)]'}"
|
||||
>
|
||||
{openCount}
|
||||
</span>
|
||||
|
|
@ -1132,14 +1132,14 @@
|
|||
onclick={() => (statusChipFilter = 'won')}
|
||||
class="inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-sm font-medium transition-colors {statusChipFilter ===
|
||||
'won'
|
||||
? 'bg-emerald-600 text-white dark:bg-emerald-500'
|
||||
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700'}"
|
||||
? 'bg-[var(--stage-won)] text-white'
|
||||
: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)] hover:bg-[var(--surface-raised)]'}"
|
||||
>
|
||||
Won
|
||||
<span
|
||||
class="rounded-full px-1.5 py-0.5 text-xs {statusChipFilter === 'won'
|
||||
? 'bg-emerald-700 text-emerald-100 dark:bg-emerald-600'
|
||||
: 'bg-gray-200 text-gray-500 dark:bg-gray-700 dark:text-gray-500'}"
|
||||
? 'bg-black/20 text-white/90'
|
||||
: 'bg-[var(--border-default)] text-[var(--text-tertiary)]'}"
|
||||
>
|
||||
{wonCount}
|
||||
</span>
|
||||
|
|
@ -1149,14 +1149,14 @@
|
|||
onclick={() => (statusChipFilter = 'lost')}
|
||||
class="inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-sm font-medium transition-colors {statusChipFilter ===
|
||||
'lost'
|
||||
? 'bg-red-600 text-white dark:bg-red-500'
|
||||
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700'}"
|
||||
? 'bg-[var(--stage-lost)] text-white'
|
||||
: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)] hover:bg-[var(--surface-raised)]'}"
|
||||
>
|
||||
Lost
|
||||
<span
|
||||
class="rounded-full px-1.5 py-0.5 text-xs {statusChipFilter === 'lost'
|
||||
? 'bg-red-700 text-red-100 dark:bg-red-600'
|
||||
: 'bg-gray-200 text-gray-500 dark:bg-gray-700 dark:text-gray-500'}"
|
||||
? 'bg-black/20 text-white/90'
|
||||
: 'bg-[var(--border-default)] text-[var(--text-tertiary)]'}"
|
||||
>
|
||||
{lostCount}
|
||||
</span>
|
||||
|
|
@ -1176,7 +1176,7 @@
|
|||
Filters
|
||||
{#if activeFiltersCount > 0}
|
||||
<span
|
||||
class="rounded-full bg-blue-100 px-1.5 py-0.5 text-xs font-medium text-blue-700 dark:bg-blue-900/30 dark:text-blue-400"
|
||||
class="rounded-full bg-[var(--color-primary-light)] px-1.5 py-0.5 text-xs font-medium text-[var(--color-primary-default)]"
|
||||
>
|
||||
{activeFiltersCount}
|
||||
</span>
|
||||
|
|
@ -1192,7 +1192,7 @@
|
|||
Columns
|
||||
{#if visibleColumns.length < columns.length}
|
||||
<span
|
||||
class="rounded-full bg-blue-100 px-1.5 py-0.5 text-xs font-medium text-blue-700 dark:bg-blue-900/30 dark:text-blue-400"
|
||||
class="rounded-full bg-[var(--color-primary-light)] px-1.5 py-0.5 text-xs font-medium text-[var(--color-primary-default)]"
|
||||
>
|
||||
{visibleColumns.length}/{columns.length}
|
||||
</span>
|
||||
|
|
@ -1261,8 +1261,11 @@
|
|||
<!-- Table View -->
|
||||
{#if filteredOpportunities.length === 0}
|
||||
<div class="flex flex-col items-center justify-center py-16 text-center">
|
||||
<Target class="text-muted-foreground/50 mb-4 h-12 w-12" />
|
||||
<h3 class="text-foreground text-lg font-medium">No opportunities found</h3>
|
||||
<div class="mb-4 flex size-16 items-center justify-center rounded-[var(--radius-xl)] bg-[var(--surface-sunken)]">
|
||||
<Target class="size-8 text-[var(--text-tertiary)]" />
|
||||
</div>
|
||||
<h3 class="text-[var(--text-primary)] text-lg font-medium">No opportunities found</h3>
|
||||
<p class="text-[var(--text-secondary)] mt-1 text-sm">Try adjusting your filters or create a new opportunity</p>
|
||||
</div>
|
||||
{:else}
|
||||
<CrmTable
|
||||
|
|
@ -1274,8 +1277,10 @@
|
|||
>
|
||||
{#snippet emptyState()}
|
||||
<div class="flex flex-col items-center justify-center py-16 text-center">
|
||||
<Target class="text-muted-foreground/50 mb-4 h-12 w-12" />
|
||||
<h3 class="text-foreground text-lg font-medium">No opportunities found</h3>
|
||||
<div class="mb-4 flex size-16 items-center justify-center rounded-[var(--radius-xl)] bg-[var(--surface-sunken)]">
|
||||
<Target class="size-8 text-[var(--text-tertiary)]" />
|
||||
</div>
|
||||
<h3 class="text-[var(--text-primary)] text-lg font-medium">No opportunities found</h3>
|
||||
</div>
|
||||
{/snippet}
|
||||
</CrmTable>
|
||||
|
|
@ -1311,37 +1316,37 @@
|
|||
{#if drawerMode !== 'create' && selectedRow}
|
||||
<div class="mb-4">
|
||||
<p
|
||||
class="mb-2 text-xs font-medium tracking-wider text-gray-500 uppercase dark:text-gray-400"
|
||||
class="mb-2 text-xs font-medium tracking-wider text-[var(--text-tertiary)] uppercase"
|
||||
>
|
||||
Details
|
||||
</p>
|
||||
<div class="grid grid-cols-2 gap-3 text-sm">
|
||||
<div>
|
||||
<p class="text-xs text-gray-400 dark:text-gray-500">Account</p>
|
||||
<p class="font-medium text-gray-900 dark:text-gray-100">
|
||||
<p class="text-xs text-[var(--text-tertiary)]">Account</p>
|
||||
<p class="font-medium text-[var(--text-primary)]">
|
||||
{selectedRow.account?.name || '-'}
|
||||
</p>
|
||||
</div>
|
||||
{#if selectedRow.createdAt}
|
||||
<div>
|
||||
<p class="text-xs text-gray-400 dark:text-gray-500">Created</p>
|
||||
<p class="font-medium text-gray-900 dark:text-gray-100">
|
||||
<p class="text-xs text-[var(--text-tertiary)]">Created</p>
|
||||
<p class="font-medium text-[var(--text-primary)]">
|
||||
{formatRelativeDate(selectedRow.createdAt)}
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
{#if isClosed && selectedRow.closedBy}
|
||||
<div>
|
||||
<p class="text-xs text-gray-400 dark:text-gray-500">Closed By</p>
|
||||
<p class="font-medium text-gray-900 dark:text-gray-100">
|
||||
<p class="text-xs text-[var(--text-tertiary)]">Closed By</p>
|
||||
<p class="font-medium text-[var(--text-primary)]">
|
||||
{selectedRow.closedBy.name || '-'}
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
{#if isClosed && selectedRow.closedOn}
|
||||
<div>
|
||||
<p class="text-xs text-gray-400 dark:text-gray-500">Closed On</p>
|
||||
<p class="font-medium text-gray-900 dark:text-gray-100">
|
||||
<p class="text-xs text-[var(--text-tertiary)]">Closed On</p>
|
||||
<p class="font-medium text-[var(--text-primary)]">
|
||||
{formatDate(selectedRow.closedOn)}
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -1350,16 +1355,16 @@
|
|||
</div>
|
||||
|
||||
<!-- Products Section -->
|
||||
<div class="mt-6 border-t pt-4 dark:border-gray-700">
|
||||
<div class="mt-6 border-t border-[var(--border-default)] pt-4">
|
||||
<div class="mb-3 flex items-center justify-between">
|
||||
<p
|
||||
class="flex items-center gap-2 text-xs font-medium tracking-wider text-gray-500 uppercase dark:text-gray-400"
|
||||
class="flex items-center gap-2 text-xs font-medium tracking-wider text-[var(--text-tertiary)] uppercase"
|
||||
>
|
||||
<Package class="h-4 w-4" />
|
||||
<Package class="size-4" />
|
||||
Products
|
||||
</p>
|
||||
{#if hasLineItems}
|
||||
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
<span class="text-sm font-medium text-[var(--text-primary)]">
|
||||
Total: {formatCurrency(
|
||||
selectedRow.lineItemsTotal || 0,
|
||||
selectedRow.currency || 'USD'
|
||||
|
|
@ -1373,13 +1378,13 @@
|
|||
<div class="mb-4 space-y-2">
|
||||
{#each lineItems as item (item.id)}
|
||||
<div
|
||||
class="flex items-center justify-between rounded-lg border bg-gray-50 px-3 py-2 dark:border-gray-700 dark:bg-gray-800"
|
||||
class="flex items-center justify-between rounded-[var(--radius-lg)] border border-[var(--border-default)] bg-[var(--surface-sunken)] px-3 py-2"
|
||||
>
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="truncate text-sm font-medium text-gray-900 dark:text-gray-100">
|
||||
<p class="truncate text-sm font-medium text-[var(--text-primary)]">
|
||||
{item.productName || item.name || 'Custom Item'}
|
||||
</p>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
||||
<p class="text-xs text-[var(--text-secondary)]">
|
||||
{item.quantity} x {formatCurrency(
|
||||
item.unitPrice,
|
||||
selectedRow.currency || 'USD'
|
||||
|
|
@ -1390,12 +1395,12 @@
|
|||
{#if !isClosed}
|
||||
<button
|
||||
type="button"
|
||||
class="ml-2 rounded p-1 text-gray-400 hover:bg-gray-200 hover:text-red-600 disabled:cursor-not-allowed disabled:opacity-50 dark:hover:bg-gray-700 dark:hover:text-red-400"
|
||||
class="ml-2 rounded-[var(--radius-md)] p-1 text-[var(--text-tertiary)] hover:bg-[var(--surface-raised)] hover:text-[var(--color-negative-default)] disabled:cursor-not-allowed disabled:opacity-50"
|
||||
onclick={() => deleteLineItem(item.id)}
|
||||
disabled={isDeletingLineItem === item.id}
|
||||
>
|
||||
{#if isDeletingLineItem === item.id}
|
||||
<svg class="h-4 w-4 animate-spin" viewBox="0 0 24 24">
|
||||
<svg class="size-4 animate-spin" viewBox="0 0 24 24">
|
||||
<circle
|
||||
class="opacity-25"
|
||||
cx="12"
|
||||
|
|
@ -1412,7 +1417,7 @@
|
|||
></path>
|
||||
</svg>
|
||||
{:else}
|
||||
<Trash2 class="h-4 w-4" />
|
||||
<Trash2 class="size-4" />
|
||||
{/if}
|
||||
</button>
|
||||
{/if}
|
||||
|
|
@ -1420,15 +1425,15 @@
|
|||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<p class="mb-4 text-sm text-gray-500 dark:text-gray-400">No products added yet.</p>
|
||||
<p class="mb-4 text-sm text-[var(--text-secondary)]">No products added yet.</p>
|
||||
{/if}
|
||||
|
||||
<!-- Add product form (only if not closed) -->
|
||||
{#if !isClosed}
|
||||
<div
|
||||
class="space-y-3 rounded-lg border bg-gray-50 p-3 dark:border-gray-700 dark:bg-gray-800/50"
|
||||
class="space-y-3 rounded-[var(--radius-lg)] border border-[var(--border-default)] bg-[var(--surface-sunken)] p-3"
|
||||
>
|
||||
<p class="text-xs font-medium text-gray-600 dark:text-gray-400">Add Product</p>
|
||||
<p class="text-xs font-medium text-[var(--text-secondary)]">Add Product</p>
|
||||
<div class="grid grid-cols-1 gap-2">
|
||||
<Select.Root
|
||||
type="single"
|
||||
|
|
@ -1446,7 +1451,7 @@
|
|||
'Select product...'}
|
||||
</Select.Trigger>
|
||||
<Select.Content class="max-h-60">
|
||||
<div class="sticky top-0 bg-white p-2 dark:bg-gray-800">
|
||||
<div class="sticky top-0 bg-[var(--surface-raised)] p-2">
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Search products..."
|
||||
|
|
@ -1500,7 +1505,7 @@
|
|||
</div>
|
||||
|
||||
<!-- Comments Section -->
|
||||
<div class="mt-6 border-t pt-4 dark:border-gray-700">
|
||||
<div class="mt-6 border-t border-[var(--border-default)] pt-4">
|
||||
<CommentSection
|
||||
entityId={selectedRow.id}
|
||||
entityType="opportunity"
|
||||
|
|
@ -1521,20 +1526,20 @@
|
|||
{#if !isClosed}
|
||||
<Button
|
||||
variant="outline"
|
||||
class="text-green-600 hover:bg-green-50 hover:text-green-700 dark:text-green-400 dark:hover:bg-green-900/30 dark:hover:text-green-300"
|
||||
class="text-[var(--stage-won)] hover:bg-[var(--stage-won-bg)] hover:text-[var(--stage-won)]"
|
||||
onclick={handleMarkWon}
|
||||
disabled={isLoading}
|
||||
>
|
||||
<Trophy class="mr-1.5 h-4 w-4" />
|
||||
<Trophy class="mr-1.5 size-4" />
|
||||
Mark Won
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
class="text-red-600 hover:bg-red-50 hover:text-red-700 dark:text-red-400 dark:hover:bg-red-900/30 dark:hover:text-red-300"
|
||||
class="text-[var(--stage-lost)] hover:bg-[var(--stage-lost-bg)] hover:text-[var(--stage-lost)]"
|
||||
onclick={handleMarkLost}
|
||||
disabled={isLoading}
|
||||
>
|
||||
<XCircle class="mr-1.5 h-4 w-4" />
|
||||
<XCircle class="mr-1.5 size-4" />
|
||||
Mark Lost
|
||||
</Button>
|
||||
{/if}
|
||||
|
|
@ -1545,10 +1550,10 @@
|
|||
<Button
|
||||
{...props}
|
||||
variant="outline"
|
||||
class="text-blue-600 hover:bg-blue-50 hover:text-blue-700 dark:text-blue-400 dark:hover:bg-blue-900/30 dark:hover:text-blue-300"
|
||||
class="text-[var(--color-primary-default)] hover:bg-[var(--color-primary-light)] hover:text-[var(--color-primary-default)]"
|
||||
disabled={isLoading}
|
||||
>
|
||||
<Receipt class="mr-1.5 h-4 w-4" />
|
||||
<Receipt class="mr-1.5 size-4" />
|
||||
Create Invoice
|
||||
</Button>
|
||||
{/snippet}
|
||||
|
|
|
|||
|
|
@ -103,14 +103,14 @@
|
|||
<div class="flex-1 space-y-6 p-4 md:p-6">
|
||||
<!-- Success/Error Messages -->
|
||||
{#if form?.success}
|
||||
<Card.Root class="border-green-200 bg-green-50 dark:border-green-800 dark:bg-green-900/20">
|
||||
<Card.Root class="border-[var(--color-success-default)]/20 bg-[var(--color-success-light)]">
|
||||
<Card.Content class="flex items-center gap-3 p-4">
|
||||
<div
|
||||
class="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-green-100 dark:bg-green-800"
|
||||
class="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-[var(--color-success-light)] dark:bg-[var(--color-success-default)]/20"
|
||||
>
|
||||
<Check class="h-4 w-4 text-green-600 dark:text-green-300" />
|
||||
<Check class="h-4 w-4 text-[var(--color-success-default)]" />
|
||||
</div>
|
||||
<p class="text-sm font-medium text-green-800 dark:text-green-200">
|
||||
<p class="text-sm font-medium text-[var(--color-success-default)]">
|
||||
{form.message}
|
||||
</p>
|
||||
</Card.Content>
|
||||
|
|
@ -118,14 +118,14 @@
|
|||
{/if}
|
||||
|
||||
{#if form?.error}
|
||||
<Card.Root class="border-red-200 bg-red-50 dark:border-red-800 dark:bg-red-900/20">
|
||||
<Card.Root class="border-[var(--color-negative-default)]/20 bg-[var(--color-negative-light)]">
|
||||
<Card.Content class="flex items-center gap-3 p-4">
|
||||
<div
|
||||
class="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-red-100 dark:bg-red-800"
|
||||
class="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-[var(--color-negative-light)] dark:bg-[var(--color-negative-default)]/20"
|
||||
>
|
||||
<X class="h-4 w-4 text-red-600 dark:text-red-300" />
|
||||
<X class="h-4 w-4 text-[var(--color-negative-default)]" />
|
||||
</div>
|
||||
<p class="text-sm font-medium text-red-800 dark:text-red-200">
|
||||
<p class="text-sm font-medium text-[var(--color-negative-default)]">
|
||||
{form.error}
|
||||
</p>
|
||||
</Card.Content>
|
||||
|
|
@ -146,7 +146,7 @@
|
|||
class=""
|
||||
/>
|
||||
{/if}
|
||||
<Avatar.Fallback class="bg-gradient-to-br from-blue-500 to-purple-600 text-white">
|
||||
<Avatar.Fallback class="bg-[var(--color-primary-default)] text-white">
|
||||
{getInitials(data.user.name)}
|
||||
</Avatar.Fallback>
|
||||
</Avatar.Root>
|
||||
|
|
@ -315,7 +315,7 @@
|
|||
<div class="bg-muted/30 flex items-center justify-between rounded-lg border p-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div
|
||||
class="flex h-10 w-10 items-center justify-center rounded-lg bg-gradient-to-br from-blue-500 to-purple-600"
|
||||
class="flex h-10 w-10 items-center justify-center rounded-lg bg-[var(--color-primary-default)]"
|
||||
>
|
||||
<Building2 class="h-5 w-5 text-white" />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -157,7 +157,7 @@
|
|||
Create and manage tags to organize contacts, companies, deals, and tickets.
|
||||
</p>
|
||||
</div>
|
||||
<Button onclick={openCreate} class="bg-[#ff7a59] hover:bg-[#ff5c35] text-white border-0">
|
||||
<Button onclick={openCreate} class="bg-[var(--color-primary-default)] hover:bg-[var(--color-primary-dark)] text-white border-0">
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Create tag
|
||||
</Button>
|
||||
|
|
@ -217,7 +217,7 @@
|
|||
<p class="mt-1 max-w-sm text-sm text-muted-foreground">
|
||||
Tags help you organize and filter your CRM records. Create your first tag to get started.
|
||||
</p>
|
||||
<Button onclick={openCreate} class="mt-4 bg-[#ff7a59] hover:bg-[#ff5c35] text-white border-0">
|
||||
<Button onclick={openCreate} class="mt-4 bg-[var(--color-primary-default)] hover:bg-[var(--color-primary-dark)] text-white border-0">
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Create tag
|
||||
</Button>
|
||||
|
|
@ -457,7 +457,7 @@
|
|||
<Button
|
||||
type="submit"
|
||||
disabled={!formName.trim() || isLoading}
|
||||
class="bg-[#ff7a59] hover:bg-[#ff5c35] text-white border-0"
|
||||
class="bg-[var(--color-primary-default)] hover:bg-[var(--color-primary-dark)] text-white border-0"
|
||||
>
|
||||
{isLoading ? 'Creating...' : 'Create tag'}
|
||||
</Button>
|
||||
|
|
@ -540,7 +540,7 @@
|
|||
<Button
|
||||
type="submit"
|
||||
disabled={!formName.trim() || isLoading}
|
||||
class="bg-[#ff7a59] hover:bg-[#ff5c35] text-white border-0"
|
||||
class="bg-[var(--color-primary-default)] hover:bg-[var(--color-primary-dark)] text-white border-0"
|
||||
>
|
||||
{isLoading ? 'Saving...' : 'Save'}
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -56,22 +56,22 @@
|
|||
let accountNameFromUrl = $state('');
|
||||
let accountIdFromUrl = $state('');
|
||||
|
||||
// Status and priority options with colors
|
||||
// Status and priority options with colors - using design system tokens
|
||||
const statusOptions = [
|
||||
{
|
||||
value: 'New',
|
||||
label: 'New',
|
||||
color: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400'
|
||||
color: 'bg-[var(--task-upcoming-bg)] text-[var(--task-upcoming)] dark:bg-[var(--task-upcoming)]/15'
|
||||
},
|
||||
{
|
||||
value: 'In Progress',
|
||||
label: 'In Progress',
|
||||
color: 'bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400'
|
||||
color: 'bg-[var(--task-due-today-bg)] text-[var(--task-due-today)] dark:bg-[var(--task-due-today)]/15'
|
||||
},
|
||||
{
|
||||
value: 'Completed',
|
||||
label: 'Completed',
|
||||
color: 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400'
|
||||
color: 'bg-[var(--task-completed-bg)] text-[var(--task-completed)] dark:bg-[var(--task-completed)]/15'
|
||||
}
|
||||
];
|
||||
|
||||
|
|
@ -79,17 +79,17 @@
|
|||
{
|
||||
value: 'High',
|
||||
label: 'High',
|
||||
color: 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400'
|
||||
color: 'bg-[var(--priority-high-bg)] text-[var(--priority-high)] dark:bg-[var(--priority-high)]/15'
|
||||
},
|
||||
{
|
||||
value: 'Medium',
|
||||
label: 'Medium',
|
||||
color: 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400'
|
||||
color: 'bg-[var(--priority-medium-bg)] text-[var(--priority-medium)] dark:bg-[var(--priority-medium)]/15'
|
||||
},
|
||||
{
|
||||
value: 'Low',
|
||||
label: 'Low',
|
||||
color: 'bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400'
|
||||
color: 'bg-[var(--priority-low-bg)] text-[var(--priority-low)] dark:bg-[var(--priority-low)]/15'
|
||||
}
|
||||
];
|
||||
|
||||
|
|
@ -550,7 +550,7 @@
|
|||
*/
|
||||
function getOptionStyle(value, options) {
|
||||
const option = options.find((o) => o.value === value);
|
||||
return option?.color ?? 'bg-gray-100 text-gray-600';
|
||||
return option?.color ?? 'bg-[var(--surface-sunken)] text-[var(--text-secondary)]';
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1088,14 +1088,14 @@
|
|||
onclick={() => (statusChipFilter = 'ALL')}
|
||||
class="inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-sm font-medium transition-colors {statusChipFilter ===
|
||||
'ALL'
|
||||
? 'bg-gray-900 text-white dark:bg-white dark:text-gray-900'
|
||||
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700'}"
|
||||
? 'bg-[var(--color-primary-default)] text-white'
|
||||
: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)] hover:bg-[var(--surface-raised)]'}"
|
||||
>
|
||||
All
|
||||
<span
|
||||
class="rounded-full px-1.5 py-0.5 text-xs {statusChipFilter === 'ALL'
|
||||
? 'bg-gray-700 text-gray-200 dark:bg-gray-200 dark:text-gray-700'
|
||||
: 'bg-gray-200 text-gray-500 dark:bg-gray-700 dark:text-gray-500'}"
|
||||
? 'bg-[var(--color-primary-dark)] text-white/90'
|
||||
: 'bg-[var(--border-default)] text-[var(--text-tertiary)]'}"
|
||||
>
|
||||
{tasks.length}
|
||||
</span>
|
||||
|
|
@ -1105,14 +1105,14 @@
|
|||
onclick={() => (statusChipFilter = 'active')}
|
||||
class="inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-sm font-medium transition-colors {statusChipFilter ===
|
||||
'active'
|
||||
? 'bg-blue-600 text-white dark:bg-blue-500'
|
||||
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700'}"
|
||||
? 'bg-[var(--task-due-today)] text-white'
|
||||
: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)] hover:bg-[var(--surface-raised)]'}"
|
||||
>
|
||||
Active
|
||||
<span
|
||||
class="rounded-full px-1.5 py-0.5 text-xs {statusChipFilter === 'active'
|
||||
? 'bg-blue-700 text-blue-100 dark:bg-blue-600'
|
||||
: 'bg-gray-200 text-gray-500 dark:bg-gray-700 dark:text-gray-500'}"
|
||||
? 'bg-black/20 text-white/90'
|
||||
: 'bg-[var(--border-default)] text-[var(--text-tertiary)]'}"
|
||||
>
|
||||
{activeTaskCount}
|
||||
</span>
|
||||
|
|
@ -1122,14 +1122,14 @@
|
|||
onclick={() => (statusChipFilter = 'completed')}
|
||||
class="inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-sm font-medium transition-colors {statusChipFilter ===
|
||||
'completed'
|
||||
? 'bg-emerald-600 text-white dark:bg-emerald-500'
|
||||
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700'}"
|
||||
? 'bg-[var(--task-completed)] text-white'
|
||||
: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)] hover:bg-[var(--surface-raised)]'}"
|
||||
>
|
||||
Completed
|
||||
<span
|
||||
class="rounded-full px-1.5 py-0.5 text-xs {statusChipFilter === 'completed'
|
||||
? 'bg-emerald-700 text-emerald-100 dark:bg-emerald-600'
|
||||
: 'bg-gray-200 text-gray-500 dark:bg-gray-700 dark:text-gray-500'}"
|
||||
? 'bg-black/20 text-white/90'
|
||||
: 'bg-[var(--border-default)] text-[var(--text-tertiary)]'}"
|
||||
>
|
||||
{completedCount}
|
||||
</span>
|
||||
|
|
@ -1180,7 +1180,7 @@
|
|||
Filters
|
||||
{#if activeFiltersCount > 0}
|
||||
<span
|
||||
class="rounded-full bg-blue-100 px-1.5 py-0.5 text-xs font-medium text-blue-700 dark:bg-blue-900/30 dark:text-blue-400"
|
||||
class="rounded-full bg-[var(--color-primary-light)] px-1.5 py-0.5 text-xs font-medium text-[var(--color-primary-default)]"
|
||||
>
|
||||
{activeFiltersCount}
|
||||
</span>
|
||||
|
|
@ -1196,7 +1196,7 @@
|
|||
Columns
|
||||
{#if columnCounts.visible < columnCounts.total}
|
||||
<span
|
||||
class="rounded-full bg-blue-100 px-1.5 py-0.5 text-xs font-medium text-blue-700 dark:bg-blue-900/30 dark:text-blue-400"
|
||||
class="rounded-full bg-[var(--color-primary-light)] px-1.5 py-0.5 text-xs font-medium text-[var(--color-primary-default)]"
|
||||
>
|
||||
{columnCounts.visible}/{columnCounts.total}
|
||||
</span>
|
||||
|
|
@ -1272,8 +1272,11 @@
|
|||
>
|
||||
{#snippet emptyState()}
|
||||
<div class="flex flex-col items-center justify-center py-16 text-center">
|
||||
<CheckSquare class="text-muted-foreground/50 mb-4 h-12 w-12" />
|
||||
<h3 class="text-foreground text-lg font-medium">No tasks found</h3>
|
||||
<div class="mb-4 flex size-16 items-center justify-center rounded-[var(--radius-xl)] bg-[var(--surface-sunken)]">
|
||||
<CheckSquare class="size-8 text-[var(--text-tertiary)]" />
|
||||
</div>
|
||||
<h3 class="text-[var(--text-primary)] text-lg font-medium">No tasks found</h3>
|
||||
<p class="text-[var(--text-secondary)] mt-1 text-sm">Try adjusting your filters or create a new task</p>
|
||||
</div>
|
||||
{/snippet}
|
||||
</CrmTable>
|
||||
|
|
@ -1516,23 +1519,23 @@
|
|||
<!-- Task metadata (only for existing tasks) -->
|
||||
{#if selectedTask && !isCreateMode}
|
||||
<div class="space-y-2 text-sm">
|
||||
<div class="mb-3 text-[13px] font-medium text-gray-500 dark:text-gray-400">Details</div>
|
||||
<div class="mb-3 text-[13px] font-medium text-[var(--text-tertiary)]">Details</div>
|
||||
{#if selectedTask.createdBy}
|
||||
<div class="flex items-center gap-2 text-gray-600 dark:text-gray-400">
|
||||
<User class="h-3.5 w-3.5" />
|
||||
<div class="flex items-center gap-2 text-[var(--text-secondary)]">
|
||||
<User class="size-3.5" />
|
||||
<span>Created by {selectedTask.createdBy.name}</span>
|
||||
</div>
|
||||
{/if}
|
||||
{#if selectedTask.createdAt}
|
||||
<div class="flex items-center gap-2 text-gray-600 dark:text-gray-400">
|
||||
<Clock class="h-3.5 w-3.5" />
|
||||
<div class="flex items-center gap-2 text-[var(--text-secondary)]">
|
||||
<Clock class="size-3.5" />
|
||||
<span>Created {formatRelativeDate(selectedTask.createdAt)}</span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Comments Section -->
|
||||
<div class="mt-6 border-t pt-4 dark:border-gray-700">
|
||||
<div class="mt-6 border-t border-[var(--border-default)] pt-4">
|
||||
<CommentSection
|
||||
entityId={selectedTask.id}
|
||||
entityType="tasks"
|
||||
|
|
|
|||
|
|
@ -259,9 +259,9 @@
|
|||
<Card.Header class="pb-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div
|
||||
class="flex h-10 w-10 items-center justify-center rounded-lg bg-green-100 dark:bg-green-900/30"
|
||||
class="flex h-10 w-10 items-center justify-center rounded-lg bg-[var(--color-success-light)] dark:bg-[var(--color-success-default)]/15"
|
||||
>
|
||||
<Plus class="h-5 w-5 text-green-600 dark:text-green-400" />
|
||||
<Plus class="h-5 w-5 text-[var(--color-success-default)]" />
|
||||
</div>
|
||||
<div>
|
||||
<Card.Title class="">Add New Member</Card.Title>
|
||||
|
|
@ -312,9 +312,9 @@
|
|||
<Card.Header class="pb-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div
|
||||
class="flex h-10 w-10 items-center justify-center rounded-lg bg-blue-100 dark:bg-blue-900/30"
|
||||
class="flex h-10 w-10 items-center justify-center rounded-lg bg-[var(--color-primary-light)] dark:bg-[var(--color-primary-default)]/15"
|
||||
>
|
||||
<Users class="h-5 w-5 text-blue-600 dark:text-blue-400" />
|
||||
<Users class="h-5 w-5 text-[var(--color-primary-default)]" />
|
||||
</div>
|
||||
<div>
|
||||
<Card.Title class="">Team Members</Card.Title>
|
||||
|
|
@ -345,7 +345,7 @@
|
|||
<Avatar.Image class="" src={user.avatar} alt={user.name} />
|
||||
{/if}
|
||||
<Avatar.Fallback
|
||||
class="bg-gradient-to-br from-blue-500 to-purple-600 text-sm text-white"
|
||||
class="bg-[var(--color-primary-default)] text-sm text-white"
|
||||
>
|
||||
{getInitials(user.name)}
|
||||
</Avatar.Fallback>
|
||||
|
|
@ -432,7 +432,7 @@
|
|||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-8 w-8 text-green-600 hover:bg-green-100"
|
||||
class="h-8 w-8 text-[var(--color-success-default)] hover:bg-[var(--color-success-light)]"
|
||||
onclick={() => handleActivateUser(user.id)}
|
||||
title="Activate user"
|
||||
>
|
||||
|
|
@ -514,9 +514,9 @@
|
|||
<Card.Root class="py-12">
|
||||
<Card.Content class="text-center">
|
||||
<div
|
||||
class="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-purple-100 dark:bg-purple-900/30"
|
||||
class="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-[var(--color-primary-light)] dark:bg-[var(--color-primary-default)]/15"
|
||||
>
|
||||
<UsersRound class="h-8 w-8 text-purple-600 dark:text-purple-400" />
|
||||
<UsersRound class="h-8 w-8 text-[var(--color-primary-default)]" />
|
||||
</div>
|
||||
<h3 class="mb-2 text-lg font-semibold">No teams yet</h3>
|
||||
<p class="text-muted-foreground mx-auto mb-6 max-w-sm text-sm">
|
||||
|
|
|
|||
|
|
@ -15,17 +15,17 @@
|
|||
<title>Select Organization | BottleCRM</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="flex min-h-screen flex-col bg-slate-50">
|
||||
<div class="flex min-h-screen flex-col bg-[var(--surface-sunken)]">
|
||||
<!-- Header -->
|
||||
<header class="border-b border-slate-200 bg-white">
|
||||
<header class="border-b border-[var(--border-default)] bg-[var(--surface-default)]">
|
||||
<div class="mx-auto flex max-w-5xl items-center justify-between px-6 py-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<img src={imgLogo} alt="BottleCRM" class="h-8 w-auto" />
|
||||
<span class="text-lg font-semibold text-slate-900">BottleCRM</span>
|
||||
<span class="text-lg font-semibold text-[var(--text-primary)]">BottleCRM</span>
|
||||
</div>
|
||||
<a
|
||||
href="/logout"
|
||||
class="flex items-center gap-2 rounded-lg px-3 py-2 text-sm font-medium text-slate-600 transition-colors hover:bg-slate-100 hover:text-slate-900"
|
||||
class="flex items-center gap-2 rounded-lg px-3 py-2 text-sm font-medium text-[var(--text-secondary)] transition-colors hover:bg-[var(--surface-raised)] hover:text-[var(--text-primary)]"
|
||||
>
|
||||
<LogOut class="h-4 w-4" />
|
||||
<span class="hidden sm:inline">Sign out</span>
|
||||
|
|
@ -38,8 +38,8 @@
|
|||
<div class="w-full max-w-2xl">
|
||||
<!-- Page Header -->
|
||||
<div class="mb-8 text-center">
|
||||
<h1 class="text-2xl font-bold text-slate-900">Select an organization</h1>
|
||||
<p class="mt-2 text-slate-600">Choose which organization you'd like to work in</p>
|
||||
<h1 class="text-2xl font-bold text-[var(--text-primary)]">Select an organization</h1>
|
||||
<p class="mt-2 text-[var(--text-secondary)]">Choose which organization you'd like to work in</p>
|
||||
</div>
|
||||
|
||||
<!-- Organizations List -->
|
||||
|
|
@ -64,20 +64,20 @@
|
|||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
class="group w-full rounded-xl border border-slate-200 bg-white p-5 text-left shadow-sm transition-all hover:border-slate-300 hover:shadow-md focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:outline-none disabled:opacity-60"
|
||||
class="group w-full rounded-xl border border-[var(--border-default)] bg-[var(--surface-default)] p-5 text-left shadow-sm transition-all hover:border-[var(--border-strong)] hover:shadow-md focus:ring-2 focus:ring-[var(--color-primary-default)] focus:ring-offset-2 focus:outline-none disabled:opacity-60"
|
||||
>
|
||||
<div class="flex items-center gap-4">
|
||||
<!-- Org Icon -->
|
||||
<div
|
||||
class="flex h-12 w-12 shrink-0 items-center justify-center rounded-lg bg-blue-50 text-blue-600 transition-colors group-hover:bg-blue-100"
|
||||
class="flex h-12 w-12 shrink-0 items-center justify-center rounded-lg bg-[var(--color-primary-light)] text-[var(--color-primary-default)] transition-colors group-hover:bg-[var(--color-primary-default)]/20"
|
||||
>
|
||||
<Building2 class="h-6 w-6" />
|
||||
</div>
|
||||
|
||||
<!-- Org Info -->
|
||||
<div class="min-w-0 flex-1">
|
||||
<h3 class="truncate font-semibold text-slate-900">{org.name}</h3>
|
||||
<div class="mt-1 flex items-center gap-3 text-sm text-slate-500">
|
||||
<h3 class="truncate font-semibold text-[var(--text-primary)]">{org.name}</h3>
|
||||
<div class="mt-1 flex items-center gap-3 text-sm text-[var(--text-secondary)]">
|
||||
<span class="inline-flex items-center gap-1 capitalize">
|
||||
<Users class="h-3.5 w-3.5" />
|
||||
{org.role?.toLowerCase() || 'Member'}
|
||||
|
|
@ -89,11 +89,11 @@
|
|||
<div class="shrink-0">
|
||||
{#if loading && selectedOrgId === org.id}
|
||||
<div
|
||||
class="h-5 w-5 animate-spin rounded-full border-2 border-slate-200 border-t-blue-600"
|
||||
class="h-5 w-5 animate-spin rounded-full border-2 border-[var(--border-default)] border-t-[var(--color-primary-default)]"
|
||||
></div>
|
||||
{:else}
|
||||
<ChevronRight
|
||||
class="h-5 w-5 text-slate-400 transition-transform group-hover:translate-x-0.5 group-hover:text-slate-600"
|
||||
class="h-5 w-5 text-[var(--text-tertiary)] transition-transform group-hover:translate-x-0.5 group-hover:text-[var(--text-secondary)]"
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
@ -107,7 +107,7 @@
|
|||
<div class="mt-6">
|
||||
<a
|
||||
href="/org/new"
|
||||
class="flex w-full items-center justify-center gap-2 rounded-xl border-2 border-dashed border-slate-300 bg-slate-50 px-5 py-4 text-sm font-medium text-slate-600 transition-all hover:border-blue-400 hover:bg-blue-50 hover:text-blue-600"
|
||||
class="flex w-full items-center justify-center gap-2 rounded-xl border-2 border-dashed border-[var(--border-default)] bg-[var(--surface-sunken)] px-5 py-4 text-sm font-medium text-[var(--text-secondary)] transition-all hover:border-[var(--color-primary-default)] hover:bg-[var(--color-primary-light)] hover:text-[var(--color-primary-default)]"
|
||||
>
|
||||
<Plus class="h-4 w-4" />
|
||||
Create new organization
|
||||
|
|
@ -115,19 +115,19 @@
|
|||
</div>
|
||||
{:else}
|
||||
<!-- Empty State -->
|
||||
<div class="rounded-xl border border-slate-200 bg-white p-12 text-center shadow-sm">
|
||||
<div class="rounded-xl border border-[var(--border-default)] bg-[var(--surface-default)] p-12 text-center shadow-sm">
|
||||
<div
|
||||
class="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-slate-100"
|
||||
class="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-[var(--surface-sunken)]"
|
||||
>
|
||||
<Building2 class="h-8 w-8 text-slate-400" />
|
||||
<Building2 class="h-8 w-8 text-[var(--text-tertiary)]" />
|
||||
</div>
|
||||
<h3 class="text-lg font-semibold text-slate-900">No organizations yet</h3>
|
||||
<p class="mt-2 text-slate-600">
|
||||
<h3 class="text-lg font-semibold text-[var(--text-primary)]">No organizations yet</h3>
|
||||
<p class="mt-2 text-[var(--text-secondary)]">
|
||||
Create your first organization to get started with BottleCRM
|
||||
</p>
|
||||
<a
|
||||
href="/org/new"
|
||||
class="mt-6 inline-flex items-center gap-2 rounded-lg bg-blue-600 px-5 py-2.5 text-sm font-medium text-white transition-colors hover:bg-blue-700"
|
||||
class="mt-6 inline-flex items-center gap-2 rounded-lg bg-[var(--color-primary-default)] px-5 py-2.5 text-sm font-medium text-white transition-colors hover:bg-[var(--color-primary-dark)]"
|
||||
>
|
||||
<Plus class="h-4 w-4" />
|
||||
Create organization
|
||||
|
|
@ -136,7 +136,7 @@
|
|||
{/if}
|
||||
|
||||
<!-- Trust Signal -->
|
||||
<div class="mt-8 flex items-center justify-center gap-2 text-sm text-slate-500">
|
||||
<div class="mt-8 flex items-center justify-center gap-2 text-sm text-[var(--text-secondary)]">
|
||||
<Shield class="h-4 w-4" />
|
||||
<span>Your data stays private and secure</span>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -24,17 +24,17 @@
|
|||
<title>Create Organization | BottleCRM</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="flex min-h-screen flex-col bg-slate-50">
|
||||
<div class="flex min-h-screen flex-col bg-[var(--surface-sunken)]">
|
||||
<!-- Header -->
|
||||
<header class="border-b border-slate-200 bg-white">
|
||||
<header class="border-b border-[var(--border-default)] bg-[var(--surface-default)]">
|
||||
<div class="mx-auto flex max-w-5xl items-center justify-between px-6 py-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<img src={imgLogo} alt="BottleCRM" class="h-8 w-auto" />
|
||||
<span class="text-lg font-semibold text-slate-900">BottleCRM</span>
|
||||
<span class="text-lg font-semibold text-[var(--text-primary)]">BottleCRM</span>
|
||||
</div>
|
||||
<a
|
||||
href="/org"
|
||||
class="flex items-center gap-2 rounded-lg px-3 py-2 text-sm font-medium text-slate-600 transition-colors hover:bg-slate-100 hover:text-slate-900"
|
||||
class="flex items-center gap-2 rounded-lg px-3 py-2 text-sm font-medium text-[var(--text-secondary)] transition-colors hover:bg-[var(--surface-raised)] hover:text-[var(--text-primary)]"
|
||||
>
|
||||
<ArrowLeft class="h-4 w-4" />
|
||||
<span>Back</span>
|
||||
|
|
@ -48,16 +48,16 @@
|
|||
<!-- Page Header -->
|
||||
<div class="mb-8 text-center">
|
||||
<div
|
||||
class="mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-blue-50"
|
||||
class="mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-[var(--color-primary-light)]"
|
||||
>
|
||||
<Sparkles class="h-6 w-6 text-blue-600" />
|
||||
<Sparkles class="h-6 w-6 text-[var(--color-primary-default)]" />
|
||||
</div>
|
||||
<h1 class="text-2xl font-bold text-slate-900">Create organization</h1>
|
||||
<p class="mt-2 text-slate-600">Set up a new workspace for your team</p>
|
||||
<h1 class="text-2xl font-bold text-[var(--text-primary)]">Create organization</h1>
|
||||
<p class="mt-2 text-[var(--text-secondary)]">Set up a new workspace for your team</p>
|
||||
</div>
|
||||
|
||||
<!-- Form Card -->
|
||||
<div class="rounded-xl border border-slate-200 bg-white p-6 shadow-sm">
|
||||
<div class="rounded-xl border border-[var(--border-default)] bg-[var(--surface-default)] p-6 shadow-sm">
|
||||
<form
|
||||
action="/org/new"
|
||||
method="POST"
|
||||
|
|
@ -72,7 +72,7 @@
|
|||
>
|
||||
<!-- Organization Name Field -->
|
||||
<div class="space-y-2">
|
||||
<label for="org_name" class="block text-sm font-medium text-slate-700">
|
||||
<label for="org_name" class="block text-sm font-medium text-[var(--text-secondary)]">
|
||||
Organization name
|
||||
</label>
|
||||
<input
|
||||
|
|
@ -81,30 +81,30 @@
|
|||
name="org_name"
|
||||
required
|
||||
disabled={isSubmitting || !!form?.data}
|
||||
class="w-full rounded-lg border border-slate-300 px-4 py-2.5 text-slate-900 placeholder-slate-400 transition-colors focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20 focus:outline-none disabled:bg-slate-50 disabled:text-slate-500"
|
||||
class="w-full rounded-lg border border-[var(--border-default)] px-4 py-2.5 text-[var(--text-primary)] placeholder-[var(--text-tertiary)] transition-colors focus:border-[var(--color-primary-default)] focus:ring-2 focus:ring-[var(--color-primary-default)]/20 focus:outline-none disabled:bg-[var(--surface-sunken)] disabled:text-[var(--text-secondary)]"
|
||||
placeholder="e.g. Acme Inc."
|
||||
/>
|
||||
<p class="text-xs text-slate-500">This will be your workspace name in BottleCRM</p>
|
||||
<p class="text-xs text-[var(--text-secondary)]">This will be your workspace name in BottleCRM</p>
|
||||
</div>
|
||||
|
||||
<!-- Error Message -->
|
||||
{#if form?.error}
|
||||
<div class="flex items-start gap-3 rounded-lg border border-red-200 bg-red-50 p-4">
|
||||
<AlertCircle class="h-5 w-5 shrink-0 text-red-500" />
|
||||
<div class="flex items-start gap-3 rounded-lg border border-[var(--color-negative-default)]/20 bg-[var(--color-negative-light)] p-4">
|
||||
<AlertCircle class="h-5 w-5 shrink-0 text-[var(--color-negative-default)]" />
|
||||
<div>
|
||||
<p class="text-sm font-medium text-red-800">Unable to create organization</p>
|
||||
<p class="mt-1 text-sm text-red-600">{form.error.name || 'Please try again'}</p>
|
||||
<p class="text-sm font-medium text-[var(--color-negative-default)]">Unable to create organization</p>
|
||||
<p class="mt-1 text-sm text-[var(--color-negative-default)]/80">{form.error.name || 'Please try again'}</p>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Success Message -->
|
||||
{#if form?.data}
|
||||
<div class="flex items-start gap-3 rounded-lg border border-green-200 bg-green-50 p-4">
|
||||
<Check class="h-5 w-5 shrink-0 text-green-500" />
|
||||
<div class="flex items-start gap-3 rounded-lg border border-[var(--color-success-default)]/20 bg-[var(--color-success-light)] p-4">
|
||||
<Check class="h-5 w-5 shrink-0 text-[var(--color-success-default)]" />
|
||||
<div>
|
||||
<p class="text-sm font-medium text-green-800">Organization created!</p>
|
||||
<p class="mt-1 text-sm text-green-600">Redirecting to your organizations...</p>
|
||||
<p class="text-sm font-medium text-[var(--color-success-default)]">Organization created!</p>
|
||||
<p class="mt-1 text-sm text-[var(--color-success-default)]/80">Redirecting to your organizations...</p>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
@ -113,7 +113,7 @@
|
|||
<button
|
||||
type="submit"
|
||||
disabled={isSubmitting || !!form?.data}
|
||||
class="flex w-full items-center justify-center gap-2 rounded-lg bg-blue-600 px-4 py-2.5 font-medium text-white transition-colors hover:bg-blue-700 focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
||||
class="flex w-full items-center justify-center gap-2 rounded-lg bg-[var(--color-primary-default)] px-4 py-2.5 font-medium text-white transition-colors hover:bg-[var(--color-primary-dark)] focus:ring-2 focus:ring-[var(--color-primary-default)] focus:ring-offset-2 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
{#if isSubmitting}
|
||||
<div
|
||||
|
|
@ -132,7 +132,7 @@
|
|||
</div>
|
||||
|
||||
<!-- Help Text -->
|
||||
<p class="mt-6 text-center text-sm text-slate-500">
|
||||
<p class="mt-6 text-center text-sm text-[var(--text-secondary)]">
|
||||
You can invite team members after creating your organization
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -18,16 +18,17 @@
|
|||
|
||||
let isSubmitting = $state(false);
|
||||
|
||||
// Status colors - using design system tokens
|
||||
function getStatusColor(status) {
|
||||
const colors = {
|
||||
Draft: 'bg-gray-100 text-gray-700',
|
||||
Sent: 'bg-blue-100 text-blue-700',
|
||||
Viewed: 'bg-indigo-100 text-indigo-700',
|
||||
Accepted: 'bg-green-100 text-green-700',
|
||||
Declined: 'bg-red-100 text-red-700',
|
||||
Expired: 'bg-orange-100 text-orange-700'
|
||||
Draft: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)]',
|
||||
Sent: 'bg-[var(--stage-contacted-bg)] text-[var(--stage-contacted)]',
|
||||
Viewed: 'bg-[var(--stage-qualified-bg)] text-[var(--stage-qualified)]',
|
||||
Accepted: 'bg-[var(--color-success-light)] text-[var(--color-success-default)]',
|
||||
Declined: 'bg-[var(--color-negative-light)] text-[var(--color-negative-default)]',
|
||||
Expired: 'bg-[var(--color-primary-light)] text-[var(--color-primary-default)]'
|
||||
};
|
||||
return colors[status] || 'bg-gray-100 text-gray-700';
|
||||
return colors[status] || 'bg-[var(--surface-sunken)] text-[var(--text-secondary)]';
|
||||
}
|
||||
|
||||
function isExpired() {
|
||||
|
|
|
|||
|
|
@ -14,17 +14,18 @@
|
|||
const secondaryColor = $derived(template?.secondaryColor || '#1E40AF');
|
||||
const footerText = $derived(template?.footerText || '');
|
||||
|
||||
// Status colors - using design system tokens
|
||||
function getStatusColor(status) {
|
||||
const colors = {
|
||||
Draft: 'bg-gray-100 text-gray-700',
|
||||
Sent: 'bg-blue-100 text-blue-700',
|
||||
Viewed: 'bg-indigo-100 text-indigo-700',
|
||||
Partially_Paid: 'bg-yellow-100 text-yellow-700',
|
||||
Paid: 'bg-green-100 text-green-700',
|
||||
Overdue: 'bg-red-100 text-red-700',
|
||||
Cancelled: 'bg-gray-200 text-gray-500'
|
||||
Draft: 'bg-[var(--surface-sunken)] text-[var(--text-secondary)]',
|
||||
Sent: 'bg-[var(--stage-contacted-bg)] text-[var(--stage-contacted)]',
|
||||
Viewed: 'bg-[var(--stage-qualified-bg)] text-[var(--stage-qualified)]',
|
||||
Partially_Paid: 'bg-[var(--stage-negotiation-bg)] text-[var(--stage-negotiation)]',
|
||||
Paid: 'bg-[var(--color-success-light)] text-[var(--color-success-default)]',
|
||||
Overdue: 'bg-[var(--color-negative-light)] text-[var(--color-negative-default)]',
|
||||
Cancelled: 'bg-[var(--surface-sunken)] text-[var(--text-tertiary)]'
|
||||
};
|
||||
return colors[status] || 'bg-gray-100 text-gray-700';
|
||||
return colors[status] || 'bg-[var(--surface-sunken)] text-[var(--text-secondary)]';
|
||||
}
|
||||
|
||||
function downloadPDF() {
|
||||
|
|
|
|||
Loading…
Reference in a new issue