Fixed Resources, Added Error Logging/Messages, Fixed Pumkin Spice Background
Build & Auto-Repair / build (push) Has been cancelled
Build & Auto-Repair / build (push) Has been cancelled
This commit is contained in:
+231
-198
@@ -1,70 +1,71 @@
|
|||||||
.dark.theme-pumpkin {
|
.dark.theme-pumpkin {
|
||||||
--background: 0 30% 4%;
|
--background: 30 40% 3%;
|
||||||
--foreground: 20 50% 95%;
|
--foreground: 30 30% 92%;
|
||||||
--card: 0 25% 8%;
|
--card: 25 30% 7%;
|
||||||
--card-foreground: 20 50% 95%;
|
--card-foreground: 30 30% 92%;
|
||||||
--popover: 0 25% 8%;
|
--popover: 25 30% 7%;
|
||||||
--popover-foreground: 20 50% 95%;
|
--popover-foreground: 30 30% 92%;
|
||||||
--primary: 10 100% 50%;
|
--primary: 25 95% 50%;
|
||||||
--primary-foreground: 0 0% 100%;
|
--primary-foreground: 30 20% 5%;
|
||||||
--secondary: 30 100% 50%;
|
--secondary: 35 100% 45%;
|
||||||
--secondary-foreground: 0 0% 0%;
|
--secondary-foreground: 30 20% 5%;
|
||||||
--muted: 0 12% 15%;
|
--muted: 25 15% 12%;
|
||||||
--muted-foreground: 20 10% 55%;
|
--muted-foreground: 30 12% 60%;
|
||||||
--accent: 10 100% 50%;
|
--accent: 15 100% 45%;
|
||||||
--accent-foreground: 0 0% 100%;
|
--accent-foreground: 0 0% 100%;
|
||||||
--destructive: 0 70% 50%;
|
--destructive: 0 70% 50%;
|
||||||
--destructive-foreground: 210 40% 98%;
|
--destructive-foreground: 0 0% 100%;
|
||||||
--border: 10 30% 18%;
|
--border: 25 20% 16%;
|
||||||
--input: 10 30% 18%;
|
--input: 25 20% 16%;
|
||||||
--ring: 10 100% 50%;
|
--ring: 25 95% 50%;
|
||||||
--radius: 0.5rem;
|
--radius: 0.5rem;
|
||||||
--sidebar: 0 30% 4%;
|
--sidebar: 25 35% 4%;
|
||||||
--sidebar-foreground: 20 50% 95%;
|
--sidebar-foreground: 30 30% 92%;
|
||||||
--sidebar-primary: 10 100% 50%;
|
--sidebar-primary: 25 95% 50%;
|
||||||
--sidebar-primary-foreground: 0 0% 100%;
|
--sidebar-primary-foreground: 30 20% 5%;
|
||||||
--sidebar-accent: 10 100% 50%;
|
--sidebar-accent: 15 100% 45%;
|
||||||
--sidebar-accent-foreground: 0 0% 100%;
|
--sidebar-accent-foreground: 0 0% 100%;
|
||||||
--sidebar-border: 10 30% 15%;
|
--sidebar-border: 25 18% 14%;
|
||||||
--sidebar-ring: 10 100% 50%;
|
--sidebar-ring: 25 95% 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.light.theme-pumpkin {
|
.light.theme-pumpkin {
|
||||||
--background: 20 30% 96%;
|
--background: 30 30% 94%;
|
||||||
--foreground: 10 50% 10%;
|
--foreground: 25 40% 12%;
|
||||||
--card: 0 0% 100%;
|
--card: 0 0% 100%;
|
||||||
--card-foreground: 10 50% 10%;
|
--card-foreground: 25 40% 12%;
|
||||||
--popover: 0 0% 100%;
|
--popover: 0 0% 100%;
|
||||||
--popover-foreground: 10 50% 10%;
|
--popover-foreground: 25 40% 12%;
|
||||||
--primary: 10 90% 45%;
|
--primary: 25 90% 42%;
|
||||||
--primary-foreground: 0 0% 100%;
|
--primary-foreground: 0 0% 100%;
|
||||||
--secondary: 30 90% 45%;
|
--secondary: 35 90% 42%;
|
||||||
--secondary-foreground: 0 0% 100%;
|
--secondary-foreground: 0 0% 100%;
|
||||||
--muted: 20 15% 88%;
|
--muted: 30 15% 86%;
|
||||||
--muted-foreground: 10 10% 45%;
|
--muted-foreground: 25 10% 42%;
|
||||||
--accent: 10 90% 45%;
|
--accent: 15 90% 42%;
|
||||||
--accent-foreground: 0 0% 100%;
|
--accent-foreground: 0 0% 100%;
|
||||||
--destructive: 0 80% 50%;
|
--destructive: 0 80% 50%;
|
||||||
--destructive-foreground: 0 0% 100%;
|
--destructive-foreground: 0 0% 100%;
|
||||||
--border: 20 15% 82%;
|
--border: 25 15% 80%;
|
||||||
--input: 20 15% 82%;
|
--input: 25 15% 80%;
|
||||||
--ring: 10 90% 45%;
|
--ring: 25 90% 42%;
|
||||||
--radius: 0.5rem;
|
--radius: 0.5rem;
|
||||||
--sidebar: 0 0% 100%;
|
--sidebar: 0 0% 100%;
|
||||||
--sidebar-foreground: 10 50% 10%;
|
--sidebar-foreground: 25 40% 12%;
|
||||||
--sidebar-primary: 10 90% 45%;
|
--sidebar-primary: 25 90% 42%;
|
||||||
--sidebar-primary-foreground: 0 0% 100%;
|
--sidebar-primary-foreground: 0 0% 100%;
|
||||||
--sidebar-accent: 10 90% 45%;
|
--sidebar-accent: 15 90% 42%;
|
||||||
--sidebar-accent-foreground: 0 0% 100%;
|
--sidebar-accent-foreground: 0 0% 100%;
|
||||||
--sidebar-border: 20 15% 82%;
|
--sidebar-border: 25 15% 80%;
|
||||||
--sidebar-ring: 10 90% 45%;
|
--sidebar-ring: 25 90% 42%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Warm ambient glow */
|
||||||
.dark.theme-pumpkin body {
|
.dark.theme-pumpkin body {
|
||||||
background: transparent
|
background: transparent
|
||||||
radial-gradient(ellipse 120% 60% at 20% 30%, rgba(200, 0, 0, 0.2) 0%, transparent 60%),
|
radial-gradient(ellipse 100% 55% at 20% 25%, rgba(200, 80, 0, 0.18) 0%, transparent 55%),
|
||||||
radial-gradient(ellipse 80% 50% at 80% 70%, rgba(255, 100, 0, 0.15) 0%, transparent 50%),
|
radial-gradient(ellipse 80% 45% at 80% 75%, rgba(255, 140, 0, 0.12) 0%, transparent 45%),
|
||||||
radial-gradient(ellipse 60% 40% at 50% 50%, rgba(150, 0, 0, 0.1) 0%, transparent 50%);
|
radial-gradient(ellipse 60% 35% at 50% 50%, rgba(180, 60, 0, 0.08) 0%, transparent 40%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark.theme-pumpkin body > div {
|
.dark.theme-pumpkin body > div {
|
||||||
@@ -78,195 +79,209 @@
|
|||||||
z-index: -1;
|
z-index: -1;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
background:
|
background:
|
||||||
radial-gradient(ellipse 80% 50% at 15% 20%, rgba(200, 50, 0, 0.04) 0%, transparent 60%),
|
radial-gradient(ellipse 70% 45% at 15% 20%, rgba(200, 80, 0, 0.05) 0%, transparent 55%),
|
||||||
radial-gradient(ellipse 80% 50% at 85% 80%, rgba(255, 100, 0, 0.03) 0%, transparent 60%);
|
radial-gradient(ellipse 70% 45% at 85% 80%, rgba(255, 140, 0, 0.04) 0%, transparent 55%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Pumpkin Spice text glow */
|
||||||
.dark.theme-pumpkin .text-glow {
|
.dark.theme-pumpkin .text-glow {
|
||||||
text-shadow: 0 0 10px hsl(10, 100%, 50%), 0 0 30px hsl(10, 100%, 50%), 0 0 60px rgba(255, 0, 0, 0.5);
|
text-shadow: 0 0 10px hsl(25, 95%, 50%), 0 0 30px hsla(25, 95%, 50%, 0.6), 0 0 60px rgba(200, 80, 0, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.light.theme-pumpkin .text-glow {
|
.light.theme-pumpkin .text-glow {
|
||||||
text-shadow: 0 0 8px hsl(10, 90%, 45%), 0 0 20px hsl(10, 90%, 45%), 0 0 40px rgba(200, 50, 0, 0.3);
|
text-shadow: 0 0 8px hsl(25, 90%, 42%), 0 0 20px hsla(25, 90%, 42%, 0.4), 0 0 40px rgba(200, 80, 0, 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark.theme-pumpkin h1, .dark.theme-pumpkin h2, .dark.theme-pumpkin h3 {
|
/* Gradient headings — warm orange to amber */
|
||||||
background: linear-gradient(135deg, hsl(10, 100%, 50%), hsl(30, 100%, 50%));
|
.dark.theme-pumpkin h1:not(.text-primary):not(.text-foreground),
|
||||||
|
.dark.theme-pumpkin h2:not(.text-primary):not(.text-foreground),
|
||||||
|
.dark.theme-pumpkin h3:not(.text-primary):not(.text-foreground) {
|
||||||
|
background: linear-gradient(135deg, hsl(25, 95%, 55%), hsl(35, 100%, 50%));
|
||||||
-webkit-background-clip: text;
|
-webkit-background-clip: text;
|
||||||
-webkit-text-fill-color: transparent;
|
-webkit-text-fill-color: transparent;
|
||||||
background-clip: text;
|
background-clip: text;
|
||||||
}
|
}
|
||||||
|
|
||||||
.light.theme-pumpkin h1, .light.theme-pumpkin h2, .light.theme-pumpkin h3 {
|
.light.theme-pumpkin h1:not(.text-primary):not(.text-foreground),
|
||||||
background: linear-gradient(135deg, hsl(10, 90%, 40%), hsl(30, 90%, 40%));
|
.light.theme-pumpkin h2:not(.text-primary):not(.text-foreground),
|
||||||
|
.light.theme-pumpkin h3:not(.text-primary):not(.text-foreground) {
|
||||||
|
background: linear-gradient(135deg, hsl(25, 90%, 40%), hsl(35, 90%, 40%));
|
||||||
-webkit-background-clip: text;
|
-webkit-background-clip: text;
|
||||||
-webkit-text-fill-color: transparent;
|
-webkit-text-fill-color: transparent;
|
||||||
background-clip: text;
|
background-clip: text;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark.theme-pumpkin a {
|
/* Links */
|
||||||
color: hsl(10, 100%, 55%);
|
.dark.theme-pumpkin a { color: hsl(25, 95%, 55%); }
|
||||||
}
|
.light.theme-pumpkin a { color: hsl(25, 90%, 40%); }
|
||||||
|
.dark.theme-pumpkin a:hover { color: hsl(35, 100%, 55%); text-shadow: 0 0 8px rgba(255, 140, 0, 0.35); }
|
||||||
.light.theme-pumpkin a {
|
.light.theme-pumpkin a:hover { color: hsl(35, 90%, 45%); }
|
||||||
color: hsl(10, 90%, 40%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark.theme-pumpkin a:hover {
|
|
||||||
color: hsl(30, 100%, 55%);
|
|
||||||
text-shadow: 0 0 8px rgba(255, 100, 0, 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
.light.theme-pumpkin a:hover {
|
|
||||||
color: hsl(30, 90%, 45%);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/* Blockquotes */
|
||||||
.dark.theme-pumpkin blockquote {
|
.dark.theme-pumpkin blockquote {
|
||||||
border-left: 3px solid hsl(10, 100%, 50%);
|
border-left: 3px solid hsl(25, 95%, 50%);
|
||||||
color: hsl(20, 15%, 70%);
|
color: hsl(30, 15%, 70%);
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
.light.theme-pumpkin blockquote {
|
.light.theme-pumpkin blockquote {
|
||||||
border-left: 3px solid hsl(10, 90%, 45%);
|
border-left: 3px solid hsl(25, 90%, 42%);
|
||||||
color: hsl(10, 10%, 45%);
|
color: hsl(25, 10%, 42%);
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Code */
|
||||||
.dark.theme-pumpkin code {
|
.dark.theme-pumpkin code {
|
||||||
color: hsl(10, 100%, 55%);
|
color: hsl(25, 95%, 55%);
|
||||||
background: rgba(200, 0, 0, 0.12);
|
background: rgba(200, 80, 0, 0.1);
|
||||||
border: 1px solid rgba(200, 0, 0, 0.15);
|
border: 1px solid rgba(200, 80, 0, 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
.light.theme-pumpkin code {
|
.light.theme-pumpkin code {
|
||||||
color: hsl(10, 90%, 40%);
|
color: hsl(25, 90%, 40%);
|
||||||
background: rgba(200, 50, 0, 0.05);
|
background: rgba(200, 80, 0, 0.04);
|
||||||
border: 1px solid rgba(200, 50, 0, 0.1);
|
border: 1px solid rgba(200, 80, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
.dark.theme-pumpkin pre { background: rgba(25, 12, 0, 0.9); border: 1px solid rgba(200, 80, 0, 0.1); }
|
||||||
|
.light.theme-pumpkin pre { background: rgba(200, 80, 0, 0.03); border: 1px solid rgba(200, 80, 0, 0.08); }
|
||||||
|
|
||||||
.dark.theme-pumpkin pre {
|
/* HR */
|
||||||
background: rgba(20, 0, 0, 0.9);
|
.dark.theme-pumpkin hr { border-color: rgba(200, 80, 0, 0.12); }
|
||||||
border: 1px solid rgba(200, 0, 0, 0.1);
|
.light.theme-pumpkin hr { border-color: rgba(200, 80, 0, 0.08); }
|
||||||
}
|
|
||||||
|
|
||||||
.light.theme-pumpkin pre {
|
/* Tables */
|
||||||
background: rgba(200, 50, 0, 0.03);
|
.dark.theme-pumpkin table th { color: hsl(25, 95%, 55%); }
|
||||||
border: 1px solid rgba(200, 50, 0, 0.08);
|
.light.theme-pumpkin table th { color: hsl(25, 90%, 40%); }
|
||||||
}
|
.dark.theme-pumpkin table td { border-color: rgba(200, 80, 0, 0.1); }
|
||||||
|
.light.theme-pumpkin table td { border-color: rgba(200, 80, 0, 0.08); }
|
||||||
|
|
||||||
.dark.theme-pumpkin hr {
|
/* Selection */
|
||||||
border-color: rgba(200, 0, 0, 0.15);
|
.dark.theme-pumpkin ::selection { background: rgba(200, 80, 0, 0.2); color: hsl(0, 0%, 100%); }
|
||||||
}
|
.light.theme-pumpkin ::selection { background: rgba(200, 80, 0, 0.1); color: hsl(0, 0%, 0%); }
|
||||||
|
|
||||||
.light.theme-pumpkin hr {
|
/* Placeholders */
|
||||||
border-color: rgba(200, 50, 0, 0.1);
|
.dark.theme-pumpkin input::placeholder, .dark.theme-pumpkin textarea::placeholder { color: hsl(25, 10%, 40%); }
|
||||||
}
|
.light.theme-pumpkin input::placeholder, .light.theme-pumpkin textarea::placeholder { color: hsl(25, 10%, 62%); }
|
||||||
|
|
||||||
.dark.theme-pumpkin table th {
|
/* Page header / stat card / badge */
|
||||||
color: hsl(10, 100%, 55%);
|
.dark.theme-pumpkin .page-header-title, .dark.theme-pumpkin .stat-card-value, .dark.theme-pumpkin .badge { color: hsl(25, 95%, 55%); }
|
||||||
}
|
.light.theme-pumpkin .page-header-title, .light.theme-pumpkin .stat-card-value, .light.theme-pumpkin .badge { color: hsl(25, 90%, 40%); }
|
||||||
|
|
||||||
.light.theme-pumpkin table th {
|
|
||||||
color: hsl(10, 90%, 40%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark.theme-pumpkin table td {
|
|
||||||
border-color: rgba(200, 0, 0, 0.12);
|
|
||||||
}
|
|
||||||
|
|
||||||
.light.theme-pumpkin table td {
|
|
||||||
border-color: rgba(200, 50, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark.theme-pumpkin ::selection {
|
|
||||||
background: rgba(200, 0, 0, 0.2);
|
|
||||||
color: hsl(0, 0%, 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.light.theme-pumpkin ::selection {
|
|
||||||
background: rgba(200, 50, 0, 0.12);
|
|
||||||
color: hsl(0, 0%, 0%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark.theme-pumpkin input::placeholder,
|
|
||||||
.dark.theme-pumpkin textarea::placeholder {
|
|
||||||
color: hsl(20, 10%, 40%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.light.theme-pumpkin input::placeholder,
|
|
||||||
.light.theme-pumpkin textarea::placeholder {
|
|
||||||
color: hsl(10, 10%, 65%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark.theme-pumpkin .page-header-title {
|
|
||||||
color: hsl(10, 100%, 55%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.light.theme-pumpkin .page-header-title {
|
|
||||||
color: hsl(10, 90%, 40%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark.theme-pumpkin .stat-card-value {
|
|
||||||
color: hsl(10, 100%, 55%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.light.theme-pumpkin .stat-card-value {
|
|
||||||
color: hsl(10, 90%, 40%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark.theme-pumpkin .badge {
|
|
||||||
color: hsl(10, 100%, 55%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.light.theme-pumpkin .badge {
|
|
||||||
color: hsl(10, 90%, 40%);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/* ============================================================
|
||||||
|
Dashboard stat card pipeline bars — Pumpkin Spice palette
|
||||||
|
============================================================ */
|
||||||
.dark.theme-pumpkin .sm\:grid-cols-2.lg\:grid-cols-3.gap-4.mt-4 > .relative:nth-child(odd) .group .h-1.w-full {
|
.dark.theme-pumpkin .sm\:grid-cols-2.lg\:grid-cols-3.gap-4.mt-4 > .relative:nth-child(odd) .group .h-1.w-full {
|
||||||
background: linear-gradient(to right, hsl(10, 100%, 50%), hsl(10, 100%, 65%), hsl(10, 100%, 50%)) !important;
|
background: linear-gradient(to right, hsl(25, 95%, 50%), hsl(25, 95%, 65%), hsl(25, 95%, 50%)) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark.theme-pumpkin .sm\:grid-cols-2.lg\:grid-cols-3.gap-4.mt-4 > .relative:nth-child(even) .group .h-1.w-full {
|
.dark.theme-pumpkin .sm\:grid-cols-2.lg\:grid-cols-3.gap-4.mt-4 > .relative:nth-child(even) .group .h-1.w-full {
|
||||||
background: linear-gradient(to right, hsl(30, 100%, 50%), hsl(30, 100%, 65%), hsl(30, 100%, 50%)) !important;
|
background: linear-gradient(to right, hsl(35, 100%, 45%), hsl(35, 100%, 60%), hsl(35, 100%, 45%)) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark.theme-pumpkin .sm\:grid-cols-2.lg\:grid-cols-3.gap-4.mt-4 > .relative:nth-child(odd) .group .rounded-xl.shrink-0 {
|
.dark.theme-pumpkin .sm\:grid-cols-2.lg\:grid-cols-3.gap-4.mt-4 > .relative:nth-child(odd) .group .rounded-xl.shrink-0 {
|
||||||
background: hsla(10, 100%, 50%, 0.1) !important;
|
background: hsla(25, 95%, 50%, 0.12) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark.theme-pumpkin .sm\:grid-cols-2.lg\:grid-cols-3.gap-4.mt-4 > .relative:nth-child(even) .group .rounded-xl.shrink-0 {
|
.dark.theme-pumpkin .sm\:grid-cols-2.lg\:grid-cols-3.gap-4.mt-4 > .relative:nth-child(even) .group .rounded-xl.shrink-0 {
|
||||||
background: hsla(30, 100%, 50%, 0.1) !important;
|
background: hsla(35, 100%, 45%, 0.12) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark.theme-pumpkin .sm\:grid-cols-2.lg\:grid-cols-3.gap-4.mt-4 > .relative:nth-child(odd) .group .rounded-xl.shrink-0 svg {
|
.dark.theme-pumpkin .sm\:grid-cols-2.lg\:grid-cols-3.gap-4.mt-4 > .relative:nth-child(odd) .group .rounded-xl.shrink-0 svg {
|
||||||
color: hsl(10, 100%, 50%) !important;
|
color: hsl(25, 95%, 50%) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark.theme-pumpkin .sm\:grid-cols-2.lg\:grid-cols-3.gap-4.mt-4 > .relative:nth-child(even) .group .rounded-xl.shrink-0 svg {
|
.dark.theme-pumpkin .sm\:grid-cols-2.lg\:grid-cols-3.gap-4.mt-4 > .relative:nth-child(even) .group .rounded-xl.shrink-0 svg {
|
||||||
color: hsl(30, 100%, 50%) !important;
|
color: hsl(35, 100%, 45%) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark.theme-pumpkin .sm\:grid-cols-2.lg\:grid-cols-3.gap-4.mt-4 > .relative:nth-child(odd) .group .absolute.bottom-0.h-1 {
|
.dark.theme-pumpkin .sm\:grid-cols-2.lg\:grid-cols-3.gap-4.mt-4 > .relative:nth-child(odd) .group .absolute.bottom-0.h-1 {
|
||||||
background: linear-gradient(to right, transparent, hsla(10, 100%, 50%, 0.25), transparent) !important;
|
background: linear-gradient(to right, transparent, hsla(25, 95%, 50%, 0.25), transparent) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark.theme-pumpkin .sm\:grid-cols-2.lg\:grid-cols-3.gap-4.mt-4 > .relative:nth-child(even) .group .absolute.bottom-0.h-1 {
|
.dark.theme-pumpkin .sm\:grid-cols-2.lg\:grid-cols-3.gap-4.mt-4 > .relative:nth-child(even) .group .absolute.bottom-0.h-1 {
|
||||||
background: linear-gradient(to right, transparent, hsla(30, 100%, 50%, 0.25), transparent) !important;
|
background: linear-gradient(to right, transparent, hsla(35, 100%, 45%, 0.25), transparent) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark.theme-pumpkin .sm\:grid-cols-2.lg\:grid-cols-3.gap-4.mt-4 .rounded-full.bg-gradient-to-r {
|
.dark.theme-pumpkin .sm\:grid-cols-2.lg\:grid-cols-3.gap-4.mt-4 .rounded-full.bg-gradient-to-r {
|
||||||
background: linear-gradient(to right, hsl(10, 100%, 50%), hsl(30, 100%, 50%)) !important;
|
background: linear-gradient(to right, hsl(25, 95%, 50%), hsl(35, 100%, 45%)) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.light.theme-pumpkin .sm\:grid-cols-2.lg\:grid-cols-3.gap-4.mt-4 > .relative:nth-child(odd) .group .h-1.w-full {
|
.light.theme-pumpkin .sm\:grid-cols-2.lg\:grid-cols-3.gap-4.mt-4 > .relative:nth-child(odd) .group .h-1.w-full {
|
||||||
background: linear-gradient(to right, hsl(10, 90%, 45%), hsl(10, 90%, 55%), hsl(10, 90%, 45%)) !important;
|
background: linear-gradient(to right, hsl(25, 90%, 42%), hsl(25, 90%, 55%), hsl(25, 90%, 42%)) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.light.theme-pumpkin .sm\:grid-cols-2.lg\:grid-cols-3.gap-4.mt-4 > .relative:nth-child(even) .group .h-1.w-full {
|
.light.theme-pumpkin .sm\:grid-cols-2.lg\:grid-cols-3.gap-4.mt-4 > .relative:nth-child(even) .group .h-1.w-full {
|
||||||
background: linear-gradient(to right, hsl(30, 90%, 45%), hsl(30, 90%, 55%), hsl(30, 90%, 45%)) !important;
|
background: linear-gradient(to right, hsl(35, 90%, 42%), hsl(35, 90%, 55%), hsl(35, 90%, 42%)) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ============================================================
|
||||||
|
Chart color overrides — Pumpkin Spice palette
|
||||||
|
============================================================ */
|
||||||
|
.dark.theme-pumpkin [class*="recharts-"] text,
|
||||||
|
.light.theme-pumpkin [class*="recharts-"] text {
|
||||||
|
fill: hsl(30, 12%, 60%) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Donut chart gradient stops */
|
||||||
|
.dark.theme-pumpkin .recharts-pie-sector:nth-child(1) path,
|
||||||
|
.light.theme-pumpkin .recharts-pie-sector:nth-child(1) path {
|
||||||
|
fill: hsl(25, 95%, 50%) !important;
|
||||||
|
}
|
||||||
|
.dark.theme-pumpkin .recharts-pie-sector:nth-child(2) path,
|
||||||
|
.light.theme-pumpkin .recharts-pie-sector:nth-child(2) path {
|
||||||
|
fill: hsl(35, 100%, 45%) !important;
|
||||||
|
}
|
||||||
|
.dark.theme-pumpkin .recharts-pie-sector:nth-child(3) path,
|
||||||
|
.light.theme-pumpkin .recharts-pie-sector:nth-child(3) path {
|
||||||
|
fill: hsl(15, 100%, 42%) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Stat card icon colors */
|
||||||
|
.dark.theme-pumpkin .grid.grid-cols-1.sm\:grid-cols-2.lg\:grid-cols-4.gap-4 > div:nth-child(1) .h-8.w-8,
|
||||||
|
.light.theme-pumpkin .grid.grid-cols-1.sm\:grid-cols-2.lg\:grid-cols-4.gap-4 > div:nth-child(1) .h-8.w-8 {
|
||||||
|
background: hsla(25, 95%, 50%, 0.12) !important;
|
||||||
|
color: hsl(25, 95%, 50%) !important;
|
||||||
|
}
|
||||||
|
.dark.theme-pumpkin .grid.grid-cols-1.sm\:grid-cols-2.lg\:grid-cols-4.gap-4 > div:nth-child(2) .h-8.w-8,
|
||||||
|
.light.theme-pumpkin .grid.grid-cols-1.sm\:grid-cols-2.lg\:grid-cols-4.gap-4 > div:nth-child(2) .h-8.w-8 {
|
||||||
|
background: hsla(35, 100%, 45%, 0.12) !important;
|
||||||
|
color: hsl(35, 100%, 45%) !important;
|
||||||
|
}
|
||||||
|
.dark.theme-pumpkin .grid.grid-cols-1.sm\:grid-cols-2.lg\:grid-cols-4.gap-4 > div:nth-child(3) .h-8.w-8,
|
||||||
|
.light.theme-pumpkin .grid.grid-cols-1.sm\:grid-cols-2.lg\:grid-cols-4.gap-4 > div:nth-child(3) .h-8.w-8 {
|
||||||
|
background: hsla(15, 100%, 42%, 0.12) !important;
|
||||||
|
color: hsl(15, 100%, 42%) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Stat card value colors */
|
||||||
|
.dark.theme-pumpkin .grid.grid-cols-1.sm\:grid-cols-2.lg\:grid-cols-4.gap-4 > div:nth-child(1) .text-2xl,
|
||||||
|
.light.theme-pumpkin .grid.grid-cols-1.sm\:grid-cols-2.lg\:grid-cols-4.gap-4 > div:nth-child(1) .text-2xl {
|
||||||
|
color: hsl(25, 95%, 50%) !important;
|
||||||
|
}
|
||||||
|
.dark.theme-pumpkin .grid.grid-cols-1.sm\:grid-cols-2.lg\:grid-cols-4.gap-4 > div:nth-child(2) .text-2xl,
|
||||||
|
.light.theme-pumpkin .grid.grid-cols-1.sm\:grid-cols-2.lg\:grid-cols-4.gap-4 > div:nth-child(2) .text-2xl {
|
||||||
|
color: hsl(35, 100%, 45%) !important;
|
||||||
|
}
|
||||||
|
.dark.theme-pumpkin .grid.grid-cols-1.sm\:grid-cols-2.lg\:grid-cols-4.gap-4 > div:nth-child(3) .text-2xl,
|
||||||
|
.light.theme-pumpkin .grid.grid-cols-1.sm\:grid-cols-2.lg\:grid-cols-4.gap-4 > div:nth-child(3) .text-2xl {
|
||||||
|
color: hsl(15, 100%, 42%) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Bar chart bar fills */
|
||||||
|
.dark.theme-pumpkin .recharts-bar-rectangle:nth-child(1) rect,
|
||||||
|
.light.theme-pumpkin .recharts-bar-rectangle:nth-child(1) rect {
|
||||||
|
fill: hsl(25, 95%, 50%) !important;
|
||||||
|
}
|
||||||
|
.dark.theme-pumpkin .recharts-bar-rectangle:nth-child(2) rect,
|
||||||
|
.light.theme-pumpkin .recharts-bar-rectangle:nth-child(2) rect {
|
||||||
|
fill: hsl(35, 100%, 45%) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================
|
||||||
|
Card gradient accent — subtle pumpkin glow on hover
|
||||||
|
============================================================ */
|
||||||
|
.dark.theme-pumpkin .rounded-xl.border.border-border {
|
||||||
|
border-color: hsla(25, 20%, 18%, 0.8) !important;
|
||||||
|
}
|
||||||
|
.dark.theme-pumpkin .rounded-xl.border.border-border:hover {
|
||||||
|
border-color: hsla(25, 95%, 50%, 0.25) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Growth word on login page — keep brand blue */
|
||||||
.dark.theme-pumpkin .growth-word,
|
.dark.theme-pumpkin .growth-word,
|
||||||
.light.theme-pumpkin .growth-word {
|
.light.theme-pumpkin .growth-word {
|
||||||
-webkit-text-fill-color: #1BB0CE !important;
|
-webkit-text-fill-color: #1BB0CE !important;
|
||||||
@@ -274,70 +289,88 @@
|
|||||||
background: none !important;
|
background: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ============================================================================
|
/* ============================================================
|
||||||
Client Portal Overrides — Background for portal pages
|
Client Portal Overrides
|
||||||
============================================================================ */
|
============================================================ */
|
||||||
|
|
||||||
/* Make the outer portal container transparent so body::before background shows */
|
|
||||||
.dark.theme-pumpkin .min-h-screen.bg-background {
|
.dark.theme-pumpkin .min-h-screen.bg-background {
|
||||||
background: transparent !important;
|
background: transparent !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Portal sidebar — dark glass */
|
|
||||||
.dark.theme-pumpkin aside.bg-card.border-r.border-border {
|
.dark.theme-pumpkin aside.bg-card.border-r.border-border {
|
||||||
background: hsl(240, 15%, 3%) !important;
|
background: hsl(25, 35%, 4%) !important;
|
||||||
border-right-color: hsl(270, 25%, 10%) !important;
|
border-right-color: hsl(25, 20%, 14%) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Portal sidebar nav items — muted text */
|
|
||||||
.dark.theme-pumpkin aside .text-muted-foreground {
|
.dark.theme-pumpkin aside .text-muted-foreground {
|
||||||
color: hsl(240, 8%, 50%) !important;
|
color: hsl(30, 10%, 50%) !important;
|
||||||
}
|
}
|
||||||
.dark.theme-pumpkin aside button:hover {
|
.dark.theme-pumpkin aside button:hover {
|
||||||
background: hsla(330, 100%, 50%, 0.08) !important;
|
background: hsla(25, 95%, 50%, 0.08) !important;
|
||||||
color: hsl(0, 0%, 90%) !important;
|
color: hsl(0, 0%, 90%) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Portal sidebar active nav item */
|
|
||||||
.dark.theme-pumpkin aside .bg-primary\/10.text-primary {
|
.dark.theme-pumpkin aside .bg-primary\/10.text-primary {
|
||||||
background: hsl(330, 100%, 50%) !important;
|
background: hsl(25, 95%, 50%) !important;
|
||||||
color: hsl(0, 0%, 100%) !important;
|
color: hsl(0, 0%, 100%) !important;
|
||||||
box-shadow: 0 0 10px rgba(255, 0, 127, 0.3), 0 0 20px rgba(255, 0, 127, 0.1);
|
box-shadow: 0 0 10px hsla(25, 95%, 50%, 0.3), 0 0 20px hsla(25, 95%, 50%, 0.1);
|
||||||
border-color: hsl(330, 100%, 50%) !important;
|
border-color: hsl(25, 95%, 50%) !important;
|
||||||
}
|
}
|
||||||
.dark.theme-pumpkin aside .bg-primary\/10.text-primary svg {
|
.dark.theme-pumpkin aside .bg-primary\/10.text-primary svg {
|
||||||
color: hsl(0, 0%, 100%) !important;
|
color: hsl(0, 0%, 100%) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Portal header — dark glass */
|
|
||||||
.dark.theme-pumpkin header.bg-card\/80 {
|
.dark.theme-pumpkin header.bg-card\/80 {
|
||||||
background: hsl(240, 15%, 3%) !important;
|
background: hsl(25, 35%, 4%) !important;
|
||||||
border-bottom-color: hsl(270, 25%, 10%) !important;
|
border-bottom-color: hsl(25, 20%, 14%) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Portal card backgrounds — semi-transparent glass */
|
|
||||||
.dark.theme-pumpkin .bg-card {
|
.dark.theme-pumpkin .bg-card {
|
||||||
background: hsla(240, 15%, 7%, 0.65) !important;
|
background: hsla(25, 30%, 7%, 0.65) !important;
|
||||||
backdrop-filter: blur(10px) !important;
|
backdrop-filter: blur(10px) !important;
|
||||||
border-color: hsla(270, 30%, 20%, 0.35) !important;
|
border-color: hsla(25, 20%, 16%, 0.35) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Portal borders */
|
|
||||||
.dark.theme-pumpkin .border-border {
|
.dark.theme-pumpkin .border-border {
|
||||||
border-color: hsl(270, 25%, 12%) !important;
|
border-color: hsl(25, 18%, 14%) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Portal input fields */
|
|
||||||
.dark.theme-pumpkin .bg-muted {
|
.dark.theme-pumpkin .bg-muted {
|
||||||
background: hsl(240, 12%, 9%) !important;
|
background: hsl(25, 15%, 10%) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Portal primary buttons */
|
|
||||||
.dark.theme-pumpkin .bg-primary {
|
.dark.theme-pumpkin .bg-primary {
|
||||||
background: hsl(330, 100%, 50%) !important;
|
background: hsl(25, 95%, 50%) !important;
|
||||||
box-shadow: 0 0 10px rgba(255, 0, 127, 0.2);
|
box-shadow: 0 0 10px hsla(25, 95%, 50%, 0.2);
|
||||||
}
|
}
|
||||||
.dark.theme-pumpkin .bg-primary:hover {
|
.dark.theme-pumpkin .bg-primary:hover {
|
||||||
background: hsl(330, 100%, 55%) !important;
|
background: hsl(25, 95%, 55%) !important;
|
||||||
box-shadow: 0 0 15px rgba(255, 0, 127, 0.4);
|
box-shadow: 0 0 15px hsla(25, 95%, 50%, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Light mode portal */
|
||||||
|
.light.theme-pumpkin .min-h-screen.bg-background {
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.light.theme-pumpkin aside.bg-card.border-r.border-border {
|
||||||
|
background: hsl(0, 0%, 100%) !important;
|
||||||
|
border-right-color: hsl(25, 15%, 82%) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.light.theme-pumpkin header.bg-card\/80 {
|
||||||
|
background: hsl(0, 0%, 98%) !important;
|
||||||
|
border-bottom-color: hsl(25, 15%, 82%) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.light.theme-pumpkin .bg-card {
|
||||||
|
background: hsla(0, 0%, 100%, 0.65) !important;
|
||||||
|
backdrop-filter: blur(10px) !important;
|
||||||
|
border-color: hsla(25, 15%, 80%, 0.5) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.light.theme-pumpkin .bg-primary {
|
||||||
|
background: hsl(25, 90%, 42%) !important;
|
||||||
|
}
|
||||||
|
.light.theme-pumpkin .bg-primary:hover {
|
||||||
|
background: hsl(25, 90%, 48%) !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,9 +73,12 @@ Example format:
|
|||||||
[{"companyName":"Acme Corp","contactName":"John Smith","email":"john@acme.com","phone":"555-0100","description":"Brief description"}]
|
[{"companyName":"Acme Corp","contactName":"John Smith","email":"john@acme.com","phone":"555-0100","description":"Brief description"}]
|
||||||
|
|
||||||
Use realistic company names, names, and emails for the ${job.industry} industry.`
|
Use realistic company names, names, and emails for the ${job.industry} industry.`
|
||||||
aiChatRef.current?.addAssistantMessage(`🔍 Finding leads for **${job.job_title}**...`)
|
|
||||||
|
const addMsg = (text: string) => aiChatRef.current?.addAssistantMessage(text)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
addMsg(`🔍 Step 1/3: Asking AI to generate leads for **${job.job_title}**...`)
|
||||||
|
|
||||||
const res = await fetch("/api/ai/chat", {
|
const res = await fetch("/api/ai/chat", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
@@ -84,11 +87,22 @@ Use realistic company names, names, and emails for the ${job.industry} industry.
|
|||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
const data = await res.json().catch(() => ({}))
|
const data = await res.json().catch(() => ({}))
|
||||||
throw new Error(data.error || `Error ${res.status}`)
|
const statusInfo = `HTTP ${res.status}: ${data.error || res.statusText}`
|
||||||
|
addMsg(`❌ Step 1/3 failed — AI chat returned ${statusInfo}. Make sure Ollama is running (port 11434) and the AI service on port 3001 is up.`)
|
||||||
|
setSearching(false)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await res.json()
|
const data = await res.json()
|
||||||
const raw = data.response || ""
|
const raw = data.response || ""
|
||||||
|
if (!raw) {
|
||||||
|
addMsg("❌ Step 1/3 failed — AI returned an empty response.")
|
||||||
|
setSearching(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
addMsg(`✅ Step 1/3 complete. Parsing AI response...`)
|
||||||
|
|
||||||
let leads: any[]
|
let leads: any[]
|
||||||
try {
|
try {
|
||||||
let text = raw.trim()
|
let text = raw.trim()
|
||||||
@@ -97,11 +111,19 @@ Use realistic company names, names, and emails for the ${job.industry} industry.
|
|||||||
leads = JSON.parse(text)
|
leads = JSON.parse(text)
|
||||||
if (!Array.isArray(leads)) leads = [leads]
|
if (!Array.isArray(leads)) leads = [leads]
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
aiChatRef.current?.addAssistantMessage(`⚠️ AI response wasn't valid JSON. Showing raw output:\n\n${raw.slice(0, 1500)}`)
|
addMsg(`⚠️ Step 2/3 failed — AI response wasn't valid JSON. Raw output:\n\n${raw.slice(0, 1500)}`)
|
||||||
setSearching(false)
|
setSearching(false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (leads.length === 0) {
|
||||||
|
addMsg("⚠️ Step 2/3 — AI returned an empty array. No leads to create.")
|
||||||
|
setSearching(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
addMsg(`✅ Step 2/3 — Parsed **${leads.length}** lead${leads.length > 1 ? "s" : ""} from AI response. Step 3/3: Saving to database...`)
|
||||||
|
|
||||||
let created = 0
|
let created = 0
|
||||||
let lastError = ""
|
let lastError = ""
|
||||||
for (const lead of leads.slice(0, 5)) {
|
for (const lead of leads.slice(0, 5)) {
|
||||||
@@ -119,18 +141,20 @@ Use realistic company names, names, and emails for the ${job.industry} industry.
|
|||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
if (r.ok) created++
|
if (r.ok) created++
|
||||||
else { const e = await r.json().catch(() => ({})); lastError = e.error || r.statusText; }
|
else { const e = await r.json().catch(() => ({})); lastError = `API ${res.status} ${e.error || r.statusText}`; }
|
||||||
} catch (e) { lastError = "Network error" }
|
} catch (e) {
|
||||||
|
lastError = `Network error: ${e instanceof Error ? e.message : "Failed to reach server"}`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (created > 0) {
|
if (created > 0) {
|
||||||
aiChatRef.current?.addAssistantMessage(`✅ Created **${created}** lead${created > 1 ? "s" : ""} for **${job.job_title}**! Go to the Leads tab to see them.`)
|
addMsg(`✅ Created **${created}** lead${created > 1 ? "s" : ""} for **${job.job_title}**! Go to the **Leads** tab to view and manage them.${lastError ? `\n\n⚠️ ${leads.length - created} lead${leads.length - created > 1 ? "s" : ""} failed: ${lastError}` : ""}`)
|
||||||
} else {
|
} else {
|
||||||
aiChatRef.current?.addAssistantMessage(`❌ Failed to create leads. Last error: ${lastError || "Unknown"}. Make sure you're logged in with an account that has permission to create leads.`)
|
addMsg(`❌ Step 3/3 failed — Could not save any leads.\n\nLast error: ${lastError || "Unknown"}\n\nPossible causes:\n• Your session expired — try logging out and back in\n• You don't have permission to create leads\n• The database connection is down`)
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const msg = err instanceof Error ? err.message : "Search failed"
|
const msg = err instanceof Error ? err.message : "Unknown error"
|
||||||
aiChatRef.current?.addAssistantMessage(`❌ Error: ${msg}. Make sure Ollama is running with the model loaded.`)
|
addMsg(`❌ Unexpected error: ${msg}\n\nThis might be a network issue or the AI service (port 3001) is not responding. Try again.`)
|
||||||
} finally {
|
} finally {
|
||||||
setSearching(false)
|
setSearching(false)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -126,11 +126,11 @@ export default function CalendarPage() {
|
|||||||
fetch("/api/event-users")
|
fetch("/api/event-users")
|
||||||
.then((r) => r.json())
|
.then((r) => r.json())
|
||||||
.then((data) => setUsers(data.users || []))
|
.then((data) => setUsers(data.users || []))
|
||||||
.catch(() => {})
|
.catch((err) => console.error("Failed to fetch event users:", err))
|
||||||
fetch("/api/leads?limit=200")
|
fetch("/api/leads?limit=200")
|
||||||
.then((r) => r.json())
|
.then((r) => r.json())
|
||||||
.then((data) => setLeads((Array.isArray(data) ? data : data?.leads || []).map((l: any) => ({ id: l.id, contactName: l.contactName, companyName: l.companyName }))))
|
.then((data) => setLeads((Array.isArray(data) ? data : data?.leads || []).map((l: any) => ({ id: l.id, contactName: l.contactName, companyName: l.companyName }))))
|
||||||
.catch(() => {})
|
.catch((err) => console.error("Failed to fetch leads:", err))
|
||||||
}, [user, router])
|
}, [user, router])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -139,13 +139,13 @@ export default function CalendarPage() {
|
|||||||
}, [fetchMonthEvents])
|
}, [fetchMonthEvents])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchUpcomingEvents().catch(() => {})
|
fetchUpcomingEvents().catch((err) => console.error("Failed to fetch upcoming events:", err))
|
||||||
}, [fetchUpcomingEvents])
|
}, [fetchUpcomingEvents])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const interval = setInterval(() => {
|
const interval = setInterval(() => {
|
||||||
fetchMonthEvents().catch(() => {})
|
fetchMonthEvents().catch((err) => console.error("Failed to fetch month events:", err))
|
||||||
fetchUpcomingEvents().catch(() => {})
|
fetchUpcomingEvents().catch((err) => console.error("Failed to fetch upcoming events:", err))
|
||||||
}, 15000)
|
}, 15000)
|
||||||
return () => clearInterval(interval)
|
return () => clearInterval(interval)
|
||||||
}, [fetchMonthEvents, fetchUpcomingEvents])
|
}, [fetchMonthEvents, fetchUpcomingEvents])
|
||||||
|
|||||||
@@ -39,8 +39,11 @@ export default function CampaignDetailPage({ params }: { params: Promise<{ id: s
|
|||||||
const searchLeads = async (q: string) => {
|
const searchLeads = async (q: string) => {
|
||||||
setLeadSearch(q)
|
setLeadSearch(q)
|
||||||
if (q.length < 2) { setSearchResults([]); return }
|
if (q.length < 2) { setSearchResults([]); return }
|
||||||
const res = await fetch(`/api/leads?search=${encodeURIComponent(q)}&limit=10`)
|
try {
|
||||||
if (res.ok) setSearchResults(await res.json())
|
const res = await fetch(`/api/leads?search=${encodeURIComponent(q)}&limit=10`)
|
||||||
|
if (res.ok) setSearchResults(await res.json())
|
||||||
|
else console.error("Failed to search leads:", res.status, res.statusText)
|
||||||
|
} catch (err) { console.error("Failed to search leads:", err) }
|
||||||
}
|
}
|
||||||
|
|
||||||
const addStep = async () => {
|
const addStep = async () => {
|
||||||
@@ -84,12 +87,15 @@ export default function CampaignDetailPage({ params }: { params: Promise<{ id: s
|
|||||||
}
|
}
|
||||||
|
|
||||||
const updateStatus = async (status: string) => {
|
const updateStatus = async (status: string) => {
|
||||||
await fetch(`/api/campaigns/${id}`, {
|
try {
|
||||||
method: "PATCH",
|
const res = await fetch(`/api/campaigns/${id}`, {
|
||||||
headers: { "Content-Type": "application/json" },
|
method: "PATCH",
|
||||||
body: JSON.stringify({ status }),
|
headers: { "Content-Type": "application/json" },
|
||||||
})
|
body: JSON.stringify({ status }),
|
||||||
fetchCampaign()
|
})
|
||||||
|
if (!res.ok) console.error("Failed to update campaign status:", res.status, res.statusText)
|
||||||
|
fetchCampaign()
|
||||||
|
} catch (err) { console.error("Failed to update campaign status:", err) }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loading) return <div className="p-8 text-center text-muted-foreground">Loading...</div>
|
if (loading) return <div className="p-8 text-center text-muted-foreground">Loading...</div>
|
||||||
|
|||||||
@@ -235,8 +235,8 @@ const formatPreviewContent = (content: string) => {
|
|||||||
setUnreadMap(new Map(convs.map((c: any) => [c.id, c.unread])))
|
setUnreadMap(new Map(convs.map((c: any) => [c.id, c.unread])))
|
||||||
if (convs.length > 0) setActiveChat(convs[0].id)
|
if (convs.length > 0) setActiveChat(convs[0].id)
|
||||||
}
|
}
|
||||||
} catch {
|
} catch (err) {
|
||||||
console.warn("Failed to fetch conversations in chats page")
|
console.error("Failed to fetch conversations:", err)
|
||||||
} finally {
|
} finally {
|
||||||
setLoadingChats(false)
|
setLoadingChats(false)
|
||||||
}
|
}
|
||||||
@@ -297,11 +297,11 @@ const formatPreviewContent = (content: string) => {
|
|||||||
return next
|
return next
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} catch {
|
} catch (err) {
|
||||||
// ignore
|
console.error("Failed to fetch messages:", err)
|
||||||
}
|
}
|
||||||
// Mark conversation as read
|
// Mark conversation as read
|
||||||
fetch(`/api/conversations/${activeChat}/read`, { method: "POST" }).catch(() => {})
|
fetch(`/api/conversations/${activeChat}/read`, { method: "POST" }).catch((err) => console.error("Failed to mark read:", err))
|
||||||
setUnreadMap((prev) => { const m = new Map(prev); m.set(activeChat, 0); return m })
|
setUnreadMap((prev) => { const m = new Map(prev); m.set(activeChat, 0); return m })
|
||||||
}
|
}
|
||||||
fetchMsgs()
|
fetchMsgs()
|
||||||
@@ -320,7 +320,7 @@ const formatPreviewContent = (content: string) => {
|
|||||||
const existingIds = new Set(conversations.map((c) => otherParticipant(c).id))
|
const existingIds = new Set(conversations.map((c) => otherParticipant(c).id))
|
||||||
setSearchResults(data.users.filter((u: any) => !existingIds.has(u.id)))
|
setSearchResults(data.users.filter((u: any) => !existingIds.has(u.id)))
|
||||||
}
|
}
|
||||||
} catch { console.warn("Failed to search users in chats page") }
|
} catch (err) { console.error("Failed to search users:", err) }
|
||||||
setSearchingUsers(false)
|
setSearchingUsers(false)
|
||||||
}, 300)
|
}, 300)
|
||||||
return () => clearTimeout(timer)
|
return () => clearTimeout(timer)
|
||||||
@@ -799,8 +799,8 @@ const formatPreviewContent = (content: string) => {
|
|||||||
prev.map((c) => (c.id === convId ? { ...c, pinned: !pinned } : c))
|
prev.map((c) => (c.id === convId ? { ...c, pinned: !pinned } : c))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} catch {
|
} catch (err) {
|
||||||
console.warn("Failed to toggle pin")
|
console.error("Failed to toggle pin:", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -912,7 +912,7 @@ const formatPreviewContent = (content: string) => {
|
|||||||
setSearchQuery("")
|
setSearchQuery("")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch { console.warn("Failed to create conversation in chats page") }
|
} catch (err) { console.error("Failed to create conversation:", err) }
|
||||||
}}
|
}}
|
||||||
className="w-full flex items-center gap-3 p-4 text-left transition-colors hover:bg-muted/50"
|
className="w-full flex items-center gap-3 p-4 text-left transition-colors hover:bg-muted/50"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -39,8 +39,8 @@ export default function DashboardPage() {
|
|||||||
const data = await res.json()
|
const data = await res.json()
|
||||||
setStats(data)
|
setStats(data)
|
||||||
}
|
}
|
||||||
} catch {
|
} catch (err) {
|
||||||
console.warn("Failed to fetch dashboard stats")
|
console.error("Failed to fetch dashboard stats:", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export default function EmailsPage() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!user) { router.push("/login"); return }
|
if (!user) { router.push("/login"); return }
|
||||||
if (user.role !== "admin" && user.role !== "super_admin") { router.push("/dashboard"); return }
|
if (user.role !== "admin" && user.role !== "super_admin") { router.push("/dashboard"); return }
|
||||||
fetch("/api/emails").then((r) => r.ok ? r.json() : { emails: [] }).then((d) => setEmails(d.emails || []))
|
fetch("/api/emails").then((r) => r.ok ? r.json() : { emails: [] }).then((d) => setEmails(d.emails || [])).catch((err) => console.error("Failed to fetch emails:", err))
|
||||||
}, [user, router])
|
}, [user, router])
|
||||||
|
|
||||||
if (!user || (user.role !== "admin" && user.role !== "super_admin")) return null
|
if (!user || (user.role !== "admin" && user.role !== "super_admin")) return null
|
||||||
|
|||||||
@@ -39,8 +39,8 @@ export default function LeadDetailsPage({ params }: { params: Promise<{ id: stri
|
|||||||
fetchNotes(),
|
fetchNotes(),
|
||||||
])
|
])
|
||||||
if (leadRes.ok) setLead(await leadRes.json())
|
if (leadRes.ok) setLead(await leadRes.json())
|
||||||
} catch {
|
} catch (err) {
|
||||||
console.warn("Failed to fetch lead details")
|
console.error("Failed to fetch lead details:", err)
|
||||||
}
|
}
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,33 +42,24 @@ interface Totals {
|
|||||||
teamUtilization: number
|
teamUtilization: number
|
||||||
}
|
}
|
||||||
|
|
||||||
function utilizationColor(pct: number): string {
|
function statusKey(pct: number): string {
|
||||||
if (pct > 100) return "bg-red-500"
|
if (pct > 100) return "overloaded"
|
||||||
if (pct > 90) return "bg-orange-500"
|
if (pct > 90) return "near-capacity"
|
||||||
if (pct > 70) return "bg-yellow-500"
|
if (pct > 70) return "busy"
|
||||||
return "bg-emerald-500"
|
if (pct > 50) return "available"
|
||||||
}
|
return "underutilized"
|
||||||
|
|
||||||
function utilizationTextColor(pct: number): string {
|
|
||||||
if (pct > 100) return "text-red-600"
|
|
||||||
if (pct > 90) return "text-orange-600"
|
|
||||||
if (pct > 70) return "text-yellow-600"
|
|
||||||
return "text-emerald-600"
|
|
||||||
}
|
|
||||||
|
|
||||||
function utilizationBgColor(pct: number): string {
|
|
||||||
if (pct > 100) return "bg-red-50 border-red-200"
|
|
||||||
if (pct > 90) return "bg-orange-50 border-orange-200"
|
|
||||||
if (pct > 70) return "bg-yellow-50 border-yellow-200"
|
|
||||||
return "bg-emerald-50 border-emerald-200"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function statusLabel(pct: number): { label: string; color: string } {
|
function statusLabel(pct: number): { label: string; color: string } {
|
||||||
if (pct > 100) return { label: "Overloaded", color: "bg-red-100 text-red-700 border-red-300" }
|
const key = statusKey(pct)
|
||||||
if (pct > 90) return { label: "Near Capacity", color: "bg-orange-100 text-orange-700 border-orange-300" }
|
const labels: Record<string, string> = {
|
||||||
if (pct > 70) return { label: "Busy", color: "bg-yellow-100 text-yellow-700 border-yellow-300" }
|
overloaded: "Overloaded",
|
||||||
if (pct > 50) return { label: "Available", color: "bg-emerald-100 text-emerald-700 border-emerald-300" }
|
"near-capacity": "Near Capacity",
|
||||||
return { label: "Underutilized", color: "bg-slate-100 text-slate-600 border-slate-300" }
|
busy: "Busy",
|
||||||
|
available: "Available",
|
||||||
|
underutilized: "Underutilized",
|
||||||
|
}
|
||||||
|
return { label: labels[key], color: `util-badge-${key}` }
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ResourcePlanningPage() {
|
export default function ResourcePlanningPage() {
|
||||||
@@ -120,7 +111,7 @@ export default function ResourcePlanningPage() {
|
|||||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||||
<TrendingUp className="h-4 w-4" /> Utilisation
|
<TrendingUp className="h-4 w-4" /> Utilisation
|
||||||
</div>
|
</div>
|
||||||
<p className={`text-2xl font-bold ${utilizationTextColor(totals?.teamUtilization ?? 0)}`}>
|
<p className={`text-2xl font-bold util-text-${statusKey(totals?.teamUtilization ?? 0)}`}>
|
||||||
{totals?.teamUtilization ?? 0}%
|
{totals?.teamUtilization ?? 0}%
|
||||||
</p>
|
</p>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -157,7 +148,7 @@ export default function ResourcePlanningPage() {
|
|||||||
{members.map((m) => {
|
{members.map((m) => {
|
||||||
const status = statusLabel(m.utilization)
|
const status = statusLabel(m.utilization)
|
||||||
return (
|
return (
|
||||||
<Card key={m.id} className={`p-4 space-y-3 border ${utilizationBgColor(m.utilization)}`}>
|
<Card key={m.id} className={`p-4 space-y-3 border util-card-${statusKey(m.utilization)}`}>
|
||||||
<div className="flex items-start justify-between">
|
<div className="flex items-start justify-between">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Avatar className="h-10 w-10">
|
<Avatar className="h-10 w-10">
|
||||||
@@ -179,11 +170,11 @@ export default function ResourcePlanningPage() {
|
|||||||
<div className="flex justify-between text-xs text-muted-foreground">
|
<div className="flex justify-between text-xs text-muted-foreground">
|
||||||
<span>Capacity: {m.capacityHours}h</span>
|
<span>Capacity: {m.capacityHours}h</span>
|
||||||
<span>Logged: {m.totalHours}h</span>
|
<span>Logged: {m.totalHours}h</span>
|
||||||
<span className={utilizationTextColor(m.utilization)}>{m.utilization}%</span>
|
<span className={`util-text-${statusKey(m.utilization)}`}>{m.utilization}%</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="h-2.5 w-full overflow-hidden rounded-full bg-muted">
|
<div className="h-2.5 w-full overflow-hidden rounded-full bg-muted">
|
||||||
<div
|
<div
|
||||||
className={`h-full rounded-full transition-all ${utilizationColor(m.utilization)}`}
|
className={`h-full rounded-full transition-all util-bar-${statusKey(m.utilization)}`}
|
||||||
style={{ width: `${Math.min(m.utilization, 100)}%` }}
|
style={{ width: `${Math.min(m.utilization, 100)}%` }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ export default function UsersPage() {
|
|||||||
const data = await res.json()
|
const data = await res.json()
|
||||||
setUsers(data.users || [])
|
setUsers(data.users || [])
|
||||||
}
|
}
|
||||||
} catch {
|
} catch (err) {
|
||||||
console.warn("Failed to fetch users in users page")
|
console.error("Failed to fetch users:", err)
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -758,4 +758,58 @@ main {
|
|||||||
opacity: 1 !important;
|
opacity: 1 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ============================================================
|
||||||
|
Utilisation status card backgrounds (theme-aware)
|
||||||
|
============================================================ */
|
||||||
|
.util-card-overloaded {
|
||||||
|
background-color: color-mix(in srgb, hsl(0 80% 50% / 0.1), hsl(var(--card))) !important;
|
||||||
|
border-color: hsl(0 70% 50% / 0.3) !important;
|
||||||
|
}
|
||||||
|
.util-card-near-capacity {
|
||||||
|
background-color: color-mix(in srgb, hsl(25 95% 50% / 0.1), hsl(var(--card))) !important;
|
||||||
|
border-color: hsl(25 85% 45% / 0.3) !important;
|
||||||
|
}
|
||||||
|
.util-card-busy {
|
||||||
|
background-color: color-mix(in srgb, hsl(45 90% 50% / 0.08), hsl(var(--card))) !important;
|
||||||
|
border-color: hsl(45 80% 45% / 0.25) !important;
|
||||||
|
}
|
||||||
|
.util-card-available {
|
||||||
|
background-color: color-mix(in srgb, hsl(150 65% 40% / 0.08), hsl(var(--card))) !important;
|
||||||
|
border-color: hsl(150 60% 40% / 0.25) !important;
|
||||||
|
}
|
||||||
|
.util-card-underutilized {
|
||||||
|
/* Default card styling — no tint needed */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Utilisation status text */
|
||||||
|
.util-text-overloaded { @apply text-red-600 dark:text-red-400; }
|
||||||
|
.util-text-near-capacity { @apply text-orange-600 dark:text-orange-400; }
|
||||||
|
.util-text-busy { @apply text-yellow-600 dark:text-yellow-400; }
|
||||||
|
.util-text-available { @apply text-emerald-600 dark:text-emerald-400; }
|
||||||
|
.util-text-underutilized { @apply text-slate-600 dark:text-slate-400; }
|
||||||
|
|
||||||
|
/* Utilisation status badge */
|
||||||
|
.util-badge-overloaded {
|
||||||
|
@apply bg-red-100 text-red-700 border-red-300 dark:bg-red-900/30 dark:text-red-300 dark:border-red-800;
|
||||||
|
}
|
||||||
|
.util-badge-near-capacity {
|
||||||
|
@apply bg-orange-100 text-orange-700 border-orange-300 dark:bg-orange-900/30 dark:text-orange-300 dark:border-orange-800;
|
||||||
|
}
|
||||||
|
.util-badge-busy {
|
||||||
|
@apply bg-yellow-100 text-yellow-700 border-yellow-300 dark:bg-yellow-900/25 dark:text-yellow-300 dark:border-yellow-800;
|
||||||
|
}
|
||||||
|
.util-badge-available {
|
||||||
|
@apply bg-emerald-100 text-emerald-700 border-emerald-300 dark:bg-emerald-900/25 dark:text-emerald-300 dark:border-emerald-800;
|
||||||
|
}
|
||||||
|
.util-badge-underutilized {
|
||||||
|
@apply bg-slate-100 text-slate-600 border-slate-300 dark:bg-slate-800/30 dark:text-slate-400 dark:border-slate-700;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Utilisation bar fill */
|
||||||
|
.util-bar-overloaded { @apply bg-red-500; }
|
||||||
|
.util-bar-near-capacity { @apply bg-orange-500; }
|
||||||
|
.util-bar-busy { @apply bg-yellow-500; }
|
||||||
|
.util-bar-available { @apply bg-emerald-500; }
|
||||||
|
.util-bar-underutilized { @apply bg-slate-400 dark:bg-slate-500; }
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user