mirror of
https://github.com/Sea-Haven-Industries/proposal-system.git
synced 2026-10-05 08:22:02 +00:00
Pin React to 19.2.3 (exact version RN 0.85.3's bundled renderer requires — 19.2.6 caused "Cannot read property 'default' of undefined" crash at getPaperRenderer). Auth: fall back to parsing user info from Cognito ID token when backend API is unreachable, removing the hard dependency on localhost:5000 for credential login. UI fixes: - Safe area insets on Settings screen (top edge was missing) - Separate refresh indicator from filter-triggered loading on proposal queue (chips no longer trigger pull-to-refresh animation) - Dashboard welcome shows first name instead of full email - Dashboard/AdminDashboard remove duplicate top safe area when navigation header already provides it - Service category selector uses wrapping chips instead of cramped SegmentedButtons - Add ErrorBoundary to App root for crash visibility
154 lines
4 KiB
TypeScript
154 lines
4 KiB
TypeScript
import React, { useState } from 'react';
|
|
import {
|
|
View,
|
|
StyleSheet,
|
|
FlatList,
|
|
RefreshControl,
|
|
} from 'react-native';
|
|
import { Searchbar, Chip, FAB } from 'react-native-paper';
|
|
import { useNavigation } from '@react-navigation/native';
|
|
import type { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
|
import { useSelector } from 'react-redux';
|
|
import { selectUser } from '../../store/slices/authSlice';
|
|
import { usePaginatedList } from '../../hooks/usePaginatedList';
|
|
import { proposalsApi, type ProposalListItem } from '../../lib/api/proposals';
|
|
import { ProposalCard } from '../../components/ProposalCard';
|
|
import { EmptyState } from '../../components/EmptyState';
|
|
import { PROPOSAL_STATUSES } from '../../constants';
|
|
import type { ProposalsStackParamList } from '../../navigation/types';
|
|
|
|
type Nav = NativeStackNavigationProp<ProposalsStackParamList>;
|
|
|
|
export function ProposalQueueScreen() {
|
|
const navigation = useNavigation<Nav>();
|
|
const user = useSelector(selectUser);
|
|
const isAdmin = user?.role === 'Admin' || user?.role === 'SysAdmin';
|
|
const [statusFilter, setStatusFilter] = useState<string | null>(null);
|
|
|
|
const {
|
|
rows,
|
|
search,
|
|
setSearch,
|
|
loading,
|
|
refreshing,
|
|
reload,
|
|
} = usePaginatedList<ProposalListItem>(
|
|
(params) =>
|
|
proposalsApi.getAll({
|
|
search: params.search as string | undefined,
|
|
page: params.page as number | undefined,
|
|
pageSize: params.pageSize as number | undefined,
|
|
status: (params.status as string) || undefined,
|
|
}),
|
|
{ status: statusFilter },
|
|
);
|
|
|
|
const handlePress = (proposal: ProposalListItem) => {
|
|
if (isAdmin) {
|
|
navigation.navigate('ProposalWorkspace', { proposalId: proposal.id });
|
|
} else {
|
|
navigation.navigate('ProposalDetail', { proposalId: proposal.id });
|
|
}
|
|
};
|
|
|
|
return (
|
|
<View style={styles.container}>
|
|
<Searchbar
|
|
placeholder="Search proposals..."
|
|
onChangeText={setSearch}
|
|
value={search}
|
|
style={styles.search}
|
|
/>
|
|
|
|
<View style={styles.filters}>
|
|
<Chip
|
|
selected={!statusFilter}
|
|
onPress={() => setStatusFilter(null)}
|
|
compact
|
|
style={styles.chip}
|
|
>
|
|
All
|
|
</Chip>
|
|
{PROPOSAL_STATUSES.map((s) => (
|
|
<Chip
|
|
key={s}
|
|
selected={statusFilter === s}
|
|
onPress={() =>
|
|
setStatusFilter(statusFilter === s ? null : s)
|
|
}
|
|
compact
|
|
style={styles.chip}
|
|
>
|
|
{s === 'InReview' ? 'In Review' : s}
|
|
</Chip>
|
|
))}
|
|
</View>
|
|
|
|
<FlatList
|
|
data={rows}
|
|
keyExtractor={(item) => item.id}
|
|
renderItem={({ item }) => (
|
|
<ProposalCard proposal={item} onPress={() => handlePress(item)} />
|
|
)}
|
|
refreshControl={
|
|
<RefreshControl refreshing={refreshing} onRefresh={reload} />
|
|
}
|
|
contentContainerStyle={rows.length === 0 ? styles.empty : styles.list}
|
|
ListEmptyComponent={
|
|
!loading ? (
|
|
<EmptyState
|
|
icon="file-search-outline"
|
|
title="No proposals found"
|
|
message={
|
|
search
|
|
? 'Try a different search term'
|
|
: 'No proposals match the current filter'
|
|
}
|
|
/>
|
|
) : null
|
|
}
|
|
/>
|
|
|
|
{!isAdmin && (
|
|
<FAB
|
|
icon="plus"
|
|
style={styles.fab}
|
|
onPress={() => navigation.navigate('NewProposal')}
|
|
/>
|
|
)}
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
flex: 1,
|
|
backgroundColor: '#FAFAFA',
|
|
},
|
|
search: {
|
|
margin: 12,
|
|
elevation: 1,
|
|
},
|
|
filters: {
|
|
flexDirection: 'row',
|
|
paddingHorizontal: 12,
|
|
paddingBottom: 8,
|
|
gap: 6,
|
|
flexWrap: 'wrap',
|
|
},
|
|
chip: {
|
|
marginBottom: 4,
|
|
},
|
|
list: {
|
|
paddingBottom: 80,
|
|
},
|
|
empty: {
|
|
flex: 1,
|
|
},
|
|
fab: {
|
|
position: 'absolute',
|
|
right: 16,
|
|
bottom: 16,
|
|
backgroundColor: '#1565C0',
|
|
},
|
|
});
|