Wire analysis/[cad]/page.tsx: replace SidebarPlaceholder with AnalysisSidebar (sticky, IntersectionObserver scrollspy on sections 1/2/3/3.1/3.2/3.3/4/5), header with AnalysisBreadcrumb (SiteFinder → cad → Анализ) and UserAvatar (gd_org_id from localStorage, SSR-safe). Add lucide-react to package.json (was used in A1 but missing from deps).
104 lines
2.7 KiB
TypeScript
104 lines
2.7 KiB
TypeScript
"use client";
|
||
|
||
import { useEffect, useState } from "react";
|
||
import { Building2 } from "lucide-react";
|
||
|
||
// ── Helpers ───────────────────────────────────────────────────────────────────
|
||
|
||
function getStoredOrgId(): string | null {
|
||
// Guard against SSR — localStorage not available on server
|
||
if (typeof window === "undefined") return null;
|
||
try {
|
||
return localStorage.getItem("gd_org_id");
|
||
} catch {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
function orgInitials(orgId: string): string {
|
||
// Build a 2-letter monogram from org ID string
|
||
const parts = orgId
|
||
.toUpperCase()
|
||
.replace(/[^A-ZА-Я0-9]/gu, " ")
|
||
.split(" ")
|
||
.filter(Boolean);
|
||
if (parts.length === 0) return "??";
|
||
if (parts.length === 1) return parts[0].slice(0, 2);
|
||
return parts[0][0] + parts[1][0];
|
||
}
|
||
|
||
// ── Component ─────────────────────────────────────────────────────────────────
|
||
|
||
export function UserAvatar() {
|
||
const [orgId, setOrgId] = useState<string | null>(null);
|
||
|
||
// Hydration-safe: read localStorage after mount
|
||
useEffect(() => {
|
||
setOrgId(getStoredOrgId());
|
||
}, []);
|
||
|
||
const displayLabel = orgId ?? "Demo Org";
|
||
const initials = orgId ? orgInitials(orgId) : "DO";
|
||
|
||
return (
|
||
<div
|
||
style={{
|
||
display: "flex",
|
||
alignItems: "center",
|
||
gap: 8,
|
||
flexShrink: 0,
|
||
}}
|
||
title={displayLabel}
|
||
>
|
||
{/* Avatar circle */}
|
||
<div
|
||
aria-hidden
|
||
style={{
|
||
width: 32,
|
||
height: 32,
|
||
borderRadius: "50%",
|
||
background: "var(--accent-soft)",
|
||
border: "1px solid var(--border-card)",
|
||
display: "flex",
|
||
alignItems: "center",
|
||
justifyContent: "center",
|
||
flexShrink: 0,
|
||
}}
|
||
>
|
||
{orgId ? (
|
||
<span
|
||
style={{
|
||
fontSize: 11,
|
||
fontWeight: 600,
|
||
color: "var(--accent)",
|
||
letterSpacing: "0.02em",
|
||
}}
|
||
>
|
||
{initials}
|
||
</span>
|
||
) : (
|
||
<Building2
|
||
size={14}
|
||
strokeWidth={1.5}
|
||
style={{ color: "var(--accent)" }}
|
||
/>
|
||
)}
|
||
</div>
|
||
|
||
{/* Org name — hidden on narrow viewports via maxWidth trick */}
|
||
<span
|
||
style={{
|
||
fontSize: 12,
|
||
fontWeight: 500,
|
||
color: "var(--fg-secondary)",
|
||
maxWidth: 140,
|
||
overflow: "hidden",
|
||
textOverflow: "ellipsis",
|
||
whiteSpace: "nowrap",
|
||
}}
|
||
>
|
||
{displayLabel}
|
||
</span>
|
||
</div>
|
||
);
|
||
}
|