Skip to content

Commit ac13f68

Browse files
committed
feat(web): add editable notes column to key list table
1 parent 8f188ac commit ac13f68

1 file changed

Lines changed: 88 additions & 6 deletions

File tree

web/src/components/KeyList.tsx

Lines changed: 88 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useState, useEffect, useCallback, useRef } from 'react';
2-
import { Trash2, Plus, RefreshCw, Terminal, CheckCircle2, Copy, Circle, X, AlertTriangle, Download, Upload, ArrowUp, ArrowDown, ChevronsUpDown } from 'lucide-react';
2+
import { Trash2, Plus, RefreshCw, Terminal, CheckCircle2, Copy, Circle, X, AlertTriangle, Download, Upload, ArrowUp, ArrowDown, ChevronsUpDown, Pencil } from 'lucide-react';
33
import { toast } from 'sonner';
44
import { sounds } from '@/lib/sound';
55
import { decryptKeys, maskKey } from '@/utils/crypto';
@@ -38,6 +38,27 @@ interface SortConfig {
3838
}
3939

4040
const SORT_STORAGE_KEY = 'oroio-key-sort';
41+
const NOTES_STORAGE_KEY = 'oroio-key-notes';
42+
43+
function loadNotes(): Record<string, string> {
44+
try {
45+
const saved = localStorage.getItem(NOTES_STORAGE_KEY);
46+
if (saved) return JSON.parse(saved);
47+
} catch {
48+
// ignore parse errors
49+
}
50+
return {};
51+
}
52+
53+
function saveNote(key: string, note: string) {
54+
const notes = loadNotes();
55+
if (note.trim()) {
56+
notes[key] = note;
57+
} else {
58+
delete notes[key];
59+
}
60+
localStorage.setItem(NOTES_STORAGE_KEY, JSON.stringify(notes));
61+
}
4162

4263
function loadSortConfig(): SortConfig {
4364
try {
@@ -122,6 +143,61 @@ function KeyDisplay({ keyText, isCurrent, className }: { keyText: string, isCurr
122143
);
123144
}
124145

146+
function NoteCell({ keyText, onUpdate }: { keyText: string; onUpdate: () => void }) {
147+
const [editing, setEditing] = useState(false);
148+
const [note, setNote] = useState(() => loadNotes()[keyText] || '');
149+
const inputRef = useRef<HTMLInputElement>(null);
150+
151+
useEffect(() => {
152+
if (editing && inputRef.current) {
153+
inputRef.current.focus();
154+
}
155+
}, [editing]);
156+
157+
const handleSave = () => {
158+
saveNote(keyText, note);
159+
setEditing(false);
160+
onUpdate();
161+
};
162+
163+
const handleKeyDown = (e: React.KeyboardEvent) => {
164+
if (e.key === 'Enter') {
165+
handleSave();
166+
} else if (e.key === 'Escape') {
167+
setNote(loadNotes()[keyText] || '');
168+
setEditing(false);
169+
}
170+
};
171+
172+
if (editing) {
173+
return (
174+
<input
175+
ref={inputRef}
176+
type="text"
177+
value={note}
178+
onChange={(e) => setNote(e.target.value)}
179+
onBlur={handleSave}
180+
onKeyDown={handleKeyDown}
181+
className="w-full px-1.5 py-0.5 text-xs bg-background border border-border rounded focus:outline-none focus:ring-1 focus:ring-primary"
182+
placeholder="Note..."
183+
/>
184+
);
185+
}
186+
187+
return (
188+
<div
189+
className="flex items-center gap-1 group cursor-pointer min-h-[24px]"
190+
onClick={() => setEditing(true)}
191+
title="Click to edit"
192+
>
193+
<span className="text-xs text-muted-foreground">
194+
{note || '-'}
195+
</span>
196+
<Pencil className="h-3 w-3 text-muted-foreground/40 opacity-0 group-hover:opacity-100 transition-opacity" />
197+
</div>
198+
);
199+
}
200+
125201
function IconCopyButton({ text, icon: Icon, title, className }: { text: string; icon: any; title: string; className?: string }) {
126202
const [copied, setCopied] = useState(false);
127203

@@ -202,6 +278,7 @@ export default function KeyList() {
202278
const [adding, setAdding] = useState(false);
203279
const fileInputRef = useRef<HTMLInputElement>(null);
204280
const [sortConfig, setSortConfig] = useState<SortConfig>(loadSortConfig);
281+
const [, setNotesVersion] = useState(0);
205282

206283
const handleSort = (field: SortField) => {
207284
const newConfig: SortConfig = {
@@ -474,18 +551,20 @@ export default function KeyList() {
474551
<Table className="table-fixed">
475552
<colgroup>
476553
<col style={{ width: '4%' }} />
477-
<col style={{ width: '5%' }} />
478-
<col style={{ width: '22%' }} />
479-
<col style={{ width: '8%' }} />
480-
<col style={{ width: '18%' }} />
481-
<col style={{ width: '18%' }} />
554+
<col style={{ width: '4%' }} />
555+
<col style={{ width: '17%' }} />
556+
<col style={{ width: '17%' }} />
557+
<col style={{ width: '7%' }} />
558+
<col style={{ width: '15%' }} />
559+
<col style={{ width: '15%' }} />
482560
<col style={{ width: '10%' }} />
483561
</colgroup>
484562
<TableHeader>
485563
<TableRow className="bg-muted/30 hover:bg-muted/30">
486564
<TableHead></TableHead>
487565
<TableHead className="text-xs tracking-wider">NO</TableHead>
488566
<TableHead className="text-xs tracking-wider">KEY</TableHead>
567+
<TableHead className="text-xs tracking-wider">NOTE</TableHead>
489568
<SortableHeader field="percent" label="%" align="right" sortConfig={sortConfig} onSort={handleSort} />
490569
<SortableHeader field="quota" label="QUOTA" align="right" sortConfig={sortConfig} onSort={handleSort} />
491570
<SortableHeader field="expiry" label="EXPIRY" align="center" sortConfig={sortConfig} onSort={handleSort} />
@@ -537,6 +616,9 @@ export default function KeyList() {
537616
isCurrent={info.isCurrent}
538617
/>
539618
</TableCell>
619+
<TableCell className="py-2">
620+
<NoteCell keyText={info.key} onUpdate={() => setNotesVersion(v => v + 1)} />
621+
</TableCell>
540622
<TableCell className="text-right font-mono text-sm text-muted-foreground py-2">
541623
{info.usage?.total ? `${percent}%` : '-'}
542624
</TableCell>

0 commit comments

Comments
 (0)