Files
mobile-darmasaba/app/(application)/search.tsx

307 lines
13 KiB
TypeScript

import AppHeader from "@/components/AppHeader";
import BorderBottomItem from "@/components/borderBottomItem";
import ImageUser from "@/components/imageNew";
import InputSearch from "@/components/inputSearch";
import Text from '@/components/Text';
import { ColorsStatus } from "@/constants/ColorsStatus";
import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles";
import { apiGetSearch } from "@/lib/api";
import { useAuthSession } from "@/providers/AuthProvider";
import { useTheme } from "@/providers/ThemeProvider";
import { AntDesign, Feather, MaterialIcons } from "@expo/vector-icons";
import { router, Stack } from "expo-router";
import React, { useState } from "react";
import { RefreshControl, SafeAreaView, ScrollView, TouchableOpacity, View } from "react-native";
import Toast from "react-native-toast-message";
type PropsUser = {
id: string
name: string
email: string
position: string
group: string
img: string
}
type PropProject = {
id: string
title: string
group: string
}
type PropDivisi = {
id: string
name: string
desc: string
group: string
}
type FilterType = "all" | "member" | "division" | "project"
function SectionHeader({ label, count, colors }: { label: string; count: number; colors: any }) {
return (
<View style={[Styles.rowItemsCenter, Styles.mb08]}>
<Text style={{ fontSize: 11, fontWeight: '600', color: colors.dimmed, letterSpacing: 0.8, textTransform: 'uppercase' }}>
{label}
</Text>
<View style={{
marginLeft: 6,
backgroundColor: colors.icon + '25',
borderRadius: 10,
paddingHorizontal: 7,
paddingVertical: 1,
}}>
<Text style={{ fontSize: 11, color: colors.dimmed, fontWeight: '600' }}>{count}</Text>
</View>
</View>
)
}
export default function Search() {
const { token, decryptToken } = useAuthSession()
const [dataUser, setDataUser] = useState<PropsUser[]>([])
const [dataDivisi, setDataDivisi] = useState<PropDivisi[]>([])
const [dataProject, setDataProject] = useState<PropProject[]>([])
const [refreshing, setRefreshing] = useState(false)
const [search, setSearch] = useState('')
const [activeFilter, setActiveFilter] = useState<FilterType>("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 })
if (hasil.success) {
setDataUser(hasil.data.user)
setDataDivisi(hasil.data.division)
setDataProject(hasil.data.project)
} else {
return Toast.show({ type: 'small', text1: hasil.message })
}
} else {
setDataUser([])
setDataDivisi([])
setDataProject([])
}
} catch (error: any) {
console.error(error);
const message = error?.response?.data?.message || "Gagal melakukan pencarian"
Toast.show({ type: 'small', text1: message })
}
}
const handleRefresh = async () => {
setRefreshing(true)
handleSearch(search)
await new Promise(resolve => setTimeout(resolve, 2000));
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 (
<SafeAreaView style={[Styles.flex1, { backgroundColor: colors.background }]}>
<Stack.Screen
options={{
headerTitle: 'Pencarian',
headerTitleAlign: 'center',
header: () => (
<AppHeader title="Pencarian" showBack={true} onPressLeft={() => router.back()} />
)
}}
/>
<View style={[Styles.flex1]}>
{/* Search bar */}
<View style={[Styles.ph15, { paddingTop: 15 }]}>
<InputSearch onChange={handleSearch} />
</View>
{/* Filter tabs */}
{hasSearch && totalResults > 0 && (
<View style={{ marginTop: 10, flexShrink: 0 }}>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={[Styles.ph15, { paddingRight: 5, alignItems: 'center' }]}
>
{filters.map((f) => {
const isActive = activeFilter === f.key
return (
<TouchableOpacity
key={f.key}
onPress={() => 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',
}}
>
<Text style={{
fontSize: 13,
color: isActive ? colors.tabActive : colors.dimmed,
fontWeight: isActive ? '600' : 'normal',
}}>
{f.label}
</Text>
{f.count > 0 && (
<View style={{
marginLeft: 5,
backgroundColor: isActive ? colors.tabActive : colors.icon + '30',
borderRadius: 10,
minWidth: 18,
height: 18,
alignItems: 'center',
justifyContent: 'center',
paddingHorizontal: 4,
}}>
<Text style={{ fontSize: 11, color: isActive ? 'white' : colors.dimmed, fontWeight: '600' }}>
{f.count}
</Text>
</View>
)}
</TouchableOpacity>
)
})}
</ScrollView>
</View>
)}
{/* Content */}
<View style={[Styles.flex1]}>
{!hasSearch ? (
<View style={[Styles.contentItemCenter, Styles.mt30]}>
<Feather name="search" size={42} color={colors.icon + '40'} />
<Text style={[Styles.mt10, { color: colors.dimmed, fontSize: 14 }]}>
Ketik minimal 3 karakter untuk mencari
</Text>
</View>
) : totalResults === 0 ? (
<View style={[Styles.contentItemCenter, Styles.mt30]}>
<Feather name="inbox" size={42} color={colors.icon + '40'} />
<Text style={[Styles.mt10, { color: colors.dimmed, fontSize: 14 }]}>
Tidak ada hasil untuk "{search}"
</Text>
</View>
) : (
<ScrollView
style={[Styles.flex1]}
contentContainerStyle={[Styles.ph15, { paddingTop: 14, paddingBottom: 30 }]}
showsVerticalScrollIndicator={false}
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={handleRefresh}
tintColor={colors.icon}
/>
}
>
{/* Anggota */}
{showUser && dataUser.length > 0 && (
<View style={[Styles.mb15]}>
<SectionHeader label="Anggota" count={dataUser.length} colors={colors} />
{dataUser.map((item, index) => (
<View key={index} style={index < dataUser.length - 1 ? Styles.mb05 : undefined}>
<BorderBottomItem
borderType="all"
icon={<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} />}
title={item.name}
subtitle={`${item.group} · ${item.position}`}
onPress={() => router.push(`/member/${item.id}`)}
colorPress
/>
</View>
))}
</View>
)}
{/* Divisi */}
{showDivision && dataDivisi.length > 0 && (
<View style={[Styles.mb15]}>
<SectionHeader label="Divisi" count={dataDivisi.length} colors={colors} />
{dataDivisi.map((item, index) => (
<View key={index} style={index < dataDivisi.length - 1 ? Styles.mb05 : undefined}>
<BorderBottomItem
borderType="all"
icon={
<View style={[Styles.iconContent, ColorsStatus.primary]}>
<MaterialIcons name="group" size={25} color="white" />
</View>
}
title={item.name}
subtitle={item.group}
onPress={() => router.push(`/division/${item.id}`)}
colorPress
/>
</View>
))}
</View>
)}
{/* Kegiatan */}
{showProject && dataProject.length > 0 && (
<View style={[Styles.mb15]}>
<SectionHeader label="Kegiatan" count={dataProject.length} colors={colors} />
{dataProject.map((item, index) => (
<View key={index} style={index < dataProject.length - 1 ? Styles.mb05 : undefined}>
<BorderBottomItem
borderType="all"
icon={
<View style={[Styles.iconContent, ColorsStatus.primary]}>
<AntDesign name="areachart" size={25} color="white" />
</View>
}
title={item.title}
subtitle={item.group}
onPress={() => router.push(`/project/${item.id}`)}
colorPress
/>
</View>
))}
</View>
)}
{/* Empty state untuk filter aktif */}
{activeFilter !== "all" && activeFilterEmpty && (
<View style={[Styles.contentItemCenter, Styles.mt30]}>
<Feather name="inbox" size={42} color={colors.icon + '40'} />
<Text style={[Styles.mt10, { color: colors.dimmed, fontSize: 14 }]}>
Tidak ada hasil di kategori ini
</Text>
</View>
)}
</ScrollView>
)}
</View>
</View>
</SafeAreaView>
)
}