196 lines
6.5 KiB
TypeScript
196 lines
6.5 KiB
TypeScript
|
|
'use client';
|
||
|
|
|
||
|
|
import { useState, useEffect } from 'react';
|
||
|
|
import { useParams, useRouter, useSearchParams } from 'next/navigation';
|
||
|
|
import { Card, CardContent } from '@/components/ui/card';
|
||
|
|
import { Button } from '@/components/ui/button';
|
||
|
|
import { Skeleton } from '@/components/ui/skeleton';
|
||
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||
|
|
import { ThemeToggle } from '@/components/theme-toggle';
|
||
|
|
import { PSATab } from '@/components/configuration-items/psa-tab';
|
||
|
|
import { RMMTab } from '@/components/configuration-items/rmm-tab';
|
||
|
|
import { StatusCards } from '@/components/configuration-items/status-cards';
|
||
|
|
import {
|
||
|
|
Server,
|
||
|
|
Monitor,
|
||
|
|
ArrowLeft,
|
||
|
|
AlertCircle,
|
||
|
|
RefreshCw
|
||
|
|
} from 'lucide-react';
|
||
|
|
import { ConfigurationItem } from '@/lib/types/autotask';
|
||
|
|
import { DattoRMMDevice } from '@/lib/types/datto-rmm';
|
||
|
|
|
||
|
|
interface ConfigItemDetail {
|
||
|
|
autotaskDevice?: ConfigurationItem;
|
||
|
|
rmmDevice?: DattoRMMDevice;
|
||
|
|
companyName?: string;
|
||
|
|
}
|
||
|
|
|
||
|
|
export default function ConfigurationItemDetailPage() {
|
||
|
|
const params = useParams();
|
||
|
|
const router = useRouter();
|
||
|
|
const searchParams = useSearchParams();
|
||
|
|
const [data, setData] = useState<ConfigItemDetail>({});
|
||
|
|
const [loading, setLoading] = useState(true);
|
||
|
|
const [error, setError] = useState<string | null>(null);
|
||
|
|
|
||
|
|
// Get company context from URL params
|
||
|
|
const companyId = searchParams.get('companyId');
|
||
|
|
const companyName = searchParams.get('companyName');
|
||
|
|
|
||
|
|
// Parse the ID parameter - it might be "at-123" or "rmm-abc" or "123"
|
||
|
|
const parseItemId = () => {
|
||
|
|
const id = params.id as string;
|
||
|
|
if (id.startsWith('at-')) {
|
||
|
|
return { type: 'autotask', id: id.substring(3) };
|
||
|
|
} else if (id.startsWith('rmm-')) {
|
||
|
|
return { type: 'rmm', id: id.substring(4) };
|
||
|
|
} else {
|
||
|
|
return { type: 'autotask', id }; // Default to Autotask
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
const fetchData = async () => {
|
||
|
|
setLoading(true);
|
||
|
|
setError(null);
|
||
|
|
|
||
|
|
try {
|
||
|
|
const { type, id } = parseItemId();
|
||
|
|
|
||
|
|
// Fetch the configuration item details
|
||
|
|
const response = await fetch(`/api/configuration-items/${id}?type=${type}`);
|
||
|
|
if (!response.ok) {
|
||
|
|
throw new Error('Failed to fetch configuration item');
|
||
|
|
}
|
||
|
|
|
||
|
|
const result = await response.json();
|
||
|
|
setData(result);
|
||
|
|
} catch (err) {
|
||
|
|
setError(err instanceof Error ? err.message : 'An error occurred');
|
||
|
|
} finally {
|
||
|
|
setLoading(false);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
fetchData();
|
||
|
|
}, [params.id]);
|
||
|
|
|
||
|
|
const handleUpdate = (updatedDevice: ConfigurationItem) => {
|
||
|
|
setData({ ...data, autotaskDevice: updatedDevice });
|
||
|
|
};
|
||
|
|
|
||
|
|
if (loading) {
|
||
|
|
return (
|
||
|
|
<div className="min-h-screen bg-background">
|
||
|
|
<div className="container mx-auto px-4 py-8">
|
||
|
|
<div className="space-y-4">
|
||
|
|
<Skeleton className="h-12 w-64" />
|
||
|
|
<Skeleton className="h-96 w-full" />
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (error) {
|
||
|
|
return (
|
||
|
|
<div className="min-h-screen bg-background">
|
||
|
|
<div className="container mx-auto px-4 py-8">
|
||
|
|
<Card className="border-red-500">
|
||
|
|
<CardContent className="pt-6">
|
||
|
|
<div className="flex items-center gap-2 text-red-500">
|
||
|
|
<AlertCircle className="w-5 h-5" />
|
||
|
|
<p>Error: {error}</p>
|
||
|
|
</div>
|
||
|
|
</CardContent>
|
||
|
|
</Card>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
const device = data.autotaskDevice;
|
||
|
|
const rmmDevice = data.rmmDevice;
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="min-h-screen bg-background">
|
||
|
|
{/* Header */}
|
||
|
|
<header className="sticky top-0 z-50 w-full border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
||
|
|
<div className="container flex h-16 items-center">
|
||
|
|
<div className="flex flex-1 items-center justify-between">
|
||
|
|
<div className="flex items-center space-x-4">
|
||
|
|
<Button
|
||
|
|
variant="ghost"
|
||
|
|
size="sm"
|
||
|
|
onClick={() => {
|
||
|
|
// Navigate back to configuration items with company context
|
||
|
|
if (companyId) {
|
||
|
|
const params = new URLSearchParams({
|
||
|
|
companyId: companyId,
|
||
|
|
companyName: companyName || ''
|
||
|
|
});
|
||
|
|
router.push(`/configuration-items?${params.toString()}`);
|
||
|
|
} else {
|
||
|
|
router.push('/configuration-items');
|
||
|
|
}
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
<ArrowLeft className="w-4 h-4 mr-2" />
|
||
|
|
Back to List
|
||
|
|
</Button>
|
||
|
|
<div className="flex items-center space-x-3">
|
||
|
|
<div className="flex h-9 w-9 items-center justify-center rounded-lg bg-gradient-to-br from-purple-600 to-purple-700 text-white shadow-lg">
|
||
|
|
<Server className="h-5 w-5" />
|
||
|
|
</div>
|
||
|
|
<div>
|
||
|
|
<h1 className="text-xl font-semibold tracking-tight">
|
||
|
|
{device?.referenceTitle || rmmDevice?.hostname || 'Configuration Item'}
|
||
|
|
</h1>
|
||
|
|
<p className="text-xs text-muted-foreground">
|
||
|
|
{data.companyName || 'Configuration Item Details'}
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div className="flex items-center space-x-2">
|
||
|
|
<Button variant="ghost" size="icon" onClick={() => window.location.reload()}>
|
||
|
|
<RefreshCw className="h-4 w-4" />
|
||
|
|
</Button>
|
||
|
|
<ThemeToggle />
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</header>
|
||
|
|
|
||
|
|
{/* Main Content */}
|
||
|
|
<main className="container mx-auto px-4 py-8">
|
||
|
|
{/* Status Cards */}
|
||
|
|
<StatusCards device={device} rmmDevice={rmmDevice} />
|
||
|
|
|
||
|
|
{/* Tabs */}
|
||
|
|
<Tabs defaultValue="psa" className="space-y-4 mt-6">
|
||
|
|
<TabsList className="grid w-full grid-cols-2 max-w-md">
|
||
|
|
<TabsTrigger value="psa" className="flex items-center gap-2">
|
||
|
|
<Server className="w-4 h-4" />
|
||
|
|
PSA Data
|
||
|
|
</TabsTrigger>
|
||
|
|
<TabsTrigger value="rmm" className="flex items-center gap-2">
|
||
|
|
<Monitor className="w-4 h-4" />
|
||
|
|
RMM Data
|
||
|
|
</TabsTrigger>
|
||
|
|
</TabsList>
|
||
|
|
|
||
|
|
<TabsContent value="psa">
|
||
|
|
<PSATab device={device} onUpdate={handleUpdate} />
|
||
|
|
</TabsContent>
|
||
|
|
|
||
|
|
<TabsContent value="rmm">
|
||
|
|
<RMMTab device={rmmDevice} />
|
||
|
|
</TabsContent>
|
||
|
|
</Tabs>
|
||
|
|
</main>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|