From d2774b4a1e421e68840b739c4781ea15e2d63a5a Mon Sep 17 00:00:00 2001 From: Satyam-Rastogi Date: Fri, 15 May 2026 18:50:22 +0530 Subject: [PATCH] adds filter in org commission distribution panel --- .../owner/CommissionDistributionPanel.jsx | 249 +++++++++++++++++- 1 file changed, 242 insertions(+), 7 deletions(-) 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 (
{/* Header */} -
+

Commission Distribution

- {rows.length} entr{rows.length === 1 ? 'y' : 'ies'} across {ownerProjects.length} project{ownerProjects.length !== 1 ? 's' : ''} + {hasActiveFilters + ? `${filteredRows.length} of ${rows.length} entr${rows.length === 1 ? 'y' : 'ies'}` + : `${rows.length} entr${rows.length === 1 ? 'y' : 'ies'} across ${ownerProjects.length} project${ownerProjects.length !== 1 ? 's' : ''}` + }
+ {/* Filter bar */} + {rows.length > 0 && ( +
+ {/* Search by Name */} +
+ + + setNameQuery(e.target.value)} + className="w-full bg-zinc-100 dark:bg-black/40 border border-zinc-200 dark:border-white/10 rounded-xl py-2 pl-9 pr-8 text-sm text-zinc-900 dark:text-white placeholder:text-zinc-400 dark:placeholder:text-zinc-500 outline-none focus:ring-2 focus:ring-emerald-500/20 focus:border-emerald-500/40" + /> + {nameQuery && ( + + )} +
+ + {/* Role filter */} +
+ + + +
+ + {/* Project searchable dropdown */} +
+ + {projectOpen && ( +
+
+ + setProjectQuery(e.target.value)} + className="w-full bg-zinc-50 dark:bg-black/40 border border-zinc-200 dark:border-white/10 rounded-lg py-1.5 pl-8 pr-2 text-sm text-zinc-900 dark:text-white placeholder:text-zinc-400 dark:placeholder:text-zinc-500 outline-none" + /> +
+
+ + {filteredProjectOptions.length === 0 ? ( +

No projects match "{projectQuery}"

+ ) : ( + filteredProjectOptions.map(p => { + const isSelected = p.id === projectFilter; + return ( + + ); + }) + )} +
+
+ )} +
+ + {/* Commission Type filter */} +
+ + + +
+ + {hasActiveFilters && ( +
+ +
+ )} +
+ )} + {rows.length === 0 ? (
@@ -124,7 +344,22 @@ const CommissionDistributionPanel = ({ - {rows.map((row) => ( + {filteredRows.length === 0 && ( + + + +

No commission entries match the current filters.

+ + + + )} + {filteredRows.map((row) => (
@@ -160,7 +395,7 @@ const CommissionDistributionPanel = ({ - Total Commission Amount + {hasActiveFilters ? 'Filtered Commission Total' : 'Total Commission Amount'}