added legend on team shedule page

This commit is contained in:
Mayur Shinde
2026-06-18 18:59:31 +05:30
parent 1dff3943a0
commit a88e45542f
2 changed files with 72 additions and 8 deletions
+16
View File
@@ -59,3 +59,19 @@ export const LEAD_URGENCY = {
export const REP_STATUS_COLORS = Object.fromEntries(Object.entries(REP_STATUS).map(([k, v]) => [k, v.color])); export const REP_STATUS_COLORS = Object.fromEntries(Object.entries(REP_STATUS).map(([k, v]) => [k, v.color]));
export const REP_STATUS_LABELS = Object.fromEntries(Object.entries(REP_STATUS).map(([k, v]) => [k, v.label])); export const REP_STATUS_LABELS = Object.fromEntries(Object.entries(REP_STATUS).map(([k, v]) => [k, v.label]));
export const URGENCY_COLORS = Object.fromEntries(Object.entries(LEAD_URGENCY).map(([k, v]) => [k, v.color])); export const URGENCY_COLORS = Object.fromEntries(Object.entries(LEAD_URGENCY).map(([k, v]) => [k, v.color]));
// ── Team Schedule — appointment / meeting status ────────────────────────────
// `badge` — Tailwind classes for the status pill
// `dot` — Tailwind bg class for the legend swatch
// `desc` — one-line meaning shown in the legend (so Converted vs Scheduled is clear)
export const MEETING_STATUS = {
Scheduled: { label: 'Scheduled', badge: 'bg-blue-500/20 text-blue-600 dark:text-blue-400', dot: 'bg-blue-500', desc: 'Upcoming appointment' },
Rescheduled: { label: 'Rescheduled', badge: 'bg-amber-500/20 text-amber-600 dark:text-amber-400', dot: 'bg-amber-500', desc: 'Moved to a new date or time' },
Completed: { label: 'Completed', badge: 'bg-emerald-500/20 text-emerald-600 dark:text-emerald-400', dot: 'bg-emerald-500', desc: 'Visit took place' },
Converted: { label: 'Converted', badge: 'bg-violet-500/20 text-violet-600 dark:text-violet-400', dot: 'bg-violet-500', desc: 'Won — became a signed job' },
Cancelled: { label: 'Cancelled', badge: 'bg-slate-500/20 text-slate-600 dark:text-slate-400', dot: 'bg-slate-400', desc: 'Called off' },
};
export const MEETING_STATUS_ORDER = ['Scheduled', 'Rescheduled', 'Completed', 'Converted', 'Cancelled'];
export const MEETING_STATUS_DEFAULT = { label: 'Unknown', badge: 'bg-zinc-400/20 text-zinc-500 dark:text-zinc-400', dot: 'bg-zinc-400', desc: '' };
+56 -8
View File
@@ -1,7 +1,8 @@
import React, { useState, useMemo, useEffect, useRef } from 'react'; import React, { useState, useMemo, useEffect, useRef } from 'react';
import { useMockStore } from '../data/mockStore'; import { useMockStore } from '../data/mockStore';
import { MEETING_STATUS, MEETING_STATUS_ORDER, MEETING_STATUS_DEFAULT } from '../data/statusTaxonomy';
import { useTheme } from '../context/ThemeContext'; import { useTheme } from '../context/ThemeContext';
import { Calendar, User, Clock, MapPin, MoreHorizontal, Filter, X, ChevronDown } from 'lucide-react'; import { Calendar, User, Clock, MapPin, MoreHorizontal, Filter, X, ChevronDown, Info } from 'lucide-react';
import { SpotlightCard } from '../components/SpotlightCard'; import { SpotlightCard } from '../components/SpotlightCard';
const AdminSchedule = () => { const AdminSchedule = () => {
@@ -11,6 +12,10 @@ const AdminSchedule = () => {
// Refs for click-outside detection // Refs for click-outside detection
const dateDropdownRef = useRef(null); const dateDropdownRef = useRef(null);
const statusDropdownRef = useRef(null); const statusDropdownRef = useRef(null);
const legendRef = useRef(null);
// Status legend popover (opens as a card from the Info button)
const [showLegend, setShowLegend] = useState(false);
// Filter states // Filter states
const [dateFilter, setDateFilter] = useState('all'); // all, today, week, month, quarter, custom const [dateFilter, setDateFilter] = useState('all'); // all, today, week, month, quarter, custom
@@ -29,6 +34,9 @@ const AdminSchedule = () => {
if (statusDropdownRef.current && !statusDropdownRef.current.contains(event.target)) { if (statusDropdownRef.current && !statusDropdownRef.current.contains(event.target)) {
setShowStatusDropdown(false); setShowStatusDropdown(false);
} }
if (legendRef.current && !legendRef.current.contains(event.target)) {
setShowLegend(false);
}
}; };
document.addEventListener('mousedown', handleClickOutside); document.addEventListener('mousedown', handleClickOutside);
@@ -314,11 +322,56 @@ const AdminSchedule = () => {
</div> </div>
<SpotlightCard className="overflow-hidden"> <SpotlightCard className="overflow-hidden">
<div className="p-6 border-b border-zinc-200 dark:border-zinc-800"> <div className="p-6 border-b border-zinc-200 dark:border-zinc-800 flex items-center justify-between gap-3">
<h2 className="text-xl font-bold text-zinc-900 dark:text-white flex items-center"> <h2 className="text-xl font-bold text-zinc-900 dark:text-white flex items-center">
<Calendar size={20} className="text-blue-500 mr-2" /> <Calendar size={20} className="text-blue-500 mr-2" />
{viewMode === 'list' ? 'All Active Appointments' : 'Calendar Overview'} {viewMode === 'list' ? 'All Active Appointments' : 'Calendar Overview'}
</h2> </h2>
{/* Status legend — opens as a card from the Info button; labels the
badge colors (Converted vs Scheduled, etc.). Responsive on all sizes. */}
{viewMode === 'list' && (
<div className="relative shrink-0" ref={legendRef}>
<button
type="button"
onClick={() => setShowLegend(o => !o)}
aria-expanded={showLegend}
aria-label="Status legend"
className="flex items-center gap-1.5 px-3 py-2 rounded-lg text-sm font-medium text-zinc-600 dark:text-zinc-300 bg-zinc-100 dark:bg-white/5 hover:bg-zinc-200 dark:hover:bg-white/10 border border-zinc-200 dark:border-white/10 transition-colors"
>
<Info size={16} />
<span className="hidden sm:inline">Legend</span>
</button>
{showLegend && (
<div className="absolute right-0 top-full mt-2 z-50 w-72 max-w-[calc(100vw-2rem)] rounded-xl border border-zinc-200 dark:border-white/10 bg-white dark:bg-zinc-900 shadow-2xl shadow-black/10 dark:shadow-black/50 overflow-hidden">
<div className="flex items-center justify-between px-4 py-2.5 border-b border-zinc-100 dark:border-white/[0.06]">
<span className="text-[11px] font-black uppercase tracking-widest text-zinc-500 dark:text-zinc-400">Status Legend</span>
<button
type="button"
onClick={() => setShowLegend(false)}
aria-label="Close legend"
className="p-1 rounded-md text-zinc-400 hover:bg-zinc-100 dark:hover:bg-white/5 transition-colors"
>
<X size={14} />
</button>
</div>
<div className="p-3 space-y-2.5">
{MEETING_STATUS_ORDER.map(status => {
const cfg = MEETING_STATUS[status];
return (
<div key={status} className="flex items-start gap-2.5">
<span className={`w-2.5 h-2.5 rounded-full shrink-0 mt-1 ${cfg.dot}`} />
<div className="min-w-0">
<p className="text-xs font-bold text-zinc-800 dark:text-zinc-100">{cfg.label}</p>
<p className="text-[11px] text-zinc-500 dark:text-zinc-400 leading-snug">{cfg.desc}</p>
</div>
</div>
);
})}
</div>
</div>
)}
</div>
)}
</div> </div>
{viewMode === 'list' ? ( {viewMode === 'list' ? (
@@ -361,12 +414,7 @@ const AdminSchedule = () => {
</div> </div>
</td> </td>
<td className="px-6 py-4"> <td className="px-6 py-4">
<span className={`px-2 py-1 text-xs font-semibold rounded whitespace-nowrap ${meeting.status === 'Completed' ? 'bg-green-500/20 text-green-600 dark:text-green-400' : <span className={`px-2 py-1 text-xs font-semibold rounded whitespace-nowrap ${(MEETING_STATUS[meeting.status] || MEETING_STATUS_DEFAULT).badge}`}>
meeting.status === 'Scheduled' ? 'bg-blue-500/20 text-blue-600 dark:text-blue-400' :
meeting.status === 'Converted' ? 'bg-purple-500/20 text-purple-600 dark:text-purple-400' :
meeting.status === 'Cancelled' ? 'bg-red-500/20 text-red-600 dark:text-red-400' :
'bg-yellow-500/20 text-yellow-600 dark:text-yellow-400'
}`}>
{meeting.status} {meeting.status}
</span> </span>
</td> </td>