AwsLinker/app/components/consultation/ConsultationDetailPageClient.tsx
2025-09-16 17:19:58 +08:00

203 lines
8.4 KiB
TypeScript

'use client';
import { useState, useEffect } from 'react';
import { useRouter } from 'next/navigation';
import { useTranslation } from 'react-i18next';
import { Button } from '@/app/components/ui/button';
import { Card, CardContent } from '@/app/components/ui/card';
import { Badge } from '@/app/components/ui/badge';
import { Skeleton } from '@/app/components/ui/skeleton';
import { ArrowLeft, Calendar, User, Tag, Phone, Mail, MessageSquare } from 'lucide-react';
import { formatDate } from '@/lib/utils';
interface ConsultationDetailPageClientProps {
consultationId: string;
locale: string;
}
interface Consultation {
id: string;
metadata: {
title: string;
date: string;
author: string;
category: string;
status: string;
contact: {
name: string;
phone: string;
email: string;
message: string;
};
};
}
export default function ConsultationDetailPageClient({
consultationId,
locale,
}: ConsultationDetailPageClientProps) {
const router = useRouter();
const { t } = useTranslation(['consultation', 'common']);
const [consultation, setConsultation] = useState<Consultation | null>(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const fetchConsultation = async () => {
try {
const response = await fetch(
`/api/consultations/${consultationId}?locale=${locale}`,
);
if (!response.ok) {
if (response.status === 404) {
throw new Error(t('consultation.error.notFound'));
}
throw new Error(t('consultation.error.fetchFailed'));
}
const data = await response.json();
setConsultation(data.consultation);
} catch (err) {
setError(err instanceof Error ? err.message : t('consultation.error.unknown'));
} finally {
setIsLoading(false);
}
};
fetchConsultation();
}, [consultationId, locale, t]);
// 渲染加载状态
const renderSkeleton = () => (
<div className="container mx-auto px-4 py-8" data-oid="sxmuh4m">
<div className="max-w-4xl mx-auto" data-oid="nvvi7m3">
<Skeleton className="h-12 w-3/4 mb-8" data-oid="rjja.9:" />
<div className="space-y-4 mb-8" data-oid=":elb21i">
<Skeleton className="h-6 w-24" data-oid="7hbea.3" />
<Skeleton className="h-6 w-24" data-oid="j--iy-s" />
<Skeleton className="h-6 w-24" data-oid="huo.du2" />
</div>
<div className="space-y-4" data-oid="-.-t5x6">
<Skeleton className="h-4 w-full" data-oid=":-y-sy:" />
<Skeleton className="h-4 w-full" data-oid="lexxi27" />
<Skeleton className="h-4 w-3/4" data-oid="lk3ih5q" />
</div>
</div>
</div>
);
// 渲染错误状态
const renderError = () => (
<div className="container mx-auto px-4 py-8" data-oid="nk0sm3m">
<div className="max-w-4xl mx-auto text-center" data-oid="wkl4avi">
<h2 className="text-2xl font-bold text-red-600 mb-4" data-oid="al5.d3h">
{t('consultation.error.title')}
</h2>
<p className="text-gray-600 mb-6" data-oid="inkewhh">
{error}
</p>
<Button onClick={() => router.back()} variant="outline" data-oid="7qp7p7o">
{t('consultation.error.goBack')}
</Button>
</div>
</div>
);
if (isLoading) {
return renderSkeleton();
}
if (error || !consultation) {
return renderError();
}
return (
<div className="container mx-auto px-4 py-8" data-oid="necnv9y">
<div className="max-w-4xl mx-auto" data-oid="m43sd-3">
{/* 返回按钮 */}
<Button
variant="ghost"
className="mb-8"
onClick={() => router.back()}
data-oid="h5u87_m"
>
<ArrowLeft className="mr-2" size={20} data-oid="w8ntmqo" />
{t('consultation.backToList')}
</Button>
{/* 咨询标题 */}
<h1 className="text-4xl font-bold mb-8" data-oid="iq340lp">
{consultation.metadata.title}
</h1>
{/* 咨询元数据 */}
<div
className="flex flex-wrap items-center gap-4 mb-8 text-gray-600"
data-oid="gz-1uwt"
>
<div className="flex items-center" data-oid="snd80xz">
<Calendar className="mr-2" size={16} data-oid="0_v45vt" />
{formatDate(consultation.metadata.date, locale)}
</div>
<div className="flex items-center" data-oid="4u.95u-">
<User className="mr-2" size={16} data-oid="ba_jol7" />
{consultation.metadata.author}
</div>
<div className="flex items-center" data-oid="j_am5.w">
<Tag className="mr-2" size={16} data-oid="8dfyx_o" />
<Badge variant="secondary" data-oid="_:-v0x6">
{t(`consultation.categories.${consultation.metadata.category}`)}
</Badge>
</div>
<div className="flex items-center" data-oid="v9kzwmm">
<Badge
variant={
consultation.metadata.status === 'pending'
? 'outline'
: consultation.metadata.status === 'in_progress'
? 'secondary'
: 'default'
}
data-oid=":8-06ys"
>
{t(`consultation.status.${consultation.metadata.status}`)}
</Badge>
</div>
</div>
{/* 联系信息 */}
<Card className="mb-8" data-oid="8uha3r3">
<CardContent className="p-6" data-oid="-mib74k">
<h2 className="text-xl font-semibold mb-4" data-oid="2n.4k5h">
{t('consultation.contactInfo')}
</h2>
<div className="space-y-4" data-oid="4:.ptqu">
<div className="flex items-center" data-oid="_u0178p">
<User className="mr-2" size={16} data-oid="8dbq.i-" />
<span data-oid="nxaq1w8">{consultation.metadata.contact.name}</span>
</div>
<div className="flex items-center" data-oid=":3s8nk7">
<Phone className="mr-2" size={16} data-oid="nj78b25" />
<span data-oid="7yj8j61">
{consultation.metadata.contact.phone}
</span>
</div>
<div className="flex items-center" data-oid="jmg55i2">
<Mail className="mr-2" size={16} data-oid="lsc_6:d" />
<span data-oid="c5t2gbe">
{consultation.metadata.contact.email}
</span>
</div>
<div className="flex items-start" data-oid="rdyas_p">
<MessageSquare className="mr-2 mt-1" size={16} data-oid="0_30ig-" />
<span className="whitespace-pre-wrap" data-oid="4v0sw8j">
{consultation.metadata.contact.message}
</span>
</div>
</div>
</CardContent>
</Card>
</div>
</div>
);
}