Fix file sharing load speed and move error; misc updates

- Remove recursive directory size calculations (single Seafile API call per list)
- Remove 'Used in this location' usage display
- Fix move using v2 per-type endpoints instead of broken batch endpoint
- Send entry type from frontend for correct move routing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Krao Hasanee
2026-05-13 14:20:38 -04:00
parent c9e7816e28
commit eee0885811
117 changed files with 17592 additions and 4057 deletions
+90 -27
View File
@@ -3,15 +3,35 @@ import { NavLink, useNavigate, useLocation } from 'react-router-dom';
import { useAuth } from '../context/AuthContext';
function TeamNav({ onNav }) {
const primaryLinks = [
{ to: '/dashboard', label: 'Dashboard' },
{ to: '/requests', label: 'Requests' },
{ to: '/file-sharing', label: 'File Sharing' },
{ to: '/companies', label: 'Clients & Users' },
];
const utilityLinks = [
{ to: '/meeting-notes', label: 'Meeting Notes' },
{ to: '/invoices', label: 'Invoices & Expenses' },
{ to: '/survey-maker', label: 'Survey Maker' },
{ to: '/brand-book', label: 'Brand Book Maker' },
{ to: '/converters', label: 'Image Converter' },
{ to: '/fourge-passwords', label: 'Fourge Passwords' },
{ to: '/server-status', label: 'Server Status' },
];
return (
<div className="sidebar-section">
{[
{ to: '/dashboard', label: 'Dashboard' },
{ to: '/requests', label: 'Requests Inbox' },
{ to: '/brand-book', label: 'Brand Book (beta)' },
{ to: '/invoices', label: 'Invoices' },
{ to: '/companies', label: 'Clients & Users' },
].map(({ to, label }) => (
{primaryLinks.map(({ to, label }) => (
<NavLink key={to} to={to} onClick={onNav} className={({ isActive }) => `sidebar-link${isActive ? ' active' : ''}`}>
{label}
</NavLink>
))}
<div style={{ height: 1, margin: '10px 12px', background: 'var(--border)' }} />
<div style={{ padding: '0 12px 8px', fontSize: 11, fontWeight: 700, letterSpacing: 0.8, textTransform: 'uppercase', color: 'var(--text-muted)' }}>
Team Tools
</div>
{utilityLinks.map(({ to, label }) => (
<NavLink key={to} to={to} onClick={onNav} className={({ isActive }) => `sidebar-link${isActive ? ' active' : ''}`}>
{label}
</NavLink>
@@ -26,6 +46,8 @@ function ClientNav({ onNav }) {
{[
{ to: '/my-dashboard', label: 'Dashboard' },
{ to: '/my-projects', label: 'Projects' },
{ to: '/my-requests', label: 'Requests' },
{ to: '/file-sharing', label: 'File Sharing' },
{ to: '/my-invoices', label: 'Invoices' },
{ to: '/my-company', label: 'Company' },
].map(({ to, label }) => (
@@ -38,11 +60,33 @@ function ClientNav({ onNav }) {
}
function ExternalNav({ onNav }) {
const links = [
{ to: '/dashboard', label: 'Dashboard' },
{ to: '/assigned-requests', label: 'Requests' },
{ to: '/my-purchase-orders', label: 'Purchase Orders' },
{ to: '/file-sharing', label: 'File Sharing' },
{ to: '/survey-maker', label: 'Survey Maker' },
{ to: '/brand-book', label: 'Brand Book Maker' },
{ to: '/converters', label: 'Image Converter' },
];
return (
<div className="sidebar-section">
<NavLink to="/dashboard" onClick={onNav} className={({ isActive }) => `sidebar-link${isActive ? ' active' : ''}`}>
Dashboard
</NavLink>
{links.map(({ to, label }, index) => (
<div key={to}>
{index === 2 && (
<>
<div style={{ height: 1, margin: '10px 12px', background: 'var(--border)' }} />
<div style={{ padding: '0 12px 8px', fontSize: 11, fontWeight: 700, letterSpacing: 0.8, textTransform: 'uppercase', color: 'var(--text-muted)' }}>
Team Tools
</div>
</>
)}
<NavLink to={to} onClick={onNav} className={({ isActive }) => `sidebar-link${isActive ? ' active' : ''}`}>
{label}
</NavLink>
</div>
))}
</div>
);
}
@@ -53,36 +97,58 @@ export default function Layout({ children }) {
const location = useLocation();
const [theme, setTheme] = useState(() => localStorage.getItem('theme') || 'dark');
const [menuOpen, setMenuOpen] = useState(false);
const [sidebarCollapsed, setSidebarCollapsed] = useState(() => localStorage.getItem('sidebarCollapsed') === 'true');
useEffect(() => {
document.documentElement.setAttribute('data-theme', theme);
localStorage.setItem('theme', theme);
}, [theme]);
// Close menu on route change
useEffect(() => { setMenuOpen(false); }, [location.pathname]);
useEffect(() => {
localStorage.setItem('sidebarCollapsed', String(sidebarCollapsed));
}, [sidebarCollapsed]);
// Close menu on route change (derived-state pattern, no effect needed)
const [lastPathname, setLastPathname] = useState(location.pathname);
if (lastPathname !== location.pathname) {
setLastPathname(location.pathname);
setMenuOpen(false);
}
const toggleTheme = () => setTheme(t => t === 'dark' ? 'light' : 'dark');
const handleLogout = () => { logout(); navigate('/'); };
const handleLogout = async () => {
await logout();
navigate('/');
};
const initials = currentUser?.name
?.split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2);
return (
<div className="app-layout">
<div className={`app-layout${sidebarCollapsed ? ' sidebar-collapsed' : ''}`}>
{/* Overlay */}
{menuOpen && <div className="sidebar-overlay" onClick={() => setMenuOpen(false)} />}
<aside className={`sidebar${menuOpen ? ' sidebar-open' : ''}`}>
<div className="sidebar-logo">
<img src="/fourge-logo.png" alt="Fourge Branding" style={{ width: 140, display: 'block' }} />
<img className="brand-logo brand-logo-sidebar" src="/fourge-logo.png" alt="Fourge Branding" />
<button
className="sidebar-pin-toggle"
onClick={() => setSidebarCollapsed(current => !current)}
title={sidebarCollapsed ? 'Pin sidebar open' : 'Collapse sidebar'}
aria-label={sidebarCollapsed ? 'Pin sidebar open' : 'Collapse sidebar'}
>
{sidebarCollapsed ? '' : ''}
</button>
</div>
{currentUser?.role === 'team'
? <TeamNav onNav={() => setMenuOpen(false)} />
: currentUser?.role === 'external'
? <ExternalNav onNav={() => setMenuOpen(false)} />
: <ClientNav onNav={() => setMenuOpen(false)} />}
{!sidebarCollapsed && (
currentUser?.role === 'team'
? <TeamNav onNav={() => setMenuOpen(false)} />
: currentUser?.role === 'external'
? <ExternalNav onNav={() => setMenuOpen(false)} />
: <ClientNav onNav={() => setMenuOpen(false)} />
)}
<div className="sidebar-bottom">
<NavLink to="/settings" onClick={() => setMenuOpen(false)} className={({ isActive }) => `sidebar-link${isActive ? ' active' : ''}`}>
@@ -92,16 +158,12 @@ export default function Layout({ children }) {
<div className="sidebar-user-role">{currentUser?.role === 'external' ? 'Team' : currentUser?.role}</div>
</div>
</NavLink>
<div style={{ display: 'flex', alignItems: 'center', padding: '0 12px', gap: 8 }}>
<button className="sidebar-link" style={{ flex: 1 }} onClick={handleLogout}>Sign Out</button>
<div className="sidebar-bottom-actions">
{!sidebarCollapsed && <button className="sidebar-link" style={{ flex: 1 }} onClick={handleLogout}>Sign Out</button>}
<button
onClick={toggleTheme}
className="sidebar-theme-toggle"
title={theme === 'dark' ? 'Switch to light mode' : 'Switch to dark mode'}
style={{
background: 'transparent', border: '1px solid #333', borderRadius: '6px',
padding: '7px 10px', cursor: 'pointer', color: '#888',
fontSize: 13, lineHeight: 1, transition: 'all 0.15s', flexShrink: 0,
}}
>
{theme === 'dark' ? '☀' : '☾'}
</button>
@@ -115,6 +177,7 @@ export default function Layout({ children }) {
<button className="hamburger" onClick={() => setMenuOpen(o => !o)} aria-label="Menu">
<span /><span /><span />
</button>
<img className="brand-logo brand-logo-mobile" src="/fourge-logo.png" alt="Fourge Branding" />
</div>
<main className="main-content">
{children}
+8
View File
@@ -0,0 +1,8 @@
export default function LoadingButton({ loading = false, disabled = false, children, loadingText, className = 'btn', type = 'button', ...props }) {
return (
<button type={type} className={className} disabled={disabled || loading} {...props}>
{loading && <span className="btn-spinner" aria-hidden="true" />}
<span>{loading ? (loadingText || children) : children}</span>
</button>
);
}
+15
View File
@@ -0,0 +1,15 @@
export default function PageLoader({ opacity = 0.6 }) {
return (
<div
style={{
minHeight: '100vh',
background: 'var(--bg, #111)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<img src="/fourge-logo.png" alt="Loading" style={{ width: 160, opacity }} />
</div>
);
}