260321:1700 Correct Coresspondence / Doing RFA
This commit is contained in:
@@ -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 "New Workflow" 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">
|
||||
|
||||
+12
-3
@@ -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 ? (
|
||||
+12
-3
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user