fix: Submenu active state + currency symbols + flags integration

1. Fixed Submenu Active State 
   Problem: First submenu always active due to pathname.startsWith()
   - /dashboard matches /dashboard/analytics
   - Both items show as active

   Solution: Use exact match instead
   - const isActive = pathname === it.path
   - Only clicked item shows as active

   Files: DashboardSubmenuBar.tsx, SubmenuBar.tsx

2. Fixed Currency Symbol Display 
   Problem: HTML entities showing (ءإ)
   Solution: Use currency code when symbol has HTML entities

   Before: United Arab Emirates dirham (ءإ)
   After: United Arab Emirates dirham (AED)

   Logic:
   const displaySymbol = (!currency.symbol || currency.symbol.includes('&#'))
     ? currency.code
     : currency.symbol;

3. Integrated Flags.json 

   A. Moved flags.json to admin-spa/src/data/
   B. Added flag support to SearchableSelect component
      - New icon prop in Option interface
      - Displays flag before label in trigger
      - Displays flag before label in dropdown

   C. Currency select now shows flags
      - Flag icon next to each currency
      - Visual country identification
      - Better UX for currency selection

   D. Dynamic store summary with flag
      Before: 🇮🇩 Your store is located in Indonesia
      After: [FLAG] Your store is located in Indonesia

      - Flag based on selected currency
      - Country name from flags.json
      - Currency name (not just code)
      - Dynamic updates when currency changes

Benefits:
 Clear submenu navigation
 Readable currency symbols
 Visual country flags
 Better currency selection UX
 Dynamic store location display

Files Modified:
- DashboardSubmenuBar.tsx: Exact match for active state
- SubmenuBar.tsx: Exact match for active state
- Store.tsx: Currency symbol fix + flags integration
- searchable-select.tsx: Icon support
- flags.json: Moved to admin-spa/src/data/
This commit is contained in:
dwindown
2025-11-06 11:35:32 +07:00
parent cd644d339c
commit 2a679ffd15
9 changed files with 2276 additions and 36 deletions

View File

@@ -30,14 +30,13 @@ export default function DashboardSubmenuBar({ items = [], fullscreen = false }:
return (
<div data-submenubar className={`border-b border-border bg-background md:bg-background/95 md:backdrop-blur md:supports-[backdrop-filter]:bg-background/60 sticky ${topClass} z-20`}>
<div className="px-4 py-2">
<div className="flex flex-col lg:flex-row items-center justify-between gap-4">
<div className="flex flex-col xl:flex-row items-center justify-between gap-4">
{/* Submenu Links */}
<div className="flex gap-2 overflow-x-auto no-scrollbar w-full flex-shrink">
{items.map((it) => {
const key = `${it.label}-${it.path || it.href}`;
const isActive = !!it.path && (
it.exact ? pathname === it.path : pathname.startsWith(it.path)
);
// Fix: Always use exact match to prevent first submenu from being always active
const isActive = !!it.path && pathname === it.path;
const cls = [
'inline-flex items-center gap-2 rounded-md px-2.5 py-1.5 border text-sm whitespace-nowrap',
'focus:outline-none focus:ring-0 focus:shadow-none',
@@ -65,9 +64,9 @@ export default function DashboardSubmenuBar({ items = [], fullscreen = false }:
</div>
{/* Period Selector, Refresh & Dummy Toggle */}
<div className="flex justify-end lg:items-center gap-2 flex-shrink-0 w-full flex-shrink">
<div className="flex justify-end xl:items-center gap-2 flex-shrink-0 w-full xl:w-auto flex-shrink">
<Select value={period} onValueChange={setPeriod}>
<SelectTrigger className="w-full lg:w-[140px] h-8">
<SelectTrigger className="w-full xl:w-[140px] h-8">
<SelectValue />
</SelectTrigger>
<SelectContent>

View File

@@ -17,9 +17,8 @@ export default function SubmenuBar({ items = [] }: Props) {
<div className="flex gap-2 overflow-x-auto no-scrollbar">
{items.map((it) => {
const key = `${it.label}-${it.path || it.href}`;
const isActive = !!it.path && (
it.exact ? pathname === it.path : pathname.startsWith(it.path)
);
// Fix: Always use exact match to prevent first submenu from being always active
const isActive = !!it.path && pathname === it.path;
const cls = [
'inline-flex items-center gap-2 rounded-md px-2.5 py-1.5 border text-sm whitespace-nowrap',
'focus:outline-none focus:ring-0 focus:shadow-none',

View File

@@ -22,6 +22,8 @@ export interface Option {
label: React.ReactNode;
/** Optional text used for filtering. Falls back to string label or value. */
searchText?: string;
/** Optional icon (base64 image or URL) to display before the label */
icon?: string;
}
interface Props {
@@ -65,7 +67,12 @@ export function SearchableSelect({
aria-disabled={disabled}
tabIndex={disabled ? -1 : 0}
>
{selected ? selected.label : placeholder}
<div className="flex items-center gap-2 flex-1 min-w-0">
{selected?.icon && (
<img src={selected.icon} alt="" className="w-5 h-4 object-cover rounded-sm flex-shrink-0" />
)}
<span className="truncate">{selected ? selected.label : placeholder}</span>
</div>
<ChevronsUpDown className="opacity-50 h-4 w-4 shrink-0" />
</Button>
</PopoverTrigger>
@@ -99,12 +106,15 @@ export function SearchableSelect({
{showCheckIndicator && (
<Check
className={cn(
"mr-2 h-4 w-4",
"mr-2 h-4 w-4 flex-shrink-0",
opt.value === value ? "opacity-100" : "opacity-0"
)}
/>
)}
{opt.label}
{opt.icon && (
<img src={opt.icon} alt="" className="w-5 h-4 object-cover rounded-sm mr-2 flex-shrink-0" />
)}
<span className="truncate">{opt.label}</span>
</CommandItem>
))}
</CommandList>