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 { toast } from '@/hooks/use-toast';
|
||||
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 { id } from 'date-fns/locale';
|
||||
|
||||
@@ -72,6 +72,7 @@ export default function AdminConsulting() {
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [creatingMeet, setCreatingMeet] = useState(false);
|
||||
const [activeTab, setActiveTab] = useState('upcoming');
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
if (!authLoading) {
|
||||
@@ -282,10 +283,24 @@ export default function AdminConsulting() {
|
||||
})).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 upcomingOrders = groupedOrders.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 todayOrders = groupedOrders.filter(o => isToday(parseISO(o.firstDate)) && o.slots.some(s => s.status === 'confirmed'));
|
||||
const upcomingOrders = filteredGroupedOrders.filter(o => o.firstDate >= today && o.slots.some(s => s.status === 'confirmed' || s.status === 'pending_payment'));
|
||||
const pastOrders = filteredGroupedOrders.filter(o => o.firstDate < today || o.slots.every(s => s.status === 'completed' || s.status === 'cancelled'));
|
||||
const todayOrders = filteredGroupedOrders.filter(o => isToday(parseISO(o.firstDate)) && o.slots.some(s => s.status === 'confirmed'));
|
||||
|
||||
return (
|
||||
<AppLayout>
|
||||
@@ -360,6 +375,24 @@ export default function AdminConsulting() {
|
||||
</Card>
|
||||
</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 value={activeTab} onValueChange={setActiveTab}>
|
||||
<TabsList className="mb-4">
|
||||
|
||||
Reference in New Issue
Block a user