From bc2c89e0301d076f8eff36789f60768b525d3bd0 Mon Sep 17 00:00:00 2001 From: amaliadwiy Date: Fri, 8 May 2026 11:37:21 +0800 Subject: [PATCH] feat: redesign halaman pencarian dengan filter tabs, section badge, dan card style --- app/(application)/search.tsx | 341 +++++++++++++++++++++++------------ 1 file changed, 225 insertions(+), 116 deletions(-) diff --git a/app/(application)/search.tsx b/app/(application)/search.tsx index 6541b76..3f418f2 100644 --- a/app/(application)/search.tsx +++ b/app/(application)/search.tsx @@ -9,13 +9,12 @@ import Styles from "@/constants/Styles"; import { apiGetSearch } from "@/lib/api"; import { useAuthSession } from "@/providers/AuthProvider"; import { useTheme } from "@/providers/ThemeProvider"; -import { AntDesign, MaterialIcons } from "@expo/vector-icons"; +import { AntDesign, Feather, MaterialIcons } from "@expo/vector-icons"; import { router, Stack } from "expo-router"; import React, { useState } from "react"; -import { RefreshControl, SafeAreaView, ScrollView, View } from "react-native"; +import { RefreshControl, SafeAreaView, ScrollView, TouchableOpacity, View } from "react-native"; import Toast from "react-native-toast-message"; -// ... types ... type PropsUser = { id: string name: string @@ -38,6 +37,27 @@ type PropDivisi = { group: string } +type FilterType = "all" | "member" | "division" | "project" + +function SectionHeader({ label, count, colors }: { label: string; count: number; colors: any }) { + return ( + + + {label} + + + {count} + + + ) +} + export default function Search() { const { token, decryptToken } = useAuthSession() const [dataUser, setDataUser] = useState([]) @@ -45,11 +65,16 @@ export default function Search() { const [dataProject, setDataProject] = useState([]) const [refreshing, setRefreshing] = useState(false) const [search, setSearch] = useState('') + const [activeFilter, setActiveFilter] = useState("all") const { colors } = useTheme(); + const totalResults = dataUser.length + dataDivisi.length + dataProject.length + const hasSearch = search.length >= 3 + async function handleSearch(cari: string) { try { setSearch(cari) + setActiveFilter("all") if (cari.length >= 3) { const user = await decryptToken(String(token?.current)) const hasil = await apiGetSearch({ text: cari, user: user }) @@ -58,7 +83,7 @@ export default function Search() { setDataDivisi(hasil.data.division) setDataProject(hasil.data.project) } else { - return Toast.show({ type: 'small', text1: hasil.message, }) + return Toast.show({ type: 'small', text1: hasil.message }) } } else { setDataUser([]) @@ -68,15 +93,10 @@ export default function Search() { } catch (error: any) { console.error(error); const message = error?.response?.data?.message || "Gagal melakukan pencarian" - - Toast.show({ - type: 'small', - text1: message - }) + Toast.show({ type: 'small', text1: message }) } } - const handleRefresh = async () => { setRefreshing(true) handleSearch(search) @@ -84,114 +104,203 @@ export default function Search() { setRefreshing(false) }; + const filters: { key: FilterType; label: string; count: number }[] = [ + { key: "all", label: "Semua", count: totalResults }, + { key: "member", label: "Anggota", count: dataUser.length }, + { key: "division", label: "Divisi", count: dataDivisi.length }, + { key: "project", label: "Kegiatan", count: dataProject.length }, + ] + + const showUser = activeFilter === "all" || activeFilter === "member" + const showDivision = activeFilter === "all" || activeFilter === "division" + const showProject = activeFilter === "all" || activeFilter === "project" + + const activeFilterEmpty = + (activeFilter === "member" && dataUser.length === 0) || + (activeFilter === "division" && dataDivisi.length === 0) || + (activeFilter === "project" && dataProject.length === 0) return ( - <> - - ( - router.back()} /> - ) - }} - /> - + + ( + router.back()} /> + ) + }} + /> + + + {/* Search bar */} + - { - dataProject.length + dataDivisi.length + dataUser.length > 0 - ? - - } - > - { - dataUser.length > 0 && - - ANGGOTA - { - dataUser.map((item, index) => ( - } - title={item.name} - subtitle={`${item.group}-${item.position}`} - onPress={() => { - router.push(`/member/${item.id}`) - }} - /> - )) - } - - } - - { - dataDivisi.length > 0 && - - DIVISI - { - dataDivisi.map((item, index) => ( - - - - } - title={item.name} - subtitle={item.group} - onPress={() => { - router.push(`/division/${item.id}`) - }} - /> - )) - } - - } - - - { - dataProject.length > 0 && - - KEGIATAN - { - dataProject.map((item, index) => ( - - - - } - title={item.title} - subtitle={item.group} - onPress={() => { - router.push(`/project/${item.id}`) - }} - /> - )) - } - - } - - : - - Tidak ada data - - } - - - + + {/* Filter tabs */} + {hasSearch && totalResults > 0 && ( + + + {filters.map((f) => { + const isActive = activeFilter === f.key + return ( + setActiveFilter(f.key)} + style={{ + flexDirection: 'row', + alignItems: 'center', + paddingVertical: 6, + paddingHorizontal: 14, + borderRadius: 20, + marginRight: 8, + borderWidth: 1, + borderColor: isActive ? colors.tabActive : colors.icon + '40', + backgroundColor: isActive ? colors.tabActive + '20' : 'transparent', + }} + > + + {f.label} + + {f.count > 0 && ( + + + {f.count} + + + )} + + ) + })} + + + )} + + {/* Content */} + + {!hasSearch ? ( + + + + Ketik minimal 3 karakter untuk mencari + + + ) : totalResults === 0 ? ( + + + + Tidak ada hasil untuk "{search}" + + + ) : ( + + } + > + {/* Anggota */} + {showUser && dataUser.length > 0 && ( + + + {dataUser.map((item, index) => ( + + } + title={item.name} + subtitle={`${item.group} ยท ${item.position}`} + onPress={() => router.push(`/member/${item.id}`)} + colorPress + /> + + ))} + + )} + + {/* Divisi */} + {showDivision && dataDivisi.length > 0 && ( + + + {dataDivisi.map((item, index) => ( + + + + + } + title={item.name} + subtitle={item.group} + onPress={() => router.push(`/division/${item.id}`)} + colorPress + /> + + ))} + + )} + + {/* Kegiatan */} + {showProject && dataProject.length > 0 && ( + + + {dataProject.map((item, index) => ( + + + + + } + title={item.title} + subtitle={item.group} + onPress={() => router.push(`/project/${item.id}`)} + colorPress + /> + + ))} + + )} + + {/* Empty state untuk filter aktif */} + {activeFilter !== "all" && activeFilterEmpty && ( + + + + Tidak ada hasil di kategori ini + + + )} + + )} + + + ) -} \ No newline at end of file +}