/**
* 企业端 · 收到的投递(工作台列表)
*
* 这是企业侧投递链路的第一层:一行一个**投递**(不是候选人),
* 展示状态、岗位、时间与下一步动作。状态变更与候选人反馈都从详情页发起。
*
* 设计取舍:
* - 筛选条上的计数来自**独立于筛选的**计数口,切到「面试中」不会让其他状态计数归零
* - 用 useFocusEffect 在返回时刷新:详情页改完状态后回到列表,徽标必须已经跟着变,
* 否则用户会以为刚才那次操作没生效
*/
import { Ionicons } from '@expo/vector-icons';
import { useFocusEffect, useRouter } from 'expo-router';
import { useCallback, useRef, useState } from 'react';
import {
Pressable,
ScrollView,
StyleSheet,
Text,
TextInput,
View,
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { EmptyState, ErrorState, LoadingState } from '../src/components/StateView';
import {
APPLICATION_STATUS_LABELS,
COMPANY_APPLICATION_FILTERS,
ENTERPRISE_DISPLAY_NAME,
} from '../src/constants';
import { useCompanyApplications } from '../src/hooks';
import { applicationStatusColors, colors, spacing } from '../src/theme';
import type { ApplicationStatus, CompanyApplicationItem } from '../src/types';
/** 把 ISO 时间压成「9-28 10:12」这种一眼能排序的相对短格式 */
function formatAppliedAt(iso?: string): string {
if (!iso) return '';
const date = new Date(iso);
if (Number.isNaN(date.getTime())) return '';
const mm = date.getMonth() + 1;
const dd = date.getDate();
const hh = date.getHours().toString().padStart(2, '0');
const mi = date.getMinutes().toString().padStart(2, '0');
return `${mm}-${dd} ${hh}:${mi}`;
}
/* ===================== 子组件 ===================== */
function StatusBadge({ status }: { status: ApplicationStatus }) {
const tone = applicationStatusColors[status];
return (
{APPLICATION_STATUS_LABELS[status]}
);
}
/** 单行投递。整行可点 → 进详情做状态流转与反馈 */
function ApplicationRow({
item,
onPress,
}: {
item: CompanyApplicationItem;
onPress: () => void;
}) {
return (
{item.candidateName.slice(0, 1)}
{item.candidateName}
{[item.candidateSchool, item.candidateEducation, item.candidateGraduationYear
? `${item.candidateGraduationYear} 届`
: undefined]
.filter(Boolean)
.join(' · ') || '学历信息未公开'}
{item.jobTitle}
{formatAppliedAt(item.createdAt)}
);
}
/* ===================== 页面 ===================== */
export default function EnterpriseApplicationsScreen() {
const router = useRouter();
const [status, setStatus] = useState(null);
const [keyword, setKeyword] = useState('');
const { applications, total, counts, loading, error, refresh, isEmpty } =
useCompanyApplications({ status, keyword });
/**
* 从详情页返回时刷新:状态改了、反馈写了,列表上的数字与徽标都要跟着变。
*
* refresh 的引用会随筛选条件(keyword)变化,直接写进 useCallback 依赖的话,
* 每敲一个字都会重跑一次 focus effect。故用 ref 取最新值,effect 依赖留空。
*/
const refreshRef = useRef(refresh);
refreshRef.current = refresh;
useFocusEffect(
useCallback(() => {
refreshRef.current();
}, []),
);
const allCount = Object.values(counts).reduce((sum, n) => sum + n, 0);
return (
{/* 顶部导航 */}
router.back()} hitSlop={8}>
{ENTERPRISE_DISPLAY_NAME}
收到的投递
{/* 关键词搜索 */}
{keyword.length > 0 ? (
setKeyword('')} hitSlop={8} accessibilityLabel="清空搜索">
) : null}
{/* 状态筛选:计数来自独立计数口,不随当前筛选变化 */}
{COMPANY_APPLICATION_FILTERS.map((filter) => {
const active = filter.value === status;
const count = filter.value === null ? allCount : counts[filter.value] ?? 0;
return (
setStatus(filter.value)}
>
{filter.label} {count}
);
})}
{/* 列表区:四态齐全 */}
{loading && applications.length === 0 ? (
) : error ? (
) : isEmpty ? (
{
setKeyword('');
setStatus(null);
}
: undefined
}
/>
) : (
共 {total} 条投递
{status ? ` · 已按「${APPLICATION_STATUS_LABELS[status]}」筛选` : ''}
{applications.map((item) => (
router.push(`/application-detail?id=${item.applicationId}`)}
/>
))}
)}
);
}
const styles = StyleSheet.create({
safeArea: {
flex: 1,
backgroundColor: colors.pageBg,
},
/* ---- 顶部导航 ---- */
header: {
height: 48,
flexDirection: 'row',
alignItems: 'center',
},
headerBtn: {
width: 48,
height: 48,
alignItems: 'center',
justifyContent: 'center',
},
headerCenter: {
flex: 1,
alignItems: 'center',
},
headerCompany: {
fontSize: 11,
color: colors.textHint,
},
headerTitle: {
fontSize: 17,
fontWeight: '700',
color: colors.textPrimary,
},
/* ---- 搜索 ---- */
searchWrap: {
flexDirection: 'row',
alignItems: 'center',
gap: 6,
height: 36,
marginHorizontal: spacing.framework,
paddingHorizontal: spacing.card,
borderRadius: spacing.card,
backgroundColor: colors.white,
borderWidth: 1,
borderColor: colors.border,
},
searchInput: {
flex: 1,
fontSize: 13,
color: colors.textPrimary,
padding: 0,
},
/* ---- 筛选 ---- */
filterRow: {
paddingHorizontal: spacing.framework,
paddingVertical: spacing.card,
gap: spacing.card,
},
filterChip: {
paddingHorizontal: 12,
height: 30,
borderRadius: 15,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: colors.white,
borderWidth: 1,
borderColor: colors.border,
},
filterChipActive: {
backgroundColor: colors.primaryLight,
borderColor: colors.primary,
},
filterText: {
fontSize: 12,
color: colors.textSecondary,
},
filterTextActive: {
color: colors.primaryText,
fontWeight: '600',
},
/* ---- 列表 ---- */
list: {
flex: 1,
},
listContent: {
paddingHorizontal: spacing.framework,
paddingBottom: 24,
},
summary: {
marginBottom: spacing.card,
fontSize: 12,
color: colors.textHint,
},
row: {
flexDirection: 'row',
alignItems: 'center',
gap: spacing.card,
backgroundColor: colors.white,
borderRadius: spacing.card,
padding: spacing.card + 4,
marginBottom: spacing.card,
},
avatar: {
width: 40,
height: 40,
borderRadius: 20,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: colors.primaryLight,
},
avatarText: {
fontSize: 16,
fontWeight: '700',
color: colors.primaryText,
},
rowMain: {
flex: 1,
},
rowTop: {
flexDirection: 'row',
alignItems: 'center',
gap: spacing.card,
},
name: {
flexShrink: 1,
fontSize: 15,
fontWeight: '700',
color: colors.textPrimary,
},
badge: {
paddingHorizontal: 8,
paddingVertical: 2,
borderRadius: spacing.card,
},
badgeText: {
fontSize: 11,
fontWeight: '600',
},
rowMeta: {
marginTop: 4,
fontSize: 12,
color: colors.textSecondary,
},
rowBottom: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
marginTop: 6,
},
jobChip: {
flexDirection: 'row',
alignItems: 'center',
gap: 4,
flexShrink: 1,
},
jobChipText: {
fontSize: 11,
color: colors.textSecondary,
},
time: {
fontSize: 11,
color: colors.textHint,
},
});