import { useState } from 'react' import { Pencil, Trash2, Check, X, Plus, Link } from 'lucide-react' import { useMappings, useCreateMapping, useUpdateMapping, useDeleteMapping } from '../api/mappings' import { CATEGORY_COLORS } from '../lib/constants' import type { AppMapping } from '../types' const MATCH_TYPES = ['ProcessName', 'TitleContains', 'UrlContains'] as const const MATCH_TYPE_COLORS: Record = { ProcessName: '#6366f1', TitleContains: '#06b6d4', UrlContains: '#f97316', } interface FormData { pattern: string matchType: string category: string friendlyName: string } const emptyForm: FormData = { pattern: '', matchType: 'ProcessName', category: '', friendlyName: '' } function formFromMapping(m: AppMapping): FormData { return { pattern: m.pattern, matchType: m.matchType, category: m.category, friendlyName: m.friendlyName ?? '', } } export default function Mappings() { const { data: mappings, isLoading } = useMappings() const createMapping = useCreateMapping() const updateMapping = useUpdateMapping() const deleteMapping = useDeleteMapping() const [addingNew, setAddingNew] = useState(false) const [newForm, setNewForm] = useState(emptyForm) const [editingId, setEditingId] = useState(null) const [editForm, setEditForm] = useState(emptyForm) function handleAddSave() { if (!newForm.pattern.trim() || !newForm.category.trim()) return createMapping.mutate( { pattern: newForm.pattern.trim(), matchType: newForm.matchType, category: newForm.category.trim(), friendlyName: newForm.friendlyName.trim() || undefined, }, { onSuccess: () => { setAddingNew(false) setNewForm(emptyForm) }, }, ) } function handleAddCancel() { setAddingNew(false) setNewForm(emptyForm) } function handleEditStart(mapping: AppMapping) { setEditingId(mapping.id) setEditForm(formFromMapping(mapping)) // Cancel any add-new row when starting an edit setAddingNew(false) } function handleEditSave() { if (editingId === null) return if (!editForm.pattern.trim() || !editForm.category.trim()) return updateMapping.mutate( { id: editingId, pattern: editForm.pattern.trim(), matchType: editForm.matchType, category: editForm.category.trim(), friendlyName: editForm.friendlyName.trim() || undefined, }, { onSuccess: () => { setEditingId(null) setEditForm(emptyForm) }, }, ) } function handleEditCancel() { setEditingId(null) setEditForm(emptyForm) } function handleDelete(id: number) { if (!window.confirm('Delete this mapping rule?')) return deleteMapping.mutate(id) } const inputClass = 'bg-[var(--color-page)] text-[var(--color-text-primary)] text-sm rounded border border-[var(--color-border)] px-2 py-1.5 focus:outline-none focus:border-[var(--color-accent)] transition-colors w-full' const selectClass = 'bg-[var(--color-page)] text-[var(--color-text-primary)] text-sm rounded border border-[var(--color-border)] px-2 py-1.5 focus:outline-none focus:border-[var(--color-accent)] transition-colors appearance-none cursor-pointer w-full' function renderFormRow(form: FormData, setForm: (f: FormData) => void, onSave: () => void, onCancel: () => void, isSaving: boolean) { return ( setForm({ ...form, pattern: e.target.value })} className={inputClass} autoFocus /> setForm({ ...form, category: e.target.value })} className={inputClass} /> setForm({ ...form, friendlyName: e.target.value })} className={inputClass} />
) } return (
{/* Header */}

App Mappings

{/* Table */} {isLoading ? (
Loading mappings...
) : !mappings?.length && !addingNew ? (

No mappings configured

) : (
{/* Add-new row */} {addingNew && renderFormRow(newForm, setNewForm, handleAddSave, handleAddCancel, createMapping.isPending)} {/* Data rows */} {mappings?.map((m) => editingId === m.id ? ( renderFormRow(editForm, setEditForm, handleEditSave, handleEditCancel, updateMapping.isPending) ) : ( ), )}
Pattern Match Type Category Friendly Name Actions
{m.pattern} {m.matchType} {m.category} {m.friendlyName ?? '\u2014'}
)}
) }