diff --git a/src/components/owner/CommissionDistributionPanel.jsx b/src/components/owner/CommissionDistributionPanel.jsx index 7a6441e..6de5f51 100644 --- a/src/components/owner/CommissionDistributionPanel.jsx +++ b/src/components/owner/CommissionDistributionPanel.jsx @@ -1,5 +1,5 @@ -import React, { useMemo } from 'react'; -import { DollarSign, Users } from 'lucide-react'; +import React, { useMemo, useState, useRef, useEffect } from 'react'; +import { DollarSign, Users, Search, ChevronDown, X, Check, FilterX } from 'lucide-react'; const COMMISSION_TYPES = { percent_gross: '% of Gross', @@ -90,21 +90,241 @@ const CommissionDistributionPanel = ({ return entries; }, [ownerProjects, userCommissionOverrides, roleCommissionOverrides, orgMembers, orgCommissionDefaults]); - const total = useMemo(() => rows.reduce((s, r) => s + r.amount, 0), [rows]); + // ── Filter state ─────────────────────────────────────────────── + const [nameQuery, setNameQuery] = useState(''); + const [roleFilter, setRoleFilter] = useState('all'); + const [projectFilter, setProjectFilter] = useState('all'); + const [typeFilter, setTypeFilter] = useState('all'); + + // Searchable project dropdown + const [projectOpen, setProjectOpen] = useState(false); + const [projectQuery, setProjectQuery] = useState(''); + const projectBoxRef = useRef(null); + + useEffect(() => { + if (!projectOpen) return; + const onClick = (e) => { + if (projectBoxRef.current && !projectBoxRef.current.contains(e.target)) { + setProjectOpen(false); + setProjectQuery(''); + } + }; + document.addEventListener('mousedown', onClick); + return () => document.removeEventListener('mousedown', onClick); + }, [projectOpen]); + + // Derive available roles, projects, and commission types from current data + const roleOptions = useMemo(() => { + const set = new Set(rows.map(r => r.role).filter(Boolean)); + return Array.from(set).sort(); + }, [rows]); + + const projectOptions = useMemo(() => { + const map = new Map(); + for (const r of rows) { + if (!map.has(r.projectId)) { + map.set(r.projectId, { id: r.projectId, label: r.projectLabel }); + } + } + return Array.from(map.values()); + }, [rows]); + + const typeOptions = useMemo(() => { + const set = new Set(rows.map(r => r.commissionType).filter(Boolean)); + return Array.from(set).sort(); + }, [rows]); + + const filteredProjectOptions = useMemo(() => { + const q = projectQuery.trim().toLowerCase(); + if (!q) return projectOptions; + return projectOptions.filter(p => + (p.label || '').toLowerCase().includes(q) || + (p.id || '').toLowerCase().includes(q) + ); + }, [projectOptions, projectQuery]); + + const selectedProjectLabel = useMemo(() => { + if (projectFilter === 'all') return 'All Projects'; + const p = projectOptions.find(p => p.id === projectFilter); + return p ? `${p.label} — ${p.id}` : 'All Projects'; + }, [projectFilter, projectOptions]); + + const filteredRows = useMemo(() => { + const q = nameQuery.trim().toLowerCase(); + return rows.filter(r => { + if (q && !(r.name || '').toLowerCase().includes(q)) return false; + if (roleFilter !== 'all' && r.role !== roleFilter) return false; + if (projectFilter !== 'all' && r.projectId !== projectFilter) return false; + if (typeFilter !== 'all' && r.commissionType !== typeFilter) return false; + return true; + }); + }, [rows, nameQuery, roleFilter, projectFilter, typeFilter]); + + const total = useMemo(() => filteredRows.reduce((s, r) => s + r.amount, 0), [filteredRows]); + + const hasActiveFilters = + nameQuery.trim() !== '' || roleFilter !== 'all' || projectFilter !== 'all' || typeFilter !== 'all'; + + const clearFilters = () => { + setNameQuery(''); + setRoleFilter('all'); + setProjectFilter('all'); + setTypeFilter('all'); + setProjectQuery(''); + }; + + const selectClass = + "appearance-none w-full pl-3 pr-8 py-2 text-sm rounded-xl bg-zinc-100 dark:bg-black/40 border border-zinc-200 dark:border-white/10 text-zinc-900 dark:text-white outline-none focus:ring-2 focus:ring-emerald-500/20 focus:border-emerald-500/40 transition-colors cursor-pointer"; return (
No projects match "{projectQuery}"
+ ) : ( + filteredProjectOptions.map(p => { + const isSelected = p.id === projectFilter; + return ( + + ); + }) + )} +No commission entries match the current filters.
+ +