114 lines
4.0 KiB
TypeScript
114 lines
4.0 KiB
TypeScript
import React, { useMemo, useState } from 'react';
|
|
import { FlatList, RefreshControl, ScrollView, StyleSheet, Text, View } from 'react-native';
|
|
import { useRouter } from 'expo-router';
|
|
import { useQuery } from '@tanstack/react-query';
|
|
import { api, DocCategory } from '@/api';
|
|
import { useT } from '@/i18n';
|
|
import { AppBar } from '@/components/AppBar';
|
|
import { Chip, EmptyState, Loading } from '@/components/ui';
|
|
import { DocumentRow } from '@/components/domain';
|
|
import { DocWithProperty, fetchAllDocuments } from '@/lib/allDocuments';
|
|
import { daysUntil } from '@/lib/format';
|
|
import { colors, fontSize, spacing } from '@/theme';
|
|
|
|
type Filter = 'all' | 'expiring' | 'expired';
|
|
|
|
export default function ExpiringScreen() {
|
|
const router = useRouter();
|
|
const t = useT();
|
|
const [filter, setFilter] = useState<Filter>('all');
|
|
|
|
const docsQuery = useQuery({ queryKey: ['all-documents'], queryFn: fetchAllDocuments });
|
|
const catsQuery = useQuery({ queryKey: ['categories'], queryFn: () => api.reference.categories() });
|
|
const catName = (slug: string) =>
|
|
(catsQuery.data as DocCategory[] | undefined)?.find((c) => c.slug === slug)?.name ?? slug;
|
|
|
|
// Only documents that actually need attention, soonest deadline first.
|
|
const attention = useMemo(() => {
|
|
const docs: DocWithProperty[] = docsQuery.data ?? [];
|
|
return docs
|
|
.filter((d) => d.expiryStatus === 'expiring' || d.expiryStatus === 'expired')
|
|
.sort((a, b) => (daysUntil(a.expiryDate) ?? 0) - (daysUntil(b.expiryDate) ?? 0));
|
|
}, [docsQuery.data]);
|
|
|
|
const counts = useMemo(
|
|
() => ({
|
|
all: attention.length,
|
|
expiring: attention.filter((d) => d.expiryStatus === 'expiring').length,
|
|
expired: attention.filter((d) => d.expiryStatus === 'expired').length,
|
|
}),
|
|
[attention],
|
|
);
|
|
|
|
const visible = filter === 'all' ? attention : attention.filter((d) => d.expiryStatus === filter);
|
|
|
|
return (
|
|
<View style={{ flex: 1, backgroundColor: colors.bgApp }}>
|
|
<AppBar
|
|
title={t('expiring.title')}
|
|
subtitle={docsQuery.data ? t('expiring.subtitle', { count: counts.all }) : undefined}
|
|
large
|
|
/>
|
|
|
|
<ScrollView
|
|
horizontal
|
|
showsHorizontalScrollIndicator={false}
|
|
contentContainerStyle={styles.chips}
|
|
style={{ flexGrow: 0 }}
|
|
>
|
|
<Chip label={t('common.all')} count={counts.all} active={filter === 'all'} onPress={() => setFilter('all')} />
|
|
<Chip
|
|
label={t('documents.filterExpiring')}
|
|
count={counts.expiring}
|
|
active={filter === 'expiring'}
|
|
onPress={() => setFilter('expiring')}
|
|
/>
|
|
<Chip
|
|
label={t('documents.filterExpired')}
|
|
count={counts.expired}
|
|
active={filter === 'expired'}
|
|
onPress={() => setFilter('expired')}
|
|
/>
|
|
</ScrollView>
|
|
|
|
{docsQuery.isLoading ? (
|
|
<Loading />
|
|
) : (
|
|
<FlatList
|
|
data={visible}
|
|
keyExtractor={(d) => String(d.id)}
|
|
contentContainerStyle={styles.list}
|
|
refreshControl={
|
|
<RefreshControl
|
|
refreshing={docsQuery.isRefetching}
|
|
onRefresh={() => docsQuery.refetch()}
|
|
tintColor={colors.teal600}
|
|
/>
|
|
}
|
|
renderItem={({ item }) => (
|
|
<View style={{ marginBottom: spacing[3] }}>
|
|
<DocumentRow
|
|
document={item}
|
|
subtitle={`${item.propertyName} · ${catName(item.category)}`}
|
|
onPress={() => router.push(`/document/${item.id}?propertyId=${item.propertyId}`)}
|
|
/>
|
|
</View>
|
|
)}
|
|
ListEmptyComponent={
|
|
<EmptyState
|
|
icon="checkmark-circle-outline"
|
|
title={t('expiring.emptyTitle')}
|
|
subtitle={t('expiring.emptySubtitle')}
|
|
/>
|
|
}
|
|
/>
|
|
)}
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
chips: { gap: 8, paddingHorizontal: spacing[4], paddingVertical: spacing[2] },
|
|
list: { padding: spacing[4], paddingTop: spacing[2], paddingBottom: spacing[9] },
|
|
});
|