diff --git a/src/pages/admin/AdminRoles.js b/src/pages/admin/AdminRoles.js index 921a060..5e071c8 100644 --- a/src/pages/admin/AdminRoles.js +++ b/src/pages/admin/AdminRoles.js @@ -350,44 +350,83 @@ const AdminRoles = () => { Select permissions for this role. You can also add permissions later.

- {Object.entries(groupedPermissions).map(([module, perms]) => ( -
- - {expandedModules[module] && ( -
- {perms.map(perm => ( -
- { - setFormData(prev => ({ - ...prev, - permissions: prev.permissions.includes(perm.code) - ? prev.permissions.filter(p => p !== perm.code) - : [...prev.permissions, perm.code] - })); - }} - /> - -
- ))} + {Object.entries(groupedPermissions).map(([module, perms]) => { + const moduleCodes = perms.map(perm => perm.code); + const selectedCount = moduleCodes.filter(code => formData.permissions.includes(code)).length; + const hasPermissions = moduleCodes.length > 0; + const isAllSelected = hasPermissions && selectedCount === moduleCodes.length; + const isNoneSelected = selectedCount === 0; + + return ( +
+
+ +
+ + +
- )} -
- ))} + {expandedModules[module] && ( +
+ {perms.map(perm => ( +
+ { + setFormData(prev => ({ + ...prev, + permissions: prev.permissions.includes(perm.code) + ? prev.permissions.filter(p => p !== perm.code) + : [...prev.permissions, perm.code] + })); + }} + /> + +
+ ))} +
+ )} +
+ ); + })}
diff --git a/src/pages/members/Bylaws.js b/src/pages/members/Bylaws.js index cce734e..25dc9c2 100644 --- a/src/pages/members/Bylaws.js +++ b/src/pages/members/Bylaws.js @@ -5,20 +5,34 @@ import MemberFooter from '../../components/MemberFooter'; import { Card } from '../../components/ui/card'; import { Button } from '../../components/ui/button'; import { Badge } from '../../components/ui/badge'; +import { Input } from '../../components/ui/input'; import { toast } from 'sonner'; -import { Scale, ExternalLink, History, Check } from 'lucide-react'; +import { Scale, ExternalLink, History, Check, Search, Calendar } from 'lucide-react'; export default function Bylaws() { const [currentBylaws, setCurrentBylaws] = useState(null); const [history, setHistory] = useState([]); + const [years, setYears] = useState([]); + const [selectedYear, setSelectedYear] = useState(null); + const [searchTerm, setSearchTerm] = useState(''); const [showHistory, setShowHistory] = useState(false); const [loading, setLoading] = useState(true); useEffect(() => { fetchCurrentBylaws(); + fetchYears(); fetchHistory(); }, []); + const fetchYears = async () => { + try { + const response = await api.get('/bylaws/years'); + setYears(response.data); + } catch (error) { + console.error('Failed to load years'); + } + }; + const fetchCurrentBylaws = async () => { try { const response = await api.get('/bylaws/current'); @@ -32,15 +46,46 @@ export default function Bylaws() { } }; - const fetchHistory = async () => { + const fetchHistory = async (year = null) => { try { - const response = await api.get('/bylaws/history'); + const url = year ? `/bylaws/history?year=${year}` : '/bylaws/history'; + const response = await api.get(url); setHistory(response.data); } catch (error) { console.error('Failed to load bylaws history'); } }; + const handleYearFilter = (year) => { + setSelectedYear(year); + fetchHistory(year); + }; + + const clearFilter = () => { + setSelectedYear(null); + fetchHistory(); + }; + + const filteredHistory = history.filter(bylaws => + bylaws.title.toLowerCase().includes(searchTerm.toLowerCase()) || + (bylaws.version && bylaws.version.toLowerCase().includes(searchTerm.toLowerCase())) + ); + + const groupByYear = (items) => { + const grouped = {}; + items.forEach(item => { + const year = new Date(item.effective_date).getFullYear(); + if (!grouped[year]) { + grouped[year] = []; + } + grouped[year].push(item); + }); + return grouped; + }; + + const groupedHistory = groupByYear(filteredHistory.filter(b => !b.is_current)); + const sortedYears = Object.keys(groupedHistory).sort((a, b) => b - a); + const formatDate = (dateString) => { return new Date(dateString).toLocaleDateString('en-US', { year: 'numeric', @@ -75,6 +120,44 @@ export default function Bylaws() {

Review the official governing bylaws and policies of the LOAF community.

+ + {/* Filters */} +
+ {/* Search */} +
+ + setSearchTerm(e.target.value)} + className="pl-10 border-[var(--neutral-800)] focus:border-brand-purple " + /> +
+ + {/* Year Filter */} +
+ + {years.map(year => ( + + ))} +
+
{/* Current Bylaws */} @@ -124,7 +207,7 @@ export default function Bylaws() { )} {/* Version History Toggle */} - {history.length > 1 && ( + {filteredHistory.filter(b => !b.is_current).length > 0 && (
)} {/* Version History */} - {showHistory && history.length > 1 && ( -
-

- Previous Versions -

- {history.filter(b => !b.is_current).map(bylaws => ( - -
-
-

- {bylaws.title} -

-
- Version {bylaws.version} - - Effective {formatDate(bylaws.effective_date)} -
-
- + {showHistory && filteredHistory.filter(b => !b.is_current).length > 0 && ( +
+ {sortedYears.map(year => ( +
+

+ + {year} +

+
+ {groupedHistory[year].map(bylaws => ( + +
+
+

+ {bylaws.title} +

+
+ Version {bylaws.version} + + Effective {formatDate(bylaws.effective_date)} +
+
+ +
+
+ ))}
- +
))}
)} diff --git a/src/pages/members/Financials.js b/src/pages/members/Financials.js index d4989d9..5953851 100644 --- a/src/pages/members/Financials.js +++ b/src/pages/members/Financials.js @@ -5,20 +5,36 @@ import MemberFooter from '../../components/MemberFooter'; import { Card } from '../../components/ui/card'; import { Button } from '../../components/ui/button'; import { Badge } from '../../components/ui/badge'; +import { Input } from '../../components/ui/input'; import { toast } from 'sonner'; -import { DollarSign, ExternalLink, TrendingUp } from 'lucide-react'; +import { DollarSign, ExternalLink, TrendingUp, Search, Calendar } from 'lucide-react'; export default function Financials() { const [reports, setReports] = useState([]); + const [years, setYears] = useState([]); + const [selectedYear, setSelectedYear] = useState(null); + const [searchTerm, setSearchTerm] = useState(''); const [loading, setLoading] = useState(true); useEffect(() => { + fetchYears(); fetchReports(); }, []); - const fetchReports = async () => { + const fetchYears = async () => { try { - const response = await api.get('/financials'); + const response = await api.get('/financials/years'); + setYears(response.data); + } catch (error) { + console.error('Failed to load years'); + } + }; + + const fetchReports = async (year = null) => { + try { + setLoading(true); + const url = year ? `/financials?year=${year}` : '/financials'; + const response = await api.get(url); setReports(response.data); } catch (error) { toast.error('Failed to load financial reports'); @@ -27,6 +43,36 @@ export default function Financials() { } }; + const handleYearFilter = (year) => { + setSelectedYear(year); + fetchReports(year); + }; + + const clearFilter = () => { + setSelectedYear(null); + fetchReports(); + }; + + const filteredReports = reports.filter(report => + report.title.toLowerCase().includes(searchTerm.toLowerCase()) || + report.year.toString().includes(searchTerm) + ); + + const groupByYear = (items) => { + const grouped = {}; + items.forEach(item => { + const year = item.year; + if (!grouped[year]) { + grouped[year] = []; + } + grouped[year].push(item); + }); + return grouped; + }; + + const groupedReports = groupByYear(filteredReports); + const sortedYears = Object.keys(groupedReports).sort((a, b) => b - a); + if (loading) { return (
@@ -53,56 +99,104 @@ export default function Financials() {

Access annual financial reports and stay informed about LOAF's fiscal responsibility.

+ + {/* Filters */} +
+ {/* Search */} +
+ + setSearchTerm(e.target.value)} + className="pl-10 border-[var(--neutral-800)] focus:border-brand-purple " + /> +
+ + {/* Year Filter */} +
+ + {years.map(year => ( + + ))} +
+
{/* Reports List */} - {reports.length === 0 ? ( + {filteredReports.length === 0 ? (

- No financial reports available yet + No financial reports found

) : ( -
- {reports.map(report => ( - -
- {/* Year Badge */} -
- -
- {report.year} -
-
Fiscal Year
-
+
+ {sortedYears.map(year => ( +
+

+ + {year} +

+
+ {groupedReports[year].map(report => ( + +
+ {/* Year Badge */} +
+ +
+ {report.year} +
+
Fiscal Year
+
- {/* Report Details */} -
-

- {report.title} -

-
- - {report.document_type === 'google_drive' ? 'Google Drive' : report.document_type.toUpperCase()} - -
- -
+ {/* Report Details */} +
+

+ {report.title} +

+
+ + {report.document_type === 'google_drive' ? 'Google Drive' : report.document_type.toUpperCase()} + +
+ +
+
+
+ ))}
- +
))}
)} {/* Transparency Note */} - {reports.length > 0 && ( + {filteredReports.length > 0 && (