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.
This commit is contained in:
80
webapp-next/src/components/duty/DutyItem.tsx
Normal file
80
webapp-next/src/components/duty/DutyItem.tsx
Normal file
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user