Files
casadoc-app/app/documents/[propertyId].tsx
T
2026-08-26 23:01:07 +03:00

121 lines
4.9 KiB
TypeScript

import React, { useMemo, useState } from 'react';
import { FlatList, ScrollView, StyleSheet, View } from 'react-native';
import { useLocalSearchParams, useRouter } from 'expo-router';
import { useQuery } from '@tanstack/react-query';
import { api, DocCategory, Document } from '@/api';
import { useT } from '@/i18n';
import { AppBar } from '@/components/AppBar';
import { Chip, EmptyState, IconButton, Loading } from '@/components/ui';
import { DocumentRow } from '@/components/domain';
import { colors, spacing } from '@/theme';
type StatusFilter = 'all' | 'attention' | 'valid' | 'expiring' | 'expired';
const STATUSES: StatusFilter[] = ['all', 'attention', 'valid', 'expiring', 'expired'];
export default function DocumentsScreen() {
const { propertyId, category, status: initialStatus } = useLocalSearchParams<{
propertyId: string;
category?: string;
status?: string;
}>();
const pid = Number(propertyId);
const router = useRouter();
const t = useT();
// Links can preselect a filter (e.g. the "needs attention" banner).
const [status, setStatus] = useState<StatusFilter>(
STATUSES.includes(initialStatus as StatusFilter) ? (initialStatus as StatusFilter) : 'all',
);
const docsQuery = useQuery({ queryKey: ['documents', pid], queryFn: () => api.documents.listByProperty(pid) });
const propertyQuery = useQuery({ queryKey: ['property', pid], queryFn: () => api.properties.get(pid) });
const catsQuery = useQuery({ queryKey: ['categories'], queryFn: () => api.reference.categories() });
const categories: DocCategory[] = catsQuery.data ?? [];
const catName = (slug: string) => categories.find((c) => c.slug === slug)?.name ?? slug;
const inCategory = useMemo<Document[]>(
() => ((docsQuery.data ?? []) as Document[]).filter((d) => !category || d.category === category),
[docsQuery.data, category],
);
const counts = useMemo(() => {
const c = { all: inCategory.length, attention: 0, valid: 0, expiring: 0, expired: 0 };
for (const d of inCategory) {
if (d.expiryStatus === 'valid') c.valid += 1;
else if (d.expiryStatus === 'expiring') c.expiring += 1;
else if (d.expiryStatus === 'expired') c.expired += 1;
}
c.attention = c.expiring + c.expired;
return c;
}, [inCategory]);
const visible = useMemo(() => {
if (status === 'all') return inCategory;
if (status === 'attention') {
return inCategory.filter((d) => d.expiryStatus === 'expiring' || d.expiryStatus === 'expired');
}
return inCategory.filter((d) => d.expiryStatus === status);
}, [inCategory, status]);
const title = category ? catName(category) : t('documents.allTitle');
const subtitle = `${propertyQuery.data?.name ?? ''} · ${t('properties.docCount', { count: inCategory.length })}`;
return (
<View style={{ flex: 1, backgroundColor: colors.bgApp }}>
<AppBar
title={title}
subtitle={subtitle}
back
right={<IconButton name="search" onPress={() => router.push('/search')} />}
/>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={styles.chips}
style={{ flexGrow: 0 }}
>
<Chip label={t('common.all')} count={counts.all} active={status === 'all'} onPress={() => setStatus('all')} />
<Chip
label={t('documents.filterAttention')}
count={counts.attention}
active={status === 'attention'}
onPress={() => setStatus('attention')}
/>
<Chip label={t('documents.filterValid')} count={counts.valid} active={status === 'valid'} onPress={() => setStatus('valid')} />
<Chip label={t('documents.filterExpiring')} count={counts.expiring} active={status === 'expiring'} onPress={() => setStatus('expiring')} />
<Chip label={t('documents.filterExpired')} count={counts.expired} active={status === 'expired'} onPress={() => setStatus('expired')} />
</ScrollView>
{docsQuery.isLoading ? (
<Loading />
) : (
<FlatList
data={visible as Document[]}
keyExtractor={(d) => String(d.id)}
contentContainerStyle={styles.list}
renderItem={({ item }) => (
<View style={{ marginBottom: spacing[3] }}>
<DocumentRow
document={item}
categoryName={catName(item.category)}
showCategory={!category}
onPress={() => router.push(`/document/${item.id}?propertyId=${pid}`)}
/>
</View>
)}
ListEmptyComponent={
<EmptyState icon="documents-outline" title={t('documents.emptyTitle')} subtitle={t('documents.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] },
});