Files
lcbp3/frontend/app/(dashboard)/settings/review-teams/page.tsx
T
Nattanin ef20839f99 feat(rfa): complete RFA Approval Refactor - all 9 phases (T001-T080)
Phase 1-2: Setup, SQL schema, enums, queue constants, base entities
Phase 3 (US1): ReviewTeam, ReviewTeamMember, ReviewTask, TaskCreationService
Phase 4 (US2): ResponseCode, ResponseCodeRule, ImplicationsService, NotificationTriggerService
Phase 5 (US3): Delegation entity, CircularDetectionService, DelegationService/Controller/Module
Phase 6 (US4): ReminderRule, SchedulerService, EscalationService, ReminderProcessor, ReminderModule
Phase 7 (US5): DistributionMatrix, DistributionRecipient, ApprovalListenerService (Strangler),
               TransmittalCreatorService, DistributionProcessor, DistributionModule
Phase 8 (US6): MatrixManagementService, InheritanceService (global→project override)
Phase 9 (Polish): AggregateStatusService, ConsensusService, VetoOverrideService,
                  ParallelGatewayHandler, review-validators, optimistic locking in completeReview,
                  test stubs (unit/integration/e2e), jest.config.js updated for tests/ directory

Frontend: ReviewTaskInbox, ParallelProgress, VetoOverrideDialog, DelegationForm,
          DelegatedBadge, MatrixEditor, ProjectOverrideManager, DistributionStatus,
          ReminderHistory, ResponseCodeSelector, CodeImplications, CompleteReviewForm,
          ReviewTeamForm, ReviewTeamSelector, TeamMemberManager

Closes #1
2026-05-12 16:17:27 +07:00

168 lines
5.9 KiB
TypeScript

'use client';
// File: app/(dashboard)/settings/review-teams/page.tsx
// หน้าจัดการ Review Teams (FR-001, FR-002)
import { useState } from 'react';
import { Plus, Users, ChevronDown, ChevronUp } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@/components/ui/dialog';
import { useReviewTeams, useCreateReviewTeam, useUpdateReviewTeam } from '@/hooks/use-review-teams';
import { ReviewTeamForm } from '@/components/review-team/ReviewTeamForm';
import { TeamMemberManager } from '@/components/review-team/TeamMemberManager';
import { ReviewTeam } from '@/types/review-team';
// TODO: ดึง projectPublicId จาก context หรือ URL param จริง
const MOCK_PROJECT_ID = 'current-project-public-id';
export default function ReviewTeamsPage() {
const [expandedTeam, setExpandedTeam] = useState<string | null>(null);
const [createOpen, setCreateOpen] = useState(false);
const [editTeam, setEditTeam] = useState<ReviewTeam | null>(null);
const { data: teams = [], isLoading } = useReviewTeams({
projectPublicId: MOCK_PROJECT_ID,
});
const createTeam = useCreateReviewTeam();
const updateTeam = useUpdateReviewTeam();
return (
<div className="p-6 space-y-6">
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold">Review Teams</h1>
<p className="text-muted-foreground text-sm mt-1">
Discipline Parallel Review
</p>
</div>
<Dialog open={createOpen} onOpenChange={setCreateOpen}>
<DialogTrigger asChild>
<Button>
<Plus className="h-4 w-4 mr-2" />
New Team
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Create Review Team</DialogTitle>
</DialogHeader>
<ReviewTeamForm
projectPublicId={MOCK_PROJECT_ID}
onSubmit={(values) =>
createTeam.mutate(values, {
onSuccess: () => setCreateOpen(false),
})
}
isLoading={createTeam.isPending}
/>
</DialogContent>
</Dialog>
</div>
{isLoading && (
<div className="text-center text-muted-foreground py-8">Loading teams...</div>
)}
<div className="space-y-3">
{(teams as ReviewTeam[]).map((team) => (
<Card key={team.publicId}>
<CardHeader className="pb-2">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<Users className="h-5 w-5 text-muted-foreground" />
<CardTitle className="text-base">{team.name}</CardTitle>
{!team.isActive && (
<Badge variant="secondary">Inactive</Badge>
)}
{(team.defaultForRfaTypes ?? []).map((type) => (
<Badge key={type} variant="outline" className="text-xs">
{type}
</Badge>
))}
</div>
<div className="flex items-center gap-2">
<Button
variant="ghost"
size="sm"
onClick={() => setEditTeam(team)}
>
Edit
</Button>
<Button
variant="ghost"
size="icon"
onClick={() =>
setExpandedTeam(expandedTeam === team.publicId ? null : team.publicId)
}
>
{expandedTeam === team.publicId ? (
<ChevronUp className="h-4 w-4" />
) : (
<ChevronDown className="h-4 w-4" />
)}
</Button>
</div>
</div>
{team.description && (
<p className="text-xs text-muted-foreground mt-1">{team.description}</p>
)}
</CardHeader>
{expandedTeam === team.publicId && (
<CardContent>
<div className="text-sm font-medium mb-3">
Members ({(team.members ?? []).length})
</div>
<TeamMemberManager
teamPublicId={team.publicId}
members={team.members ?? []}
availableUsers={[]}
availableDisciplines={[]}
/>
</CardContent>
)}
</Card>
))}
{!isLoading && (teams as ReviewTeam[]).length === 0 && (
<div className="text-center py-12 text-muted-foreground">
<Users className="h-12 w-12 mx-auto mb-3 opacity-30" />
<p>No Review Teams yet. Create one to enable Parallel Review.</p>
</div>
)}
</div>
{/* Edit Dialog */}
<Dialog open={!!editTeam} onOpenChange={() => setEditTeam(null)}>
<DialogContent>
<DialogHeader>
<DialogTitle>Edit Review Team</DialogTitle>
</DialogHeader>
{editTeam && (
<ReviewTeamForm
projectPublicId={MOCK_PROJECT_ID}
defaultValues={editTeam}
onSubmit={(values) =>
updateTeam.mutate(
{ publicId: editTeam.publicId, data: values },
{ onSuccess: () => setEditTeam(null) },
)
}
isLoading={updateTeam.isPending}
/>
)}
</DialogContent>
</Dialog>
</div>
);
}