# TASK-FE-004: Correspondence Management UI **ID:** TASK-FE-004 **Title:** Correspondence List, Create, View & Edit UI **Category:** Business Modules **Priority:** P1 (High) **Effort:** 5-7 days **Dependencies:** TASK-FE-003, TASK-BE-005 **Assigned To:** Frontend Developer --- ## 📋 Overview Build complete UI for Correspondence Management including list view with filters, create/edit forms, detail view, and status workflows. --- ## 🎯 Objectives 1. Create correspondence list with pagination and filters 2. Implement create/edit forms with validation 3. Build detail view with attachments 4. Add status workflow actions (Submit, Approve, Reject) 5. Implement file upload for attachments 6. Add search and filtering --- ## ✅ Acceptance Criteria - [ ] List displays correspondences with pagination - [ ] Filter by status, date range, organization - [ ] Create form validates all required fields - [ ] File attachments upload successfully - [ ] Detail view shows complete information - [ ] Workflow actions work (Submit, Approve, Reject) - [ ] Real-time status updates --- ## 🔧 Implementation Steps ### Step 1: Correspondence List Page ```typescript // File: src/app/(dashboard)/correspondences/page.tsx import { CorrespondenceList } from '@/components/correspondences/list'; import { CorrespondenceFilters } from '@/components/correspondences/filters'; import { Button } from '@/components/ui/button'; import Link from 'next/link'; import { Plus } from 'lucide-react'; import { getCorrespondences } from '@/lib/api/correspondences'; export default async function CorrespondencesPage({ searchParams, }: { searchParams: { page?: string; status?: string; search?: string }; }) { const page = parseInt(searchParams.page || '1'); const data = await getCorrespondences({ page, status: searchParams.status, search: searchParams.search, }); return (

Correspondences

Manage official letters and communications

); } ``` ### Step 2: Correspondence List Component ```typescript // File: src/components/correspondences/list.tsx 'use client'; import { useState } from 'react'; import { Correspondence } from '@/types'; import { Card } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import Link from 'next/link'; import { format } from 'date-fns'; import { Eye, Edit } from 'lucide-react'; import { Pagination } from '@/components/common/pagination'; interface CorrespondenceListProps { data: { items: Correspondence[]; total: number; page: number; totalPages: number; }; } export function CorrespondenceList({ data }: CorrespondenceListProps) { const getStatusColor = (status: string) => { const colors = { DRAFT: 'gray', PENDING: 'yellow', IN_REVIEW: 'blue', APPROVED: 'green', REJECTED: 'red', }; return colors[status] || 'gray'; }; return (
{data.items.map((item) => (

{item.subject}

{item.status}

{item.description || 'No description'}

From: {item.from_organization?.org_name} To: {item.to_organization?.org_name} Date:{' '} {format(new Date(item.created_at), 'dd MMM yyyy')}
{item.status === 'DRAFT' && ( )}
))}
); } ``` ### Step 3: Create/Edit Form ```typescript // File: src/app/(dashboard)/correspondences/new/page.tsx import { CorrespondenceForm } from '@/components/correspondences/form'; export default function NewCorrespondencePage() { return (

New Correspondence

); } ``` ```typescript // File: src/components/correspondences/form.tsx 'use client'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Textarea } from '@/components/ui/textarea'; import { Label } from '@/components/ui/label'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { FileUpload } from '@/components/common/file-upload'; import { useRouter } from 'next/navigation'; import { correspondenceApi } from '@/lib/api/correspondences'; const correspondenceSchema = z.object({ subject: z.string().min(5, 'Subject must be at least 5 characters'), description: z.string().optional(), document_type_id: z.number(), from_organization_id: z.number(), to_organization_id: z.number(), importance: z.enum(['NORMAL', 'HIGH', 'URGENT']).default('NORMAL'), attachments: z.array(z.instanceof(File)).optional(), }); type FormData = z.infer; export function CorrespondenceForm() { const router = useRouter(); const { register, handleSubmit, setValue, formState: { errors, isSubmitting }, } = useForm({ resolver: zodResolver(correspondenceSchema), }); const onSubmit = async (data: FormData) => { try { await correspondenceApi.create(data); router.push('/correspondences'); } catch (error) { console.error(error); } }; return (
{/* Subject */}
{errors.subject && (

{errors.subject.message}

)}
{/* Description */}