|
@@ -160,71 +160,60 @@ export default function AdminTagsPage() {
|
|
|
</button>
|
|
</button>
|
|
|
</div>
|
|
</div>
|
|
|
<div className="max-h-[calc(100vh-13rem)] space-y-1 overflow-y-auto pb-8 pr-1">
|
|
<div className="max-h-[calc(100vh-13rem)] space-y-1 overflow-y-auto pb-8 pr-1">
|
|
|
- {tags.map((tag) => (
|
|
|
|
|
- <div key={tag.id} className="flex items-center justify-between rounded-lg bg-white/5 px-3 py-2">
|
|
|
|
|
- {editingTagId === tag.id ? (
|
|
|
|
|
- <div className="flex flex-1 items-center gap-2">
|
|
|
|
|
- <input value={editTagName} onChange={(e) => setEditTagName(e.target.value)}
|
|
|
|
|
- className="flex-1 rounded border border-white/10 bg-white/10 px-2 py-1 text-sm text-white outline-none"
|
|
|
|
|
- autoFocus
|
|
|
|
|
- onKeyDown={(e) => {
|
|
|
|
|
- if (e.key === 'Enter') saveTag(tag.id)
|
|
|
|
|
- if (e.key === 'Escape') cancelEditTag()
|
|
|
|
|
- }}
|
|
|
|
|
- />
|
|
|
|
|
- <button onClick={() => saveTag(tag.id)}
|
|
|
|
|
- className="rounded bg-green-600 px-2 py-1 text-xs text-white hover:bg-green-500">
|
|
|
|
|
- ✓
|
|
|
|
|
- </button>
|
|
|
|
|
- <button onClick={cancelEditTag}
|
|
|
|
|
- className="rounded bg-white/10 px-2 py-1 text-xs text-white/60 hover:text-white">
|
|
|
|
|
- ✕
|
|
|
|
|
- </button>
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ {tags.map((tag) => {
|
|
|
|
|
+ const isEditing = editingTagId === tag.id
|
|
|
|
|
+ const isDeleting = deletingTagId === tag.id
|
|
|
|
|
+ const showConfirm = isEditing || isDeleting
|
|
|
|
|
+ return (
|
|
|
|
|
+ <div key={tag.id} className="flex items-center justify-between rounded-lg bg-white/5 px-3 py-2 gap-2">
|
|
|
|
|
+ {isEditing ? (
|
|
|
|
|
+ <input value={editTagName} onChange={(e) => setEditTagName(e.target.value)}
|
|
|
|
|
+ className="flex-1 rounded border border-white/10 bg-white/10 px-2 py-1 text-sm text-white outline-none"
|
|
|
|
|
+ autoFocus
|
|
|
|
|
+ onKeyDown={(e) => {
|
|
|
|
|
+ if (e.key === 'Enter') saveTag(tag.id)
|
|
|
|
|
+ if (e.key === 'Escape') cancelEditTag()
|
|
|
|
|
+ }}
|
|
|
|
|
+ />
|
|
|
) : (
|
|
) : (
|
|
|
- <>
|
|
|
|
|
- <span className="text-sm text-white">{tag.name}</span>
|
|
|
|
|
- <div className="flex items-center gap-2">
|
|
|
|
|
- <span className="text-xs text-white/40">{tag.id}</span>
|
|
|
|
|
- <div className="relative h-5 w-12 overflow-hidden">
|
|
|
|
|
- <div className={`absolute inset-0 flex items-center gap-2 transition-all duration-200 ${
|
|
|
|
|
- deletingTagId === tag.id
|
|
|
|
|
- ? '-translate-y-full opacity-0'
|
|
|
|
|
- : 'translate-y-0 opacity-100'
|
|
|
|
|
- }`}>
|
|
|
|
|
- <button onClick={() => startEditTag(tag)}
|
|
|
|
|
- className="text-xs text-white/40 hover:text-white"
|
|
|
|
|
- title="Редактировать">
|
|
|
|
|
- ✏️
|
|
|
|
|
- </button>
|
|
|
|
|
- <button onClick={() => setDeletingTagId(tag.id)}
|
|
|
|
|
- className="text-xs text-white/40 hover:text-red-400"
|
|
|
|
|
- title="Удалить">
|
|
|
|
|
- 🗑️
|
|
|
|
|
- </button>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div className={`absolute inset-0 flex items-center gap-2 transition-all duration-200 ${
|
|
|
|
|
- deletingTagId === tag.id
|
|
|
|
|
- ? 'translate-y-0 opacity-100'
|
|
|
|
|
- : 'translate-y-full opacity-0'
|
|
|
|
|
- }`}>
|
|
|
|
|
- <button onClick={() => confirmDeleteTag(tag.id)}
|
|
|
|
|
- className="text-xs text-green-400 hover:text-green-300"
|
|
|
|
|
- title="Подтвердить удаление">
|
|
|
|
|
- ✓
|
|
|
|
|
- </button>
|
|
|
|
|
- <button onClick={() => setDeletingTagId(null)}
|
|
|
|
|
- className="text-xs text-white/40 hover:text-white"
|
|
|
|
|
- title="Отмена">
|
|
|
|
|
- ✕
|
|
|
|
|
- </button>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </>
|
|
|
|
|
|
|
+ <span className="text-sm text-white truncate">{tag.name}</span>
|
|
|
)}
|
|
)}
|
|
|
|
|
+ <div className="flex items-center gap-2 shrink-0">
|
|
|
|
|
+ <span className="text-xs text-white/40">{tag.id}</span>
|
|
|
|
|
+ <div className="relative h-5 w-12 overflow-hidden">
|
|
|
|
|
+ <div className={`absolute inset-0 flex items-center gap-2 transition-all duration-200 ${
|
|
|
|
|
+ showConfirm ? '-translate-y-full opacity-0' : 'translate-y-0 opacity-100'
|
|
|
|
|
+ }`}>
|
|
|
|
|
+ <button onClick={() => startEditTag(tag)}
|
|
|
|
|
+ className="text-xs text-white/40 hover:text-white"
|
|
|
|
|
+ title="Редактировать">
|
|
|
|
|
+ ✏️
|
|
|
|
|
+ </button>
|
|
|
|
|
+ <button onClick={() => setDeletingTagId(tag.id)}
|
|
|
|
|
+ className="text-xs text-white/40 hover:text-red-400"
|
|
|
|
|
+ title="Удалить">
|
|
|
|
|
+ 🗑️
|
|
|
|
|
+ </button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div className={`absolute inset-0 flex items-center gap-2 transition-all duration-200 ${
|
|
|
|
|
+ showConfirm ? 'translate-y-0 opacity-100' : 'translate-y-full opacity-0'
|
|
|
|
|
+ }`}>
|
|
|
|
|
+ <button onClick={() => isEditing ? saveTag(tag.id) : confirmDeleteTag(tag.id)}
|
|
|
|
|
+ className={`text-xs hover:brightness-125 ${isEditing ? 'text-green-400 hover:text-green-300' : 'text-red-400 hover:text-red-300'}`}
|
|
|
|
|
+ title={isEditing ? 'Сохранить' : 'Подтвердить удаление'}>
|
|
|
|
|
+ ✓
|
|
|
|
|
+ </button>
|
|
|
|
|
+ <button onClick={() => isEditing ? cancelEditTag() : setDeletingTagId(null)}
|
|
|
|
|
+ className="text-xs text-white/40 hover:text-white"
|
|
|
|
|
+ title="Отмена">
|
|
|
|
|
+ ✕
|
|
|
|
|
+ </button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
- ))}
|
|
|
|
|
|
|
+ )
|
|
|
|
|
+ })}
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
@@ -244,71 +233,60 @@ export default function AdminTagsPage() {
|
|
|
</button>
|
|
</button>
|
|
|
</div>
|
|
</div>
|
|
|
<div className="max-h-[calc(100vh-13rem)] space-y-1 overflow-y-auto pb-8 pr-1">
|
|
<div className="max-h-[calc(100vh-13rem)] space-y-1 overflow-y-auto pb-8 pr-1">
|
|
|
- {features.map((feat) => (
|
|
|
|
|
- <div key={feat.id} className="flex items-center justify-between rounded-lg bg-white/5 px-3 py-2">
|
|
|
|
|
- {editingFeatureId === feat.id ? (
|
|
|
|
|
- <div className="flex flex-1 items-center gap-2">
|
|
|
|
|
- <input value={editFeatureName} onChange={(e) => setEditFeatureName(e.target.value)}
|
|
|
|
|
- className="flex-1 rounded border border-white/10 bg-white/10 px-2 py-1 text-sm text-white outline-none"
|
|
|
|
|
- autoFocus
|
|
|
|
|
- onKeyDown={(e) => {
|
|
|
|
|
- if (e.key === 'Enter') saveFeature(feat.id)
|
|
|
|
|
- if (e.key === 'Escape') cancelEditFeature()
|
|
|
|
|
- }}
|
|
|
|
|
- />
|
|
|
|
|
- <button onClick={() => saveFeature(feat.id)}
|
|
|
|
|
- className="rounded bg-green-600 px-2 py-1 text-xs text-white hover:bg-green-500">
|
|
|
|
|
- ✓
|
|
|
|
|
- </button>
|
|
|
|
|
- <button onClick={cancelEditFeature}
|
|
|
|
|
- className="rounded bg-white/10 px-2 py-1 text-xs text-white/60 hover:text-white">
|
|
|
|
|
- ✕
|
|
|
|
|
- </button>
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ {features.map((feat) => {
|
|
|
|
|
+ const isEditingFeat = editingFeatureId === feat.id
|
|
|
|
|
+ const isDeletingFeat = deletingFeatureId === feat.id
|
|
|
|
|
+ const showConfirmFeat = isEditingFeat || isDeletingFeat
|
|
|
|
|
+ return (
|
|
|
|
|
+ <div key={feat.id} className="flex items-center justify-between rounded-lg bg-white/5 px-3 py-2 gap-2">
|
|
|
|
|
+ {isEditingFeat ? (
|
|
|
|
|
+ <input value={editFeatureName} onChange={(e) => setEditFeatureName(e.target.value)}
|
|
|
|
|
+ className="flex-1 rounded border border-white/10 bg-white/10 px-2 py-1 text-sm text-white outline-none"
|
|
|
|
|
+ autoFocus
|
|
|
|
|
+ onKeyDown={(e) => {
|
|
|
|
|
+ if (e.key === 'Enter') saveFeature(feat.id)
|
|
|
|
|
+ if (e.key === 'Escape') cancelEditFeature()
|
|
|
|
|
+ }}
|
|
|
|
|
+ />
|
|
|
) : (
|
|
) : (
|
|
|
- <>
|
|
|
|
|
- <span className="text-sm text-white">{feat.name}</span>
|
|
|
|
|
- <div className="flex items-center gap-2">
|
|
|
|
|
- <span className="text-xs text-white/40">{feat.id}</span>
|
|
|
|
|
- <div className="relative h-5 w-12 overflow-hidden">
|
|
|
|
|
- <div className={`absolute inset-0 flex items-center gap-2 transition-all duration-200 ${
|
|
|
|
|
- deletingFeatureId === feat.id
|
|
|
|
|
- ? '-translate-y-full opacity-0'
|
|
|
|
|
- : 'translate-y-0 opacity-100'
|
|
|
|
|
- }`}>
|
|
|
|
|
- <button onClick={() => startEditFeature(feat)}
|
|
|
|
|
- className="text-xs text-white/40 hover:text-white"
|
|
|
|
|
- title="Редактировать">
|
|
|
|
|
- ✏️
|
|
|
|
|
- </button>
|
|
|
|
|
- <button onClick={() => setDeletingFeatureId(feat.id)}
|
|
|
|
|
- className="text-xs text-white/40 hover:text-red-400"
|
|
|
|
|
- title="Удалить">
|
|
|
|
|
- 🗑️
|
|
|
|
|
- </button>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div className={`absolute inset-0 flex items-center gap-2 transition-all duration-200 ${
|
|
|
|
|
- deletingFeatureId === feat.id
|
|
|
|
|
- ? 'translate-y-0 opacity-100'
|
|
|
|
|
- : 'translate-y-full opacity-0'
|
|
|
|
|
- }`}>
|
|
|
|
|
- <button onClick={() => confirmDeleteFeature(feat.id)}
|
|
|
|
|
- className="text-xs text-green-400 hover:text-green-300"
|
|
|
|
|
- title="Подтвердить удаление">
|
|
|
|
|
- ✓
|
|
|
|
|
- </button>
|
|
|
|
|
- <button onClick={() => setDeletingFeatureId(null)}
|
|
|
|
|
- className="text-xs text-white/40 hover:text-white"
|
|
|
|
|
- title="Отмена">
|
|
|
|
|
- ✕
|
|
|
|
|
- </button>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </>
|
|
|
|
|
|
|
+ <span className="text-sm text-white truncate">{feat.name}</span>
|
|
|
)}
|
|
)}
|
|
|
|
|
+ <div className="flex items-center gap-2 shrink-0">
|
|
|
|
|
+ <span className="text-xs text-white/40">{feat.id}</span>
|
|
|
|
|
+ <div className="relative h-5 w-12 overflow-hidden">
|
|
|
|
|
+ <div className={`absolute inset-0 flex items-center gap-2 transition-all duration-200 ${
|
|
|
|
|
+ showConfirmFeat ? '-translate-y-full opacity-0' : 'translate-y-0 opacity-100'
|
|
|
|
|
+ }`}>
|
|
|
|
|
+ <button onClick={() => startEditFeature(feat)}
|
|
|
|
|
+ className="text-xs text-white/40 hover:text-white"
|
|
|
|
|
+ title="Редактировать">
|
|
|
|
|
+ ✏️
|
|
|
|
|
+ </button>
|
|
|
|
|
+ <button onClick={() => setDeletingFeatureId(feat.id)}
|
|
|
|
|
+ className="text-xs text-white/40 hover:text-red-400"
|
|
|
|
|
+ title="Удалить">
|
|
|
|
|
+ 🗑️
|
|
|
|
|
+ </button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div className={`absolute inset-0 flex items-center gap-2 transition-all duration-200 ${
|
|
|
|
|
+ showConfirmFeat ? 'translate-y-0 opacity-100' : 'translate-y-full opacity-0'
|
|
|
|
|
+ }`}>
|
|
|
|
|
+ <button onClick={() => isEditingFeat ? saveFeature(feat.id) : confirmDeleteFeature(feat.id)}
|
|
|
|
|
+ className={`text-xs hover:brightness-125 ${isEditingFeat ? 'text-green-400 hover:text-green-300' : 'text-red-400 hover:text-red-300'}`}
|
|
|
|
|
+ title={isEditingFeat ? 'Сохранить' : 'Подтвердить удаление'}>
|
|
|
|
|
+ ✓
|
|
|
|
|
+ </button>
|
|
|
|
|
+ <button onClick={() => isEditingFeat ? cancelEditFeature() : setDeletingFeatureId(null)}
|
|
|
|
|
+ className="text-xs text-white/40 hover:text-white"
|
|
|
|
|
+ title="Отмена">
|
|
|
|
|
+ ✕
|
|
|
|
|
+ </button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
- ))}
|
|
|
|
|
|
|
+ )
|
|
|
|
|
+ })}
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|