Update Layout.tsx
This commit is contained in:
@@ -3,14 +3,17 @@ import React, { useEffect, useState } from 'react';
|
|||||||
import { NavLink, Outlet } from 'react-router-dom';
|
import { NavLink, Outlet } from 'react-router-dom';
|
||||||
import { Users, Settings, Building, LogOut, Menu, X, ChevronDown, Check, LayoutDashboard, Megaphone, Info, AlertTriangle, Hammer, Calendar, MessageSquareWarning, PieChart, Briefcase, ReceiptEuro, FileText } from 'lucide-react';
|
import { Users, Settings, Building, LogOut, Menu, X, ChevronDown, Check, LayoutDashboard, Megaphone, Info, AlertTriangle, Hammer, Calendar, MessageSquareWarning, PieChart, Briefcase, ReceiptEuro, FileText } from 'lucide-react';
|
||||||
import { CondoService } from '../services/mockDb';
|
import { CondoService } from '../services/mockDb';
|
||||||
import { Condo, Notice, AppSettings } from '../types';
|
import { Condo, Notice, AppSettings, BrandingConfig } from '../types';
|
||||||
|
|
||||||
export const Layout: React.FC = () => {
|
interface LayoutProps {
|
||||||
|
branding?: BrandingConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Layout: React.FC<LayoutProps> = ({ branding }) => {
|
||||||
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
||||||
const user = CondoService.getCurrentUser();
|
const user = CondoService.getCurrentUser();
|
||||||
|
|
||||||
// Logic: "isPrivileged" includes Admin AND PowerUser.
|
// Logic: "isPrivileged" includes Admin AND PowerUser.
|
||||||
// This allows PowerUsers to see Reports and other admin-like features.
|
|
||||||
const isPrivileged = user?.role === 'admin' || user?.role === 'poweruser';
|
const isPrivileged = user?.role === 'admin' || user?.role === 'poweruser';
|
||||||
|
|
||||||
const [condos, setCondos] = useState<Condo[]>([]);
|
const [condos, setCondos] = useState<Condo[]>([]);
|
||||||
@@ -26,7 +29,6 @@ export const Layout: React.FC = () => {
|
|||||||
const [ticketBadgeCount, setTicketBadgeCount] = useState(0);
|
const [ticketBadgeCount, setTicketBadgeCount] = useState(0);
|
||||||
|
|
||||||
const fetchContext = async () => {
|
const fetchContext = async () => {
|
||||||
// Fetch global settings to check features
|
|
||||||
try {
|
try {
|
||||||
const globalSettings = await CondoService.getSettings();
|
const globalSettings = await CondoService.getSettings();
|
||||||
setSettings(globalSettings);
|
setSettings(globalSettings);
|
||||||
@@ -49,7 +51,7 @@ export const Layout: React.FC = () => {
|
|||||||
|
|
||||||
// 1. Tickets Badge Logic
|
// 1. Tickets Badge Logic
|
||||||
try {
|
try {
|
||||||
if (settings?.features.tickets || true) { // Check features if available or default
|
if (settings?.features.tickets || true) {
|
||||||
const tickets = await CondoService.getTickets();
|
const tickets = await CondoService.getTickets();
|
||||||
let count = 0;
|
let count = 0;
|
||||||
|
|
||||||
@@ -59,7 +61,6 @@ export const Layout: React.FC = () => {
|
|||||||
const isArchived = t.status === 'RESOLVED' || t.status === 'CLOSED';
|
const isArchived = t.status === 'RESOLVED' || t.status === 'CLOSED';
|
||||||
|
|
||||||
if (isPrivileged) {
|
if (isPrivileged) {
|
||||||
// Admin/PowerUser: Count new unarchived tickets OR tickets with new comments from users
|
|
||||||
if (isTicketNew && !isArchived) {
|
if (isTicketNew && !isArchived) {
|
||||||
count++;
|
count++;
|
||||||
} else {
|
} else {
|
||||||
@@ -71,7 +72,6 @@ export const Layout: React.FC = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// User: Count tickets with new comments from Admin (or others)
|
|
||||||
const updatedDate = new Date(t.updatedAt).getTime();
|
const updatedDate = new Date(t.updatedAt).getTime();
|
||||||
if (updatedDate > lastViewedTickets) {
|
if (updatedDate > lastViewedTickets) {
|
||||||
const comments = await CondoService.getTicketComments(t.id);
|
const comments = await CondoService.getTicketComments(t.id);
|
||||||
@@ -85,21 +85,17 @@ export const Layout: React.FC = () => {
|
|||||||
} catch(e) { console.error("Error calc ticket badges", e); }
|
} catch(e) { console.error("Error calc ticket badges", e); }
|
||||||
|
|
||||||
|
|
||||||
// Check for notices & expenses for User (non-privileged mostly, but logic works for all if needed)
|
|
||||||
if (!isPrivileged && active && user) {
|
if (!isPrivileged && active && user) {
|
||||||
try {
|
try {
|
||||||
// 2. Check Notices
|
|
||||||
const unread = await CondoService.getUnreadNoticesForUser(user.id, active.id);
|
const unread = await CondoService.getUnreadNoticesForUser(user.id, active.id);
|
||||||
if (unread.length > 0) {
|
if (unread.length > 0) {
|
||||||
setActiveNotice(unread[0]);
|
setActiveNotice(unread[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Check New Extraordinary Expenses
|
|
||||||
const myExpenses = await CondoService.getMyExpenses();
|
const myExpenses = await CondoService.getMyExpenses();
|
||||||
const lastViewed = localStorage.getItem('lastViewedExpensesTime');
|
const lastViewed = localStorage.getItem('lastViewedExpensesTime');
|
||||||
const lastViewedTime = lastViewed ? parseInt(lastViewed) : 0;
|
const lastViewedTime = lastViewed ? parseInt(lastViewed) : 0;
|
||||||
|
|
||||||
// Count expenses created AFTER the last visit
|
|
||||||
const count = myExpenses.filter((e: any) => new Date(e.createdAt).getTime() > lastViewedTime).length;
|
const count = myExpenses.filter((e: any) => new Date(e.createdAt).getTime() > lastViewedTime).length;
|
||||||
setNewExpensesCount(count);
|
setNewExpensesCount(count);
|
||||||
|
|
||||||
@@ -109,12 +105,10 @@ export const Layout: React.FC = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchContext();
|
fetchContext();
|
||||||
|
|
||||||
// Listen for updates from Settings or Expense views
|
|
||||||
const handleUpdate = () => fetchContext();
|
const handleUpdate = () => fetchContext();
|
||||||
window.addEventListener('condo-updated', handleUpdate);
|
window.addEventListener('condo-updated', handleUpdate);
|
||||||
window.addEventListener('expenses-viewed', handleUpdate);
|
window.addEventListener('expenses-viewed', handleUpdate);
|
||||||
window.addEventListener('tickets-viewed', handleUpdate); // Listen for ticket view
|
window.addEventListener('tickets-viewed', handleUpdate);
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener('condo-updated', handleUpdate);
|
window.removeEventListener('condo-updated', handleUpdate);
|
||||||
window.removeEventListener('expenses-viewed', handleUpdate);
|
window.removeEventListener('expenses-viewed', handleUpdate);
|
||||||
@@ -134,8 +128,6 @@ export const Layout: React.FC = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const closeNoticeModal = () => setActiveNotice(null);
|
|
||||||
|
|
||||||
const navClass = ({ isActive }: { isActive: boolean }) =>
|
const navClass = ({ isActive }: { isActive: boolean }) =>
|
||||||
`flex items-center gap-3 px-4 py-3 rounded-lg transition-all duration-200 ${
|
`flex items-center gap-3 px-4 py-3 rounded-lg transition-all duration-200 ${
|
||||||
isActive
|
isActive
|
||||||
@@ -154,9 +146,12 @@ export const Layout: React.FC = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check if notices are actually enabled before showing modal
|
|
||||||
const showNotice = activeNotice && settings?.features.notices;
|
const showNotice = activeNotice && settings?.features.notices;
|
||||||
|
|
||||||
|
// Use props passed from App.tsx directly
|
||||||
|
const appName = branding?.appName || 'CondoPay';
|
||||||
|
const logoUrl = branding?.logoUrl;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-screen bg-slate-50 overflow-hidden">
|
<div className="flex h-screen bg-slate-50 overflow-hidden">
|
||||||
|
|
||||||
@@ -195,11 +190,15 @@ export const Layout: React.FC = () => {
|
|||||||
{/* Mobile Header */}
|
{/* Mobile Header */}
|
||||||
<div className="lg:hidden fixed top-0 left-0 right-0 h-16 bg-white border-b border-slate-200 flex items-center justify-between px-4 z-40 shadow-sm">
|
<div className="lg:hidden fixed top-0 left-0 right-0 h-16 bg-white border-b border-slate-200 flex items-center justify-between px-4 z-40 shadow-sm">
|
||||||
<div className="flex items-center gap-2 overflow-hidden">
|
<div className="flex items-center gap-2 overflow-hidden">
|
||||||
|
{logoUrl ? (
|
||||||
|
<img src={logoUrl} alt="Logo" className="w-8 h-8 object-contain rounded-lg" />
|
||||||
|
) : (
|
||||||
<div className="bg-blue-600 p-1.5 rounded-lg flex-shrink-0">
|
<div className="bg-blue-600 p-1.5 rounded-lg flex-shrink-0">
|
||||||
<Building className="text-white w-5 h-5" />
|
<Building className="text-white w-5 h-5" />
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
<div className="flex flex-col min-w-0">
|
<div className="flex flex-col min-w-0">
|
||||||
<h1 className="font-bold text-slate-800 leading-tight truncate">CondoPay</h1>
|
<h1 className="font-bold text-slate-800 leading-tight truncate">{appName}</h1>
|
||||||
{activeCondo && <p className="text-xs text-slate-500 truncate">{activeCondo.name}</p>}
|
{activeCondo && <p className="text-xs text-slate-500 truncate">{activeCondo.name}</p>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -221,10 +220,14 @@ export const Layout: React.FC = () => {
|
|||||||
{/* Desktop Logo & Condo Switcher */}
|
{/* Desktop Logo & Condo Switcher */}
|
||||||
<div className="p-6 hidden lg:flex flex-col gap-4 border-b border-slate-100">
|
<div className="p-6 hidden lg:flex flex-col gap-4 border-b border-slate-100">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
|
{logoUrl ? (
|
||||||
|
<img src={logoUrl} alt="Logo" className="w-10 h-10 object-contain rounded-xl" />
|
||||||
|
) : (
|
||||||
<div className="bg-blue-600 p-2 rounded-lg">
|
<div className="bg-blue-600 p-2 rounded-lg">
|
||||||
<Building className="text-white w-6 h-6" />
|
<Building className="text-white w-6 h-6" />
|
||||||
</div>
|
</div>
|
||||||
<h1 className="font-bold text-xl text-slate-800 tracking-tight">CondoPay</h1>
|
)}
|
||||||
|
<h1 className="font-bold text-xl text-slate-800 tracking-tight">{appName}</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Condo Switcher (Privileged Only & MultiCondo Enabled) */}
|
{/* Condo Switcher (Privileged Only & MultiCondo Enabled) */}
|
||||||
|
|||||||
Reference in New Issue
Block a user