From 5eb94b34a48dd424de99666fb9f11e47e75d757a Mon Sep 17 00:00:00 2001 From: duckietm Date: Mon, 13 Apr 2026 16:55:39 +0200 Subject: [PATCH] Revert "Merge pull request #87 from simoleo89/pr/catalog-edit-admin" This reverts commit 1ca53e579782b1587ac8a163c174c4ac57354f17, reversing changes made to bef58bc929ffcbee72c5530aa79f3a84b3285fc6. --- .../catalog/CatalogAdminContext.tsx | 136 +---- src/components/catalog/CatalogClassicView.tsx | 152 +++-- src/components/catalog/CatalogModernView.tsx | 207 +++++-- .../views/admin/CatalogAdminEditorView.tsx | 75 --- .../views/admin/CatalogAdminIconBrowser.tsx | 191 ------ .../views/admin/CatalogAdminImageBrowser.tsx | 216 ------- .../views/admin/CatalogAdminOfferEditView.tsx | 42 +- .../views/admin/CatalogAdminOfferForm.tsx | 206 ------- .../views/admin/CatalogAdminOfferPanel.tsx | 206 ------- .../views/admin/CatalogAdminPageEditView.tsx | 26 +- .../views/admin/CatalogAdminPagePanel.tsx | 562 ------------------ .../views/admin/CatalogAdminPageTreeItem.tsx | 130 ---- .../views/admin/CatalogAdminPublishPanel.tsx | 63 -- .../navigation/CatalogNavigationItemView.tsx | 106 +++- .../page/layout/CatalogLayoutDefaultView.tsx | 43 +- 15 files changed, 438 insertions(+), 1923 deletions(-) delete mode 100644 src/components/catalog/views/admin/CatalogAdminEditorView.tsx delete mode 100644 src/components/catalog/views/admin/CatalogAdminIconBrowser.tsx delete mode 100644 src/components/catalog/views/admin/CatalogAdminImageBrowser.tsx delete mode 100644 src/components/catalog/views/admin/CatalogAdminOfferForm.tsx delete mode 100644 src/components/catalog/views/admin/CatalogAdminOfferPanel.tsx delete mode 100644 src/components/catalog/views/admin/CatalogAdminPagePanel.tsx delete mode 100644 src/components/catalog/views/admin/CatalogAdminPageTreeItem.tsx delete mode 100644 src/components/catalog/views/admin/CatalogAdminPublishPanel.tsx diff --git a/src/components/catalog/CatalogAdminContext.tsx b/src/components/catalog/CatalogAdminContext.tsx index 762e3f8..5a5fe18 100644 --- a/src/components/catalog/CatalogAdminContext.tsx +++ b/src/components/catalog/CatalogAdminContext.tsx @@ -1,10 +1,8 @@ -import { CatalogAdminCreateOfferComposer, CatalogAdminCreatePageComposer, CatalogAdminDeleteOfferComposer, CatalogAdminDeletePageComposer, CatalogAdminMoveOfferComposer, CatalogAdminMovePageComposer, CatalogAdminPublishComposer, CatalogAdminResultEvent, CatalogAdminSaveOfferComposer, CatalogAdminSavePageComposer, CatalogAdminSavePageImagesComposer } from '@nitrots/nitro-renderer'; +import { CatalogAdminCreateOfferComposer, CatalogAdminCreatePageComposer, CatalogAdminDeleteOfferComposer, CatalogAdminDeletePageComposer, CatalogAdminMoveOfferComposer, CatalogAdminMovePageComposer, CatalogAdminPublishComposer, CatalogAdminResultEvent, CatalogAdminSaveOfferComposer, CatalogAdminSavePageComposer } from '@nitrots/nitro-renderer'; import { createContext, FC, ReactNode, useCallback, useContext, useEffect, useRef, useState } from 'react'; import { ICatalogNode, IPurchasableOffer, NotificationAlertType, SendMessageComposer } from '../../api'; import { useMessageEvent, useNotification } from '../../hooks'; -export type AdminManageTab = 'pages' | 'offers' | 'publish'; - export interface IPageEditData { pageId?: number; @@ -16,7 +14,6 @@ export interface IPageEditData minRank: number; clubOnly?: string; orderNum: number; - iconId?: number; pageHeadline?: string; pageTeaser?: string; pageSpecial?: string; @@ -48,8 +45,6 @@ interface ICatalogAdminContext { adminMode: boolean; setAdminMode: (value: boolean) => void; - activeManageTab: AdminManageTab; - setActiveManageTab: (tab: AdminManageTab) => void; editingOffer: IPurchasableOffer | null; setEditingOffer: (offer: IPurchasableOffer | null) => void; editingPageData: boolean; @@ -58,23 +53,14 @@ interface ICatalogAdminContext setEditingRootPage: (value: boolean) => void; editingPageNode: ICatalogNode | null; setEditingPageNode: (node: ICatalogNode | null) => void; - selectedOfferIds: Set; - toggleOfferSelection: (id: number, multi?: boolean) => void; - selectAllOffers: (ids: number[]) => void; - clearOfferSelection: () => void; - offerSearchQuery: string; - setOfferSearchQuery: (query: string) => void; loading: boolean; lastError: string | null; savePage: (data: IPageEditData) => void; createPage: (data: IPageEditData) => void; deletePage: (pageId: number) => void; - savePageImages: (pageId: number, headerImage: string, teaserImage: string) => void; saveOffer: (data: IOfferEditData) => void; createOffer: (data: IOfferEditData) => void; deleteOffer: (offerId: number) => void; - duplicateOffer: (offer: IPurchasableOffer, pageId: number) => void; - batchUpdateOfferPrices: (offerIds: number[], credits: number, points: number, pointsType: number, pageId: number) => void; reorderOffers: (orders: { id: number; orderNumber: number }[]) => void; reorderPage: (pageId: number, newParentId: number, newIndex: number) => void; togglePageEnabled: (pageId: number) => void; @@ -90,38 +76,40 @@ export const useCatalogAdmin = () => useContext(CatalogAdminContext); export const CatalogAdminProvider: FC<{ children: ReactNode }> = ({ children }) => { const [ adminMode, setAdminMode ] = useState(false); - const [ activeManageTab, setActiveManageTab ] = useState('pages'); const [ editingOffer, setEditingOffer ] = useState(null); const [ editingPageData, setEditingPageData ] = useState(false); const [ editingRootPage, setEditingRootPage ] = useState(false); const [ editingPageNode, setEditingPageNode ] = useState(null); - const [ selectedOfferIds, setSelectedOfferIds ] = useState>(new Set()); - const [ offerSearchQuery, setOfferSearchQuery ] = useState(''); const [ loading, setLoading ] = useState(false); const [ lastError, setLastError ] = useState(null); const [ hasPendingChanges, setHasPendingChanges ] = useState(false); const pendingActionRef = useRef(null); const { simpleAlert = null } = useNotification(); - const toggleOfferSelection = useCallback((id: number, multi = false) => + // Keyboard shortcuts: Esc to close edit panels + useEffect(() => { - setSelectedOfferIds(prev => + if(!adminMode) return; + + const handleKeyDown = (e: KeyboardEvent) => { - const next = new Set(multi ? prev : []); - if(next.has(id)) next.delete(id); else next.add(id); - return next; - }); - }, []); + if(e.key === 'Escape') + { + if(editingOffer) { setEditingOffer(null); e.preventDefault(); return; } + if(editingPageData || editingRootPage || editingPageNode) + { + setEditingPageData(false); + setEditingRootPage(false); + setEditingPageNode(null); + e.preventDefault(); + } + } + }; - const selectAllOffers = useCallback((ids: number[]) => - { - setSelectedOfferIds(new Set(ids)); - }, []); + window.addEventListener('keydown', handleKeyDown); - const clearOfferSelection = useCallback(() => - { - setSelectedOfferIds(new Set()); - }, []); + return () => window.removeEventListener('keydown', handleKeyDown); + }, [ adminMode, editingOffer, editingPageData, editingRootPage, editingPageNode ]); useMessageEvent(CatalogAdminResultEvent, (event: CatalogAdminResultEvent) => { @@ -163,7 +151,6 @@ export const CatalogAdminProvider: FC<{ children: ReactNode }> = ({ children }) 'savePage': 'Page saved (publish to apply)', 'createPage': 'Page created (publish to apply)', 'deletePage': 'Page deleted (publish to apply)', - 'saveImages': 'Images saved (publish to apply)', 'saveOffer': 'Offer saved (publish to apply)', 'createOffer': 'Offer created (publish to apply)', 'deleteOffer': 'Offer deleted (publish to apply)', @@ -172,7 +159,6 @@ export const CatalogAdminProvider: FC<{ children: ReactNode }> = ({ children }) 'toggleVisible': 'Visibility toggled (publish to apply)', 'movePage': 'Page moved (publish to apply)', 'publish': 'Catalog published! All users updated.', - 'batchPrice': 'Prices updated (publish to apply)', }; simpleAlert(messages[action] || 'Operation completed', NotificationAlertType.DEFAULT, null, null, 'Catalog Admin'); @@ -186,7 +172,7 @@ export const CatalogAdminProvider: FC<{ children: ReactNode }> = ({ children }) setLastError(null); pendingActionRef.current = 'savePage'; SendMessageComposer(new CatalogAdminSavePageComposer( - data.pageId || 0, data.caption, data.caption, data.pageLayout, data.iconId ?? 0, + data.pageId || 0, data.caption, data.caption, data.pageLayout, 0, data.minRank, data.visible === '1', data.enabled === '1', data.orderNum, data.parentId, data.pageHeadline || '', data.pageTeaser || '', data.pageTextDetails || '' @@ -199,7 +185,7 @@ export const CatalogAdminProvider: FC<{ children: ReactNode }> = ({ children }) setLastError(null); pendingActionRef.current = 'createPage'; SendMessageComposer(new CatalogAdminCreatePageComposer( - data.caption, data.caption, data.pageLayout, data.iconId ?? 0, + data.caption, data.caption, data.pageLayout, 0, data.minRank, data.visible === '1', data.enabled === '1', data.orderNum, data.parentId )); @@ -213,14 +199,6 @@ export const CatalogAdminProvider: FC<{ children: ReactNode }> = ({ children }) SendMessageComposer(new CatalogAdminDeletePageComposer(pageId)); }, []); - const savePageImages = useCallback((pageId: number, headerImage: string, teaserImage: string) => - { - setLoading(true); - setLastError(null); - pendingActionRef.current = 'saveImages'; - SendMessageComposer(new CatalogAdminSavePageImagesComposer(pageId, headerImage, teaserImage)); - }, []); - const saveOffer = useCallback((data: IOfferEditData) => { setLoading(true); @@ -255,34 +233,6 @@ export const CatalogAdminProvider: FC<{ children: ReactNode }> = ({ children }) SendMessageComposer(new CatalogAdminDeleteOfferComposer(offerId)); }, []); - const duplicateOffer = useCallback((offer: IPurchasableOffer, pageId: number) => - { - setLoading(true); - setLastError(null); - pendingActionRef.current = 'createOffer'; - SendMessageComposer(new CatalogAdminCreateOfferComposer( - pageId, offer.product?.productClassId || 0, - offer.localizationId || '', offer.priceInCredits, offer.priceInActivityPoints, offer.activityPointType, - offer.product?.productCount || 1, offer.clubLevel > 0 ? 1 : 0, offer.product?.extraParam || '', - true, -1, 0, 0 - )); - }, []); - - const batchUpdateOfferPrices = useCallback((offerIds: number[], credits: number, points: number, pointsType: number, pageId: number) => - { - setLoading(true); - setLastError(null); - pendingActionRef.current = 'batchPrice'; - - for(const offerId of offerIds) - { - SendMessageComposer(new CatalogAdminSaveOfferComposer( - offerId, pageId, 0, '', credits, points, pointsType, - 1, 0, '', true, -1, 0, 0 - )); - } - }, []); - const reorderOffers = useCallback((orders: { id: number; orderNumber: number }[]) => { setLoading(true); @@ -327,52 +277,16 @@ export const CatalogAdminProvider: FC<{ children: ReactNode }> = ({ children }) SendMessageComposer(new CatalogAdminPublishComposer()); }, []); - // Keyboard shortcuts - useEffect(() => - { - if(!adminMode) return; - - const handleKeyDown = (e: KeyboardEvent) => - { - if(e.key === 'Escape') - { - if(editingOffer) { setEditingOffer(null); e.preventDefault(); return; } - if(editingPageData || editingRootPage || editingPageNode) - { - setEditingPageData(false); - setEditingRootPage(false); - setEditingPageNode(null); - e.preventDefault(); - return; - } - if(selectedOfferIds.size > 0) { clearOfferSelection(); e.preventDefault(); return; } - } - - if(e.ctrlKey && e.shiftKey && e.key === 'P') - { - e.preventDefault(); - if(hasPendingChanges && !loading) publishCatalog(); - } - }; - - window.addEventListener('keydown', handleKeyDown); - - return () => window.removeEventListener('keydown', handleKeyDown); - }, [ adminMode, editingOffer, editingPageData, editingRootPage, editingPageNode, selectedOfferIds, clearOfferSelection, hasPendingChanges, loading, publishCatalog ]); - return ( diff --git a/src/components/catalog/CatalogClassicView.tsx b/src/components/catalog/CatalogClassicView.tsx index 85d881b..e86b384 100644 --- a/src/components/catalog/CatalogClassicView.tsx +++ b/src/components/catalog/CatalogClassicView.tsx @@ -1,12 +1,12 @@ import { AddLinkEventTracker, GetSessionDataManager, ILinkEventTracker, RemoveLinkEventTracker } from '@nitrots/nitro-renderer'; -import { FC, useCallback, useEffect, useRef, useState } from 'react'; -import { FaCog } from 'react-icons/fa'; +import { FC, useEffect } from 'react'; +import { FaCog, FaEdit, FaEye, FaEyeSlash, FaPlus, FaTrash } from 'react-icons/fa'; import { GetConfigurationValue, LocalizeText } from '../../api'; -import { NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../common'; +import { Column, Grid, NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../common'; import { useCatalog } from '../../hooks'; import { CatalogAdminProvider, useCatalogAdmin } from './CatalogAdminContext'; -import { CatalogAdminEditorView } from './views/admin/CatalogAdminEditorView'; import { CatalogAdminOfferEditView } from './views/admin/CatalogAdminOfferEditView'; +import { CatalogAdminPageEditView } from './views/admin/CatalogAdminPageEditView'; import { CatalogIconView } from './views/catalog-icon/CatalogIconView'; import { CatalogGiftView } from './views/gift/CatalogGiftView'; import { CatalogNavigationView } from './views/navigation/CatalogNavigationView'; @@ -19,62 +19,54 @@ const CatalogClassicViewInner: FC<{}> = () => const catalogAdmin = useCatalogAdmin(); const adminMode = catalogAdmin?.adminMode ?? false; const setAdminMode = catalogAdmin?.setAdminMode ?? (() => {}); + const hasPendingChanges = catalogAdmin?.hasPendingChanges ?? false; + const publishCatalog = catalogAdmin?.publishCatalog ?? (() => {}); + const loading = catalogAdmin?.loading ?? false; const isMod = GetSessionDataManager().isModerator; - // Resizable nav column - const [ navWidth, setNavWidth ] = useState(() => - { - try { const s = localStorage.getItem('catalog.classic.nav.width'); return s ? Math.min(350, Math.max(100, parseInt(s))) : 220; } - catch { return 220; } - }); - const isResizing = useRef(false); - - const handleResizeStart = useCallback((e: React.MouseEvent) => - { - e.preventDefault(); - isResizing.current = true; - const startX = e.clientX; - const startWidth = navWidth; - - const onMouseMove = (ev: MouseEvent) => - { - if(!isResizing.current) return; - setNavWidth(Math.min(300, Math.max(100, startWidth + (ev.clientX - startX)))); - }; - - const onMouseUp = () => - { - isResizing.current = false; - document.removeEventListener('mousemove', onMouseMove); - document.removeEventListener('mouseup', onMouseUp); - setNavWidth(w => { try { localStorage.setItem('catalog.classic.nav.width', String(w)); } catch {} return w; }); - }; - - document.addEventListener('mousemove', onMouseMove); - document.addEventListener('mouseup', onMouseUp); - }, [ navWidth ]); - useEffect(() => { const linkTracker: ILinkEventTracker = { linkReceived: (url: string) => { const parts = url.split('/'); + if(parts.length < 2) return; switch(parts[1]) { - case 'show': setIsVisible(true); return; - case 'hide': setIsVisible(false); return; - case 'toggle': setIsVisible(prevValue => !prevValue); return; + case 'show': + setIsVisible(true); + return; + case 'hide': + setIsVisible(false); + return; + case 'toggle': + setIsVisible(prevValue => !prevValue); + return; case 'open': if(parts.length > 2) { - if(parts.length === 4 && parts[2] === 'offerId') { openPageByOfferId(parseInt(parts[3])); return; } - else { openPageByName(parts[2]); } + if(parts.length === 4) + { + switch(parts[2]) + { + case 'offerId': + openPageByOfferId(parseInt(parts[3])); + return; + } + } + else + { + openPageByName(parts[2]); + } } - else { setIsVisible(true); } + else + { + setIsVisible(true); + } + return; } }, @@ -82,6 +74,7 @@ const CatalogClassicViewInner: FC<{}> = () => }; AddLinkEventTracker(linkTracker); + return () => RemoveLinkEventTracker(linkTracker); }, [ setIsVisible, openPageByOfferId, openPageByName ]); @@ -90,46 +83,89 @@ const CatalogClassicViewInner: FC<{}> = () => { isVisible && setIsVisible(false) } /> + { /* Admin banner */ } + { adminMode && +
+ ⚙ Admin Mode + +
} { rootNode && (rootNode.children.length > 0) && rootNode.children.map((child, index) => { - if(!child.isVisible) return null; + if(!adminMode && !child.isVisible) return null; + + const isHidden = !child.isVisible; return ( { if(searchResult) setSearchResult(null); + activateNode(child); } } > -
+
{ GetConfigurationValue('catalog.tab.icons') && } { child.localization } + { adminMode && isHidden && } + { adminMode && +
e.stopPropagation() }> + { catalogAdmin.setEditingPageNode(child); catalogAdmin.setEditingRootPage(false); catalogAdmin.setEditingPageData(true); } } /> + catalogAdmin.togglePageVisible(child.pageId) }> + { isHidden ? : } + + { if(confirm(LocalizeText('catalog.admin.delete.category.confirm', [ 'name' ], [ child.localization ]))) catalogAdmin.deletePage(child.pageId); } } /> +
}
); }) } + { /* Admin toggle button in tabs bar */ } { isMod && setAdminMode(!adminMode) }> } -
- { !navigationHidden && activeNodes && activeNodes.length > 0 && - <> -
- -
-
- } -
+ { /* Admin: add new root category */ } + { adminMode && rootNode && +
+ + +
} + + { !navigationHidden && + + { activeNodes && (activeNodes.length > 0) && + } + } + + { adminMode && } { GetCatalogLayout(currentPage, () => setNavigationHidden(true)) } -
-
+ + } - { /* External windows */ } - diff --git a/src/components/catalog/CatalogModernView.tsx b/src/components/catalog/CatalogModernView.tsx index 14c53a7..95368b0 100644 --- a/src/components/catalog/CatalogModernView.tsx +++ b/src/components/catalog/CatalogModernView.tsx @@ -1,12 +1,12 @@ import { AddLinkEventTracker, GetSessionDataManager, ILinkEventTracker, RemoveLinkEventTracker } from '@nitrots/nitro-renderer'; -import { FC, useCallback, useEffect, useRef, useState } from 'react'; -import { FaCog, FaHeart, FaStar } from 'react-icons/fa'; +import { FC, useEffect, useState } from 'react'; +import { FaCog, FaEdit, FaEye, FaEyeSlash, FaHeart, FaPlus, FaStar, FaTrash } from 'react-icons/fa'; import { LocalizeText } from '../../api'; import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../common'; import { useCatalog, useCatalogFavorites } from '../../hooks'; import { CatalogAdminProvider, useCatalogAdmin } from './CatalogAdminContext'; -import { CatalogAdminEditorView } from './views/admin/CatalogAdminEditorView'; import { CatalogAdminOfferEditView } from './views/admin/CatalogAdminOfferEditView'; +import { CatalogAdminPageEditView } from './views/admin/CatalogAdminPageEditView'; import { CatalogIconView } from './views/catalog-icon/CatalogIconView'; import { CatalogFavoritesView } from './views/favorites/CatalogFavoritesView'; import { CatalogGiftView } from './views/gift/CatalogGiftView'; @@ -21,65 +21,57 @@ const CatalogModernViewInner: FC<{}> = () => const catalogAdmin = useCatalogAdmin(); const adminMode = catalogAdmin?.adminMode ?? false; const setAdminMode = catalogAdmin?.setAdminMode ?? (() => {}); + const hasPendingChanges = catalogAdmin?.hasPendingChanges ?? false; + const publishCatalog = catalogAdmin?.publishCatalog ?? (() => {}); + const loading = catalogAdmin?.loading ?? false; const { favoriteOfferIds, favoritePageIds } = useCatalogFavorites(); const [ showFavorites, setShowFavorites ] = useState(false); const isMod = GetSessionDataManager().isModerator; const totalFavs = favoriteOfferIds.length + favoritePageIds.length; - // Resizable nav column - const [ navWidth, setNavWidth ] = useState(() => - { - try { const s = localStorage.getItem('catalog.nav.width'); return s ? Math.min(400, Math.max(140, parseInt(s))) : 280; } - catch { return 280; } - }); - const isResizing = useRef(false); - - const handleResizeStart = useCallback((e: React.MouseEvent) => - { - e.preventDefault(); - isResizing.current = true; - const startX = e.clientX; - const startWidth = navWidth; - - const onMouseMove = (ev: MouseEvent) => - { - if(!isResizing.current) return; - setNavWidth(Math.min(350, Math.max(140, startWidth + (ev.clientX - startX)))); - }; - - const onMouseUp = () => - { - isResizing.current = false; - document.removeEventListener('mousemove', onMouseMove); - document.removeEventListener('mouseup', onMouseUp); - setNavWidth(w => { try { localStorage.setItem('catalog.nav.width', String(w)); } catch {} return w; }); - }; - - document.addEventListener('mousemove', onMouseMove); - document.addEventListener('mouseup', onMouseUp); - }, [ navWidth ]); - useEffect(() => { const linkTracker: ILinkEventTracker = { linkReceived: (url: string) => { const parts = url.split('/'); + if(parts.length < 2) return; switch(parts[1]) { - case 'show': setIsVisible(true); return; - case 'hide': setIsVisible(false); return; - case 'toggle': setIsVisible(prevValue => !prevValue); return; + case 'show': + setIsVisible(true); + return; + case 'hide': + setIsVisible(false); + return; + case 'toggle': + setIsVisible(prevValue => !prevValue); + return; case 'open': if(parts.length > 2) { - if(parts.length === 4 && parts[2] === 'offerId') { openPageByOfferId(parseInt(parts[3])); return; } - else { openPageByName(parts[2]); } + if(parts.length === 4) + { + switch(parts[2]) + { + case 'offerId': + openPageByOfferId(parseInt(parts[3])); + return; + } + } + else + { + openPageByName(parts[2]); + } } - else { setIsVisible(true); } + else + { + setIsVisible(true); + } + return; } }, @@ -87,6 +79,7 @@ const CatalogModernViewInner: FC<{}> = () => }; AddLinkEventTracker(linkTracker); + return () => RemoveLinkEventTracker(linkTracker); }, [ setIsVisible, openPageByOfferId, openPageByName ]); @@ -96,33 +89,73 @@ const CatalogModernViewInner: FC<{}> = () => setIsVisible(false) } /> + { /* Admin banner */ } + { adminMode && +
+ ⚙ Admin Mode + +
} +
{ /* === LEFT SIDEBAR === */ }
- { /* Favorites */ } + + { /* Favorites toggle */ }
setShowFavorites(!showFavorites) } >
0 ? 'text-danger' : 'text-muted' }` } /> - { totalFavs > 0 && { totalFavs } } + { totalFavs > 0 && + + { totalFavs } + }
{ LocalizeText('catalog.favorites') }
- { /* Categories */ } + { /* Admin: root page actions */ } + { adminMode && rootNode && +
+ + +
} + + { /* Category icons */ } { rootNode && rootNode.children.length > 0 && rootNode.children.map((child, index) => { - if(!child.isVisible) return null; + if(!adminMode && !child.isVisible) return null; + + const isHidden = !child.isVisible; return (
{ if(searchResult) setSearchResult(null); @@ -130,12 +163,57 @@ const CatalogModernViewInner: FC<{}> = () => activateNode(child); } } > -
+
+ { isHidden && }
{ child.localization } + { /* Admin actions on each root category */ } + { adminMode && +
+
+ { + e.stopPropagation(); + catalogAdmin.setEditingPageNode(child); + catalogAdmin.setEditingRootPage(false); + catalogAdmin.setEditingPageData(true); + } } + > + +
+
+ { + e.stopPropagation(); + catalogAdmin.togglePageVisible(child.pageId); + } } + > + { isHidden + ? + : } +
+
+ { + e.stopPropagation(); + if(confirm(LocalizeText('catalog.admin.delete.category.confirm', [ 'name' ], [ child.localization ]))) + { + catalogAdmin.deletePage(child.pageId); + } + } } + > + +
+
}
); }) } @@ -143,14 +221,15 @@ const CatalogModernViewInner: FC<{}> = () => { /* === MAIN AREA === */ }
- { /* Toolbar */ } -
+ { /* Toolbar: search + admin */ } +
+ { /* Breadcrumb */ }
{ activeNodes && activeNodes.length > 0 ? activeNodes.map((node, i) => ( - { i > 0 && { '\u203A' } } + { i > 0 && } activateNode(node) : undefined }> { node.localization } @@ -159,7 +238,11 @@ const CatalogModernViewInner: FC<{}> = () => )) : { LocalizeText('catalog.title') } }
-
+ +
+ +
+ { isMod && }
- { /* Content */ } + { /* Content area */ }
{ showFavorites - ?
setShowFavorites(false) } />
+ ?
+ setShowFavorites(false) } /> +
: <> { !navigationHidden && activeNodes && activeNodes.length > 0 && - <> -
- -
-
- } +
+ +
}
+ { adminMode && } { GetCatalogLayout(currentPage, () => setNavigationHidden(true)) }
} @@ -191,8 +274,6 @@ const CatalogModernViewInner: FC<{}> = () =>
} - { /* External windows */ } - diff --git a/src/components/catalog/views/admin/CatalogAdminEditorView.tsx b/src/components/catalog/views/admin/CatalogAdminEditorView.tsx deleted file mode 100644 index a5e3831..0000000 --- a/src/components/catalog/views/admin/CatalogAdminEditorView.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import { FC } from 'react'; -import { FaBoxOpen, FaCloudUploadAlt, FaSitemap } from 'react-icons/fa'; -import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../common'; -import { AdminManageTab, useCatalogAdmin } from '../../CatalogAdminContext'; -import { CatalogAdminOfferPanel } from './CatalogAdminOfferPanel'; -import { CatalogAdminPagePanel } from './CatalogAdminPagePanel'; -import { CatalogAdminPublishPanel } from './CatalogAdminPublishPanel'; - -const TABS: { key: AdminManageTab; label: string; icon: FC<{ className?: string }> }[] = [ - { key: 'pages', label: 'Pages', icon: FaSitemap }, - { key: 'offers', label: 'Offers', icon: FaBoxOpen }, - { key: 'publish', label: 'Publish', icon: FaCloudUploadAlt }, -]; - -export const CatalogAdminEditorView: FC<{}> = () => -{ - const catalogAdmin = useCatalogAdmin(); - const adminMode = catalogAdmin?.adminMode ?? false; - const setAdminMode = catalogAdmin?.setAdminMode; - const activeTab = catalogAdmin?.activeManageTab ?? 'pages'; - const setActiveTab = catalogAdmin?.setActiveManageTab; - const hasPendingChanges = catalogAdmin?.hasPendingChanges ?? false; - const loading = catalogAdmin?.loading ?? false; - const publishCatalog = catalogAdmin?.publishCatalog; - - if(!adminMode) return null; - - return ( - - setAdminMode(false) } /> - - { /* Tab bar */ } -
- { TABS.map(tab => - { - const Icon = tab.icon; - const isActive = activeTab === tab.key; - - return ( - - ); - }) } - -
- - { hasPendingChanges && - } -
- - - { activeTab === 'pages' && } - { activeTab === 'offers' && } - { activeTab === 'publish' && } - - - ); -}; diff --git a/src/components/catalog/views/admin/CatalogAdminIconBrowser.tsx b/src/components/catalog/views/admin/CatalogAdminIconBrowser.tsx deleted file mode 100644 index f8a09b0..0000000 --- a/src/components/catalog/views/admin/CatalogAdminIconBrowser.tsx +++ /dev/null @@ -1,191 +0,0 @@ -import { FC, useCallback, useEffect, useRef, useState } from 'react'; -import { FaSearch, FaSpinner, FaTimes } from 'react-icons/fa'; -import { LocalizeText } from '../../../../api'; -import { CatalogIconView } from '../catalog-icon/CatalogIconView'; - -export interface CatalogAdminIconBrowserProps -{ - currentIconId: number; - onSelect: (iconId: number) => void; - onClose: () => void; -} - -interface IconResult -{ - icons: number[]; - total: number; - offset: number; - limit: number; -} - -export const CatalogAdminIconBrowser: FC = props => -{ - const { currentIconId, onSelect, onClose } = props; - - const [ icons, setIcons ] = useState([]); - const [ total, setTotal ] = useState(0); - const [ offset, setOffset ] = useState(0); - const [ search, setSearch ] = useState(''); - const [ loading, setLoading ] = useState(false); - const [ selected, setSelected ] = useState(null); - const debounceRef = useRef>(null); - const limit = 120; - - const fetchIcons = useCallback(async (searchQuery: string, newOffset: number, append: boolean) => - { - setLoading(true); - - try - { - const params = new URLSearchParams({ limit: String(limit), offset: String(newOffset) }); - - if(searchQuery) params.set('search', searchQuery); - - const res = await fetch(`/api/admin/catalog/icons?${ params.toString() }`); - const data: IconResult = await res.json(); - - setIcons(prev => append ? [ ...prev, ...data.icons ] : data.icons); - setTotal(data.total); - setOffset(newOffset); - } - catch(e) - { - console.error('Failed to fetch catalog icons', e); - } - finally - { - setLoading(false); - } - }, []); - - useEffect(() => - { - fetchIcons('', 0, false); - }, [ fetchIcons ]); - - const handleSearchChange = useCallback((value: string) => - { - setSearch(value); - - if(debounceRef.current) clearTimeout(debounceRef.current); - - debounceRef.current = setTimeout(() => - { - setSelected(null); - fetchIcons(value, 0, false); - }, 300); - }, [ fetchIcons ]); - - const handleLoadMore = useCallback(() => - { - fetchIcons(search, offset + limit, true); - }, [ offset, search, fetchIcons ]); - - const handleConfirm = useCallback(() => - { - if(selected !== null) onSelect(selected); - }, [ selected, onSelect ]); - - const hasMore = icons.length < total; - - return ( -
-
- -
e.stopPropagation() }> - { /* Header */ } -
- - Choose Icon - -
- -
-
- - { /* Search */ } -
-
- - handleSearchChange(e.target.value) } - /> -
- { total } icons -
- - { /* Grid */ } -
- { icons.length === 0 && !loading - ?
- { search ? 'No icons found' : 'No icons available' } -
- :
- { icons.map(id => - { - const isSelected = selected === id; - const isCurrent = currentIconId === id; - - return ( -
setSelected(id) } - onDoubleClick={ () => onSelect(id) } - > - - { id } - { isCurrent && } -
- ); - }) } -
} - - { loading && -
- -
} - - { hasMore && !loading && -
- -
} -
- - { /* Footer */ } -
-
- { selected !== null ? `Icon #${ selected }` : 'Click an icon to select' } -
-
- - -
-
-
-
- ); -}; diff --git a/src/components/catalog/views/admin/CatalogAdminImageBrowser.tsx b/src/components/catalog/views/admin/CatalogAdminImageBrowser.tsx deleted file mode 100644 index 004db3a..0000000 --- a/src/components/catalog/views/admin/CatalogAdminImageBrowser.tsx +++ /dev/null @@ -1,216 +0,0 @@ -import { FC, useCallback, useEffect, useRef, useState } from 'react'; -import { FaSearch, FaSpinner, FaTimes } from 'react-icons/fa'; -import { GetConfigurationValue, LocalizeText } from '../../../../api'; - -export interface CatalogAdminImageBrowserProps -{ - type: 'header' | 'teaser'; - currentImage?: string; - onSelect: (filename: string) => void; - onClose: () => void; -} - -interface ImageResult -{ - images: string[]; - total: number; - offset: number; - limit: number; -} - -export const CatalogAdminImageBrowser: FC = props => -{ - const { type, currentImage, onSelect, onClose } = props; - - const [ images, setImages ] = useState([]); - const [ total, setTotal ] = useState(0); - const [ offset, setOffset ] = useState(0); - const [ search, setSearch ] = useState(''); - const [ loading, setLoading ] = useState(false); - const [ selected, setSelected ] = useState(null); - const debounceRef = useRef>(null); - const limit = 80; - - const buildImageUrl = useCallback((name: string) => - { - const assetUrl = GetConfigurationValue('catalog.asset.image.url'); - - return assetUrl.replace('%name%', name); - }, []); - - const fetchImages = useCallback(async (searchQuery: string, newOffset: number, append: boolean) => - { - setLoading(true); - - try - { - const params = new URLSearchParams({ type, limit: String(limit), offset: String(newOffset) }); - - if(searchQuery) params.set('search', searchQuery); - - const res = await fetch(`/api/admin/catalog/images?${ params.toString() }`); - const data: ImageResult = await res.json(); - - setImages(prev => append ? [ ...prev, ...data.images ] : data.images); - setTotal(data.total); - setOffset(newOffset); - } - catch(e) - { - console.error('Failed to fetch catalog images', e); - } - finally - { - setLoading(false); - } - }, [ type ]); - - useEffect(() => - { - fetchImages('', 0, false); - }, [ fetchImages ]); - - const handleSearchChange = useCallback((value: string) => - { - setSearch(value); - - if(debounceRef.current) clearTimeout(debounceRef.current); - - debounceRef.current = setTimeout(() => - { - setSelected(null); - fetchImages(value, 0, false); - }, 300); - }, [ fetchImages ]); - - const handleLoadMore = useCallback(() => - { - const newOffset = offset + limit; - - fetchImages(search, newOffset, true); - }, [ offset, search, fetchImages ]); - - const handleSelect = useCallback((name: string) => - { - setSelected(name); - }, []); - - const handleConfirm = useCallback(() => - { - if(selected) onSelect(selected); - }, [ selected, onSelect ]); - - const handleDoubleClick = useCallback((name: string) => - { - onSelect(name); - }, [ onSelect ]); - - const hasMore = images.length < total; - - return ( -
-
- -
e.stopPropagation() }> - { /* Header */ } -
- - { type === 'header' ? LocalizeText('catalog.admin.browse.header') : LocalizeText('catalog.admin.browse.teaser') } - -
- -
-
- - { /* Search */ } -
-
- - handleSearchChange(e.target.value) } - /> -
- { total } { LocalizeText('catalog.admin.images.found') } -
- - { /* Grid */ } -
- { images.length === 0 && !loading - ?
- { search ? LocalizeText('catalog.admin.images.noresults') : LocalizeText('catalog.admin.images.empty') } -
- :
- { images.map(name => - { - const url = buildImageUrl(name); - const isSelected = selected === name; - const isCurrent = currentImage && currentImage.includes(name); - - return ( -
handleSelect(name) } - onDoubleClick={ () => handleDoubleClick(name) } - > - { { (e.target as HTMLImageElement).style.display = 'none'; } } - /> - { name } - { isCurrent && current } -
- ); - }) } -
} - - { loading && -
- -
} - - { hasMore && !loading && -
- -
} -
- - { /* Footer */ } -
-
- { selected ? selected : LocalizeText('catalog.admin.images.selecthint') } -
-
- - -
-
-
-
- ); -}; diff --git a/src/components/catalog/views/admin/CatalogAdminOfferEditView.tsx b/src/components/catalog/views/admin/CatalogAdminOfferEditView.tsx index 06bd3ae..f6da55a 100644 --- a/src/components/catalog/views/admin/CatalogAdminOfferEditView.tsx +++ b/src/components/catalog/views/admin/CatalogAdminOfferEditView.tsx @@ -104,7 +104,7 @@ export const CatalogAdminOfferEditView: FC<{}> = () => if(setEditingOffer) setEditingOffer(null); }; - const inputClass = 'text-[13px] border-2 border-card-grid-item-border rounded px-2 py-1 bg-white focus:outline-none focus:border-primary transition-colors'; + const inputClass = 'text-[11px] border-2 border-card-grid-item-border rounded px-2 py-1 bg-white focus:outline-none focus:border-primary transition-colors'; return createPortal(
setEditingOffer(null) }> @@ -124,30 +124,30 @@ export const CatalogAdminOfferEditView: FC<{}> = () =>
{ /* Current name */ } { !isNew && -
+
{ editingOffer.localizationName }
} { /* Catalog Name */ }
- + setCatalogName(e.target.value) } />
{ /* Generale */ }
-
{ LocalizeText('catalog.admin.offer.general') }
+
{ LocalizeText('catalog.admin.offer.general') }
- + setItemIds(e.target.value) } />
- + setAmount(parseInt(e.target.value) || 1) } />
- + setOrderNumber(parseInt(e.target.value) || 0) } />
@@ -155,18 +155,18 @@ export const CatalogAdminOfferEditView: FC<{}> = () => { /* Prezzi */ }
-
{ LocalizeText('catalog.admin.offer.prices') }
+
{ LocalizeText('catalog.admin.offer.prices') }
- + setCostCredits(parseInt(e.target.value) || 0) } />
- + setCostPoints(parseInt(e.target.value) || 0) } />
- + setClubOnly(e.target.value) }>
- + setLimitedStack(parseInt(e.target.value) || 0) } />
- + setOfferIdGroup(parseInt(e.target.value) || -1) } />
- + setExtradata(e.target.value) } />
setHaveOffer(e.target.checked ? '1' : '0') } /> - +
{ /* Actions */ }
{ !isNew - ? :
} -
diff --git a/src/components/catalog/views/admin/CatalogAdminOfferForm.tsx b/src/components/catalog/views/admin/CatalogAdminOfferForm.tsx deleted file mode 100644 index 28e67fa..0000000 --- a/src/components/catalog/views/admin/CatalogAdminOfferForm.tsx +++ /dev/null @@ -1,206 +0,0 @@ -import { FC, useCallback, useEffect, useState } from 'react'; -import { FaCopy, FaSave, FaSpinner, FaTrash } from 'react-icons/fa'; -import { IPurchasableOffer, LocalizeText } from '../../../../api'; -import { IOfferEditData, useCatalogAdmin } from '../../CatalogAdminContext'; - -export interface CatalogAdminOfferFormProps -{ - offer: IPurchasableOffer | null; - pageId: number; - isNew?: boolean; - onClose?: () => void; -} - -export const CatalogAdminOfferForm: FC = props => -{ - const { offer, pageId, isNew = false, onClose } = props; - const catalogAdmin = useCatalogAdmin(); - const loading = catalogAdmin?.loading ?? false; - - const [ itemIds, setItemIds ] = useState('0'); - const [ catalogName, setCatalogName ] = useState(''); - const [ costCredits, setCostCredits ] = useState(0); - const [ costPoints, setCostPoints ] = useState(0); - const [ pointsType, setPointsType ] = useState(0); - const [ amount, setAmount ] = useState(1); - const [ clubOnly, setClubOnly ] = useState('0'); - const [ extradata, setExtradata ] = useState(''); - const [ haveOffer, setHaveOffer ] = useState('1'); - const [ offerIdGroup, setOfferIdGroup ] = useState(-1); - const [ limitedStack, setLimitedStack ] = useState(0); - const [ orderNumber, setOrderNumber ] = useState(0); - - useEffect(() => - { - if(!offer) return; - - if(isNew || offer.offerId === -1) - { - setItemIds('0'); setCatalogName(''); setCostCredits(0); setCostPoints(0); - setPointsType(0); setAmount(1); setClubOnly('0'); setExtradata(''); - setHaveOffer('1'); setOfferIdGroup(-1); setLimitedStack(0); setOrderNumber(0); - } - else - { - setItemIds(String(offer.product?.productClassId || 0)); - setCatalogName(offer.localizationId || ''); - setCostCredits(offer.priceInCredits); - setCostPoints(offer.priceInActivityPoints); - setPointsType(offer.activityPointType); - setAmount(offer.product?.productCount || 1); - setClubOnly(offer.clubLevel > 0 ? '1' : '0'); - setExtradata(offer.product?.extraParam || ''); - setHaveOffer('1'); setOfferIdGroup(offer.offerId || -1); - setLimitedStack(0); setOrderNumber(0); - } - }, [ offer, isNew ]); - - const handleSave = useCallback(() => - { - if(!catalogAdmin) return; - - const data: IOfferEditData = { - offerId: isNew ? undefined : offer?.offerId, - pageId, itemIds, catalogName, costCredits, costPoints, pointsType, - amount, clubOnly, extradata, haveOffer, offerId_group: offerIdGroup, - limitedStack, orderNumber, - }; - - if(isNew) catalogAdmin.createOffer(data); - else catalogAdmin.saveOffer(data); - }, [ catalogAdmin, offer, isNew, pageId, itemIds, catalogName, costCredits, costPoints, pointsType, amount, clubOnly, extradata, haveOffer, offerIdGroup, limitedStack, orderNumber ]); - - const handleDelete = useCallback(() => - { - if(isNew || !offer || !catalogAdmin?.deleteOffer) return; - if(!confirm(LocalizeText('catalog.admin.delete.offer.confirm'))) return; - catalogAdmin.deleteOffer(offer.offerId); - onClose?.(); - }, [ isNew, offer, catalogAdmin, onClose ]); - - const handleDuplicate = useCallback(() => - { - if(!offer || !catalogAdmin?.duplicateOffer) return; - catalogAdmin.duplicateOffer(offer, pageId); - }, [ offer, catalogAdmin, pageId ]); - - if(!offer) return null; - - const iconUrl = !isNew && offer.product?.getIconUrl?.(offer); - const inputClass = 'text-[13px] border-2 border-card-grid-item-border rounded px-2 py-1 bg-white focus:outline-none focus:border-primary transition-colors w-full'; - - return ( -
- { /* Header with icon preview */ } -
- { iconUrl && -
- -
} -
-
- { isNew ? 'New Offer' : `Offer #${ offer.offerId }` } -
- { !isNew && offer.localizationName && -
{ offer.localizationName }
} -
-
- - { /* Identity */ } -
-
Identity
-
-
- - setCatalogName(e.target.value) } /> -
-
-
- - setItemIds(e.target.value) } /> -
-
- - setAmount(parseInt(e.target.value) || 1) } /> -
-
- - setOrderNumber(parseInt(e.target.value) || 0) } /> -
-
-
-
- - { /* Pricing */ } -
-
Pricing
-
-
- - setCostCredits(parseInt(e.target.value) || 0) } /> -
-
- - setCostPoints(parseInt(e.target.value) || 0) } /> -
-
- - -
-
-
- - { /* Options */ } -
-
Options
-
-
- - -
-
- - setLimitedStack(parseInt(e.target.value) || 0) } /> -
-
- - setOfferIdGroup(parseInt(e.target.value) || -1) } /> -
-
-
- - setExtradata(e.target.value) } /> -
- -
- - { /* Actions */ } -
-
- { !isNew && - } - { !isNew && - } -
- -
-
- ); -}; diff --git a/src/components/catalog/views/admin/CatalogAdminOfferPanel.tsx b/src/components/catalog/views/admin/CatalogAdminOfferPanel.tsx deleted file mode 100644 index b493c78..0000000 --- a/src/components/catalog/views/admin/CatalogAdminOfferPanel.tsx +++ /dev/null @@ -1,206 +0,0 @@ -import { FC, useCallback, useMemo, useState } from 'react'; -import { FaCheckSquare, FaDollarSign, FaExchangeAlt, FaPlus, FaSearch, FaStar, FaTrash } from 'react-icons/fa'; -import { IPurchasableOffer, LocalizeText } from '../../../../api'; -import { useCatalog } from '../../../../hooks'; -import { useCatalogAdmin } from '../../CatalogAdminContext'; -import { CatalogAdminOfferForm } from './CatalogAdminOfferForm'; - -export const CatalogAdminOfferPanel: FC<{}> = () => -{ - const { currentPage = null, activeNodes = [], rootNode = null } = useCatalog(); - const catalogAdmin = useCatalogAdmin(); - const selectedOfferIds = catalogAdmin?.selectedOfferIds ?? new Set(); - const toggleOfferSelection = catalogAdmin?.toggleOfferSelection; - const clearOfferSelection = catalogAdmin?.clearOfferSelection; - const offerSearchQuery = catalogAdmin?.offerSearchQuery ?? ''; - const setOfferSearchQuery = catalogAdmin?.setOfferSearchQuery; - - const [ editingOffer, setEditingOffer ] = useState(null); - const [ isNewOffer, setIsNewOffer ] = useState(false); - - // Batch pricing state - const [ showBatchPrice, setShowBatchPrice ] = useState(false); - const [ batchCredits, setBatchCredits ] = useState(0); - const [ batchPoints, setBatchPoints ] = useState(0); - const [ batchPointsType, setBatchPointsType ] = useState(0); - - const offers = currentPage?.offers ?? []; - const pageId = currentPage?.pageId ?? 0; - - const filteredOffers = useMemo(() => - { - if(!offerSearchQuery) return offers; - const q = offerSearchQuery.toLowerCase(); - return offers.filter(o => - o.localizationName?.toLowerCase().includes(q) || - o.localizationId?.toLowerCase().includes(q) || - String(o.offerId).includes(q) - ); - }, [ offers, offerSearchQuery ]); - - const handleSelectOffer = useCallback((offer: IPurchasableOffer, e: React.MouseEvent) => - { - if(e.ctrlKey || e.metaKey) - { - toggleOfferSelection?.(offer.offerId, true); - } - else - { - setEditingOffer(offer); - setIsNewOffer(false); - clearOfferSelection?.(); - } - }, [ toggleOfferSelection, clearOfferSelection ]); - - const handleNewOffer = useCallback(() => - { - setEditingOffer({ offerId: -1 } as IPurchasableOffer); - setIsNewOffer(true); - }, []); - - const handleBulkDelete = useCallback(() => - { - if(selectedOfferIds.size === 0) return; - if(!confirm(`Delete ${ selectedOfferIds.size } selected offer(s)?`)) return; - for(const id of selectedOfferIds) catalogAdmin?.deleteOffer(id); - clearOfferSelection?.(); - }, [ catalogAdmin, selectedOfferIds, clearOfferSelection ]); - - const handleBatchPriceApply = useCallback(() => - { - if(selectedOfferIds.size === 0) return; - catalogAdmin?.batchUpdateOfferPrices( - Array.from(selectedOfferIds), batchCredits, batchPoints, batchPointsType, pageId - ); - setShowBatchPrice(false); - clearOfferSelection?.(); - }, [ catalogAdmin, selectedOfferIds, batchCredits, batchPoints, batchPointsType, pageId, clearOfferSelection ]); - - const handleSelectAll = useCallback(() => - { - catalogAdmin?.selectAllOffers(offers.map(o => o.offerId)); - }, [ catalogAdmin, offers ]); - - const breadcrumb = activeNodes?.map(n => n.localization).join(' > ') || 'No page selected'; - const inputClass = 'text-[13px] border-2 border-card-grid-item-border rounded px-2 py-1 bg-white focus:outline-none focus:border-primary transition-colors w-full'; - - return ( -
- { /* Left: Offer grid */ } -
- { /* Top bar */ } -
-
- - { breadcrumb } - ({ filteredOffers.length }) -
- -
- - setOfferSearchQuery(e.target.value) } - /> -
- - -
- - { /* Selection bar */ } - { selectedOfferIds.size > 0 && -
- { selectedOfferIds.size } selected - - - - -
} - - { /* Batch price editor */ } - { showBatchPrice && selectedOfferIds.size > 0 && -
- Set price for { selectedOfferIds.size } offers: - setBatchCredits(parseInt(e.target.value) || 0) } /> - setBatchPoints(parseInt(e.target.value) || 0) } /> - - -
} - - { /* Offer grid */ } -
- { !currentPage - ?
- Select a page from Browse tab to see its offers -
- : filteredOffers.length === 0 - ?
- { offerSearchQuery ? 'No matches' : 'No offers on this page' } -
- :
- { filteredOffers.map((offer, index) => - { - const isSelected = selectedOfferIds.has(offer.offerId); - const isEditing = editingOffer?.offerId === offer.offerId && !isNewOffer; - const iconUrl = offer.product?.getIconUrl?.(offer); - - return ( -
handleSelectOffer(offer, e) } - > -
- toggleOfferSelection?.(offer.offerId, true) } - onClick={ e => e.stopPropagation() } - /> -
- - { iconUrl - ? - :
?
} - - #{ offer.offerId } -
- ); - }) } -
} -
-
- - { /* Right: Offer edit form */ } -
- { editingOffer - ? setEditingOffer(null) } /> - :
- Click an offer to edit
Ctrl+click to multi-select -
} -
-
- ); -}; diff --git a/src/components/catalog/views/admin/CatalogAdminPageEditView.tsx b/src/components/catalog/views/admin/CatalogAdminPageEditView.tsx index aa917a5..6fd4a07 100644 --- a/src/components/catalog/views/admin/CatalogAdminPageEditView.tsx +++ b/src/components/catalog/views/admin/CatalogAdminPageEditView.tsx @@ -64,7 +64,7 @@ export const CatalogAdminPageEditView: FC<{}> = () => if(!editingPageData || !targetNode) return null; - const inputClass = 'text-[13px] border-2 border-card-grid-item-border rounded px-2 py-1 bg-white focus:outline-none focus:border-primary transition-colors'; + const inputClass = 'text-[11px] border-2 border-card-grid-item-border rounded px-2 py-1 bg-white focus:outline-none focus:border-primary transition-colors'; const handleSave = async () => { @@ -101,37 +101,37 @@ export const CatalogAdminPageEditView: FC<{}> = () => return (
- + { isRoot ? LocalizeText('catalog.admin.edit.root') : `${ LocalizeText('catalog.admin.edit') } ${ targetNode.localization } (#${ targetPageId })` } - +
- + setCaption(e.target.value) } />
- + setMinRank(parseInt(e.target.value) || 1) } />
- +
- + setOrderNum(parseInt(e.target.value) || 0) } />
-