Yaltopia-Tickets-App/app/news/index.tsx
2026-06-05 13:39:37 +03:00

209 lines
6.4 KiB
TypeScript

import React, { useState, useEffect } from "react";
import {
View,
ScrollView,
Pressable,
ActivityIndicator,
RefreshControl,
} from "react-native";
import { Text } from "@/components/ui/text";
import { useSirouRouter } from "@sirou/react-native";
import { AppRoutes } from "@/lib/routes";
import { api, newsApi } from "@/lib/api";
import { EmptyState } from "@/components/EmptyState";
import { ScreenWrapper } from "@/components/ScreenWrapper";
import { StandardHeader } from "@/components/StandardHeader";
interface NewsItem {
id: string;
title: string;
content: string;
category: "ANNOUNCEMENT" | "UPDATE" | "MAINTENANCE" | "NEWS";
priority: "LOW" | "MEDIUM" | "HIGH";
publishedAt: string;
viewCount: number;
}
function getCategoryColor(category: string) {
switch (category) {
case "ANNOUNCEMENT":
return "bg-amber-500";
case "UPDATE":
return "bg-blue-500";
case "MAINTENANCE":
return "bg-red-500";
default:
return "bg-emerald-500";
}
}
function getCategoryLabel(category: string) {
switch (category) {
case "ANNOUNCEMENT":
return "ANNOUNCEMENT";
case "UPDATE":
return "UPDATE";
case "MAINTENANCE":
return "MAINTENANCE";
default:
return "NEWS";
}
}
export default function NewsListScreen() {
const nav = useSirouRouter<AppRoutes>();
const getNewsApi = () => {
if (newsApi) return newsApi;
return api.news;
};
const [allNews, setAllNews] = useState<NewsItem[]>([]);
const [loading, setLoading] = useState(true);
const [page, setPage] = useState(1);
const [hasMore, setHasMore] = useState(true);
const [loadingMore, setLoadingMore] = useState(false);
const [refreshing, setRefreshing] = useState(false);
const fetchNews = async (pageNum: number, isRefresh = false) => {
try {
if (!isRefresh) {
pageNum === 1 ? setLoading(true) : setLoadingMore(true);
}
const service = getNewsApi();
if (!service) throw new Error("News service unavailable");
const response = await service.getAll({
query: { page: pageNum, limit: 10, isPublished: true },
});
const newData = response.data || [];
if (isRefresh) {
setAllNews(newData);
} else {
setAllNews((prev) => (pageNum === 1 ? newData : [...prev, ...newData]));
}
setHasMore(response?.meta?.hasNextPage ?? false);
setPage(pageNum);
} catch (err) {
console.error("[News] Fetch error:", err);
} finally {
setLoading(false);
setLoadingMore(false);
setRefreshing(false);
}
};
useEffect(() => {
fetchNews(1);
}, []);
const onRefresh = () => {
setRefreshing(true);
fetchNews(1, true);
};
const loadMore = () => {
if (hasMore && !loadingMore && !loading) {
fetchNews(page + 1);
}
};
if (loading && allNews.length === 0) {
return (
<ScreenWrapper className="bg-background">
<StandardHeader title="News & Updates" showBack />
<View className="flex-1 items-center justify-center">
<ActivityIndicator size="large" color="#E46212" />
</View>
</ScreenWrapper>
);
}
return (
<ScreenWrapper className="bg-background">
<StandardHeader title="News & Updates" showBack />
<ScrollView
showsVerticalScrollIndicator={false}
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={onRefresh}
tintColor="#E46212"
/>
}
contentContainerStyle={{ paddingBottom: 60 }}
onScroll={({ nativeEvent }) => {
const isCloseToBottom =
nativeEvent.layoutMeasurement.height +
nativeEvent.contentOffset.y >=
nativeEvent.contentSize.height - 20;
if (isCloseToBottom) loadMore();
}}
scrollEventThrottle={400}
>
<View className="px-[16px] pt-6">
<Text className="text-[9px] font-sans-black uppercase tracking-widest text-muted-foreground mb-3">
Latest News
</Text>
{allNews.length > 0 ? (
<View className="gap-2">
{allNews.map((item) => (
<Pressable
key={item.id}
onPress={() => nav.go("news/[id]", { id: item.id })}
>
<View className="rounded-xl border border-border bg-card overflow-hidden">
<View className="p-4">
<View className="flex-row items-center gap-2 mb-1.5">
<View
className={`w-1.5 h-1.5 rounded-full ${getCategoryColor(item.category)}`}
/>
<Text className="text-[9px] font-sans-black uppercase tracking-widest text-muted-foreground">
{getCategoryLabel(item.category)}
</Text>
<Text className="text-[9px] font-sans-medium text-muted-foreground ml-auto">
{new Date(item.publishedAt).toLocaleDateString()}
</Text>
</View>
<Text
className="text-foreground font-sans-bold text-sm"
numberOfLines={1}
>
{item.title}
</Text>
<Text
variant="muted"
className="text-[11px] leading-relaxed mt-1"
numberOfLines={2}
>
{item.content}
</Text>
</View>
</View>
</Pressable>
))}
{hasMore && (
<Pressable
onPress={loadMore}
disabled={loadingMore}
className="py-4 items-center"
>
{loadingMore ? (
<ActivityIndicator color="#E46212" size="small" />
) : (
<Text className="text-primary font-sans-bold text-[10px] uppercase tracking-widest">
Load More
</Text>
)}
</Pressable>
)}
</View>
) : (
<EmptyState title="No news yet" centered />
)}
</View>
</ScrollView>
</ScreenWrapper>
);
}