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

389 lines
13 KiB
TypeScript

import React, { useMemo, useState } from "react";
import {
View,
Text,
Image,
ScrollView,
TouchableOpacity,
ActivityIndicator,
} from "react-native";
import { ChevronRight } from "lucide-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 TopBar from "~/components/ui/topBar";
import BottomSheet from "~/components/ui/bottomSheet";
import { router } from "expo-router";
import { ROUTES } from "~/lib/routes";
import { useTranslation } from "react-i18next";
import { useEvents } from "~/lib/hooks/useEvents";
import Skeleton from "~/components/ui/skeleton";
export default function EventsScreen() {
const { t } = useTranslation();
const {
data: events,
loading,
error,
refetch,
} = useEvents({
status: "ACTIVE",
limit: 50,
});
const [searchQuery, setSearchQuery] = useState("");
const [filterVisible, setFilterVisible] = useState(false);
const [filterName, setFilterName] = useState("");
const [filterLocation, setFilterLocation] = useState("");
const [dateFilter, setDateFilter] = useState<"all" | "today" | "this_week">(
"all"
);
const normalizedQuery = searchQuery.trim().toLowerCase();
const normalizedFilterName = filterName.trim().toLowerCase();
const normalizedFilterLocation = filterLocation.trim().toLowerCase();
const filteredEvents = useMemo(() => {
if (!events) return [];
return events.filter((event) => {
const name = (event as any).name ?? "";
const venue = (event as any).venue ?? "";
const haystack = `${name} ${venue}`.toLowerCase();
if (normalizedQuery && !haystack.includes(normalizedQuery)) {
return false;
}
if (
normalizedFilterName &&
!name.toLowerCase().includes(normalizedFilterName)
) {
return false;
}
if (
normalizedFilterLocation &&
!venue.toLowerCase().includes(normalizedFilterLocation)
) {
return false;
}
if (dateFilter !== "all" && (event as any).startDate) {
const start = new Date((event as any).startDate);
const now = new Date();
const startDay = new Date(
start.getFullYear(),
start.getMonth(),
start.getDate()
);
const today = new Date(
now.getFullYear(),
now.getMonth(),
now.getDate()
);
if (dateFilter === "today") {
if (startDay.getTime() !== today.getTime()) return false;
} else if (dateFilter === "this_week") {
const endOfWeek = new Date(today);
endOfWeek.setDate(today.getDate() + 7);
if (startDay < today || startDay > endOfWeek) return false;
}
}
return true;
});
}, [
events,
normalizedQuery,
normalizedFilterName,
normalizedFilterLocation,
dateFilter,
]);
return (
<ScreenWrapper edges={[]}>
<View className="flex-1 bg-white">
<ScrollView
className="flex-1"
contentContainerStyle={{ paddingBottom: 32 }}
showsVerticalScrollIndicator={false}
>
<TopBar />
<View className="px-5 pt-6">
<Text className="text-lg font-dmsans-medium text-[#0F7B4A] mb-1">
{t("events.title")}
</Text>
<Text className="text-base font-dmsans text-gray-500 mb-6">
{t("events.subtitle")}
</Text>
<View className="mb-4">
<Input
placeholderText={t("events.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="flex flex-col gap-4 mb-10">
<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("events.filterButton")}
</Text>
</View>
</Button>
<Button
className="h-11 rounded-[4px] bg-[#FFB668]"
onPress={() => router.push(ROUTES.MY_TICKETS)}
>
<View className="flex-row items-center justify-center space-x-2">
<Image
source={Icons.ticketIcon}
style={{ width: 18, height: 18 }}
resizeMode="contain"
/>
<Text className="text-white ml-2 font-dmsans-medium text-sm">
{t("events.myTicketsButton")}
</Text>
</View>
</Button>
</View>
<Text className="text-lg font-dmsans-medium text-[#0F7B4A] mb-4">
{t("events.featuredTitle")}
</Text>
{loading && (
<View className="space-y-4 mb-6">
{Array.from({ length: 3 }).map((_, index) => (
<View
key={index}
className="bg-[#E9F9F0] rounded-[4px] p-6 mb-2"
>
<View className="w-full mb-4">
<Skeleton width="100%" height={176} radius={4} />
</View>
<View className="space-y-2">
<Skeleton width="60%" height={14} radius={4} />
<Skeleton width="40%" height={12} radius={4} />
</View>
<View className="mt-4 flex-row items-center justify-between">
<Skeleton width="55%" height={10} radius={4} />
<Skeleton width={26} height={26} radius={13} />
</View>
</View>
))}
</View>
)}
{!loading && error && (
<View className="flex items-center justify-center py-8">
<Text className="text-red-500 font-dmsans text-sm mb-2">
Failed to load events
</Text>
<Button
className="h-9 px-4 bg-primary rounded-full"
onPress={() => refetch()}
>
<Text className="text-white font-dmsans text-xs">Retry</Text>
</Button>
</View>
)}
{!loading && !error && events && events.length === 0 && (
<View className="flex items-center justify-center py-8">
<Text className="text-gray-500 font-dmsans text-sm">
No events found.
</Text>
</View>
)}
{!loading &&
!error &&
events &&
events.length > 0 &&
filteredEvents.length === 0 && (
<View className="flex items-center justify-center py-8">
<Text className="text-gray-500 font-dmsans text-sm">
No events match your search.
</Text>
</View>
)}
{!loading &&
!error &&
filteredEvents &&
filteredEvents.length > 0 && (
<View className="space-y-4 mb-6">
{filteredEvents.map((event) => {
const heroImage =
event.images && event.images.length > 0
? event.images[0]
: "https://images.pexels.com/photos/1190297/pexels-photo-1190297.jpeg?auto=compress&cs=tinysrgb&w=800";
const startDate = new Date(event.startDate);
const formattedDate = startDate.toLocaleDateString();
return (
<TouchableOpacity
key={event.id}
activeOpacity={0.9}
onPress={() =>
router.push({
pathname: ROUTES.EVENT_DETAIL,
params: { id: event.id },
})
}
>
<View className="bg-[#E9F9F0] rounded-[4px] p-6 mb-2">
<View className="w-full h-44 rounded-[4px] overflow-hidden mb-4 bg-gray-300">
<Image
source={{ uri: heroImage }}
style={{ width: "100%", height: "100%" }}
resizeMode="cover"
/>
</View>
<Text className="text-sm font-dmsans-bold text-[#FFB668] mb-1">
{event.name}
</Text>
<Text className="text-sm font-dmsans text-[#105D38] mb-4">
{event.venue}
</Text>
<View className="flex-row items-center justify-between">
<Text className="text-xs font-dmsans text-[#111827]">
<Text className="font-dmsans-bold">
{t("events.ticketCountPrefix")}
</Text>
{event.organizer?.name || ""} -
<Text className="italic"> {formattedDate}</Text>
</Text>
<ChevronRight size={26} color="#FFB668" />
</View>
</View>
</TouchableOpacity>
);
})}
</View>
)}
</View>
</ScrollView>
</View>
<BottomSheet
visible={filterVisible}
onClose={() => setFilterVisible(false)}
maxHeightRatio={0.7}
>
<Text className="text-lg font-dmsans-bold text-black mb-1">
Filter events
</Text>
<Text className="text-xs font-dmsans text-gray-500 mb-4">
Filter by date, name and location
</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>
<Text className="text-base font-dmsans-medium text-black mb-2">
Event name
</Text>
<View className="mb-3">
<Input
value={filterName}
onChangeText={setFilterName}
placeholderText="Search by event name"
placeholderColor="#9CA3AF"
containerClassName="w-full"
borderClassName="border-[#E5E7EB] bg-white rounded-[4px]"
textClassName="text-[#111827] text-sm"
/>
</View>
<Text className="text-base font-dmsans-medium text-black mb-2 mt-2">
Location
</Text>
<View className="mb-4">
<Input
value={filterLocation}
onChangeText={setFilterLocation}
placeholderText="Search by location"
placeholderColor="#9CA3AF"
containerClassName="w-full"
borderClassName="border-[#E5E7EB] bg-white rounded-[4px]"
textClassName="text-[#111827] text-sm"
/>
</View>
<View className="flex-row justify-between mt-4">
<TouchableOpacity
onPress={() => {
setFilterName("");
setFilterLocation("");
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>
);
}