Files
duty-teller/webapp-next/src/components/duty/DutyItem.tsx
Nikolay Tatarinov 16bf1a1043 feat: migrate to Next.js for Mini App and enhance project structure
- Replaced the previous webapp with a new Mini App built using Next.js, improving performance and maintainability.
- Updated the `.gitignore` to exclude Next.js build artifacts and node modules.
- Revised documentation in `AGENTS.md`, `README.md`, and `architecture.md` to reflect the new Mini App structure and technology stack.
- Enhanced Dockerfile to support the new build process for the Next.js application.
- Updated CI workflow to build and test the Next.js application.
- Added new configuration options for the Mini App, including `MINI_APP_SHORT_NAME` for improved deep linking.
- Refactored frontend testing setup to accommodate the new structure and testing framework.
- Removed legacy webapp files and dependencies to streamline the project.
2026-03-03 16:04:08 +03:00

81 lines
2.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Single duty row: event type label, name, time range.
* Used inside timeline cards and day detail. Ported from webapp/js/dutyList.js dutyItemHtml.
*/
"use client";
import { useTranslation } from "@/i18n/use-translation";
import { formatHHMM, formatDateKey } from "@/lib/date-utils";
import { cn } from "@/lib/utils";
import type { DutyWithUser } from "@/types";
export interface DutyItemProps {
duty: DutyWithUser;
/** Override type label (e.g. "On duty now"). */
typeLabelOverride?: string;
/** Show "until HH:MM" instead of full range (for current duty). */
showUntilEnd?: boolean;
/** Extra class, e.g. for current duty highlight. */
isCurrent?: boolean;
className?: string;
}
const borderByType = {
duty: "border-l-duty",
unavailable: "border-l-unavailable",
vacation: "border-l-vacation",
} as const;
/**
* Renders type badge, name, and time. Timeline cards use event_type for border color.
*/
export function DutyItem({
duty,
typeLabelOverride,
showUntilEnd = false,
isCurrent = false,
className,
}: DutyItemProps) {
const { t } = useTranslation();
const typeLabel =
typeLabelOverride ?? t(`event_type.${duty.event_type || "duty"}`);
const eventType = (duty.event_type || "duty") as keyof typeof borderByType;
const borderClass = isCurrent ? "border-l-today" : borderByType[eventType] ?? "border-l-duty";
let timeOrRange: string;
if (showUntilEnd && duty.event_type === "duty") {
timeOrRange = t("duty.until", { time: formatHHMM(duty.end_at) });
} else if (duty.event_type === "vacation" || duty.event_type === "unavailable") {
const startStr = formatDateKey(duty.start_at);
const endStr = formatDateKey(duty.end_at);
timeOrRange = startStr === endStr ? startStr : `${startStr} ${endStr}`;
} else {
timeOrRange = `${formatHHMM(duty.start_at)} ${formatHHMM(duty.end_at)}`;
}
return (
<div
className={cn(
"grid grid-cols-1 gap-y-0.5 items-baseline rounded-lg bg-surface px-2.5 py-2",
"border-l-[3px] shadow-sm",
"min-h-0",
borderClass,
isCurrent && "bg-[var(--surface-today-tint)]",
className
)}
data-slot="duty-item"
>
<span className="text-xs text-muted col-span-1 row-start-1">
{typeLabel}
</span>
<span className="font-semibold min-w-0 col-span-1 row-start-2 col-start-1">
{duty.full_name}
</span>
<span className="text-[0.8rem] text-muted col-span-1 row-start-3 col-start-1">
{timeOrRange}
</span>
</div>
);
}