115 lines
3.8 KiB
TypeScript
115 lines
3.8 KiB
TypeScript
'use client'
|
||
import { useState } from 'react'
|
||
import { useSortable } from '@dnd-kit/sortable'
|
||
import { CSS } from '@dnd-kit/utilities'
|
||
import { useUpdateKeyword, useDeleteKeyword } from '@/hooks/useKeywords'
|
||
import type { Keyword } from '@/lib/types'
|
||
|
||
interface Props { keyword: Keyword }
|
||
|
||
export default function KeywordRow({ keyword }: Props) {
|
||
const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id: keyword.id })
|
||
const updateKw = useUpdateKeyword()
|
||
const deleteKw = useDeleteKeyword()
|
||
const [editTerm, setEditTerm] = useState(false)
|
||
const [editWeight, setEditWeight] = useState(false)
|
||
const [termVal, setTermVal] = useState(keyword.term)
|
||
const [weightVal, setWeightVal] = useState(String(keyword.weight))
|
||
|
||
const saveTerm = () => { updateKw.mutate({ id: keyword.id, data: { term: termVal } }); setEditTerm(false) }
|
||
const saveWeight = () => { updateKw.mutate({ id: keyword.id, data: { weight: parseFloat(weightVal) } }); setEditWeight(false) }
|
||
|
||
const style = { transform: CSS.Transform.toString(transform), transition }
|
||
|
||
return (
|
||
<tr ref={setNodeRef} style={style} className="group">
|
||
{/* Drag handle */}
|
||
<td className="w-8">
|
||
<span
|
||
{...attributes} {...listeners}
|
||
className="cursor-grab text-g-faint/30 hover:text-g-faint transition-colors select-none"
|
||
title="Drag to reorder"
|
||
>
|
||
⋮⋮
|
||
</span>
|
||
</td>
|
||
|
||
{/* Term */}
|
||
<td>
|
||
{editTerm ? (
|
||
<input
|
||
autoFocus
|
||
value={termVal}
|
||
onChange={(e) => setTermVal(e.target.value)}
|
||
onBlur={saveTerm}
|
||
onKeyDown={(e) => e.key === 'Enter' && saveTerm()}
|
||
className="g-input h-7 text-sm py-0 w-full"
|
||
/>
|
||
) : (
|
||
<button
|
||
onClick={() => setEditTerm(true)}
|
||
className="text-sm font-medium text-g-text hover:text-g-green transition-colors text-left"
|
||
>
|
||
{keyword.term}
|
||
</button>
|
||
)}
|
||
</td>
|
||
|
||
{/* Weight */}
|
||
<td className="text-center">
|
||
{editWeight ? (
|
||
<input
|
||
autoFocus
|
||
type="number"
|
||
step="0.1"
|
||
value={weightVal}
|
||
onChange={(e) => setWeightVal(e.target.value)}
|
||
onBlur={saveWeight}
|
||
onKeyDown={(e) => e.key === 'Enter' && saveWeight()}
|
||
className="g-input h-7 text-sm py-0 w-16 text-center font-mono"
|
||
/>
|
||
) : (
|
||
<button
|
||
onClick={() => setEditWeight(true)}
|
||
className="font-mono text-sm text-g-amber hover:text-g-green transition-colors font-semibold"
|
||
>
|
||
{keyword.weight}×
|
||
</button>
|
||
)}
|
||
</td>
|
||
|
||
{/* AI description */}
|
||
<td>
|
||
{keyword.ai_target ? (
|
||
<span className="text-xs text-g-muted">
|
||
{keyword.ai_target.length > 48 ? keyword.ai_target.slice(0, 48) + '…' : keyword.ai_target}
|
||
</span>
|
||
) : (
|
||
<span className="text-xs text-g-faint italic">Not set</span>
|
||
)}
|
||
</td>
|
||
|
||
{/* Price range */}
|
||
<td>
|
||
{(keyword.min_price || keyword.max_price) ? (
|
||
<span className="text-xs font-mono text-g-muted">
|
||
{[keyword.min_price ? `≥$${keyword.min_price}` : '', keyword.max_price ? `≤$${keyword.max_price}` : ''].filter(Boolean).join(' ')}
|
||
</span>
|
||
) : (
|
||
<span className="text-xs text-g-faint">—</span>
|
||
)}
|
||
</td>
|
||
|
||
{/* Delete */}
|
||
<td className="w-10 text-right">
|
||
<button
|
||
onClick={() => { if (confirm(`Delete "${keyword.term}"?`)) deleteKw.mutate(keyword.id) }}
|
||
className="text-g-faint hover:text-g-red transition-colors text-xs opacity-0 group-hover:opacity-100"
|
||
>
|
||
✕
|
||
</button>
|
||
</td>
|
||
</tr>
|
||
)
|
||
}
|