156 lines
5.3 KiB
TypeScript
156 lines
5.3 KiB
TypeScript
import {
|
|
AppstoreOutlined,
|
|
CheckCircleOutlined,
|
|
ClockCircleOutlined,
|
|
CloseCircleOutlined,
|
|
PlusOutlined,
|
|
} from "@ant-design/icons";
|
|
import { Button, Empty, Segmented, Skeleton } from "antd";
|
|
import { useMemo, useState } from "react";
|
|
|
|
import type { Material } from "../types";
|
|
|
|
import { MaterialCard } from "./MaterialCard";
|
|
import { MaterialGroup } from "./MaterialGroup";
|
|
|
|
type DateGroup = "earlier" | "thisMonth" | "thisWeek" | "today" | "yesterday";
|
|
|
|
interface MaterialListProps {
|
|
loading: boolean;
|
|
materials: readonly Material[];
|
|
onAddClick: () => void;
|
|
onDelete: (id: string) => void;
|
|
onSelect: (id: string) => void;
|
|
selectedId: null | string;
|
|
}
|
|
|
|
const GROUP_LABELS: Record<DateGroup, string> = {
|
|
earlier: "更早",
|
|
thisMonth: "本月",
|
|
thisWeek: "本周",
|
|
today: "今天",
|
|
yesterday: "昨天",
|
|
};
|
|
|
|
const GROUP_ORDER: readonly DateGroup[] = ["today", "yesterday", "thisWeek", "thisMonth", "earlier"];
|
|
|
|
interface MaterialGroupData {
|
|
items: Material[];
|
|
key: DateGroup;
|
|
}
|
|
|
|
function getDateGroup(dateStr: string, now: Date): DateGroup {
|
|
const date = new Date(dateStr);
|
|
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
const yesterday = new Date(today.getTime() - 86_400_000);
|
|
const dateDay = new Date(date.getFullYear(), date.getMonth(), date.getDate());
|
|
|
|
if (dateDay.getTime() >= today.getTime()) return "today";
|
|
if (dateDay.getTime() >= yesterday.getTime()) return "yesterday";
|
|
|
|
const dayOfWeek = today.getDay();
|
|
const mondayOffset = dayOfWeek === 0 ? 6 : dayOfWeek - 1;
|
|
const monday = new Date(today.getTime() - mondayOffset * 86_400_000);
|
|
if (dateDay.getTime() >= monday.getTime()) return "thisWeek";
|
|
|
|
if (dateDay.getFullYear() === today.getFullYear() && dateDay.getMonth() === today.getMonth()) {
|
|
return "thisMonth";
|
|
}
|
|
|
|
return "earlier";
|
|
}
|
|
|
|
function groupMaterialsByDate(materials: readonly Material[]): MaterialGroupData[] {
|
|
const now = new Date();
|
|
const groups = new Map<DateGroup, Material[]>();
|
|
|
|
for (const m of materials) {
|
|
const group = getDateGroup(m.createdAt, now);
|
|
if (!groups.has(group)) groups.set(group, []);
|
|
groups.get(group)!.push(m);
|
|
}
|
|
|
|
return GROUP_ORDER.map((key) => ({ items: groups.get(key) ?? [], key }));
|
|
}
|
|
|
|
const STATUS_FILTER_OPTIONS = [
|
|
{ icon: <AppstoreOutlined />, label: "全部", value: "all" },
|
|
{ color: "#faad14", icon: <ClockCircleOutlined />, label: "待审核", value: "pending" },
|
|
{ color: "#52c41a", icon: <CheckCircleOutlined />, label: "已通过", value: "approved" },
|
|
{ color: "#ff4d4f", icon: <CloseCircleOutlined />, label: "已放弃", value: "discarded" },
|
|
] as const;
|
|
|
|
type FilterValue = (typeof STATUS_FILTER_OPTIONS)[number]["value"];
|
|
|
|
export function MaterialList({ loading, materials, onAddClick, onDelete, onSelect, selectedId }: MaterialListProps) {
|
|
const [filterStatus, setFilterStatus] = useState<FilterValue>("all");
|
|
|
|
const filteredMaterials = useMemo(() => {
|
|
if (filterStatus === "all") return materials;
|
|
return materials.filter((m) => m.status === filterStatus);
|
|
}, [materials, filterStatus]);
|
|
|
|
const groupedMaterials = useMemo(() => groupMaterialsByDate(filteredMaterials), [filteredMaterials]);
|
|
|
|
const segmentedOptions = useMemo(
|
|
() =>
|
|
STATUS_FILTER_OPTIONS.map((opt) => ({
|
|
label: (
|
|
<span>
|
|
{"color" in opt && opt.color ? <span style={{ color: opt.color }}>{opt.icon}</span> : opt.icon}
|
|
<span className="app-inbox-filter-count">{getStatusCount(materials, opt.value)}</span>
|
|
</span>
|
|
),
|
|
value: opt.value,
|
|
})),
|
|
[materials],
|
|
);
|
|
|
|
const showAllGroups = filterStatus === "all";
|
|
|
|
return (
|
|
<div className="app-inbox-sidebar">
|
|
<Button block icon={<PlusOutlined />} onClick={onAddClick} type="primary">
|
|
新增素材
|
|
</Button>
|
|
<Segmented block onChange={(value) => setFilterStatus(value)} options={segmentedOptions} value={filterStatus} />
|
|
<div className="app-inbox-list">
|
|
{loading ? (
|
|
<Skeleton active paragraph={{ rows: 6 }} title={false} />
|
|
) : materials.length === 0 ? (
|
|
<Empty description="暂无素材" image={Empty.PRESENTED_IMAGE_SIMPLE} />
|
|
) : filteredMaterials.length === 0 ? (
|
|
<Empty description="当前筛选条件下无素材" image={Empty.PRESENTED_IMAGE_SIMPLE} />
|
|
) : (
|
|
groupedMaterials.map((group) => {
|
|
if (!showAllGroups && group.items.length === 0) return null;
|
|
return (
|
|
<MaterialGroup
|
|
count={group.items.length}
|
|
emptyText="暂无"
|
|
key={group.key}
|
|
label={GROUP_LABELS[group.key]}
|
|
>
|
|
{group.items.map((material) => (
|
|
<MaterialCard
|
|
key={material.id}
|
|
material={material}
|
|
onDelete={() => onDelete(material.id)}
|
|
onSelect={() => onSelect(material.id)}
|
|
selected={material.id === selectedId}
|
|
/>
|
|
))}
|
|
</MaterialGroup>
|
|
);
|
|
})
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function getStatusCount(materials: readonly Material[], status: string): number {
|
|
if (status === "all") return materials.length;
|
|
return materials.filter((m) => m.status === status).length;
|
|
}
|