feat: add search to AdminConsulting page
- Add search by client name, email, category, or order ID - Show result count for filtered data - Integrate with existing upcoming/past tabs 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -14,7 +14,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@
|
|||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||||
import { toast } from '@/hooks/use-toast';
|
import { toast } from '@/hooks/use-toast';
|
||||||
import { formatIDR } from '@/lib/format';
|
import { formatIDR } from '@/lib/format';
|
||||||
import { Video, Calendar, Clock, User, Link as LinkIcon, ExternalLink, CheckCircle, XCircle, Loader2 } from 'lucide-react';
|
import { Video, Calendar, Clock, User, Link as LinkIcon, ExternalLink, CheckCircle, XCircle, Loader2, Search } from 'lucide-react';
|
||||||
import { format, parseISO, isToday, isTomorrow, isPast } from 'date-fns';
|
import { format, parseISO, isToday, isTomorrow, isPast } from 'date-fns';
|
||||||
import { id } from 'date-fns/locale';
|
import { id } from 'date-fns/locale';
|
||||||
|
|
||||||
@@ -72,6 +72,7 @@ export default function AdminConsulting() {
|
|||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
const [creatingMeet, setCreatingMeet] = useState(false);
|
const [creatingMeet, setCreatingMeet] = useState(false);
|
||||||
const [activeTab, setActiveTab] = useState('upcoming');
|
const [activeTab, setActiveTab] = useState('upcoming');
|
||||||
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!authLoading) {
|
if (!authLoading) {
|
||||||
@@ -282,10 +283,24 @@ export default function AdminConsulting() {
|
|||||||
})).sort((a, b) => new Date(a.firstDate).getTime() - new Date(b.firstDate).getTime());
|
})).sort((a, b) => new Date(a.firstDate).getTime() - new Date(b.firstDate).getTime());
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
// Filter orders based on search query
|
||||||
|
const filteredGroupedOrders = groupedOrders.filter(order => {
|
||||||
|
if (!searchQuery) return true;
|
||||||
|
const query = searchQuery.toLowerCase();
|
||||||
|
const firstSlot = order.slots[0];
|
||||||
|
|
||||||
|
return (
|
||||||
|
order.profile?.name?.toLowerCase().includes(query) ||
|
||||||
|
order.profile?.email?.toLowerCase().includes(query) ||
|
||||||
|
firstSlot.topic_category?.toLowerCase().includes(query) ||
|
||||||
|
order.orderId?.toLowerCase().includes(query)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
const today = new Date().toISOString().split('T')[0];
|
const today = new Date().toISOString().split('T')[0];
|
||||||
const upcomingOrders = groupedOrders.filter(o => o.firstDate >= today && o.slots.some(s => s.status === 'confirmed' || s.status === 'pending_payment'));
|
const upcomingOrders = filteredGroupedOrders.filter(o => o.firstDate >= today && o.slots.some(s => s.status === 'confirmed' || s.status === 'pending_payment'));
|
||||||
const pastOrders = groupedOrders.filter(o => o.firstDate < today || o.slots.every(s => s.status === 'completed' || s.status === 'cancelled'));
|
const pastOrders = filteredGroupedOrders.filter(o => o.firstDate < today || o.slots.every(s => s.status === 'completed' || s.status === 'cancelled'));
|
||||||
const todayOrders = groupedOrders.filter(o => isToday(parseISO(o.firstDate)) && o.slots.some(s => s.status === 'confirmed'));
|
const todayOrders = filteredGroupedOrders.filter(o => isToday(parseISO(o.firstDate)) && o.slots.some(s => s.status === 'confirmed'));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppLayout>
|
<AppLayout>
|
||||||
@@ -360,6 +375,24 @@ export default function AdminConsulting() {
|
|||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Search */}
|
||||||
|
<Card className="border-2 border-border mb-6">
|
||||||
|
<CardContent className="pt-6">
|
||||||
|
<div className="relative">
|
||||||
|
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground" />
|
||||||
|
<Input
|
||||||
|
placeholder="Cari nama klien, email, kategori, atau order ID..."
|
||||||
|
value={searchQuery}
|
||||||
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
|
className="pl-10 border-2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mt-2 text-sm text-muted-foreground">
|
||||||
|
Menampilkan {filteredGroupedOrders.length} dari {groupedOrders.length} jadwal konsultasi
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
{/* Tabs */}
|
{/* Tabs */}
|
||||||
<Tabs value={activeTab} onValueChange={setActiveTab}>
|
<Tabs value={activeTab} onValueChange={setActiveTab}>
|
||||||
<TabsList className="mb-4">
|
<TabsList className="mb-4">
|
||||||
|
|||||||
Reference in New Issue
Block a user