From 28dd246c5f8feabb42a239adf3c1e3c53ab4493f Mon Sep 17 00:00:00 2001 From: estifanos Date: Fri, 21 Aug 2026 06:43:42 +0000 Subject: [PATCH 1/4] fixes --- .../pages/MedicalVerificationPage/index.tsx | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/apps/backoffice/src/app/features/medical-verification/pages/MedicalVerificationPage/index.tsx b/apps/backoffice/src/app/features/medical-verification/pages/MedicalVerificationPage/index.tsx index 9d79075a7..d43ada55f 100644 --- a/apps/backoffice/src/app/features/medical-verification/pages/MedicalVerificationPage/index.tsx +++ b/apps/backoffice/src/app/features/medical-verification/pages/MedicalVerificationPage/index.tsx @@ -198,20 +198,21 @@ export type VerificationKind = 'medical' | 'sea-service'; */ export function MedicalVerificationPage({ kind = 'medical' }: { kind?: VerificationKind }) { const { t } = useTranslation(); + const isMedical = kind === 'medical'; const [filter, setFilter] = useState('SUBMITTED'); const { data: pendingMedical, isLoading: loadingMedical, isFetching: fetchingMedical, refetch: refetchMedical, - } = useGetPendingMedicalQuery(filter); + } = useGetPendingMedicalQuery(filter, { skip: !isMedical }); const { data: pendingSeaService, isLoading: loadingSeaService, isFetching: fetchingSeaService, refetch: refetchSeaService, - } = useGetPendingSeaServiceQuery(filter); + } = useGetPendingSeaServiceQuery(filter, { skip: isMedical }); const [verifyMedical, { isLoading: rulingMedical }] = useVerifyMedicalCertificateMutation(); @@ -372,8 +373,6 @@ export function MedicalVerificationPage({ kind = 'medical' }: { kind?: Verificat [rulingSeaService, rule, verifySeaService, showDate, t], ); - const isMedical = kind === 'medical'; - return ( @@ -393,6 +392,8 @@ export function MedicalVerificationPage({ kind = 'medical' }: { kind?: Verificat )} </Text> + {statusFilter} + {isMedical ? ( <AdvancedTable columns={medicalTableColumns} @@ -408,7 +409,7 @@ export function MedicalVerificationPage({ kind = 'medical' }: { kind?: Verificat onPageSizeChange={handleMedicalPageSizeChange} refresh={refetchMedical} isLoading={loadingMedical || fetchingMedical} - emptyText={t('recordVerification.emptyText', 'Nothing awaiting verification.')} + emptyText={emptyText} /> ) : ( <AdvancedTable @@ -425,7 +426,7 @@ export function MedicalVerificationPage({ kind = 'medical' }: { kind?: Verificat onPageSizeChange={handleSeaServicePageSizeChange} refresh={refetchSeaService} isLoading={loadingSeaService || fetchingSeaService} - emptyText={t('recordVerification.emptyText', 'Nothing awaiting verification.')} + emptyText={emptyText} /> )} From 84a1f7b47cd7757173d1e7b9839dd802176f70d2 Mon Sep 17 00:00:00 2001 From: estifanos <estifanos.tria@gmail.com> Date: Fri, 21 Aug 2026 06:57:19 +0000 Subject: [PATCH 2/4] feat: add minDate and maxDate constraints to AmharicDatePicker and restrict form inputs to prevent future and invalid dates --- .../seafarer/pages/SeaRecords/index.tsx | 48 ++++++++++++++----- libs/ui/src/lib/input/AmharicDatePicker.tsx | 27 ++++++++++- 2 files changed, 63 insertions(+), 12 deletions(-) diff --git a/apps/portal/src/app/features/seafarer/pages/SeaRecords/index.tsx b/apps/portal/src/app/features/seafarer/pages/SeaRecords/index.tsx index 01e419ddc..bdf96eef2 100644 --- a/apps/portal/src/app/features/seafarer/pages/SeaRecords/index.tsx +++ b/apps/portal/src/app/features/seafarer/pages/SeaRecords/index.tsx @@ -164,6 +164,13 @@ function EvidenceField({ // ---------------------------------------------------------------- sea service +/** Today as a `yyyy-mm-dd` key — same shape the pickers emit, so plain + * string comparison is a valid date comparison. Taken in the authority's + * timezone, matching the server's check, so a seafarer logging in from a + * zone ahead of Addis isn't offered a day the server then rejects. */ +const todayKey = () => + new Date().toLocaleDateString('en-CA', { timeZone: 'Africa/Addis_Ababa' }); + const EMPTY_SEA_SERVICE = { vesselName: '', imoNumber: '', @@ -276,12 +283,27 @@ function SeaServiceTab() { } }; + // Service already served — neither end of an engagement can be in the future. + const today = todayKey(); + const dateError = + form.engagementDate > today || form.dischargeDate > today + ? t('seaRecords.seaService.dateFuture', { + defaultValue: 'Engagement and discharge dates cannot be in the future.', + }) + : form.engagementDate && + form.dischargeDate && + form.engagementDate >= form.dischargeDate + ? t('seaRecords.seaService.dateOrder', { + defaultValue: 'Discharge date must be after the engagement date.', + }) + : null; + const valid = form.vesselName.trim().length > 1 && form.rank.trim().length > 1 && form.engagementDate && form.dischargeDate && - form.engagementDate < form.dischargeDate; + !dateError; // Shown under the date pickers as they are filled: the seafarer sees what // the engagement is worth before saving it. @@ -408,6 +430,7 @@ function SeaServiceTab() { onChange={(val) => setForm({ ...form, engagementDate: val }) } + maxDate={form.dischargeDate || today} dateFormat="date" /> <AmharicDatePicker @@ -417,24 +440,23 @@ function SeaServiceTab() { onChange={(val) => setForm({ ...form, dischargeDate: val }) } + minDate={form.engagementDate || undefined} + maxDate={today} dateFormat="date" /> </Group> - {form.engagementDate && form.dischargeDate && ( + {(dateError || (form.engagementDate && form.dischargeDate)) && ( <Alert variant="light" - color={formDays === null ? 'red' : 'teal'} + color={dateError ? 'red' : 'teal'} icon={<IconInfoCircle size={16} />} py={6} > - {formDays === null - ? t('seaRecords.seaService.dateOrder', { - defaultValue: 'Discharge date must be after the engagement date.', - }) - : t('seaRecords.seaService.daysServed', { - days: formDays, - defaultValue: 'Days served on this engagement: {{days}} (both days counted)', - })} + {dateError ?? + t('seaRecords.seaService.daysServed', { + days: formDays, + defaultValue: 'Days served on this engagement: {{days}} (both days counted)', + })} </Alert> )} <Textarea @@ -581,10 +603,12 @@ function MedicalTab() { } }; + const today = todayKey(); const valid = form.issuerName.trim().length > 1 && form.issueDate && form.expiryDate && + form.issueDate <= today && form.issueDate < form.expiryDate; const { setPageIndex, pageSize, setPageSize, paginate } = useServerTable({ pageSize: 10 }); @@ -664,6 +688,7 @@ function MedicalTab() { required value={form.issueDate} onChange={(val) => setForm({ ...form, issueDate: val })} + maxDate={today} dateFormat="date" /> <AmharicDatePicker @@ -671,6 +696,7 @@ function MedicalTab() { required value={form.expiryDate} onChange={(val) => setForm({ ...form, expiryDate: val })} + minDate={form.issueDate || undefined} dateFormat="date" /> </Group> diff --git a/libs/ui/src/lib/input/AmharicDatePicker.tsx b/libs/ui/src/lib/input/AmharicDatePicker.tsx index 78919ee2a..5d18ada87 100644 --- a/libs/ui/src/lib/input/AmharicDatePicker.tsx +++ b/libs/ui/src/lib/input/AmharicDatePicker.tsx @@ -15,7 +15,7 @@ import { import { TimeInput } from '@mantine/dates'; import { useDisclosure } from '@mantine/hooks'; import { DayPicker as EthiopicDayPicker } from '@daypicker/ethiopic'; -import { DayPicker as GregorianDayPicker } from '@daypicker/react'; +import { DayPicker as GregorianDayPicker, type Matcher } from '@daypicker/react'; import { IconCalendarEvent } from '@tabler/icons-react'; import '@daypicker/react/dist/style.css'; import './AmharicDatePicker.css'; @@ -131,6 +131,11 @@ export interface AmharicDatePickerProps { /** Show a time-of-day field alongside the calendar. Off by default — * most callers only need a calendar day. */ withTime?: boolean; + /** Earliest/latest selectable day. Accepts a Date or a value in the same + * wire format as `value`. Days outside the range are disabled in both + * calendars. */ + minDate?: Date | string; + maxDate?: Date | string; /** Wire format for `value`/`onChange`: a full ISO-8601 instant (default, * what most backend date fields expect) or a bare `yyyy-mm-dd` calendar * date (what filter query params and plain `date: string` DTO fields @@ -151,6 +156,8 @@ export function AmharicDatePicker({ onBlur, w, withTime = false, + minDate, + maxDate, dateFormat = 'iso', }: AmharicDatePickerProps) { const { t, i18n } = useTranslation(); @@ -161,6 +168,21 @@ export function AmharicDatePicker({ const selected = parseWireValue(value, dateFormat, withTime); + const asDate = (limit: Date | string | undefined) => + limit instanceof Date ? limit : parseWireValue(limit, dateFormat, withTime); + const min = asDate(minDate); + const max = asDate(maxDate); + const outOfRange: Matcher[] = [ + ...(min ? [{ before: min }] : []), + ...(max ? [{ after: max }] : []), + ]; + // Compared as calendar days — the limits carry a midnight time-of-day, so + // an instant comparison would call today "after" a max of today. + const todayKey = formatPlainDate(new Date()); + const todayOutOfRange = + (!!min && todayKey < formatPlainDate(min)) || + (!!max && todayKey > formatPlainDate(max)); + const dateLabel = selected ? calendarType === 'EN' ? selected.toLocaleDateString('en-US', { @@ -266,6 +288,7 @@ export function AmharicDatePicker({ endMonth={YEAR_DROPDOWN_END} numerals="latn" captionLayout="dropdown" + disabled={outOfRange} formatters={ETH_FORMATTERS} onSelect={(date: Date | undefined) => { onChange?.(date ? formatWireValue(mergeDateTime(selected, date), dateFormat, withTime) : ''); @@ -281,6 +304,7 @@ export function AmharicDatePicker({ startMonth={YEAR_DROPDOWN_START} endMonth={YEAR_DROPDOWN_END} captionLayout="dropdown" + disabled={outOfRange} onSelect={(date: Date | undefined) => { onChange?.(date ? formatWireValue(mergeDateTime(selected, date), dateFormat, withTime) : ''); if (!withTime) close(); @@ -389,6 +413,7 @@ export function AmharicDatePicker({ <Button variant="light" size="xs" + disabled={todayOutOfRange} onClick={() => { onChange?.(formatWireValue(new Date(), dateFormat, withTime)); close(); From 5e671b8ac07457336bd0efe23d05c968f6781700 Mon Sep 17 00:00:00 2001 From: estifanos <estifanos.tria@gmail.com> Date: Fri, 21 Aug 2026 07:17:38 +0000 Subject: [PATCH 3/4] fixes --- .../src/app/features/profile/pages/ProfilePage.tsx | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/apps/backoffice/src/app/features/profile/pages/ProfilePage.tsx b/apps/backoffice/src/app/features/profile/pages/ProfilePage.tsx index 1cffa5444..0a6afc228 100644 --- a/apps/backoffice/src/app/features/profile/pages/ProfilePage.tsx +++ b/apps/backoffice/src/app/features/profile/pages/ProfilePage.tsx @@ -31,7 +31,6 @@ import { IconLock, IconMail, IconMoon, - IconPhone, IconSettings, IconShieldLock, IconSun, @@ -42,7 +41,7 @@ import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; import { useTranslation } from 'react-i18next'; -import { notify, PageHeader, useErrorHandler, passwordSchema as strongPasswordSchema, PasswordRequirements, phoneNumber } from '@ema-platform/ui'; +import { notify, PageHeader, useErrorHandler, passwordSchema as strongPasswordSchema, PasswordRequirements, phoneNumber, PhoneInput } from '@ema-platform/ui'; import { useApiMutation } from '@ema-platform/api'; import { ActiveSessions, setUser } from '@ema-platform/auth'; import type { AuthUser } from '@ema-platform/auth'; @@ -136,6 +135,9 @@ export function ProfilePage() { register: registerProfile, handleSubmit: handleProfileSubmit, reset: resetProfile, + watch: watchProfile, + setValue: setValueProfile, + trigger: triggerProfile, formState: { errors: profileErrors }, } = useForm<ProfileValues>({ resolver: zodResolver(profileSchema), @@ -372,11 +374,12 @@ export function ProfilePage() { error={profileErrors.email?.message} {...registerProfile('email')} /> - <TextInput + <PhoneInput label={t('profile.fields.phone')} - leftSection={<IconPhone size={18} />} + value={watchProfile('phoneNumber') || ''} + onChange={(val) => setValueProfile('phoneNumber', val, { shouldValidate: !!profileErrors.phoneNumber })} + onBlur={() => triggerProfile('phoneNumber')} error={profileErrors.phoneNumber?.message} - {...registerProfile('phoneNumber')} /> </SimpleGrid> </div> From d4a5ba767341a3b694edaa456537d07c575d6c5a Mon Sep 17 00:00:00 2001 From: estifanos <estifanos.tria@gmail.com> Date: Fri, 21 Aug 2026 07:38:01 +0000 Subject: [PATCH 4/4] UI fixes --- .../src/app/layouts/BackofficeLayout.tsx | 90 +++++++++++------- libs/ui/src/lib/layout/AppHeader.tsx | 16 ++++ libs/ui/src/lib/layout/AppTopNav.tsx | 92 +++++++++++-------- 3 files changed, 127 insertions(+), 71 deletions(-) diff --git a/apps/backoffice/src/app/layouts/BackofficeLayout.tsx b/apps/backoffice/src/app/layouts/BackofficeLayout.tsx index de599a69a..d65aaac45 100644 --- a/apps/backoffice/src/app/layouts/BackofficeLayout.tsx +++ b/apps/backoffice/src/app/layouts/BackofficeLayout.tsx @@ -1,5 +1,5 @@ import { useCallback, useMemo, useState } from 'react'; -import { AppShell, Drawer } from '@mantine/core'; +import { AppShell, Box, Drawer, Group, Text } from '@mantine/core'; import { useDisclosure } from '@mantine/hooks'; import { Outlet, useLocation, useNavigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; @@ -26,6 +26,14 @@ const BADGE_POLL_MS = 60_000; const HEADER_HEIGHT = 116; +/** + * Horizontal inset of the header chrome. `AppHeader` adds its own `px="lg"` + * inside this, so the nav strip below needs the sum to line up with the + * controls above it — it used to start 20px to their left. + */ +const CHROME_PAD_X = 32; +const NAV_STRIP_PAD_X = CHROME_PAD_X + 20; + /** * A desk left unlocked with a license-review or medical-record screen open is * the actual threat model here, not a slow token. 15 minutes of no mouse, @@ -144,7 +152,9 @@ export function BackofficeLayout() { return ( <AppShell - header={{ height: isSidebar ? 74 : HEADER_HEIGHT }} + // The top layout drops its nav strip on small screens — the drawer is + // the nav there — so the header shrinks back to a single row with it. + header={{ height: isSidebar ? 74 : { base: 74, sm: HEADER_HEIGHT } }} navbar={ isSidebar ? { @@ -162,13 +172,28 @@ export function BackofficeLayout() { <AppShell.Header style={{ background: "var(--mantine-color-body)", - borderBottom: "1px solid var(--mantine-color-gray-2)", + borderBottom: "1px solid var(--mantine-color-default-border)", display: "flex", flexDirection: "column", }} > - <div style={{ height: 74, flexShrink: 0, padding: "0 32px" }}> + <div + style={{ height: 74, flexShrink: 0, padding: `0 ${CHROME_PAD_X}px` }} + > <AppHeader + brand={ + isSidebar ? undefined : ( + <Group gap="xs" wrap="nowrap"> + <BrandMark size={28} /> + <Text fw={700} size="sm" lh={1.1} visibleFrom="xs"> + {t('app.name')} + </Text> + </Group> + ) + } + // Nothing to toggle on a desktop top bar; on mobile it opens the + // drawer below. + burgerHiddenFrom={isSidebar ? undefined : 'sm'} onToggleNav={toggleNav} onToggleSidebar={isSidebar ? handleToggleCollapse : toggleNav} navOpened={opened} @@ -182,13 +207,14 @@ export function BackofficeLayout() { </div> {!isSidebar && ( - <div + <Box + visibleFrom="sm" style={{ display: 'flex', alignItems: 'center', - padding: '0 32px', + padding: `0 ${NAV_STRIP_PAD_X}px`, height: 42, - borderTop: '1px solid var(--mantine-color-gray-1)', + borderTop: '1px solid var(--mantine-color-default-border)', flexShrink: 0, }} > @@ -199,7 +225,7 @@ export function BackofficeLayout() { activePath={location.pathname} onNavigate={go} /> - </div> + </Box> )} </AppShell.Header> @@ -210,7 +236,7 @@ export function BackofficeLayout() { overflow: "hidden", transition: "width 200ms ease", background: "var(--mantine-color-body)", - borderRight: "1px solid var(--mantine-color-gray-2)", + borderRight: "1px solid var(--mantine-color-default-border)", }} > <AppSidebar @@ -238,30 +264,28 @@ export function BackofficeLayout() { {/* Mobile nav: a proper Drawer (sized, backdrop, closes on outside click) instead of AppShell's full-width mobile navbar. Mirrors the landing page's mobile menu. */} - {isSidebar && ( - <Drawer - opened={opened} - onClose={closeNav} - hiddenFrom="sm" - size="75%" - padding={0} - withCloseButton={false} - > - <AppSidebar - navItems={sections} - collapsed={false} - activePath={location.pathname} - onToggleCollapse={handleToggleCollapse} - onNavigate={(item) => { - go(item); - closeNav(); - }} - brandName={t('app.name')} - brandSubtitle={t('app.authority')} - brandLogo={<BrandMark size={32} />} - /> - </Drawer> - )} + <Drawer + opened={opened} + onClose={closeNav} + hiddenFrom="sm" + size="75%" + padding={0} + withCloseButton={false} + > + <AppSidebar + navItems={sections} + collapsed={false} + activePath={location.pathname} + onToggleCollapse={handleToggleCollapse} + onNavigate={(item) => { + go(item); + closeNav(); + }} + brandName={t('app.name')} + brandSubtitle={t('app.authority')} + brandLogo={<BrandMark size={32} />} + /> + </Drawer> </AppShell> ); } diff --git a/libs/ui/src/lib/layout/AppHeader.tsx b/libs/ui/src/lib/layout/AppHeader.tsx index a7e14a148..0e5755955 100644 --- a/libs/ui/src/lib/layout/AppHeader.tsx +++ b/libs/ui/src/lib/layout/AppHeader.tsx @@ -14,6 +14,7 @@ import { IconLogout, IconUserCircle, } from '@tabler/icons-react'; +import type { ReactNode } from 'react'; import { useTranslation } from 'react-i18next'; import { LanguageSwitcher } from './LanguageSwitcher'; import { ColorSchemeToggle } from './ColorSchemeToggle'; @@ -36,6 +37,17 @@ interface AppHeaderProps { supportedLanguages: readonly string[]; onNotificationsClick?: () => void; notificationCount?: number; + /** + * Rendered at the far left. The sidebar layout carries the brand in the + * sidebar itself; the top-bar layout has no sidebar, so it passes the brand + * here rather than leaving the chrome unbranded. + */ + brand?: ReactNode; + /** + * Breakpoint from which the burger is hidden. The top-bar layout only needs + * it on small screens, where the drawer replaces the nav strip. + */ + burgerHiddenFrom?: string; } export function AppHeader({ @@ -50,17 +62,21 @@ export function AppHeader({ supportedLanguages, onNotificationsClick, notificationCount, + brand, + burgerHiddenFrom, }: AppHeaderProps) { const isMobile = typeof window !== 'undefined' && window.innerWidth < 768; return ( <Group h="100%" px="lg" justify="space-between" wrap="nowrap"> <Group gap="md" wrap="nowrap"> + {brand} {/* Hamburger — styled like user-management Top.tsx */} {/* The Burger itself owns the click so the control is a real, keyboard reachable <button>; the Box is chrome only. It previously wrapped a no-op button, which no keyboard user could operate. */} <Box + hiddenFrom={burgerHiddenFrom} style={{ display: 'flex', alignItems: 'center', diff --git a/libs/ui/src/lib/layout/AppTopNav.tsx b/libs/ui/src/lib/layout/AppTopNav.tsx index 84e6d63d8..99345ab14 100644 --- a/libs/ui/src/lib/layout/AppTopNav.tsx +++ b/libs/ui/src/lib/layout/AppTopNav.tsx @@ -1,4 +1,5 @@ import { Badge, Group, Menu, UnstyledButton, rem } from '@mantine/core'; +import { forwardRef } from 'react'; import { IconChevronDown } from '@tabler/icons-react'; import { useTranslation } from 'react-i18next'; import type { NavItem } from './AppSidebar'; @@ -23,7 +24,9 @@ interface AppTopNavProps { * scrolling strip, so with twenty-odd of them most were off-screen and the * grouping that the sidebar already had was thrown away. Here each section * collapses to a single labelled dropdown, which fits and keeps the same - * information architecture as the sidebar. + * information architecture as the sidebar. Sections that still do not fit + * scroll horizontally rather than dropping off the edge — under ~1100px the + * last one or two were simply unreachable. */ export function AppTopNav({ navItems, activePath, onNavigate }: AppTopNavProps) { const { t } = useTranslation(); @@ -36,6 +39,7 @@ export function AppTopNav({ navItems, activePath, onNavigate }: AppTopNavProps) wrap="nowrap" role="navigation" aria-label={t('nav.primary', 'Primary')} + style={{ flex: 1, minWidth: 0, overflowX: 'auto', scrollbarWidth: 'none' }} > {sections.map((section, index) => { // An unlabelled leading block (Dashboard) is a plain link, not a menu. @@ -148,40 +152,52 @@ interface TopNavButtonProps { onClick?: () => void; } -function TopNavButton({ - label, - active, - badge, - soon, - withChevron, - onClick, -}: TopNavButtonProps) { - return ( - <UnstyledButton - onClick={onClick} - style={{ - display: 'flex', - alignItems: 'center', - gap: rem(6), - padding: `0 ${rem(14)}`, - height: '100%', - borderBottom: '2px solid', - borderBottomColor: active ? 'var(--mantine-color-blue-6)' : 'transparent', - color: active ? 'var(--mantine-color-blue-6)' : 'var(--mantine-color-gray-6)', - fontWeight: active ? 600 : 500, - fontSize: rem(14), - whiteSpace: 'nowrap', - opacity: soon ? 0.55 : 1, - marginBottom: -1, - }} - > - <span>{label}</span> - {badge !== null && badge !== undefined && ( - <Badge size="xs" variant="filled" color="red" radius="sm"> - {badge} - </Badge> - )} - {withChevron && <IconChevronDown size={14} stroke={2} />} - </UnstyledButton> - ); -} +/** + * `Menu.Target` positions its dropdown against the ref it passes to its child, + * so a plain function component here left every section menu anchored at the + * top-left of the viewport, covering the header instead of opening under the + * button that was clicked. The rest props carry Menu's own click and aria + * handling onto the real button. + */ +const TopNavButton = forwardRef<HTMLButtonElement, TopNavButtonProps>( + function TopNavButton( + { label, active, badge, soon, withChevron, onClick, ...others }, + ref, + ) { + return ( + <UnstyledButton + ref={ref} + onClick={onClick} + {...others} + style={{ + display: 'flex', + alignItems: 'center', + gap: rem(6), + padding: `0 ${rem(14)}`, + height: '100%', + flexShrink: 0, + borderBottom: '2px solid', + borderBottomColor: active + ? 'var(--mantine-color-blue-6)' + : 'transparent', + color: active + ? 'var(--mantine-color-blue-6)' + : 'var(--mantine-color-gray-6)', + fontWeight: active ? 600 : 500, + fontSize: rem(14), + whiteSpace: 'nowrap', + opacity: soon ? 0.55 : 1, + marginBottom: -1, + }} + > + <span>{label}</span> + {badge !== null && badge !== undefined && ( + <Badge size="xs" variant="filled" color="red" radius="sm"> + {badge} + </Badge> + )} + {withChevron && <IconChevronDown size={14} stroke={2} />} + </UnstyledButton> + ); + }, +);