mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage
synced 2026-04-11 18:28:15 +00:00
fix: wire role-based views — sidebar auth, user display, table tabs, card actions
- Sidebar: use useAuth() for isAdmin and pass auth user to NavAccountCard - NavAccountCard: fix bug where items prop was ignored (used placeholderAccounts) - TopBar: replace hardcoded "SM" initials with user.initials from auth - All Leads: add "My Leads" tab filtering by assignedAgent matching user - Lead Card: add role-aware action buttons (Call/Disposition for assigned leads) - Lead Workspace: pass onLogCall/onUpdateStatus handlers to LeadCard Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -167,7 +167,7 @@ export const NavAccountCard = ({
|
|||||||
const triggerRef = useRef<HTMLDivElement>(null);
|
const triggerRef = useRef<HTMLDivElement>(null);
|
||||||
const isDesktop = useBreakpoint("lg");
|
const isDesktop = useBreakpoint("lg");
|
||||||
|
|
||||||
const selectedAccount = placeholderAccounts.find((account) => account.id === selectedAccountId);
|
const selectedAccount = items.find((account) => account.id === selectedAccountId);
|
||||||
|
|
||||||
if (!selectedAccount) {
|
if (!selectedAccount) {
|
||||||
console.warn(`Account with ID ${selectedAccountId} not found in <NavAccountCard />`);
|
console.warn(`Account with ID ${selectedAccountId} not found in <NavAccountCard />`);
|
||||||
|
|||||||
@@ -110,7 +110,17 @@ export const Sidebar = ({ activeUrl = "/" }: SidebarProps) => {
|
|||||||
|
|
||||||
{/* Account card */}
|
{/* Account card */}
|
||||||
<div className="mt-auto flex flex-col gap-5 px-2 py-4 lg:gap-6 lg:px-4 lg:py-4">
|
<div className="mt-auto flex flex-col gap-5 px-2 py-4 lg:gap-6 lg:px-4 lg:py-4">
|
||||||
<NavAccountCard onSignOut={handleSignOut} />
|
<NavAccountCard
|
||||||
|
items={[{
|
||||||
|
id: 'current',
|
||||||
|
name: user.name,
|
||||||
|
email: user.email,
|
||||||
|
avatar: '',
|
||||||
|
status: 'online' as const,
|
||||||
|
}]}
|
||||||
|
selectedAccountId="current"
|
||||||
|
onSignOut={handleSignOut}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { SearchLg } from "@untitledui/icons";
|
import { SearchLg } from "@untitledui/icons";
|
||||||
import { Avatar } from "@/components/base/avatar/avatar";
|
import { Avatar } from "@/components/base/avatar/avatar";
|
||||||
import { Input } from "@/components/base/input/input";
|
import { Input } from "@/components/base/input/input";
|
||||||
|
import { useAuth } from "@/providers/auth-provider";
|
||||||
|
|
||||||
interface TopBarProps {
|
interface TopBarProps {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -8,6 +9,8 @@ interface TopBarProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const TopBar = ({ title, subtitle }: TopBarProps) => {
|
export const TopBar = ({ title, subtitle }: TopBarProps) => {
|
||||||
|
const { user } = useAuth();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="flex h-16 items-center justify-between border-b border-secondary bg-primary px-6">
|
<header className="flex h-16 items-center justify-between border-b border-secondary bg-primary px-6">
|
||||||
<div className="flex flex-col justify-center">
|
<div className="flex flex-col justify-center">
|
||||||
@@ -23,7 +26,7 @@ export const TopBar = ({ title, subtitle }: TopBarProps) => {
|
|||||||
aria-label="Search"
|
aria-label="Search"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Avatar initials="SM" size="sm" />
|
<Avatar initials={user.initials} size="sm" />
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ interface LeadCardProps {
|
|||||||
onMessage: (lead: Lead) => void;
|
onMessage: (lead: Lead) => void;
|
||||||
onMarkSpam: (lead: Lead) => void;
|
onMarkSpam: (lead: Lead) => void;
|
||||||
onMerge: (lead: Lead) => void;
|
onMerge: (lead: Lead) => void;
|
||||||
|
onLogCall?: (lead: Lead) => void;
|
||||||
|
onUpdateStatus?: (lead: Lead) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sourceLabelMap: Record<string, string> = {
|
const sourceLabelMap: Record<string, string> = {
|
||||||
@@ -29,7 +31,7 @@ const sourceLabelMap: Record<string, string> = {
|
|||||||
OTHER: 'Other',
|
OTHER: 'Other',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LeadCard = ({ lead, onAssign, onMessage, onMarkSpam, onMerge }: LeadCardProps) => {
|
export const LeadCard = ({ lead, onAssign, onMessage, onMarkSpam, onMerge, onLogCall, onUpdateStatus }: LeadCardProps) => {
|
||||||
const firstName = lead.contactName?.firstName ?? '';
|
const firstName = lead.contactName?.firstName ?? '';
|
||||||
const lastName = lead.contactName?.lastName ?? '';
|
const lastName = lead.contactName?.lastName ?? '';
|
||||||
const name = `${firstName} ${lastName}`.trim() || 'Unknown';
|
const name = `${firstName} ${lastName}`.trim() || 'Unknown';
|
||||||
@@ -38,6 +40,7 @@ export const LeadCard = ({ lead, onAssign, onMessage, onMarkSpam, onMerge }: Lea
|
|||||||
const sourceLabel = lead.leadSource ? sourceLabelMap[lead.leadSource] ?? lead.leadSource : '';
|
const sourceLabel = lead.leadSource ? sourceLabelMap[lead.leadSource] ?? lead.leadSource : '';
|
||||||
const isSpam = (lead.spamScore ?? 0) >= 60;
|
const isSpam = (lead.spamScore ?? 0) >= 60;
|
||||||
const isDuplicate = lead.isDuplicate === true;
|
const isDuplicate = lead.isDuplicate === true;
|
||||||
|
const isAssigned = lead.assignedAgent !== null && lead.leadStatus !== 'NEW';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -99,6 +102,15 @@ export const LeadCard = ({ lead, onAssign, onMessage, onMarkSpam, onMerge }: Lea
|
|||||||
Merge
|
Merge
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
|
) : isAssigned ? (
|
||||||
|
<>
|
||||||
|
<Button size="sm" color="primary" onClick={() => onLogCall?.(lead)}>
|
||||||
|
Call
|
||||||
|
</Button>
|
||||||
|
<Button size="sm" color="secondary" onClick={() => onUpdateStatus?.(lead)}>
|
||||||
|
Disposition
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Button size="sm" color="primary" onClick={() => onAssign(lead)}>
|
<Button size="sm" color="primary" onClick={() => onAssign(lead)}>
|
||||||
|
|||||||
@@ -9,9 +9,10 @@ import { LeadTable } from '@/components/leads/lead-table';
|
|||||||
import { BulkActionBar } from '@/components/leads/bulk-action-bar';
|
import { BulkActionBar } from '@/components/leads/bulk-action-bar';
|
||||||
import { FilterPills } from '@/components/leads/filter-pills';
|
import { FilterPills } from '@/components/leads/filter-pills';
|
||||||
import { useLeads } from '@/hooks/use-leads';
|
import { useLeads } from '@/hooks/use-leads';
|
||||||
|
import { useAuth } from '@/providers/auth-provider';
|
||||||
import type { LeadSource, LeadStatus } from '@/types/entities';
|
import type { LeadSource, LeadStatus } from '@/types/entities';
|
||||||
|
|
||||||
type TabKey = 'new' | 'all';
|
type TabKey = 'new' | 'my-leads' | 'all';
|
||||||
|
|
||||||
type ActiveFilter = {
|
type ActiveFilter = {
|
||||||
key: string;
|
key: string;
|
||||||
@@ -22,6 +23,7 @@ type ActiveFilter = {
|
|||||||
const PAGE_SIZE = 25;
|
const PAGE_SIZE = 25;
|
||||||
|
|
||||||
export const AllLeadsPage = () => {
|
export const AllLeadsPage = () => {
|
||||||
|
const { user } = useAuth();
|
||||||
const [tab, setTab] = useState<TabKey>('new');
|
const [tab, setTab] = useState<TabKey>('new');
|
||||||
const [selectedIds, setSelectedIds] = useState<string[]>([]);
|
const [selectedIds, setSelectedIds] = useState<string[]>([]);
|
||||||
const [sortField, setSortField] = useState('createdAt');
|
const [sortField, setSortField] = useState('createdAt');
|
||||||
@@ -31,6 +33,7 @@ export const AllLeadsPage = () => {
|
|||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
|
||||||
const statusFilter: LeadStatus | undefined = tab === 'new' ? 'NEW' : undefined;
|
const statusFilter: LeadStatus | undefined = tab === 'new' ? 'NEW' : undefined;
|
||||||
|
const myLeadsOnly = tab === 'my-leads';
|
||||||
|
|
||||||
const { leads: filteredLeads, total } = useLeads({
|
const { leads: filteredLeads, total } = useLeads({
|
||||||
source: sourceFilter ?? undefined,
|
source: sourceFilter ?? undefined,
|
||||||
@@ -93,9 +96,17 @@ export const AllLeadsPage = () => {
|
|||||||
return sorted;
|
return sorted;
|
||||||
}, [filteredLeads, sortField, sortDirection]);
|
}, [filteredLeads, sortField, sortDirection]);
|
||||||
|
|
||||||
|
// Apply "My Leads" filter when on that tab
|
||||||
|
const displayLeads = useMemo(() => {
|
||||||
|
if (myLeadsOnly) {
|
||||||
|
return sortedLeads.filter((l) => l.assignedAgent === user.name);
|
||||||
|
}
|
||||||
|
return sortedLeads;
|
||||||
|
}, [sortedLeads, myLeadsOnly, user.name]);
|
||||||
|
|
||||||
// Client-side pagination
|
// Client-side pagination
|
||||||
const totalPages = Math.max(1, Math.ceil(sortedLeads.length / PAGE_SIZE));
|
const totalPages = Math.max(1, Math.ceil(displayLeads.length / PAGE_SIZE));
|
||||||
const pagedLeads = sortedLeads.slice((currentPage - 1) * PAGE_SIZE, currentPage * PAGE_SIZE);
|
const pagedLeads = displayLeads.slice((currentPage - 1) * PAGE_SIZE, currentPage * PAGE_SIZE);
|
||||||
|
|
||||||
const handleSort = (field: string) => {
|
const handleSort = (field: string) => {
|
||||||
if (field === sortField) {
|
if (field === sortField) {
|
||||||
@@ -139,8 +150,11 @@ export const AllLeadsPage = () => {
|
|||||||
setCurrentPage(1);
|
setCurrentPage(1);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const myLeadsCount = sortedLeads.filter((l) => l.assignedAgent === user.name).length;
|
||||||
|
|
||||||
const tabItems = [
|
const tabItems = [
|
||||||
{ id: 'new', label: 'New', badge: tab === 'new' ? total : undefined },
|
{ id: 'new', label: 'New', badge: tab === 'new' ? total : undefined },
|
||||||
|
{ id: 'my-leads', label: 'My Leads', badge: tab === 'my-leads' ? myLeadsCount : undefined },
|
||||||
{ id: 'all', label: 'All Leads', badge: tab === 'all' ? total : undefined },
|
{ id: 'all', label: 'All Leads', badge: tab === 'all' ? total : undefined },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,14 @@ export const LeadWorkspacePage = () => {
|
|||||||
// placeholder
|
// placeholder
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleLogCall = () => {
|
||||||
|
// placeholder
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUpdateStatus = () => {
|
||||||
|
// placeholder
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-1 flex-col">
|
<div className="flex flex-1 flex-col">
|
||||||
<TopBar title="Lead Workspace" subtitle="Ramaiah Memorial Hospital · Last 24 hours" />
|
<TopBar title="Lead Workspace" subtitle="Ramaiah Memorial Hospital · Last 24 hours" />
|
||||||
@@ -69,6 +77,8 @@ export const LeadWorkspacePage = () => {
|
|||||||
onMessage={handleMessage}
|
onMessage={handleMessage}
|
||||||
onMarkSpam={handleMarkSpam}
|
onMarkSpam={handleMarkSpam}
|
||||||
onMerge={handleMerge}
|
onMerge={handleMerge}
|
||||||
|
onLogCall={handleLogCall}
|
||||||
|
onUpdateStatus={handleUpdateStatus}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{displayLeads.length === 0 && (
|
{displayLeads.length === 0 && (
|
||||||
|
|||||||
Reference in New Issue
Block a user