260321:1700 Correct Coresspondence / Doing RFA

This commit is contained in:
admin
2026-03-21 17:00:41 +07:00
parent dcf55f4d08
commit 03d16cfd64
57 changed files with 1923 additions and 663 deletions
@@ -37,17 +37,20 @@ import {
import { Skeleton } from "@/components/ui/skeleton";
import { Organization } from "@/types/organization";
import { getApiErrorMessage } from "@/types/api-error";
export default function UsersPage() {
const [search, setSearch] = useState("");
const [selectedOrgId, setSelectedOrgId] = useState<string | null>(null);
const { data: users, isLoading } = useUsers({
const { data: users, isLoading, isError, error } = useUsers({
search: search || undefined,
primaryOrganizationId: selectedOrgId ?? undefined,
});
const { data: organizations = [] } = useOrganizations();
const userList = Array.isArray(users) ? users : [];
const organizationList = Array.isArray(organizations) ? organizations : [];
const deleteMutation = useDeleteUser();
const [dialogOpen, setDialogOpen] = useState(false);
@@ -94,8 +97,12 @@ export default function UsersPage() {
header: "Organization",
cell: ({ row }) => {
const orgId = row.original.primaryOrganizationId;
const org = (organizations as Organization[]).find((o) => (o.id ?? o.uuid) === orgId?.toString() || o.uuid === orgId?.toString());
return org ? org.organizationCode : "-";
if (!orgId) {
return "All Organizations";
}
const org = organizationList.find((o) => (o.id ?? o.uuid) === orgId?.toString() || o.uuid === orgId?.toString());
return org ? org.organizationCode : "All Organizations";
},
},
{
@@ -185,7 +192,7 @@ export default function UsersPage() {
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All Organizations</SelectItem>
{Array.isArray(organizations) && (organizations as Organization[]).map((org) => (
{organizationList.map((org) => (
<SelectItem key={org.uuid} value={org.uuid}>
{org.organizationCode} - {org.organizationName}
</SelectItem>
@@ -195,6 +202,12 @@ export default function UsersPage() {
</div>
</div>
{isError && (
<div className="rounded-md border border-destructive/30 bg-destructive/5 px-4 py-3 text-sm text-destructive">
{getApiErrorMessage(error, "Failed to load users")}
</div>
)}
{isLoading ? (
<div className="space-y-2">
{[1, 2, 3, 4, 5].map((i) => (
@@ -204,7 +217,7 @@ export default function UsersPage() {
))}
</div>
) : (
<DataTable columns={columns} data={users || []} />
<DataTable columns={columns} data={userList} />
)}
<UserDialog
@@ -29,7 +29,7 @@ export default function EditTemplatePage() {
const { data: disciplines = [] } = useDisciplines(contractId);
const selectedProjectName =
projects.find((p: { id: number; projectName: string }) => p.id === projectId)?.projectName ||
projects.find((p: { id?: number; uuid?: string; projectCode: string; projectName: string }) => p.id === projectId)?.projectName ||
'LCBP3';
useEffect(() => {
@@ -10,6 +10,7 @@ import { Workflow } from '@/types/workflow';
export default function WorkflowsPage() {
const { data: workflows = [], isLoading: loading, error } = useWorkflowDefinitions();
const workflowList = Array.isArray(workflows) ? workflows : [];
return (
<div className="p-6 space-y-6">
@@ -34,13 +35,13 @@ export default function WorkflowsPage() {
<div className="text-center py-12 text-destructive border rounded-lg border-dashed border-destructive/50 bg-destructive/10">
Failed to load workflows. Please try again later.
</div>
) : workflows.length === 0 ? (
) : workflowList.length === 0 ? (
<div className="text-center py-12 text-muted-foreground border rounded-lg border-dashed">
No workflow definitions found. Click &quot;New Workflow&quot; to create one.
</div>
) : (
<div className="grid gap-4">
{workflows.map((workflow: Workflow) => (
{workflowList.map((workflow: Workflow) => (
<Card key={workflow.workflowId} className="p-6">
<div className="flex justify-between items-start">
<div className="flex-1">
@@ -17,10 +17,12 @@ import { format } from "date-fns";
import { ArrowLeftIcon } from "lucide-react";
import Link from "next/link";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { getApiErrorMessage } from "@/types/api-error";
export default function MigrationErrorsPage() {
const [items, setItems] = useState<MigrationErrorItem[]>([]);
const [loading, setLoading] = useState(true);
const [errorMessage, setErrorMessage] = useState<string | null>(null);
useEffect(() => {
fetchData();
@@ -29,10 +31,12 @@ export default function MigrationErrorsPage() {
const fetchData = async () => {
try {
setLoading(true);
setErrorMessage(null);
const res = await migrationService.getErrors({ limit: 100 });
setItems(res.items);
} catch (error) {
// Failed to fetch errors - loading state handles display
setItems(Array.isArray(res.items) ? res.items : []);
} catch (error: unknown) {
setItems([]);
setErrorMessage(getApiErrorMessage(error, "Failed to load errors"));
} finally {
setLoading(false);
}
@@ -59,6 +63,11 @@ export default function MigrationErrorsPage() {
<CardTitle>Error Audit Log</CardTitle>
</CardHeader>
<CardContent>
{errorMessage && (
<div className="mb-4 rounded-md border border-destructive/30 bg-destructive/5 px-4 py-3 text-sm text-destructive">
{errorMessage}
</div>
)}
{loading ? (
<div className="py-10 text-center">Loading errors...</div>
) : items.length === 0 ? (
@@ -20,6 +20,7 @@ import { format } from "date-fns";
import { EyeIcon, FileXIcon, CheckSquareIcon } from "lucide-react";
import Link from "next/link";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { getApiErrorMessage } from "@/types/api-error";
export default function MigrationReviewQueuePage() {
const [items, setItems] = useState<MigrationReviewQueueItem[]>([]);
@@ -27,6 +28,7 @@ export default function MigrationReviewQueuePage() {
const [submitting, setSubmitting] = useState(false);
const [statusFilter, setStatusFilter] = useState<string>("PENDING");
const [selectedIds, setSelectedIds] = useState<number[]>([]);
const [errorMessage, setErrorMessage] = useState<string | null>(null);
useEffect(() => {
fetchData();
@@ -35,14 +37,16 @@ export default function MigrationReviewQueuePage() {
const fetchData = async () => {
try {
setLoading(true);
setErrorMessage(null);
const res = await migrationService.getReviewQueue({
status: statusFilter === "ALL" ? undefined : (statusFilter as MigrationReviewStatus),
limit: 50,
});
setItems(res.items);
setItems(Array.isArray(res.items) ? res.items : []);
setSelectedIds([]); // reset selection on fetch
} catch (error) {
// Failed to fetch queue - loading state handles display
} catch (error: unknown) {
setItems([]);
setErrorMessage(getApiErrorMessage(error, "Failed to load queue"));
} finally {
setLoading(false);
}
@@ -148,6 +152,11 @@ export default function MigrationReviewQueuePage() {
<CardTitle>Queue Items - {statusFilter}</CardTitle>
</CardHeader>
<CardContent>
{errorMessage && (
<div className="mb-4 rounded-md border border-destructive/30 bg-destructive/5 px-4 py-3 text-sm text-destructive">
{errorMessage}
</div>
)}
{loading ? (
<div className="py-10 text-center">Loading queue...</div>
) : items.length === 0 ? (
@@ -1,12 +1,13 @@
'use client';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { sessionService } from '@/lib/services/session.service';
import { Session, sessionService } from '@/lib/services/session.service';
import { Button } from '@/components/ui/button';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
import { toast } from 'sonner';
import { Loader2, Trash2, Monitor, Smartphone } from 'lucide-react';
import { Loader2, Trash2, Monitor } from 'lucide-react';
import { format } from 'date-fns';
import { getApiErrorMessage } from '@/types/api-error';
export default function SessionManagementPage() {
const queryClient = useQueryClient();
@@ -15,10 +16,11 @@ export default function SessionManagementPage() {
data: sessions,
isLoading,
error,
} = useQuery({
} = useQuery<Session[]>({
queryKey: ['sessions'],
queryFn: sessionService.getActiveSessions,
});
const sessionList = Array.isArray(sessions) ? sessions : [];
const revokeMutation = useMutation({
mutationFn: sessionService.revokeSession,
@@ -26,8 +28,10 @@ export default function SessionManagementPage() {
toast.success('Session revoked successfully');
queryClient.invalidateQueries({ queryKey: ['sessions'] });
},
onError: (error) => {
toast.error('Failed to revoke session');
onError: (mutationError: unknown) => {
toast.error('Failed to revoke session', {
description: getApiErrorMessage(mutationError, 'Unknown error'),
});
},
});
@@ -46,7 +50,7 @@ export default function SessionManagementPage() {
}
if (error) {
return <div className="p-8 text-center text-red-500">Failed to load sessions. Please try again.</div>;
return <div className="p-8 text-center text-red-500">{getApiErrorMessage(error, 'Failed to load sessions. Please try again.')}</div>;
}
return (
@@ -67,7 +71,7 @@ export default function SessionManagementPage() {
</TableRow>
</TableHeader>
<TableBody>
{sessions?.map((session: any) => (
{sessionList.map((session) => (
<TableRow key={session.id}>
<TableCell>
<div className="flex flex-col">
@@ -94,7 +98,7 @@ export default function SessionManagementPage() {
variant="destructive"
size="sm"
className="h-8"
onClick={() => handleRevoke(Number(session.id))}
onClick={() => handleRevoke(session.id)}
disabled={revokeMutation.isPending}
>
<Trash2 className="mr-2 h-3.5 w-3.5" />
@@ -103,7 +107,7 @@ export default function SessionManagementPage() {
</TableCell>
</TableRow>
))}
{(!sessions || sessions.length === 0) && (
{sessionList.length === 0 && (
<TableRow>
<TableCell colSpan={4} className="h-24 text-center text-muted-foreground">
No active sessions found.