feat: update API endpoint, add hardcoded auth token, and configure detail page highlight key

This commit is contained in:
Firman Ramdhani
2026-07-20 15:15:01 +07:00
parent ac049c7cf7
commit 7cec05464a
2 changed files with 3 additions and 3 deletions
@@ -0,0 +1,346 @@
import React, { ReactNode, useCallback, useMemo } from 'react';
import {
Title,
TitleProps,
Breadcrumbs,
Anchor,
Box,
Text,
ThemeIcon,
Flex,
Divider,
ActionIcon,
Tooltip,
} from '@mantine/core';
import { useLocalStorage } from '@mantine/hooks';
import { ChevronRight, LucideIcon, Maximize2, Minimize2 } from 'lucide-react'; // <-- Update Import Icon
import { PageActions, PageActionsProps } from '../../../../components';
// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------
export interface BreadcrumbItem {
label: string;
href?: string;
type: 'text' | 'link';
}
export interface ModulePageHeaderProps {
title?: ReactNode;
titleProps?: Omit<TitleProps, 'children'>;
miniTitleProps?: Omit<TitleProps, 'children'>;
description?: React.ReactNode;
icon?: LucideIcon;
badges?: React.ReactNode;
showBadges?: boolean;
breadcrumbs?: BreadcrumbItem[];
showPageHeader?: boolean;
disableMinimize?: boolean;
moduleKey: string;
actions?: PageActionsProps['actions'];
customButtonProps?: PageActionsProps['customButtonProps'];
}
// ---------------------------------------------------------------------------
// Shared transition style applied to all animated wrappers
// ---------------------------------------------------------------------------
const TRANSITION_STYLE: React.CSSProperties = {
transition: 'all 0.25s cubic-bezier(0.4, 0, 0.2, 1)',
};
// ---------------------------------------------------------------------------
// Sub-components
// ---------------------------------------------------------------------------
interface BreadcrumbBarProps {
breadcrumbs: BreadcrumbItem[];
}
function BreadcrumbBar({ breadcrumbs }: BreadcrumbBarProps) {
return (
<Breadcrumbs
style={{ flexWrap: 'wrap' }}
// visibleFrom="xs"
separator={
<ChevronRight
size={12}
strokeWidth={3}
style={{
color: 'light-dark(var(--mantine-color-gray-5), var(--mantine-color-dark-3))',
}}
/>
}
>
{breadcrumbs.map((item, index) => {
const isText = item.type === 'text';
const isLast = index === breadcrumbs.length - 1;
const sharedProps = {
c: !isLast ? ('dimmed' as const) : undefined,
size: 'xs' as const,
fw: 500,
style: {
color: isLast ? 'var(--mantine-color-text)' : undefined,
transition: 'color 0.2s ease',
letterSpacing: '0.2px',
},
};
return !isText ? (
<Anchor key={index} {...sharedProps} href={item.href}>
{item.label}
</Anchor>
) : (
<Text key={index} {...sharedProps}>
{item.label}
</Text>
);
})}
</Breadcrumbs>
);
}
// ---------------------------------------------------------------------------
// HeaderToggle
// ---------------------------------------------------------------------------
interface HeaderToggleProps {
isMinimized: boolean;
onToggle: () => void;
}
function HeaderToggle({ isMinimized, onToggle }: HeaderToggleProps) {
// const ToggleIcon = isMinimized ? ChevronDown : ChevronUp;
const ToggleIcon = isMinimized ? Maximize2 : Minimize2;
const label = isMinimized ? 'Expand header' : 'Collapse header';
return (
<Tooltip label={label} position="bottom-end" withArrow openDelay={400}>
<ActionIcon
variant="subtle"
color="gray"
size="sm"
radius="md"
onClick={onToggle}
aria-label={label}
style={{
opacity: 0.6,
...TRANSITION_STYLE,
}}
>
<ToggleIcon size={13} strokeWidth={2} />
</ActionIcon>
</Tooltip>
);
}
// ---------------------------------------------------------------------------
// Main component
// ---------------------------------------------------------------------------
export function ModulePageHeader(_props: ModulePageHeaderProps) {
const {
title,
titleProps,
miniTitleProps,
description,
icon: Icon,
badges,
showBadges = true,
breadcrumbs,
actions,
customButtonProps,
showPageHeader = true,
disableMinimize = false,
moduleKey,
} = _props;
const [isMinimized, setIsMinimized] = useLocalStorage<boolean>({
key: `page-header-minimized__${btoa(moduleKey)}`,
defaultValue: false,
getInitialValueInEffect: false,
});
if (!showPageHeader) {
return null;
}
const showToggle = !disableMinimize;
const handleToggle = useCallback(() => setIsMinimized((v) => !v), [setIsMinimized]);
const compactButtonProps = useMemo(
() => (action: any) => {
if (customButtonProps) return customButtonProps(action);
return { size: 'xs' as const };
},
[customButtonProps],
);
// -------------------------------------------------------------------------
// Compact / Toolbar mode
// -------------------------------------------------------------------------
if (isMinimized) {
return (
<Box mb={0} mt="xs" pb={6} style={TRANSITION_STYLE} className="module-page-header">
<Flex
direction="row"
justify="space-between"
align="center"
gap="sm"
wrap="nowrap"
style={{ minHeight: 36, ...TRANSITION_STYLE }}
>
{/* Left cluster: title + badges */}
<Flex gap="xs" align="center" style={{ flex: 1, minWidth: 0 }}>
{title && (
<Title
fw={600}
fz={{ base: 'lg', sm: 'xl' }}
lineClamp={1}
{...(miniTitleProps ?? {})}
style={{
color: 'var(--mantine-color-text)',
letterSpacing: '-0.2px',
lineHeight: 1.3,
...TRANSITION_STYLE,
...miniTitleProps?.style,
}}
>
{title}
</Title>
)}
{showBadges && badges && <Box style={{ flexShrink: 0 }}>{badges}</Box>}
</Flex>
{/* Right: actions + inline toggle (separated by divider) */}
<Flex align="center" gap="sm" style={{ flexShrink: 0 }}>
{actions && <PageActions actions={actions} customButtonProps={compactButtonProps} />}
{showToggle && (
<>
{actions && actions.length > 0 && (
<Divider orientation="vertical" style={{ height: 20, opacity: 0.4 }} />
)}
<HeaderToggle isMinimized={isMinimized} onToggle={handleToggle} />
</>
)}
</Flex>
</Flex>
</Box>
);
}
// -------------------------------------------------------------------------
// Expanded / Full mode
// -------------------------------------------------------------------------
// Helper variable agar kode lebih bersih
const hasBreadcrumbs = breadcrumbs && breadcrumbs.length > 0;
return (
<Box mb={0} mt="xs" pb={18} style={TRANSITION_STYLE} className="module-page-header">
{/* 1. Top Navigation Row: Breadcrumbs & Toggle */}
{((breadcrumbs && breadcrumbs.length) || showToggle) && (
<Flex
align="center"
justify="space-between"
mb={{ base: showToggle ? 'xs' : 0, sm: hasBreadcrumbs || showToggle ? 'md' : 0 }}
>
<Box>{hasBreadcrumbs && <BreadcrumbBar breadcrumbs={breadcrumbs} />}</Box>
{showToggle && (
<Box ml="auto">
<HeaderToggle isMinimized={isMinimized} onToggle={handleToggle} />
</Box>
)}
</Flex>
)}
{/* 2. Main Header Row 3-column layout */}
<Flex
direction="row"
justify="space-between"
align={{ base: 'stretch', sm: 'flex-start' }}
gap={{ base: 'md', sm: 'md' }}
wrap="nowrap"
style={TRANSITION_STYLE}
>
<Flex gap="md" align={{ base: 'flex-start', sm: 'flex-start' }} style={{ flex: 1, minWidth: 0 }}>
{Icon && (
<ThemeIcon
w={{ base: 40, sm: 48 }}
h={{ base: 40, sm: 48 }}
radius="md"
variant="light"
color="brand"
visibleFrom="sm"
style={{
flexShrink: 0,
border: '1px solid light-dark(var(--mantine-color-brand-1), var(--mantine-color-brand-6))',
boxShadow: 'light-dark(0 4px 12px rgba(0,0,0,0.03), 0 4px 12px rgba(0,0,0,0.2))',
...TRANSITION_STYLE,
}}
>
<Icon size={24} strokeWidth={1.5} />
</ThemeIcon>
)}
{(title || badges || description) && (
<Box style={{ flex: 1, minWidth: 0, ...TRANSITION_STYLE }}>
<Flex gap="xs" align="center" wrap="wrap">
{title && (
<Title
order={2}
fw={600}
lineClamp={1}
fz={{ base: 20, sm: 24 }}
lh={{ base: 1.3, sm: 1.2 }}
{...(titleProps ?? {})}
style={{
color: 'var(--mantine-color-text)',
letterSpacing: '-0.3px',
...TRANSITION_STYLE,
...titleProps?.style,
}}
>
{title}
</Title>
)}
{showBadges && badges && <Box>{badges}</Box>}
</Flex>
{description && (
<Text
fz={{ base: 'xs', sm: 'sm' }}
c="dimmed"
fw={400}
lineClamp={1}
mt={4}
style={{
wordBreak: 'break-word',
...TRANSITION_STYLE,
}}
>
{description}
</Text>
)}
</Box>
)}
</Flex>
<Flex
align="center"
gap="xs"
mt={{ base: 'xs', sm: 0 }}
ml={{ base: 'lg', sm: 0 }}
style={{ flexShrink: 0, alignSelf: 'center' }}
>
{actions && <PageActions actions={actions} customButtonProps={customButtonProps} />}
</Flex>
</Flex>
</Box>
);
}