mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 09:33:46 +00:00
feat: add delete buttons for categories and tags in combobox dropdown
- Add onDelete prop to ColoredItemCombobox with X button per item - Wire up deleteCategory/deleteTag API calls in AdminNewsCreate - Auto-clear selection if the deleted item was selected - Spinner on delete, haptic feedback, i18n aria-label
This commit is contained in:
@@ -25,6 +25,7 @@ interface ColoredItemComboboxProps {
|
||||
value: ColoredItem | null;
|
||||
onChange: (item: ColoredItem | null) => void;
|
||||
onCreateNew: (name: string, color: string) => Promise<ColoredItem>;
|
||||
onDelete?: (item: ColoredItem) => Promise<void>;
|
||||
placeholder?: string;
|
||||
isLoading?: boolean;
|
||||
colors?: string[];
|
||||
@@ -36,6 +37,7 @@ export function ColoredItemCombobox({
|
||||
value,
|
||||
onChange,
|
||||
onCreateNew,
|
||||
onDelete,
|
||||
placeholder,
|
||||
isLoading = false,
|
||||
colors = DEFAULT_COLORS,
|
||||
@@ -48,6 +50,7 @@ export function ColoredItemCombobox({
|
||||
const [search, setSearch] = useState('');
|
||||
const [newColor, setNewColor] = useState(colors[0] ?? '#00e5a0');
|
||||
const [isCreating, setIsCreating] = useState(false);
|
||||
const [deletingId, setDeletingId] = useState<number | null>(null);
|
||||
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const searchInputRef = useRef<HTMLInputElement>(null);
|
||||
@@ -137,6 +140,30 @@ export function ColoredItemCombobox({
|
||||
}
|
||||
}, [search, newColor, isCreating, onCreateNew, onChange, haptic]);
|
||||
|
||||
const handleDelete = useCallback(
|
||||
async (e: React.MouseEvent, item: ColoredItem) => {
|
||||
e.stopPropagation();
|
||||
if (!onDelete || deletingId !== null) return;
|
||||
|
||||
haptic.buttonPress();
|
||||
setDeletingId(item.id);
|
||||
|
||||
try {
|
||||
await onDelete(item);
|
||||
haptic.success();
|
||||
// Clear selection if deleted item was selected
|
||||
if (value?.id === item.id) {
|
||||
onChange(null);
|
||||
}
|
||||
} catch {
|
||||
haptic.error();
|
||||
} finally {
|
||||
setDeletingId(null);
|
||||
}
|
||||
},
|
||||
[onDelete, deletingId, value, onChange, haptic],
|
||||
);
|
||||
|
||||
// Keyboard handling
|
||||
const handleKeyDown = useCallback(
|
||||
(e: React.KeyboardEvent) => {
|
||||
@@ -257,6 +284,23 @@ export function ColoredItemCombobox({
|
||||
<path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z" />
|
||||
</svg>
|
||||
)}
|
||||
{onDelete && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => handleDelete(e, item)}
|
||||
disabled={deletingId === item.id}
|
||||
className="shrink-0 rounded p-1 text-dark-600 transition-colors hover:bg-red-500/10 hover:text-red-400 disabled:opacity-50"
|
||||
aria-label={t('news.admin.combobox.delete', { name: item.name })}
|
||||
>
|
||||
{deletingId === item.id ? (
|
||||
<div className="h-3.5 w-3.5 animate-spin rounded-full border-2 border-red-400 border-t-transparent" />
|
||||
) : (
|
||||
<svg className="h-3.5 w-3.5" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" />
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -4522,7 +4522,8 @@
|
||||
"searchOrCreate": "Search or create...",
|
||||
"noItems": "Nothing found",
|
||||
"placeholder": "Select...",
|
||||
"clear": "Clear"
|
||||
"clear": "Clear",
|
||||
"delete": "Delete {{name}}"
|
||||
},
|
||||
"toolbar": {
|
||||
"bold": "Bold",
|
||||
|
||||
@@ -5089,7 +5089,8 @@
|
||||
"searchOrCreate": "Поиск или создание...",
|
||||
"noItems": "Ничего не найдено",
|
||||
"placeholder": "Выберите...",
|
||||
"clear": "Очистить"
|
||||
"clear": "Очистить",
|
||||
"delete": "Удалить {{name}}"
|
||||
},
|
||||
"toolbar": {
|
||||
"bold": "Жирный",
|
||||
|
||||
@@ -445,6 +445,22 @@ export default function AdminNewsCreate() {
|
||||
[queryClient],
|
||||
);
|
||||
|
||||
const handleDeleteCategory = useCallback(
|
||||
async (item: { id: number }) => {
|
||||
await newsApi.deleteCategory(item.id);
|
||||
await queryClient.invalidateQueries({ queryKey: ['admin', 'news', 'categories'] });
|
||||
},
|
||||
[queryClient],
|
||||
);
|
||||
|
||||
const handleDeleteTag = useCallback(
|
||||
async (item: { id: number }) => {
|
||||
await newsApi.deleteTag(item.id);
|
||||
await queryClient.invalidateQueries({ queryKey: ['admin', 'news', 'tags'] });
|
||||
},
|
||||
[queryClient],
|
||||
);
|
||||
|
||||
// Fetch article for editing
|
||||
const { data: articleData, isLoading: isLoadingArticle } = useQuery({
|
||||
queryKey: ['admin', 'news', 'article', articleId],
|
||||
@@ -626,6 +642,7 @@ export default function AdminNewsCreate() {
|
||||
value={selectedCategory}
|
||||
onChange={setSelectedCategory}
|
||||
onCreateNew={handleCreateCategory}
|
||||
onDelete={handleDeleteCategory}
|
||||
placeholder={t('news.admin.combobox.selectCategory')}
|
||||
/>
|
||||
</div>
|
||||
@@ -636,6 +653,7 @@ export default function AdminNewsCreate() {
|
||||
value={selectedTag}
|
||||
onChange={setSelectedTag}
|
||||
onCreateNew={handleCreateTag}
|
||||
onDelete={handleDeleteTag}
|
||||
placeholder={t('news.admin.combobox.selectTag')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user