Route article detail tabs in the URL and pin a preferred default.
Use /artikel/:sku/:tab with a localStorage-backed default, and a pin control to set which tab opens first.
This commit is contained in:
@@ -1,11 +1,108 @@
|
||||
export const ARTICLE_TAB = {
|
||||
history: 'history',
|
||||
supplier: 'supplier',
|
||||
seo: 'seo',
|
||||
label: 'label',
|
||||
attributes: 'attributes',
|
||||
compound: 'compound',
|
||||
settings: 'settings',
|
||||
};
|
||||
|
||||
/** Factory fallback when nothing is stored in localStorage. */
|
||||
export const DEFAULT_ARTICLE_TAB = ARTICLE_TAB.seo;
|
||||
|
||||
const PREFERRED_ARTICLE_TAB_KEY = 'dashfront:article-default-tab';
|
||||
|
||||
const ALL_ARTICLE_TAB_IDS = new Set(Object.values(ARTICLE_TAB));
|
||||
|
||||
const BASE_ARTICLE_TAB_IDS = [
|
||||
ARTICLE_TAB.history,
|
||||
ARTICLE_TAB.supplier,
|
||||
ARTICLE_TAB.seo,
|
||||
ARTICLE_TAB.label,
|
||||
ARTICLE_TAB.attributes,
|
||||
];
|
||||
|
||||
export function articleSku(article) {
|
||||
if (!article) return null;
|
||||
const sku = article.sku?.trim();
|
||||
return sku || null;
|
||||
}
|
||||
|
||||
export function artikelDetailPath(article) {
|
||||
const sku = articleSku(article);
|
||||
if (!sku) return null;
|
||||
return `/artikel/${encodeURIComponent(sku)}`;
|
||||
export function articleShowsCompoundTab(article) {
|
||||
return (
|
||||
article?.articleListCount > 0 ||
|
||||
article?.optionsCount > 0 ||
|
||||
article?.childCount > 0 ||
|
||||
article?.fatherArticleId != null
|
||||
);
|
||||
}
|
||||
|
||||
export function articleTabIds(article) {
|
||||
const ids = [...BASE_ARTICLE_TAB_IDS];
|
||||
if (articleShowsCompoundTab(article)) {
|
||||
ids.push(ARTICLE_TAB.compound);
|
||||
}
|
||||
ids.push(ARTICLE_TAB.settings);
|
||||
return ids;
|
||||
}
|
||||
|
||||
export function isArticleTabId(tab) {
|
||||
return ALL_ARTICLE_TAB_IDS.has(tab);
|
||||
}
|
||||
|
||||
export function readPreferredArticleTab() {
|
||||
try {
|
||||
const raw = localStorage.getItem(PREFERRED_ARTICLE_TAB_KEY);
|
||||
if (raw && isArticleTabId(raw)) return raw;
|
||||
} catch {
|
||||
// ignore quota / private mode
|
||||
}
|
||||
return DEFAULT_ARTICLE_TAB;
|
||||
}
|
||||
|
||||
export function writePreferredArticleTab(tab) {
|
||||
if (!isArticleTabId(tab)) return;
|
||||
try {
|
||||
localStorage.setItem(PREFERRED_ARTICLE_TAB_KEY, tab);
|
||||
} catch {
|
||||
// ignore quota / private mode
|
||||
}
|
||||
}
|
||||
|
||||
/** Preferred default, clamped to tabs visible for this article. */
|
||||
export function preferredArticleTab(article) {
|
||||
const preferred = readPreferredArticleTab();
|
||||
const ids = articleTabIds(article);
|
||||
if (ids.includes(preferred)) return preferred;
|
||||
return DEFAULT_ARTICLE_TAB;
|
||||
}
|
||||
|
||||
export function resolveArticleTab(tabParam, article) {
|
||||
const ids = articleTabIds(article);
|
||||
if (tabParam && ids.includes(tabParam)) return tabParam;
|
||||
return preferredArticleTab(article);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object|string} articleOrSku
|
||||
* @param {string} [tab] omitted → preferred default from localStorage
|
||||
*/
|
||||
export function artikelDetailPath(articleOrSku, tab) {
|
||||
const sku =
|
||||
typeof articleOrSku === 'string'
|
||||
? articleOrSku.trim() || null
|
||||
: articleSku(articleOrSku);
|
||||
if (!sku) return null;
|
||||
|
||||
let segment;
|
||||
if (isArticleTabId(tab)) {
|
||||
segment = tab;
|
||||
} else if (articleOrSku && typeof articleOrSku === 'object') {
|
||||
segment = preferredArticleTab(articleOrSku);
|
||||
} else {
|
||||
segment = readPreferredArticleTab();
|
||||
}
|
||||
|
||||
return `/artikel/${encodeURIComponent(sku)}/${segment}`;
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
parseFilter,
|
||||
} from './searchFormat';
|
||||
import { preserveSearchFocus } from './searchFocus';
|
||||
import { artikelDetailPath } from './articleRoute';
|
||||
import withSearchParams from './withSearchParams';
|
||||
|
||||
const resultRowSx = {
|
||||
@@ -430,7 +431,7 @@ class ArtikelSearchPage extends Component {
|
||||
onMouseDown={(e) => e.preventDefault()}
|
||||
onClick={() => {
|
||||
this.persistQuery();
|
||||
this.props.navigate(`/artikel/${encodeURIComponent(row.sku)}`);
|
||||
this.props.navigate(artikelDetailPath(row));
|
||||
}}
|
||||
sx={resultRowSx}
|
||||
>
|
||||
|
||||
@@ -8,9 +8,13 @@ import { ArticleView } from '../../components/article';
|
||||
import BackendError from '../../components/BackendError';
|
||||
import { requireObject } from '../../utils/apiShape';
|
||||
import { backendErrorFrom, isBackendFailure } from '../../utils/backendError';
|
||||
import {
|
||||
artikelDetailPath,
|
||||
resolveArticleTab,
|
||||
} from './articleRoute';
|
||||
|
||||
export default function ArtikelSkuPage() {
|
||||
const { sku: skuParam } = useParams();
|
||||
const { sku: skuParam, tab: tabParam } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const sku = skuParam ? decodeURIComponent(skuParam) : '';
|
||||
const [data, setData] = useState(null);
|
||||
@@ -69,6 +73,14 @@ export default function ArtikelSkuPage() {
|
||||
};
|
||||
}, [sku, reloadKey]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!data) return;
|
||||
const resolved = resolveArticleTab(tabParam, data);
|
||||
if (tabParam !== resolved) {
|
||||
navigate(artikelDetailPath(data, resolved), { replace: true });
|
||||
}
|
||||
}, [data, tabParam, navigate]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<Box sx={{ display: 'flex', justifyContent: 'center', py: 6 }}>
|
||||
@@ -95,5 +107,17 @@ export default function ArtikelSkuPage() {
|
||||
|
||||
if (!data) return null;
|
||||
|
||||
return <ArticleView article={data} onArticleUpdated={setData} />;
|
||||
const tab = resolveArticleTab(tabParam, data);
|
||||
if (tabParam !== tab) return null;
|
||||
|
||||
return (
|
||||
<ArticleView
|
||||
article={data}
|
||||
tab={tab}
|
||||
onTabChange={(nextTab) => {
|
||||
navigate(artikelDetailPath(data, nextTab));
|
||||
}}
|
||||
onArticleUpdated={setData}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user