Amba-Agent-App/app/(root)/(screens)/mytickets.tsx
2026-01-16 00:22:35 +03:00

324 lines
11 KiB
TypeScript

import React, { useMemo, useState } from "react";
import {
View,
Text,
Image,
ScrollView,
TouchableOpacity,
ActivityIndicator,
} from "react-native";
import ScreenWrapper from "~/components/ui/ScreenWrapper";
import { Button } from "~/components/ui/button";
import { Input } from "~/components/ui/input";
import { Icons } from "~/assets/icons";
import { ChevronRight } from "lucide-react-native";
import { router } from "expo-router";
import { ROUTES } from "~/lib/routes";
import { useTranslation } from "react-i18next";
import BackButton from "~/components/ui/backButton";
import { useTickets } from "~/lib/hooks/useTickets";
import BottomSheet from "~/components/ui/bottomSheet";
export default function MyTicketsScreen() {
const { t } = useTranslation();
const {
data: tickets,
loading,
error,
refetch,
} = useTickets({ status: "ACTIVE", limit: 50, immediate: true });
const [searchQuery, setSearchQuery] = useState("");
const [filterVisible, setFilterVisible] = useState(false);
const [dateFilter, setDateFilter] = useState<"all" | "today" | "this_week">(
"all"
);
const normalizedQuery = searchQuery.trim().toLowerCase();
const filteredTickets = useMemo(() => {
if (!tickets) return [];
return tickets.filter((ticket) => {
const anyTicket = ticket as any;
const eventName = (anyTicket.event?.name ?? "").toLowerCase();
if (normalizedQuery && !eventName.includes(normalizedQuery)) {
return false;
}
if (dateFilter !== "all") {
const rawDate = anyTicket.event?.startDate || anyTicket.createdAt;
if (!rawDate) {
return true;
}
const date = new Date(rawDate);
const now = new Date();
const ticketDay = new Date(
date.getFullYear(),
date.getMonth(),
date.getDate()
);
const today = new Date(
now.getFullYear(),
now.getMonth(),
now.getDate()
);
if (dateFilter === "today") {
if (ticketDay.getTime() !== today.getTime()) return false;
} else if (dateFilter === "this_week") {
const endOfWeek = new Date(today);
endOfWeek.setDate(today.getDate() + 7);
if (ticketDay < today || ticketDay > endOfWeek) return false;
}
}
return true;
});
}, [tickets, normalizedQuery, dateFilter]);
return (
<ScreenWrapper edges={[]}>
<View className="flex-1 bg-white">
<ScrollView
className="flex-1"
contentContainerStyle={{ paddingBottom: 32 }}
showsVerticalScrollIndicator={false}
>
<View className="">
<BackButton />
</View>
<View className="px-5">
<Text className="text-lg font-dmsans-medium text-[#0F7B4A] mb-1">
{t("mytickets.title")}
</Text>
<Text className="text-base font-dmsans text-gray-500 mb-6">
{t("mytickets.subtitle")}
</Text>
<View className="mb-4">
<Input
placeholderText={t("mytickets.searchPlaceholder")}
containerClassName="w-full"
borderClassName="border-[#E5E7EB] bg-white rounded-[4px]"
placeholderColor="#9CA3AF"
textClassName="text-[#111827] text-sm"
value={searchQuery}
onChangeText={setSearchQuery}
/>
</View>
<View className="mb-8">
<Button
className="h-11 rounded-[4px] bg-[#0F7B4A]"
onPress={() => setFilterVisible(true)}
>
<View className="flex-row items-center justify-center space-x-2">
<Image
source={Icons.filterBar}
style={{ width: 18, height: 18 }}
resizeMode="contain"
/>
<Text className="text-white ml-2 font-dmsans-medium text-sm">
{t("mytickets.filterButton")}
</Text>
</View>
</Button>
<Button
className="bg-secondary mt-4 rounded-md border border-dashed border-secondary h-11"
onPress={() => router.back()}
>
<Text className="font-dmsans text-white">
{t("common.back")}
</Text>
</Button>
</View>
<Text className="text-lg font-dmsans-medium text-[#0F7B4A] mb-4">
{t("mytickets.ticketsTitle")}
</Text>
<View className="flex flex-col gap-3">
{loading && (
<View className="flex items-center justify-center py-8">
<ActivityIndicator size="small" color="#0F7B4A" />
<Text className="mt-2 text-gray-500 font-dmsans text-sm">
{t("mytickets.loading")}
</Text>
</View>
)}
{!loading && error && (
<View className="flex items-center justify-center py-8">
<Text className="text-red-500 font-dmsans text-sm mb-2">
{t("mytickets.error")}
</Text>
<Button
className="h-9 px-4 bg-primary rounded-full"
onPress={() => refetch()}
>
<Text className="text-white font-dmsans text-xs">
{t("common.retry")}
</Text>
</Button>
</View>
)}
{!loading && !error && tickets && tickets.length === 0 && (
<View className="flex items-center justify-center py-12">
<Image
source={Icons.ticketHome}
style={{ width: 64, height: 64, marginBottom: 12 }}
resizeMode="contain"
/>
<Text className="text-base font-dmsans-medium text-[#0F7B4A] mb-1">
No tickets found
</Text>
<Text className="text-sm font-dmsans text-gray-500 text-center px-4">
You don't have any tickets yet.
</Text>
</View>
)}
{!loading &&
!error &&
tickets &&
filteredTickets.length === 0 && (
<View className="flex items-center justify-center py-8">
<Text className="text-sm font-dmsans text-gray-500">
No tickets match your search.
</Text>
</View>
)}
{!loading &&
!error &&
filteredTickets &&
filteredTickets.length > 0 && (
<View className="flex flex-col gap-3">
{filteredTickets.map((ticket) => {
const anyTicket = ticket as any;
const eventName = anyTicket.event?.name || "Ticket";
const ticketNo = anyTicket.ticketNo || anyTicket.id;
const qr = anyTicket.qr || ticketNo;
const qrImage = anyTicket.qrImage as string | undefined;
const rawDate =
anyTicket.event?.startDate || anyTicket.createdAt;
const formattedDate = rawDate
? new Date(rawDate).toLocaleDateString()
: "";
return (
<TouchableOpacity
key={anyTicket.id}
activeOpacity={0.9}
onPress={() =>
router.push({
pathname: ROUTES.EVENT_QR,
params: {
code: qr,
packageName: eventName,
...(qrImage ? { qrImage } : {}),
},
})
}
className="flex-row items-center justify-between bg-[#F3FFF7] rounded-[6px] px-4 py-3"
>
<View className="flex-row items-center">
<View className="w-10 h-10 rounded-[4px] bg-[#FFEEDB] items-center justify-center mr-3">
<Image
source={Icons.ticketIcon}
style={{ width: 20, height: 20 }}
resizeMode="contain"
/>
</View>
<View>
<Text className="text-sm font-dmsans-medium text-[#FFB668]">
{eventName}
</Text>
<Text className="text-xs font-dmsans text-[#105D38] mt-1">
{formattedDate}
</Text>
</View>
</View>
<ChevronRight size={20} color="#FFB668" />
</TouchableOpacity>
);
})}
</View>
)}
</View>
</View>
</ScrollView>
</View>
<BottomSheet
visible={filterVisible}
onClose={() => setFilterVisible(false)}
maxHeightRatio={0.5}
>
<Text className="text-lg font-dmsans-bold text-black mb-1">
Filter tickets
</Text>
<Text className="text-xs font-dmsans text-gray-500 mb-4">
Filter by date of event or purchase
</Text>
<Text className="text-base font-dmsans-medium text-black mb-2">
Date
</Text>
<View className="flex-row mb-4">
{[
{ key: "all", label: "All dates" },
{ key: "today", label: "Today" },
{ key: "this_week", label: "This week" },
].map((option) => (
<TouchableOpacity
key={option.key}
onPress={() =>
setDateFilter(option.key as "all" | "today" | "this_week")
}
className={`px-3 py-1 rounded-full mr-2 border ${
dateFilter === option.key
? "bg-[#0F7B4A] border-[#0F7B4A]"
: "bg-white border-gray-300"
}`}
>
<Text
className={`text-xs font-dmsans ${
dateFilter === option.key ? "text-white" : "text-gray-700"
}`}
>
{option.label}
</Text>
</TouchableOpacity>
))}
</View>
<View className="flex-row justify-between mt-4">
<TouchableOpacity
onPress={() => {
setSearchQuery("");
setDateFilter("all");
}}
>
<Text className="text-sm font-dmsans text-primary">Clear</Text>
</TouchableOpacity>
<Button
className="h-9 px-4 rounded-full bg-[#0F7B4A]"
onPress={() => setFilterVisible(false)}
>
<Text className="text-xs font-dmsans text-white">
Apply filters
</Text>
</Button>
</View>
</BottomSheet>
</ScreenWrapper>
);
}