feat(ui): reconcile backup UI changes with current routing and interaction features
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { Paperclip, Plus } from "lucide-react";
|
||||
import { useQueries } from "@tanstack/react-query";
|
||||
import {
|
||||
DndContext,
|
||||
closestCenter,
|
||||
@@ -18,6 +19,9 @@ import { CSS } from "@dnd-kit/utilities";
|
||||
import { useCompany } from "../context/CompanyContext";
|
||||
import { useDialog } from "../context/DialogContext";
|
||||
import { cn } from "../lib/utils";
|
||||
import { queryKeys } from "../lib/queryKeys";
|
||||
import { sidebarBadgesApi } from "../api/sidebarBadges";
|
||||
import { heartbeatsApi } from "../api/heartbeats";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
@@ -65,10 +69,14 @@ function sortByStoredOrder(companies: Company[]): Company[] {
|
||||
function SortableCompanyItem({
|
||||
company,
|
||||
isSelected,
|
||||
hasLiveAgents,
|
||||
hasUnreadInbox,
|
||||
onSelect,
|
||||
}: {
|
||||
company: Company;
|
||||
isSelected: boolean;
|
||||
hasLiveAgents: boolean;
|
||||
hasUnreadInbox: boolean;
|
||||
onSelect: () => void;
|
||||
}) {
|
||||
const {
|
||||
@@ -88,28 +96,28 @@ function SortableCompanyItem({
|
||||
};
|
||||
|
||||
return (
|
||||
<div ref={setNodeRef} style={style} {...attributes} {...listeners}>
|
||||
<div ref={setNodeRef} style={style} {...attributes} {...listeners} className="overflow-visible">
|
||||
<Tooltip delayDuration={300}>
|
||||
<TooltipTrigger asChild>
|
||||
<a
|
||||
href={`/dashboard?company=${company.id}`}
|
||||
href={`/${company.issuePrefix}/dashboard`}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
onSelect();
|
||||
}}
|
||||
className="relative flex items-center justify-center group"
|
||||
className="relative flex items-center justify-center group overflow-visible"
|
||||
>
|
||||
{/* Selection indicator pill */}
|
||||
<div
|
||||
className={cn(
|
||||
"absolute left-[-14px] w-1 rounded-r-full bg-foreground transition-all duration-200",
|
||||
"absolute left-[-14px] w-1 rounded-r-full bg-foreground transition-[height] duration-150",
|
||||
isSelected
|
||||
? "h-5"
|
||||
: "h-0 group-hover:h-2"
|
||||
)}
|
||||
/>
|
||||
<div
|
||||
className={cn("transition-all duration-200", isDragging && "scale-105")}
|
||||
className={cn("relative overflow-visible transition-transform duration-150", isDragging && "scale-105")}
|
||||
>
|
||||
<CompanyPatternIcon
|
||||
companyName={company.name}
|
||||
@@ -121,6 +129,17 @@ function SortableCompanyItem({
|
||||
isDragging && "shadow-lg",
|
||||
)}
|
||||
/>
|
||||
{hasLiveAgents && (
|
||||
<span className="pointer-events-none absolute -right-0.5 -top-0.5 z-10">
|
||||
<span className="relative flex h-2.5 w-2.5">
|
||||
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-blue-400 opacity-80" />
|
||||
<span className="relative inline-flex h-2.5 w-2.5 rounded-full bg-blue-500 ring-2 ring-background" />
|
||||
</span>
|
||||
</span>
|
||||
)}
|
||||
{hasUnreadInbox && (
|
||||
<span className="pointer-events-none absolute -bottom-0.5 -right-0.5 z-10 h-2.5 w-2.5 rounded-full bg-red-500 ring-2 ring-background" />
|
||||
)}
|
||||
</div>
|
||||
</a>
|
||||
</TooltipTrigger>
|
||||
@@ -139,6 +158,36 @@ export function CompanyRail() {
|
||||
() => companies.filter((company) => company.status !== "archived"),
|
||||
[companies],
|
||||
);
|
||||
const companyIds = useMemo(() => sidebarCompanies.map((company) => company.id), [sidebarCompanies]);
|
||||
|
||||
const liveRunsQueries = useQueries({
|
||||
queries: companyIds.map((companyId) => ({
|
||||
queryKey: queryKeys.liveRuns(companyId),
|
||||
queryFn: () => heartbeatsApi.liveRunsForCompany(companyId),
|
||||
refetchInterval: 10_000,
|
||||
})),
|
||||
});
|
||||
const sidebarBadgeQueries = useQueries({
|
||||
queries: companyIds.map((companyId) => ({
|
||||
queryKey: queryKeys.sidebarBadges(companyId),
|
||||
queryFn: () => sidebarBadgesApi.get(companyId),
|
||||
refetchInterval: 15_000,
|
||||
})),
|
||||
});
|
||||
const hasLiveAgentsByCompanyId = useMemo(() => {
|
||||
const result = new Map<string, boolean>();
|
||||
companyIds.forEach((companyId, index) => {
|
||||
result.set(companyId, (liveRunsQueries[index]?.data?.length ?? 0) > 0);
|
||||
});
|
||||
return result;
|
||||
}, [companyIds, liveRunsQueries]);
|
||||
const hasUnreadInboxByCompanyId = useMemo(() => {
|
||||
const result = new Map<string, boolean>();
|
||||
companyIds.forEach((companyId, index) => {
|
||||
result.set(companyId, (sidebarBadgeQueries[index]?.data?.inbox ?? 0) > 0);
|
||||
});
|
||||
return result;
|
||||
}, [companyIds, sidebarBadgeQueries]);
|
||||
|
||||
// Maintain sorted order in local state, synced from companies + localStorage
|
||||
const [orderedIds, setOrderedIds] = useState<string[]>(() =>
|
||||
@@ -219,7 +268,7 @@ export function CompanyRail() {
|
||||
</div>
|
||||
|
||||
{/* Company list */}
|
||||
<div className="flex-1 flex flex-col items-center gap-2 py-2 overflow-y-auto scrollbar-none">
|
||||
<div className="flex-1 flex flex-col items-center gap-2 py-3 w-full overflow-y-auto overflow-x-hidden scrollbar-none">
|
||||
<DndContext
|
||||
sensors={sensors}
|
||||
collisionDetection={closestCenter}
|
||||
@@ -234,6 +283,8 @@ export function CompanyRail() {
|
||||
key={company.id}
|
||||
company={company}
|
||||
isSelected={company.id === selectedCompanyId}
|
||||
hasLiveAgents={hasLiveAgentsByCompanyId.get(company.id) ?? false}
|
||||
hasUnreadInbox={hasUnreadInboxByCompanyId.get(company.id) ?? false}
|
||||
onSelect={() => setSelectedCompanyId(company.id)}
|
||||
/>
|
||||
))}
|
||||
@@ -250,7 +301,8 @@ export function CompanyRail() {
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
onClick={() => openOnboarding()}
|
||||
className="flex items-center justify-center w-11 h-11 rounded-[22px] hover:rounded-[14px] border-2 border-dashed border-border text-muted-foreground hover:border-foreground/30 hover:text-foreground transition-all duration-200"
|
||||
className="flex items-center justify-center w-11 h-11 rounded-[22px] hover:rounded-[14px] border-2 border-dashed border-border text-muted-foreground hover:border-foreground/30 hover:text-foreground transition-[border-color,color,border-radius] duration-150"
|
||||
aria-label="Add company"
|
||||
>
|
||||
<Plus className="h-5 w-5" />
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user