🐛 fix: Update order_id to order_no in BalanceLogPage and related typings; enhance timezone switch component with additional features and localization updates

This commit is contained in:
web
2025-09-05 08:50:57 -07:00
parent a4de9df1fc
commit ac36075e7b
33 changed files with 287 additions and 66 deletions
+207 -55
View File
@@ -1,79 +1,156 @@
'use client';
import { Button } from '@workspace/ui/components/button';
import { Command, CommandInput, CommandItem, CommandList } from '@workspace/ui/components/command';
import {
Command,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from '@workspace/ui/components/command';
import { Popover, PopoverContent, PopoverTrigger } from '@workspace/ui/components/popover';
import { Icon } from '@workspace/ui/custom-components/icon';
import { cn } from '@workspace/ui/lib/utils';
import { useMemo, useState } from 'react';
import { useLocale } from 'next-intl';
import { useEffect, useMemo, useState } from 'react';
interface TimezoneOption {
value: string;
label: string;
offset: string;
timezone: string;
}
function getAllTimezones(): TimezoneOption[] {
function getCurrentTime(timezone: string): string {
try {
const now = new Date();
return now.toLocaleTimeString('en-US', {
timeZone: timezone,
hour12: false,
hour: '2-digit',
minute: '2-digit',
});
} catch {
return '--:--';
}
}
function getAllTimezones(locale: string = 'en-US'): TimezoneOption[] {
try {
const timeZones = Intl.supportedValuesOf('timeZone');
return [
{
value: 'UTC',
label: 'UTC',
offset: '+00:00',
},
].concat(
timeZones
.map((tz) => {
const parts = tz.split('/');
let label = tz;
if (parts.length >= 2) {
const region = parts[0];
const city = parts[1]?.replace(/_/g, ' ') || '';
label = `${city} (${region})`;
}
const processed = timeZones
.map((tz) => {
try {
return {
value: tz,
label: label,
offset: getTimezoneOffset(tz),
label: tz,
timezone: getTimezoneOffset(tz),
};
})
.sort((a, b) => a.label.localeCompare(b.label)),
);
} catch {
return {
value: tz,
label: tz,
timezone: 'UTC+00:00',
};
}
})
.filter(Boolean)
.sort((a, b) => a.label.localeCompare(b.label, locale));
const hasUTC = processed.some((tz) => tz.value === 'UTC');
if (!hasUTC) {
processed.unshift({
value: 'UTC',
label: 'UTC',
timezone: 'UTC+00:00',
});
}
return processed;
} catch {
return [
{
value: 'UTC',
label: 'UTC',
offset: '+00:00',
timezone: 'UTC+00:00',
},
];
}
}
function getServerTimezones(): string[] {
return ['UTC'];
}
function getRecommendedTimezones(): string[] {
try {
const browserTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
if (browserTimezone.startsWith('Asia/')) {
return ['Asia/Shanghai', 'Asia/Tokyo', 'Asia/Kolkata', 'Asia/Singapore', 'Asia/Seoul'];
} else if (browserTimezone.startsWith('Europe/')) {
return ['Europe/London', 'Europe/Paris', 'Europe/Berlin', 'Europe/Rome', 'Europe/Madrid'];
} else if (browserTimezone.startsWith('America/')) {
return [
'America/New_York',
'America/Los_Angeles',
'America/Chicago',
'America/Denver',
'America/Toronto',
];
} else if (browserTimezone.startsWith('Australia/')) {
return ['Australia/Sydney', 'Australia/Melbourne', 'Australia/Perth', 'Australia/Brisbane'];
} else {
return [
'America/New_York',
'Europe/London',
'Asia/Shanghai',
'Asia/Tokyo',
'Australia/Sydney',
];
}
} catch {
return ['America/New_York', 'Europe/London', 'Asia/Shanghai', 'Asia/Tokyo', 'Australia/Sydney'];
}
}
function getTimezoneOffset(timezone: string): string {
try {
const now = new Date();
const utc = new Date(now.getTime() + now.getTimezoneOffset() * 60000);
const targetTime = new Date(utc.toLocaleString('en-US', { timeZone: timezone }));
const offset = (targetTime.getTime() - utc.getTime()) / (1000 * 60 * 60);
const sign = offset >= 0 ? '+' : '-';
const hours = Math.floor(Math.abs(offset));
const minutes = Math.floor((Math.abs(offset) - hours) * 60);
return `${sign}${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`;
return `UTC${sign}${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`;
} catch {
return '+00:00';
return 'UTC+00:00';
}
}
export default function TimezoneSwitch() {
const locale = useLocale();
const [timezone, setTimezone] = useState<string>('UTC');
const [open, setOpen] = useState(false);
const timezoneOptions = useMemo(() => getAllTimezones(), []);
const timezoneOptions = useMemo(() => getAllTimezones(locale), [locale]);
useEffect(() => {
const savedTimezone = localStorage.getItem('timezone');
if (savedTimezone) {
setTimezone(savedTimezone);
} else {
try {
const browserTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
setTimezone(browserTimezone);
localStorage.setItem('timezone', browserTimezone);
} catch {
setTimezone('UTC');
}
}
}, []);
const handleTimezoneChange = (newTimezone: string) => {
setTimezone(newTimezone);
@@ -86,6 +163,9 @@ export default function TimezoneSwitch() {
}),
);
};
const serverTimezones = timezoneOptions.filter(
(option) => getServerTimezones().includes(option.value) && option.value !== timezone,
);
return (
<Popover open={open} onOpenChange={setOpen}>
@@ -98,29 +178,101 @@ export default function TimezoneSwitch() {
<Command>
<CommandInput placeholder='Search...' />
<CommandList>
{timezoneOptions.map((option) => (
<CommandItem
key={option.value}
value={`${option.label} ${option.value}`}
onSelect={() => handleTimezoneChange(option.value)}
>
<div className='flex w-full items-center gap-3'>
<div className='flex flex-1 flex-col'>
<span className='font-medium'>{option.label}</span>
<span className='text-muted-foreground text-xs'>
{option.value} {option.offset}
</span>
</div>
<Icon
icon='uil:check'
className={cn(
'h-4 w-4',
timezone === option.value ? 'opacity-100' : 'opacity-0',
)}
/>
</div>
</CommandItem>
))}
<CommandGroup heading='Current'>
{timezoneOptions
.filter((option) => option.value === timezone)
.map((option) => (
<CommandItem
key={option.value}
value={`${option.label} ${option.value}`}
onSelect={() => handleTimezoneChange(option.value)}
className='bg-primary/10'
>
<div className='flex w-full items-center gap-3'>
<div className='flex flex-1 flex-col'>
<span className='font-medium'>{option.value}</span>
<span className='text-muted-foreground text-xs'>
{option.timezone} {getCurrentTime(option.value)}
</span>
</div>
<Icon icon='uil:check' className='h-4 w-4 opacity-100' />
</div>
</CommandItem>
))}
</CommandGroup>
{serverTimezones.length > 0 && (
<CommandGroup heading='Server'>
{serverTimezones.map((option) => (
<CommandItem
key={option.value}
value={`${option.label} ${option.value}`}
onSelect={() => handleTimezoneChange(option.value)}
>
<div className='flex w-full items-center gap-3'>
<div className='flex flex-1 flex-col'>
<span className='font-medium'>{option.value}</span>
<span className='text-muted-foreground text-xs'>
{option.timezone} {getCurrentTime(option.value)}
</span>
</div>
<Icon icon='uil:check' className='h-4 w-4 opacity-0' />
</div>
</CommandItem>
))}
</CommandGroup>
)}
<CommandGroup heading='Recommended'>
{timezoneOptions
.filter(
(option) =>
getRecommendedTimezones().includes(option.value) && option.value !== timezone,
)
.map((option) => (
<CommandItem
key={option.value}
value={`${option.label} ${option.value}`}
onSelect={() => handleTimezoneChange(option.value)}
>
<div className='flex w-full items-center gap-3'>
<div className='flex flex-1 flex-col'>
<span className='font-medium'>{option.value}</span>
<span className='text-muted-foreground text-xs'>
{option.timezone} {getCurrentTime(option.value)}
</span>
</div>
<Icon icon='uil:check' className='h-4 w-4 opacity-0' />
</div>
</CommandItem>
))}
</CommandGroup>
<CommandGroup heading='All'>
{timezoneOptions
.filter(
(option) =>
!getServerTimezones().includes(option.value) &&
!getRecommendedTimezones().includes(option.value) &&
option.value !== timezone,
)
.map((option) => (
<CommandItem
key={option.value}
value={`${option.label} ${option.value}`}
onSelect={() => handleTimezoneChange(option.value)}
>
<div className='flex w-full items-center gap-3'>
<div className='flex flex-1 flex-col'>
<span className='font-medium'>{option.value}</span>
<span className='text-muted-foreground text-xs'>
{option.timezone} {getCurrentTime(option.value)}
</span>
</div>
<Icon icon='uil:check' className='h-4 w-4 opacity-0' />
</div>
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>