feat(client): vendor digital employee source
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
import { createContext, useContext, useState, useCallback, type ReactNode } from 'react';
|
||||
|
||||
export type ConsoleRole = 'finance' | 'engineering' | 'executive' | 'hr' | 'sales';
|
||||
export type MotionLevel = 'none' | 'minimal';
|
||||
export type Density = 'compact' | 'balanced' | 'presentation';
|
||||
export type Tone = 'strict' | 'technical' | 'executive' | 'warm-professional';
|
||||
|
||||
export interface RoleProfile {
|
||||
id: ConsoleRole;
|
||||
labelKey: string;
|
||||
density: Density;
|
||||
tone: Tone;
|
||||
contentPriority: string[];
|
||||
motion: {
|
||||
defaultLevel: MotionLevel;
|
||||
allowParticles: false;
|
||||
allowCelebrations: false;
|
||||
allowDecorativeBadges: false;
|
||||
};
|
||||
actionPolicy: {
|
||||
requireConfirmationForDestructive: boolean;
|
||||
showReversalHint: boolean;
|
||||
preferOneClickApproval: boolean;
|
||||
};
|
||||
staleDataThresholdMs: number;
|
||||
cssVars: Record<string, string>;
|
||||
}
|
||||
|
||||
export const ROLE_PROFILES: Record<ConsoleRole, RoleProfile> = {
|
||||
finance: {
|
||||
id: 'finance',
|
||||
labelKey: 'console.role.finance',
|
||||
density: 'compact',
|
||||
tone: 'strict',
|
||||
contentPriority: ['audit', 'sources', 'exactNumbers'],
|
||||
motion: { defaultLevel: 'none', allowParticles: false, allowCelebrations: false, allowDecorativeBadges: false },
|
||||
actionPolicy: { requireConfirmationForDestructive: true, showReversalHint: true, preferOneClickApproval: false },
|
||||
staleDataThresholdMs: 30000,
|
||||
cssVars: {
|
||||
'--role-accent': '#2563eb',
|
||||
'--role-accent-soft': 'rgba(37,99,235,0.12)',
|
||||
'--role-surface-tint': 'rgba(248,250,252,0.78)',
|
||||
'--role-border': 'rgba(37,99,235,0.22)',
|
||||
'--role-focus-ring': '#1d4ed8',
|
||||
},
|
||||
},
|
||||
engineering: {
|
||||
id: 'engineering',
|
||||
labelKey: 'console.role.engineering',
|
||||
density: 'compact',
|
||||
tone: 'technical',
|
||||
contentPriority: ['alarms', 'timestamps', 'reliability'],
|
||||
motion: { defaultLevel: 'none', allowParticles: false, allowCelebrations: false, allowDecorativeBadges: false },
|
||||
actionPolicy: { requireConfirmationForDestructive: true, showReversalHint: true, preferOneClickApproval: false },
|
||||
staleDataThresholdMs: 15000,
|
||||
cssVars: {
|
||||
'--role-accent': '#0f766e',
|
||||
'--role-accent-soft': 'rgba(15,118,110,0.12)',
|
||||
'--role-surface-tint': 'rgba(240,253,250,0.72)',
|
||||
'--role-border': 'rgba(15,118,110,0.24)',
|
||||
'--role-focus-ring': '#0f766e',
|
||||
},
|
||||
},
|
||||
executive: {
|
||||
id: 'executive',
|
||||
labelKey: 'console.role.executive',
|
||||
density: 'presentation',
|
||||
tone: 'executive',
|
||||
contentPriority: ['kpi', 'approvals', 'exactNumbers'],
|
||||
motion: { defaultLevel: 'minimal', allowParticles: false, allowCelebrations: false, allowDecorativeBadges: false },
|
||||
actionPolicy: { requireConfirmationForDestructive: true, showReversalHint: true, preferOneClickApproval: true },
|
||||
staleDataThresholdMs: 60000,
|
||||
cssVars: {
|
||||
'--role-accent': '#334155',
|
||||
'--role-accent-soft': 'rgba(51,65,85,0.10)',
|
||||
'--role-surface-tint': 'rgba(248,250,252,0.82)',
|
||||
'--role-border': 'rgba(51,65,85,0.20)',
|
||||
'--role-focus-ring': '#475569',
|
||||
},
|
||||
},
|
||||
hr: {
|
||||
id: 'hr',
|
||||
labelKey: 'console.role.hr',
|
||||
density: 'balanced',
|
||||
tone: 'warm-professional',
|
||||
contentPriority: ['queues', 'timestamps', 'approvals'],
|
||||
motion: { defaultLevel: 'minimal', allowParticles: false, allowCelebrations: false, allowDecorativeBadges: false },
|
||||
actionPolicy: { requireConfirmationForDestructive: true, showReversalHint: true, preferOneClickApproval: false },
|
||||
staleDataThresholdMs: 45000,
|
||||
cssVars: {
|
||||
'--role-accent': '#7c3aed',
|
||||
'--role-accent-soft': 'rgba(124,58,237,0.10)',
|
||||
'--role-surface-tint': 'rgba(250,245,255,0.72)',
|
||||
'--role-border': 'rgba(124,58,237,0.20)',
|
||||
'--role-focus-ring': '#6d28d9',
|
||||
},
|
||||
},
|
||||
sales: {
|
||||
id: 'sales',
|
||||
labelKey: 'console.role.sales',
|
||||
density: 'balanced',
|
||||
tone: 'warm-professional',
|
||||
contentPriority: ['pipeline', 'sources', 'exactNumbers'],
|
||||
motion: { defaultLevel: 'minimal', allowParticles: false, allowCelebrations: false, allowDecorativeBadges: false },
|
||||
actionPolicy: { requireConfirmationForDestructive: true, showReversalHint: true, preferOneClickApproval: false },
|
||||
staleDataThresholdMs: 45000,
|
||||
cssVars: {
|
||||
'--role-accent': '#b45309',
|
||||
'--role-accent-soft': 'rgba(180,83,9,0.10)',
|
||||
'--role-surface-tint': 'rgba(255,251,235,0.70)',
|
||||
'--role-border': 'rgba(180,83,9,0.20)',
|
||||
'--role-focus-ring': '#92400e',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
interface RoleContextValue {
|
||||
role: ConsoleRole;
|
||||
profile: RoleProfile;
|
||||
setRole: (role: ConsoleRole) => void;
|
||||
}
|
||||
|
||||
const RoleContext = createContext<RoleContextValue | null>(null);
|
||||
|
||||
const STORAGE_KEY = 'sgrobot-console-role';
|
||||
|
||||
function getStoredRole(): ConsoleRole {
|
||||
try {
|
||||
const stored = localStorage.getItem(STORAGE_KEY);
|
||||
if (stored && stored in ROLE_PROFILES) return stored as ConsoleRole;
|
||||
} catch { /* localStorage blocked */ }
|
||||
return 'sales';
|
||||
}
|
||||
|
||||
export function RoleProvider({ children }: { children: ReactNode }) {
|
||||
const [role, setRoleState] = useState<ConsoleRole>(getStoredRole);
|
||||
|
||||
const setRole = useCallback((newRole: ConsoleRole) => {
|
||||
setRoleState(newRole);
|
||||
try { localStorage.setItem(STORAGE_KEY, newRole); } catch { /* noop */ }
|
||||
}, []);
|
||||
|
||||
const profile = ROLE_PROFILES[role];
|
||||
|
||||
return (
|
||||
<RoleContext.Provider value={{ role, profile, setRole }}>
|
||||
<div style={profile.cssVars as React.CSSProperties} data-role={role}>
|
||||
{children}
|
||||
</div>
|
||||
</RoleContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useRole(): RoleContextValue {
|
||||
const ctx = useContext(RoleContext);
|
||||
if (!ctx) throw new Error('useRole must be used within RoleProvider');
|
||||
return ctx;
|
||||
}
|
||||
Reference in New Issue
Block a user