'use client' import { useState } from 'react' import { useRouter } from 'next/navigation' import { Card, CardContent, CardHeader } from '@/components/ui/card' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Textarea } from '@/components/ui/textarea' import { Badge } from '@/components/ui/badge' import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu' import { Laptop, Link2, MoreHorizontal, Trash2, Edit3, Check, X, Tag, StickyNote, } from 'lucide-react' import { formatDistanceToNow } from 'date-fns' import Link from 'next/link' interface Group { id: string name: string } interface Machine { id: string name: string hostname: string | null os: string | null osVersion: string | null agentVersion: string | null lastSeen: Date | null isOnline: boolean tags: string[] | null notes: string | null groupId: string | null } interface MachineCardProps { machine: Machine groups: Group[] } const TAG_COLORS: Record = { server: 'bg-blue-500/10 text-blue-500 border-blue-500/20', windows: 'bg-cyan-500/10 text-cyan-500 border-cyan-500/20', linux: 'bg-orange-500/10 text-orange-500 border-orange-500/20', mac: 'bg-gray-500/10 text-gray-400 border-gray-500/20', workstation: 'bg-purple-500/10 text-purple-500 border-purple-500/20', laptop: 'bg-green-500/10 text-green-500 border-green-500/20', } function tagColor(tag: string) { return TAG_COLORS[tag.toLowerCase()] || 'bg-muted text-muted-foreground border-border/50' } export function MachineCard({ machine, groups }: MachineCardProps) { const router = useRouter() const [editing, setEditing] = useState(false) const [name, setName] = useState(machine.name) const [notes, setNotes] = useState(machine.notes ?? '') const [tagInput, setTagInput] = useState('') const [tags, setTags] = useState(machine.tags ?? []) const [groupId, setGroupId] = useState(machine.groupId ?? '') const [saving, setSaving] = useState(false) const [showNotes, setShowNotes] = useState(false) const save = async () => { setSaving(true) await fetch(`/api/machines/${machine.id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name, notes: notes || null, tags, groupId: groupId || null }), }) setSaving(false) setEditing(false) router.refresh() } const cancel = () => { setName(machine.name) setNotes(machine.notes ?? '') setTags(machine.tags ?? []) setGroupId(machine.groupId ?? '') setEditing(false) } const addTag = () => { const t = tagInput.trim().toLowerCase().replace(/\s+/g, '-') if (t && !tags.includes(t)) setTags([...tags, t]) setTagInput('') } const removeTag = (t: string) => setTags(tags.filter(x => x !== t)) const deleteMachine = async () => { if (!confirm(`Delete "${machine.name}"? This cannot be undone.`)) return await fetch(`/api/machines/${machine.id}`, { method: 'DELETE' }) router.refresh() } const currentGroup = groups.find(g => g.id === (groupId || machine.groupId)) return (
{editing ? ( setName(e.target.value)} className="h-7 text-sm font-semibold" autoFocus /> ) : (

{machine.name}

)}

{machine.hostname || 'Unknown host'}

setEditing(true)}> Edit setShowNotes(v => !v)}> {showNotes ? 'Hide notes' : 'Show notes'} Delete
{/* Tags */}
{tags.map(t => ( {t} {editing && ( )} ))} {editing && (
setTagInput(e.target.value)} onKeyDown={e => { if (e.key === 'Enter') { e.preventDefault(); addTag() } }} placeholder="Add tag…" className="h-5 text-xs py-0 px-1.5 w-20" />
)}
{/* Group */} {editing ? (
) : currentGroup ? (

Group: {currentGroup.name}

) : null} {/* Stats */}
Status
{machine.isOnline ? 'Online' : 'Offline'}
OS
{machine.os || 'Unknown'} {machine.osVersion || ''}
Last seen
{machine.lastSeen ? formatDistanceToNow(new Date(machine.lastSeen), { addSuffix: true }) : 'Never'}
Agent
{machine.agentVersion || 'N/A'}
{/* Notes */} {(showNotes || editing) && (
{editing ? (