diff --git a/src/components/admin/MenuEditorTab.tsx b/src/components/admin/MenuEditorTab.tsx index c40359d..d157d3a 100644 --- a/src/components/admin/MenuEditorTab.tsx +++ b/src/components/admin/MenuEditorTab.tsx @@ -86,6 +86,30 @@ const LinkIcon = () => ( ); +const ArrowUpIcon = () => ( + + + +); + +const ArrowDownIcon = () => ( + + + +); + // ============ Helpers ============ function generateId(): string { @@ -137,6 +161,8 @@ interface ButtonChipProps { onToggleExpand: () => void; onUpdate: (updates: Partial) => void; onRemove: () => void; + onMoveUp: (() => void) | null; + onMoveDown: (() => void) | null; isBuiltin: boolean; } @@ -146,6 +172,8 @@ function ButtonChip({ onToggleExpand, onUpdate, onRemove, + onMoveUp, + onMoveDown, isBuiltin, }: ButtonChipProps) { const { t } = useTranslation(); @@ -168,6 +196,32 @@ function ButtonChip({ > {/* Collapsed header */}
+
+ + +
{displayName} @@ -288,24 +342,30 @@ interface SortableRowProps { row: MenuRowConfig; rowIndex: number; expandedButtons: Set; + usedBuiltinIds: Set; onToggleExpand: (buttonId: string) => void; onUpdateRow: (rowId: string, updates: Partial) => void; onRemoveRow: (rowId: string) => void; onUpdateButton: (rowId: string, buttonId: string, updates: Partial) => void; onRemoveButton: (rowId: string, buttonId: string) => void; - onAddButtonClick: (rowId: string) => void; + onAddBuiltin: (rowId: string, sectionId: string) => void; + onAddCustom: (rowId: string) => void; + onReorderButton: (rowId: string, buttonIndex: number, direction: 'up' | 'down') => void; } function SortableRow({ row, rowIndex, expandedButtons, + usedBuiltinIds, onToggleExpand, onUpdateRow, onRemoveRow, onUpdateButton, onRemoveButton, - onAddButtonClick, + onAddBuiltin, + onAddCustom, + onReorderButton, }: SortableRowProps) { const { t } = useTranslation(); const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ @@ -359,7 +419,7 @@ function SortableRow({ {/* Row body */}
- {row.buttons.map((button) => ( + {row.buttons.map((button, btnIndex) => ( onToggleExpand(button.id)} onUpdate={(updates) => onUpdateButton(row.id, button.id, updates)} onRemove={() => onRemoveButton(row.id, button.id)} + onMoveUp={btnIndex > 0 ? () => onReorderButton(row.id, btnIndex, 'up') : null} + onMoveDown={ + btnIndex < row.buttons.length - 1 + ? () => onReorderButton(row.id, btnIndex, 'down') + : null + } isBuiltin={button.type === 'builtin'} /> ))} - {/* Add button */} - + {/* Inline add button panel */} +
); } -// ============ AddButtonDialog ============ +// ============ InlineAddPanel ============ -interface AddButtonDialogProps { +interface InlineAddPanelProps { + rowId: string; usedBuiltinIds: Set; - onAddBuiltin: (sectionId: string) => void; - onAddCustom: () => void; - onClose: () => void; + onAddBuiltin: (rowId: string, sectionId: string) => void; + onAddCustom: (rowId: string) => void; } -function AddButtonDialog({ - usedBuiltinIds, - onAddBuiltin, - onAddCustom, - onClose, -}: AddButtonDialogProps) { +function InlineAddPanel({ rowId, usedBuiltinIds, onAddBuiltin, onAddCustom }: InlineAddPanelProps) { const { t } = useTranslation(); + const [isOpen, setIsOpen] = useState(false); const availableBuiltins = BUILTIN_SECTIONS.filter((id) => !usedBuiltinIds.has(id)); - return ( -
-
e.stopPropagation()} + if (!isOpen) { + return ( + + ); + } + + return ( +
+ {availableBuiltins.length > 0 && ( + <> +

+ {t('admin.menuEditor.builtinButtons')} +

+ {availableBuiltins.map((id) => ( + -
- -
- {/* Available builtins */} - {availableBuiltins.length > 0 && ( - <> -

- {t('admin.menuEditor.builtinButtons')} -

- {availableBuiltins.map((id) => ( - - ))} -
- - )} - - {/* Custom URL button */} - -
-
+ {t(`admin.buttons.sections.${id}`)} + + ))} +
+ + )} + +
); } @@ -490,7 +538,6 @@ export function MenuEditorTab() { // Draft state const [draftConfig, setDraftConfig] = useState(DEFAULT_CONFIG); const [expandedButtons, setExpandedButtons] = useState>(new Set()); - const [addingToRow, setAddingToRow] = useState(null); const savedConfigRef = useRef(DEFAULT_CONFIG); const draftConfigRef = useRef(draftConfig); draftConfigRef.current = draftConfig; @@ -645,9 +692,20 @@ export function MenuEditorTab() { })); }, []); - const handleAddButtonClick = useCallback((rowId: string) => { - setAddingToRow(rowId); - }, []); + const reorderButton = useCallback( + (rowId: string, buttonIndex: number, direction: 'up' | 'down') => { + setDraftConfig((prev) => ({ + ...prev, + rows: prev.rows.map((r) => { + if (r.id !== rowId) return r; + const newIndex = direction === 'up' ? buttonIndex - 1 : buttonIndex + 1; + if (newIndex < 0 || newIndex >= r.buttons.length) return r; + return { ...r, buttons: arrayMove(r.buttons, buttonIndex, newIndex) }; + }), + })); + }, + [], + ); // Expand/collapse const toggleExpand = useCallback((buttonId: string) => { @@ -746,12 +804,15 @@ export function MenuEditorTab() { row={row} rowIndex={index} expandedButtons={expandedButtons} + usedBuiltinIds={usedBuiltinIds} onToggleExpand={toggleExpand} onUpdateRow={updateRow} onRemoveRow={removeRow} onUpdateButton={updateButton} onRemoveButton={removeButton} - onAddButtonClick={handleAddButtonClick} + onAddBuiltin={addBuiltinButton} + onAddCustom={addCustomButton} + onReorderButton={reorderButton} /> ))}
@@ -801,16 +862,6 @@ export function MenuEditorTab() { {t('admin.buttons.resetAll')}
- - {/* Add button dialog */} - {addingToRow && ( - addBuiltinButton(addingToRow, sectionId)} - onAddCustom={() => addCustomButton(addingToRow)} - onClose={() => setAddingToRow(null)} - /> - )} ); } diff --git a/src/locales/en.json b/src/locales/en.json index fcbb9bc..d89c9a0 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -1779,6 +1779,8 @@ "resetConfirm": "Reset menu layout to defaults?", "buttonTextPlaceholder": "Custom button text", "customLabelsHint": "Empty = default label", + "moveUp": "Move up", + "moveDown": "Move down", "invalidUrl": "Please provide a valid URL (http:// or https://) for all custom buttons" }, "apps": { diff --git a/src/locales/fa.json b/src/locales/fa.json index 2d335d9..43c3f91 100644 --- a/src/locales/fa.json +++ b/src/locales/fa.json @@ -1441,6 +1441,8 @@ "resetConfirm": "منو به تنظیمات پیش‌فرض بازنشانی شود؟ تمام تغییرات از بین می‌رود.", "buttonTextPlaceholder": "متن دکمه", "customLabelsHint": "متن دکمه برای هر زبان ربات", + "moveUp": "انتقال به بالا", + "moveDown": "انتقال به پایین", "invalidUrl": "لطفاً برای تمام دکمه‌های سفارشی یک URL معتبر وارد کنید (http:// یا https://)" }, "apps": { diff --git a/src/locales/ru.json b/src/locales/ru.json index 85aa4a5..39b1611 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -2290,6 +2290,8 @@ "resetConfirm": "Сбросить компоновку меню к значениям по умолчанию?", "buttonTextPlaceholder": "Свой текст кнопки", "customLabelsHint": "Пустое поле = название по умолчанию", + "moveUp": "Переместить вверх", + "moveDown": "Переместить вниз", "invalidUrl": "Укажите корректный URL (http:// или https://) для всех пользовательских кнопок" }, "apps": { diff --git a/src/locales/zh.json b/src/locales/zh.json index 53a7eba..1edb919 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -1479,6 +1479,8 @@ "resetConfirm": "将菜单重置为默认设置?所有更改将丢失。", "buttonTextPlaceholder": "按钮文本", "customLabelsHint": "每种语言的按钮文本", + "moveUp": "上移", + "moveDown": "下移", "invalidUrl": "请为所有自定义按钮提供有效的URL(http:// 或 https://)" }, "apps": {