Tasks: client filter search, unified fetchTasks, fix transfer refresh
This commit is contained in:
parent
881b82f986
commit
c5c3d5a4ab
1 changed files with 74 additions and 10 deletions
|
|
@ -1,6 +1,6 @@
|
|||
'use client'
|
||||
|
||||
import { useState, useCallback } from 'react'
|
||||
import { useState, useCallback, useEffect } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
|
|
@ -22,7 +22,8 @@ import {
|
|||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import { CheckSquare, Clock, AlertCircle, MessageSquare, CheckCircle2, RotateCcw, Eye, CalendarRange, ArrowRightLeft } from 'lucide-react'
|
||||
import { CheckSquare, Clock, AlertCircle, MessageSquare, CheckCircle2, RotateCcw, Eye, CalendarRange, ArrowRightLeft, Search, X, Building2 } from 'lucide-react'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { formatDate } from '@/lib/utils'
|
||||
|
||||
interface TaskUser { displayName: string | null; email: string }
|
||||
|
|
@ -291,14 +292,21 @@ export function TasksClient({ initialTasks, currentUserId, isPrivileged, users }
|
|||
const [transferring, setTransferring] = useState(false)
|
||||
const [viewingUserId, setViewingUserId] = useState(currentUserId)
|
||||
const [loadingTasks, setLoadingTasks] = useState(false)
|
||||
const [clientFilter, setClientFilter] = useState('')
|
||||
const [clientSearch, setClientSearch] = useState('')
|
||||
const [clientOptions, setClientOptions] = useState<{id: string; name: string}[]>([])
|
||||
const [clientSearchOpen, setClientSearchOpen] = useState(false)
|
||||
|
||||
const viewingUser = users.find((u) => u.id === viewingUserId)
|
||||
const isViewingSelf = viewingUserId === currentUserId
|
||||
const selectedClient = clientOptions.find((c) => c.id === clientFilter)
|
||||
|
||||
const fetchForUser = useCallback(async (uid: string) => {
|
||||
const fetchTasks = useCallback(async (uid: string, cid?: string) => {
|
||||
setLoadingTasks(true)
|
||||
try {
|
||||
const params = new URLSearchParams({ userId: uid, limit: '100' })
|
||||
const params = new URLSearchParams({ limit: '200' })
|
||||
params.set('userId', uid)
|
||||
if (cid) params.set('clientId', cid)
|
||||
const res = await fetch(`/api/tasks?${params}`)
|
||||
const data = await res.json()
|
||||
if (!res.ok) throw new Error(data.error)
|
||||
|
|
@ -312,13 +320,28 @@ export function TasksClient({ initialTasks, currentUserId, isPrivileged, users }
|
|||
|
||||
const handleUserChange = (uid: string) => {
|
||||
setViewingUserId(uid)
|
||||
if (uid === currentUserId) {
|
||||
setTasks(initialTasks)
|
||||
} else {
|
||||
fetchForUser(uid)
|
||||
}
|
||||
fetchTasks(uid, clientFilter || undefined)
|
||||
}
|
||||
|
||||
const handleClientFilterChange = (cid: string) => {
|
||||
setClientFilter(cid)
|
||||
fetchTasks(viewingUserId, cid || undefined)
|
||||
setClientSearchOpen(false)
|
||||
setClientSearch('')
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!clientSearch.trim()) { setClientOptions([]); return }
|
||||
const t = setTimeout(async () => {
|
||||
try {
|
||||
const res = await fetch(`/api/clients?search=${encodeURIComponent(clientSearch)}&limit=20`)
|
||||
const data = await res.json()
|
||||
setClientOptions((data.clients || []).map((c: any) => ({ id: c.id, name: c.name })))
|
||||
} catch {}
|
||||
}, 250)
|
||||
return () => clearTimeout(t)
|
||||
}, [clientSearch])
|
||||
|
||||
const handleTransfer = async () => {
|
||||
if (!transferTo || !visibleTasks.length) return
|
||||
setTransferring(true)
|
||||
|
|
@ -341,7 +364,7 @@ export function TasksClient({ initialTasks, currentUserId, isPrivileged, users }
|
|||
if (viewingUserId === currentUserId) {
|
||||
setTasks((prev) => prev.filter((t) => !visibleTasks.find((v) => v.id === t.id)))
|
||||
} else {
|
||||
fetchForUser(viewingUserId)
|
||||
fetchTasks(viewingUserId, clientFilter || undefined)
|
||||
}
|
||||
} catch (err: any) {
|
||||
toast.error(err.message || 'Transfer failed')
|
||||
|
|
@ -437,6 +460,47 @@ export function TasksClient({ initialTasks, currentUserId, isPrivileged, users }
|
|||
)}
|
||||
</div>
|
||||
|
||||
{/* Client filter */}
|
||||
<div className="relative">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-3 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder="Filter by client..."
|
||||
value={clientFilter ? (selectedClient?.name ?? '') : clientSearch}
|
||||
onChange={(e) => {
|
||||
setClientSearch(e.target.value)
|
||||
if (!e.target.value) { setClientFilter(''); fetchTasks(viewingUserId) }
|
||||
setClientSearchOpen(true)
|
||||
}}
|
||||
onFocus={() => { if (!clientFilter) setClientSearchOpen(true) }}
|
||||
className="pl-9 pr-8"
|
||||
readOnly={!!clientFilter}
|
||||
/>
|
||||
{clientFilter && (
|
||||
<button
|
||||
onClick={() => { setClientFilter(''); setClientSearch(''); fetchTasks(viewingUserId) }}
|
||||
className="absolute right-2 top-2.5 text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{clientSearchOpen && clientOptions.length > 0 && !clientFilter && (
|
||||
<div className="absolute z-50 mt-1 w-full rounded-md border border-border bg-popover shadow-md">
|
||||
{clientOptions.map((c) => (
|
||||
<button
|
||||
key={c.id}
|
||||
onClick={() => handleClientFilterChange(c.id)}
|
||||
className="flex items-center gap-2 w-full px-3 py-2 text-sm hover:bg-muted text-left"
|
||||
>
|
||||
<Building2 className="h-3.5 w-3.5 text-muted-foreground shrink-0" />
|
||||
{c.name}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Stats / filter cards */}
|
||||
<div className="grid gap-3 grid-cols-2 lg:grid-cols-5">
|
||||
{/* Overdue */}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue