refactor: improve layout structure and consistency across components
- Refactored layout structure in multiple components to enhance consistency and maintainability by introducing outer and inner wrapper classes. - Updated the `MonthNavHeader` component for shared month navigation functionality, improving code reuse. - Adjusted padding and margin properties in various components to ensure a cohesive design and better responsiveness. - Removed unnecessary padding from certain elements to streamline the layout and improve visual clarity.
This commit is contained in:
@@ -10,14 +10,15 @@ import { useEffect } from "react";
|
||||
import { useAdminPage, AdminDutyList, ReassignSheet } from "@/components/admin";
|
||||
import { useTranslation } from "@/i18n/use-translation";
|
||||
import { useAppStore } from "@/store/app-store";
|
||||
import { MonthNavHeader } from "@/components/calendar/MonthNavHeader";
|
||||
import { AccessDeniedScreen } from "@/components/states/AccessDeniedScreen";
|
||||
import { LoadingState } from "@/components/states/LoadingState";
|
||||
import { ErrorState } from "@/components/states/ErrorState";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ChevronLeft as ChevronLeftIcon, ChevronRight as ChevronRightIcon } from "lucide-react";
|
||||
|
||||
const PAGE_WRAPPER_CLASS =
|
||||
"content-safe mx-auto flex min-h-[var(--tg-viewport-stable-height,100vh)] w-full max-w-[var(--max-width-app)] flex-col bg-background px-3 pb-6";
|
||||
const OUTER_CLASS =
|
||||
"content-safe min-h-[var(--tg-viewport-stable-height,100vh)] bg-background";
|
||||
const INNER_CLASS =
|
||||
"mx-auto flex w-full max-w-[var(--max-width-app)] flex-col";
|
||||
|
||||
export default function AdminPage() {
|
||||
const { t, monthName } = useTranslation();
|
||||
@@ -31,31 +32,37 @@ export default function AdminPage() {
|
||||
|
||||
if (!admin.isAllowed) {
|
||||
return (
|
||||
<div className={PAGE_WRAPPER_CLASS}>
|
||||
<AccessDeniedScreen primaryAction="reload" />
|
||||
<div className={OUTER_CLASS}>
|
||||
<div className={INNER_CLASS}>
|
||||
<AccessDeniedScreen primaryAction="reload" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (admin.adminCheckComplete === null) {
|
||||
return (
|
||||
<div className={PAGE_WRAPPER_CLASS}>
|
||||
<div className={OUTER_CLASS}>
|
||||
<div className={INNER_CLASS}>
|
||||
<div className="py-4 flex flex-col items-center gap-2">
|
||||
<LoadingState />
|
||||
<p className="text-sm text-muted-foreground">{t("admin.loading_users")}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (admin.adminAccessDenied) {
|
||||
return (
|
||||
<div className={PAGE_WRAPPER_CLASS}>
|
||||
<div className={OUTER_CLASS}>
|
||||
<div className={INNER_CLASS}>
|
||||
<div className="flex flex-col gap-4 py-6">
|
||||
<p className="text-muted-foreground">
|
||||
{admin.adminAccessDeniedDetail ?? t("admin.access_denied")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -64,46 +71,17 @@ export default function AdminPage() {
|
||||
const year = admin.adminMonth.getFullYear();
|
||||
|
||||
return (
|
||||
<div className={PAGE_WRAPPER_CLASS}>
|
||||
<div className={OUTER_CLASS}>
|
||||
<div className={INNER_CLASS}>
|
||||
<header className="sticky top-[var(--app-safe-top)] z-10 flex flex-col items-center border-b border-border bg-background py-3">
|
||||
<div className="flex w-full items-center justify-between px-1">
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
size="icon"
|
||||
className="size-10 rounded-[10px] bg-surface text-accent hover:bg-[var(--surface-hover)] focus-visible:outline-accent active:scale-95 disabled:opacity-50"
|
||||
aria-label={t("nav.prev_month")}
|
||||
disabled={admin.loading}
|
||||
onClick={admin.onPrevMonth}
|
||||
>
|
||||
<ChevronLeftIcon className="size-5" aria-hidden />
|
||||
</Button>
|
||||
<h1
|
||||
className="m-0 flex flex-col items-center justify-center gap-0 leading-none"
|
||||
aria-label={`${t("admin.title")}, ${monthName(month)} ${year}`}
|
||||
>
|
||||
<span className="text-xs font-normal leading-none text-muted">
|
||||
{year}
|
||||
</span>
|
||||
<span className="text-[1.1rem] font-semibold leading-tight sm:text-[1.25rem]">
|
||||
{monthName(month)}
|
||||
</span>
|
||||
</h1>
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
size="icon"
|
||||
className="size-10 rounded-[10px] bg-surface text-accent hover:bg-[var(--surface-hover)] focus-visible:outline-accent active:scale-95 disabled:opacity-50"
|
||||
aria-label={t("nav.next_month")}
|
||||
disabled={admin.loading}
|
||||
onClick={admin.onNextMonth}
|
||||
>
|
||||
<ChevronRightIcon className="size-5" aria-hidden />
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground m-0 mt-1">
|
||||
{admin.loading ? "…" : t("admin.duties_count", { count: String(admin.dutyOnly.length) })}
|
||||
</p>
|
||||
<MonthNavHeader
|
||||
month={admin.adminMonth}
|
||||
disabled={admin.loading}
|
||||
onPrevMonth={admin.onPrevMonth}
|
||||
onNextMonth={admin.onNextMonth}
|
||||
titleAriaLabel={`${t("admin.title")}, ${monthName(month)} ${year}`}
|
||||
className="w-full px-1"
|
||||
/>
|
||||
</header>
|
||||
|
||||
{admin.successMessage && (
|
||||
@@ -157,6 +135,7 @@ export default function AdminPage() {
|
||||
onCloseAnimationEnd={admin.closeReassign}
|
||||
t={t}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -56,15 +56,19 @@ export default function Home() {
|
||||
}, [setCurrentView, setSelectedDay]);
|
||||
|
||||
const content = accessDenied ? (
|
||||
<div className="content-safe mx-auto flex min-h-[var(--tg-viewport-stable-height,100vh)] w-full max-w-[var(--max-width-app)] flex-col bg-background px-3 pb-6">
|
||||
<AccessDeniedScreen primaryAction="reload" />
|
||||
<div className="content-safe min-h-[var(--tg-viewport-stable-height,100vh)] bg-background">
|
||||
<div className="mx-auto flex w-full max-w-[var(--max-width-app)] flex-col">
|
||||
<AccessDeniedScreen primaryAction="reload" />
|
||||
</div>
|
||||
</div>
|
||||
) : currentView === "currentDuty" ? (
|
||||
<div className="content-safe mx-auto flex min-h-[var(--tg-viewport-stable-height,100vh)] w-full max-w-[var(--max-width-app)] flex-col bg-background px-3 pb-6">
|
||||
<CurrentDutyView
|
||||
onBack={handleBackFromCurrentDuty}
|
||||
openedFromPin={startParam === "duty"}
|
||||
/>
|
||||
<div className="content-safe min-h-[var(--tg-viewport-stable-height,100vh)] bg-background">
|
||||
<div className="mx-auto flex w-full max-w-[var(--max-width-app)] flex-col">
|
||||
<CurrentDutyView
|
||||
onBack={handleBackFromCurrentDuty}
|
||||
openedFromPin={startParam === "duty"}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<CalendarPage isAllowed={isAllowed} initDataRaw={initDataRaw} />
|
||||
|
||||
@@ -168,41 +168,43 @@ export function CalendarPage({ isAllowed, initDataRaw }: CalendarPageProps) {
|
||||
}, [isAdmin, router]);
|
||||
|
||||
return (
|
||||
<div className="content-safe mx-auto flex min-h-[var(--tg-viewport-stable-height,100vh)] w-full max-w-[var(--max-width-app)] flex-col bg-background px-3 pb-6">
|
||||
<div
|
||||
ref={calendarStickyRef}
|
||||
className="sticky top-[var(--app-safe-top)] z-10 min-h-[var(--calendar-block-min-height)] bg-background pb-2 touch-pan-y"
|
||||
>
|
||||
<CalendarHeader
|
||||
month={currentMonth}
|
||||
disabled={navDisabled}
|
||||
onPrevMonth={handlePrevMonth}
|
||||
onNextMonth={handleNextMonth}
|
||||
/>
|
||||
<CalendarGrid
|
||||
currentMonth={currentMonth}
|
||||
<div className="content-safe min-h-[var(--tg-viewport-stable-height,100vh)] bg-background">
|
||||
<div className="mx-auto flex w-full max-w-[var(--max-width-app)] flex-col">
|
||||
<div
|
||||
ref={calendarStickyRef}
|
||||
className="sticky top-[var(--app-safe-top)] z-10 min-h-[var(--calendar-block-min-height)] bg-background pb-2 touch-pan-y"
|
||||
>
|
||||
<CalendarHeader
|
||||
month={currentMonth}
|
||||
disabled={navDisabled}
|
||||
onPrevMonth={handlePrevMonth}
|
||||
onNextMonth={handleNextMonth}
|
||||
/>
|
||||
<CalendarGrid
|
||||
currentMonth={currentMonth}
|
||||
duties={duties}
|
||||
calendarEvents={calendarEvents}
|
||||
onDayClick={handleDayClick}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<ErrorState message={error} onRetry={retry} className="my-3" />
|
||||
)}
|
||||
{!error && (
|
||||
<DutyList
|
||||
scrollMarginTop={stickyBlockHeight}
|
||||
className="mt-2"
|
||||
/>
|
||||
)}
|
||||
|
||||
<DayDetail
|
||||
ref={dayDetailRef}
|
||||
duties={duties}
|
||||
calendarEvents={calendarEvents}
|
||||
onDayClick={handleDayClick}
|
||||
onClose={handleCloseDayDetail}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<ErrorState message={error} onRetry={retry} className="my-3" />
|
||||
)}
|
||||
{!error && (
|
||||
<DutyList
|
||||
scrollMarginTop={stickyBlockHeight}
|
||||
className="mt-2"
|
||||
/>
|
||||
)}
|
||||
|
||||
<DayDetail
|
||||
ref={dayDetailRef}
|
||||
duties={duties}
|
||||
calendarEvents={calendarEvents}
|
||||
onClose={handleCloseDayDetail}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,13 +5,9 @@
|
||||
|
||||
"use client";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useTranslation } from "@/i18n/use-translation";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
ChevronLeft as ChevronLeftIcon,
|
||||
ChevronRight as ChevronRightIcon,
|
||||
} from "lucide-react";
|
||||
import { MonthNavHeader } from "@/components/calendar/MonthNavHeader";
|
||||
|
||||
export interface CalendarHeaderProps {
|
||||
/** Currently displayed month (used for title). */
|
||||
@@ -30,51 +26,19 @@ export function CalendarHeader({
|
||||
onNextMonth,
|
||||
className,
|
||||
}: CalendarHeaderProps) {
|
||||
const { t, monthName, weekdayLabels } = useTranslation();
|
||||
const year = month.getFullYear();
|
||||
const monthIndex = month.getMonth();
|
||||
const { weekdayLabels } = useTranslation();
|
||||
const labels = weekdayLabels();
|
||||
|
||||
return (
|
||||
<header className={cn("flex flex-col", className)}>
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
size="icon"
|
||||
className="size-10 rounded-[10px] bg-surface text-accent hover:bg-[var(--surface-hover)] focus-visible:outline-accent active:scale-95 disabled:opacity-50"
|
||||
aria-label={t("nav.prev_month")}
|
||||
disabled={disabled}
|
||||
onClick={onPrevMonth}
|
||||
>
|
||||
<ChevronLeftIcon className="size-5" aria-hidden />
|
||||
</Button>
|
||||
<div className="flex min-h-[2rem] flex-col items-center justify-center gap-0">
|
||||
<h1
|
||||
className="m-0 flex flex-col items-center justify-center gap-0 leading-none"
|
||||
aria-live="polite"
|
||||
aria-atomic="true"
|
||||
>
|
||||
<span className="text-xs font-normal leading-none text-muted">
|
||||
{year}
|
||||
</span>
|
||||
<span className="text-[1.1rem] font-semibold leading-tight sm:text-[1.25rem]">
|
||||
{monthName(monthIndex)}
|
||||
</span>
|
||||
</h1>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
size="icon"
|
||||
className="size-10 rounded-[10px] bg-surface text-accent hover:bg-[var(--surface-hover)] focus-visible:outline-accent active:scale-95 disabled:opacity-50"
|
||||
aria-label={t("nav.next_month")}
|
||||
disabled={disabled}
|
||||
onClick={onNextMonth}
|
||||
>
|
||||
<ChevronRightIcon className="size-5" aria-hidden />
|
||||
</Button>
|
||||
</div>
|
||||
<MonthNavHeader
|
||||
month={month}
|
||||
disabled={disabled}
|
||||
onPrevMonth={onPrevMonth}
|
||||
onNextMonth={onNextMonth}
|
||||
ariaLive
|
||||
className="mb-3"
|
||||
/>
|
||||
<div className="grid grid-cols-7 gap-0.5 mb-1.5 text-center text-[0.75rem] text-muted">
|
||||
{labels.map((label, i) => (
|
||||
<span key={i} aria-hidden>
|
||||
|
||||
86
webapp-next/src/components/calendar/MonthNavHeader.tsx
Normal file
86
webapp-next/src/components/calendar/MonthNavHeader.tsx
Normal file
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* Shared month navigation row: prev button, year + month title, next button.
|
||||
* Used by CalendarHeader and admin page for consistent layout and spacing.
|
||||
*/
|
||||
|
||||
"use client";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useTranslation } from "@/i18n/use-translation";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
ChevronLeft as ChevronLeftIcon,
|
||||
ChevronRight as ChevronRightIcon,
|
||||
} from "lucide-react";
|
||||
|
||||
export interface MonthNavHeaderProps {
|
||||
/** Currently displayed month (used for title). */
|
||||
month: Date;
|
||||
/** Whether month navigation is disabled (e.g. during loading). */
|
||||
disabled?: boolean;
|
||||
onPrevMonth: () => void;
|
||||
onNextMonth: () => void;
|
||||
/** Optional aria-label for the month title (e.g. admin page). */
|
||||
titleAriaLabel?: string;
|
||||
/** When true, title is announced on change (e.g. calendar). */
|
||||
ariaLive?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const NAV_BUTTON_CLASS =
|
||||
"size-10 rounded-[10px] bg-surface text-accent hover:bg-[var(--surface-hover)] focus-visible:outline-accent active:scale-95 disabled:opacity-50";
|
||||
|
||||
export function MonthNavHeader({
|
||||
month,
|
||||
disabled = false,
|
||||
onPrevMonth,
|
||||
onNextMonth,
|
||||
titleAriaLabel,
|
||||
ariaLive = false,
|
||||
className,
|
||||
}: MonthNavHeaderProps) {
|
||||
const { t, monthName } = useTranslation();
|
||||
const year = month.getFullYear();
|
||||
const monthIndex = month.getMonth();
|
||||
|
||||
return (
|
||||
<div className={cn("flex items-center justify-between", className)}>
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
size="icon"
|
||||
className={NAV_BUTTON_CLASS}
|
||||
aria-label={t("nav.prev_month")}
|
||||
disabled={disabled}
|
||||
onClick={onPrevMonth}
|
||||
>
|
||||
<ChevronLeftIcon className="size-5" aria-hidden />
|
||||
</Button>
|
||||
<div className="flex min-h-[2rem] flex-col items-center justify-center gap-0">
|
||||
<h1
|
||||
className="m-0 flex flex-col items-center justify-center gap-0 leading-none"
|
||||
{...(titleAriaLabel ? { "aria-label": titleAriaLabel } : {})}
|
||||
{...(ariaLive ? { "aria-live": "polite", "aria-atomic": true } : {})}
|
||||
>
|
||||
<span className="text-xs font-normal leading-none text-muted">
|
||||
{year}
|
||||
</span>
|
||||
<span className="text-[1.1rem] font-semibold leading-tight sm:text-[1.25rem]">
|
||||
{monthName(monthIndex)}
|
||||
</span>
|
||||
</h1>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
size="icon"
|
||||
className={NAV_BUTTON_CLASS}
|
||||
aria-label={t("nav.next_month")}
|
||||
disabled={disabled}
|
||||
onClick={onNextMonth}
|
||||
>
|
||||
<ChevronRightIcon className="size-5" aria-hidden />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -168,7 +168,7 @@ export function CurrentDutyView({ onBack, openedFromPin = false }: CurrentDutyVi
|
||||
if (state === "loading") {
|
||||
return (
|
||||
<div
|
||||
className="flex min-h-[50vh] flex-col items-center justify-center gap-4 px-4"
|
||||
className="flex min-h-[50vh] flex-col items-center justify-center gap-4"
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
aria-label={t("loading")}
|
||||
@@ -221,7 +221,7 @@ export function CurrentDutyView({ onBack, openedFromPin = false }: CurrentDutyVi
|
||||
loadTodayDuties();
|
||||
};
|
||||
return (
|
||||
<div className="flex min-h-[50vh] flex-col items-center justify-center gap-4 px-4">
|
||||
<div className="flex min-h-[50vh] flex-col items-center justify-center gap-4">
|
||||
<Card className="w-full max-w-[var(--max-width-app)]">
|
||||
<CardContent className="pt-6">
|
||||
<p className="text-error">{errorMessage}</p>
|
||||
@@ -249,7 +249,7 @@ export function CurrentDutyView({ onBack, openedFromPin = false }: CurrentDutyVi
|
||||
|
||||
if (!duty) {
|
||||
return (
|
||||
<div className="flex min-h-[50vh] flex-col items-center justify-center gap-4 px-4">
|
||||
<div className="flex min-h-[50vh] flex-col items-center justify-center gap-4">
|
||||
<Card className="current-duty-card--no-duty w-full max-w-[var(--max-width-app)] border-t-4 border-t-muted">
|
||||
<CardHeader>
|
||||
<CardTitle>{t("current_duty.title")}</CardTitle>
|
||||
@@ -307,7 +307,7 @@ export function CurrentDutyView({ onBack, openedFromPin = false }: CurrentDutyVi
|
||||
Boolean(duty.username && String(duty.username).trim());
|
||||
|
||||
return (
|
||||
<div className="flex min-h-[50vh] flex-col items-center justify-center gap-4 px-4">
|
||||
<div className="flex min-h-[50vh] flex-col items-center justify-center gap-4">
|
||||
<Card
|
||||
className="current-duty-card w-full max-w-[var(--max-width-app)] border-t-4 border-t-duty animate-in fade-in-0 slide-in-from-bottom-4 duration-300 motion-reduce:animate-none motion-reduce:duration-0"
|
||||
role="article"
|
||||
|
||||
@@ -168,7 +168,7 @@ export const DayDetail = React.forwardRef<DayDetailHandle, DayDetailProps>(
|
||||
if (!open || !selectedDay) return null;
|
||||
|
||||
const panelClassName =
|
||||
"max-w-[min(360px,calc(100vw-24px))] max-h-[70vh] overflow-auto bg-surface text-[var(--text)] rounded-xl shadow-lg p-4 pt-9";
|
||||
"max-w-[min(360px,calc(100vw - var(--app-safe-left, 0) - var(--app-safe-right, 0) - 24px))] max-h-[70vh] overflow-auto bg-surface text-[var(--text)] rounded-xl shadow-lg p-4 pt-9";
|
||||
const closeButton = (
|
||||
<Button
|
||||
type="button"
|
||||
|
||||
@@ -20,8 +20,9 @@ export interface FullScreenStateShellProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const WRAPPER_CLASS =
|
||||
"content-safe flex min-h-[var(--tg-viewport-stable-height,100vh)] flex-col items-center justify-center gap-4 bg-background px-4 text-foreground";
|
||||
const OUTER_CLASS =
|
||||
"content-safe flex min-h-[var(--tg-viewport-stable-height,100vh)] flex-col items-center justify-center gap-4 bg-background text-foreground";
|
||||
const INNER_CLASS = "mx-auto w-full max-w-[var(--max-width-app)] px-3 flex flex-col items-center gap-4";
|
||||
|
||||
/**
|
||||
* Full-screen centered shell with title, optional description, and primary action.
|
||||
@@ -37,15 +38,17 @@ export function FullScreenStateShell({
|
||||
}: FullScreenStateShellProps) {
|
||||
return (
|
||||
<div
|
||||
className={className ? `${WRAPPER_CLASS} ${className}` : WRAPPER_CLASS}
|
||||
className={className ? `${OUTER_CLASS} ${className}` : OUTER_CLASS}
|
||||
role={role}
|
||||
>
|
||||
<h1 className="text-xl font-semibold">{title}</h1>
|
||||
{description != null && (
|
||||
<p className="text-center text-muted-foreground">{description}</p>
|
||||
)}
|
||||
{children}
|
||||
{primaryAction}
|
||||
<div className={INNER_CLASS}>
|
||||
<h1 className="text-xl font-semibold">{title}</h1>
|
||||
{description != null && (
|
||||
<p className="text-center text-muted-foreground">{description}</p>
|
||||
)}
|
||||
{children}
|
||||
{primaryAction}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||
<div
|
||||
data-slot="card-header"
|
||||
className={cn(
|
||||
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
|
||||
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-4 sm:px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -65,7 +65,7 @@ function CardContent({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-content"
|
||||
className={cn("px-6", className)}
|
||||
className={cn("px-4 sm:px-6", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
@@ -75,7 +75,7 @@ function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-footer"
|
||||
className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
|
||||
className={cn("flex items-center px-4 sm:px-6 [.border-t]:pt-6", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -96,7 +96,6 @@ export const MESSAGES: Record<Lang, Record<string, string>> = {
|
||||
"admin.no_duties": "No duties this month.",
|
||||
"admin.no_users_for_assign": "No users available for assignment.",
|
||||
"admin.save": "Save",
|
||||
"admin.duties_count": "{count} duties this month",
|
||||
"admin.list_aria": "List of duties to reassign",
|
||||
"admin.reassign_aria": "Reassign duty: {date}, {time}, {name}",
|
||||
"admin.section_aria": "Duties for {date}",
|
||||
@@ -195,7 +194,6 @@ export const MESSAGES: Record<Lang, Record<string, string>> = {
|
||||
"admin.no_duties": "В этом месяце дежурств нет.",
|
||||
"admin.no_users_for_assign": "Нет пользователей для назначения.",
|
||||
"admin.save": "Сохранить",
|
||||
"admin.duties_count": "{count} дежурств в этом месяце",
|
||||
"admin.list_aria": "Список дежурств для перераспределения",
|
||||
"admin.reassign_aria": "Переназначить дежурство: {date}, {time}, {name}",
|
||||
"admin.section_aria": "Дежурства за {date}",
|
||||
|
||||
Reference in New Issue
Block a user