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
This commit is contained in:
Nattanin
2026-05-12 16:17:27 +07:00
parent 3df8707b7f
commit ef20839f99
82 changed files with 7052 additions and 104 deletions
@@ -0,0 +1,102 @@
'use client';
// File: components/response-code/ResponseCodeSelector.tsx
// เลือก Response Code ตาม Category ของเอกสาร (FR-006)
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { Badge } from '@/components/ui/badge';
import { useResponseCodesByDocType } from '@/hooks/use-response-codes';
import { ResponseCode } from '@/types/review-team';
interface ResponseCodeSelectorProps {
documentTypeId: number;
projectId?: number;
value?: string;
onChange: (publicId: string) => void;
disabled?: boolean;
placeholder?: string;
}
const SEVERITY_COLORS: Record<string, string> = {
'1A': 'bg-green-100 text-green-800',
'1B': 'bg-emerald-100 text-emerald-800',
'1C': 'bg-yellow-100 text-yellow-800',
'1D': 'bg-orange-100 text-orange-800',
'1E': 'bg-blue-100 text-blue-800',
'1F': 'bg-sky-100 text-sky-800',
'1G': 'bg-purple-100 text-purple-800',
'2': 'bg-amber-100 text-amber-800',
'3': 'bg-red-100 text-red-800',
'4': 'bg-gray-100 text-gray-600',
};
function CodeBadge({ code }: { code: string }) {
const colorClass = SEVERITY_COLORS[code] ?? 'bg-gray-100 text-gray-600';
return (
<span className={`inline-flex items-center rounded px-1.5 py-0.5 text-xs font-bold ${colorClass}`}>
{code}
</span>
);
}
export function ResponseCodeSelector({
documentTypeId,
projectId,
value,
onChange,
disabled,
placeholder = 'Select Response Code...',
}: ResponseCodeSelectorProps) {
const { data: codes = [], isLoading } = useResponseCodesByDocType(documentTypeId, projectId);
// กลุ่ม codes ตาม category
const grouped = (codes as ResponseCode[]).reduce<Record<string, ResponseCode[]>>(
(acc, code) => {
const cat = code.category;
if (!acc[cat]) acc[cat] = [];
acc[cat].push(code);
return acc;
},
{},
);
const categories = Object.keys(grouped);
return (
<Select value={value ?? ''} onValueChange={onChange} disabled={disabled || isLoading}>
<SelectTrigger>
<SelectValue placeholder={isLoading ? 'Loading codes...' : placeholder} />
</SelectTrigger>
<SelectContent>
{categories.length === 0 && !isLoading && (
<div className="p-3 text-sm text-muted-foreground">No codes available</div>
)}
{categories.map((cat) => (
<SelectGroup key={cat}>
<SelectLabel className="text-xs text-muted-foreground uppercase tracking-wide">
{cat}
</SelectLabel>
{grouped[cat].map((code) => (
<SelectItem key={code.publicId} value={code.publicId}>
<div className="flex items-center gap-2">
<CodeBadge code={code.code} />
<span className="text-sm">{code.descriptionEn}</span>
{(code.implications?.affectsCost || code.implications?.requiresContractReview) && (
<span className="text-xs text-orange-600 font-medium"> Action Required</span>
)}
</div>
</SelectItem>
))}
</SelectGroup>
))}
</SelectContent>
</Select>
);
}