Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 67 additions & 44 deletions frontend/features/admin/components/admin-date-time-picker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { format } from "date-fns";
import { enUS, zhCN } from "date-fns/locale";
import { CalendarIcon } from "lucide-react";
import { useLocale, useTranslations } from "next-intl";
import type { Matcher } from "react-day-picker";

import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
Expand All @@ -18,6 +19,8 @@ type AdminDateTimePickerProps = {
label: string;
placeholder: string;
defaultTime?: string;
granularity?: "date" | "datetime";
disabledDate?: Matcher | Matcher[];
onChange: (value: string) => void;
};

Expand Down Expand Up @@ -77,12 +80,15 @@ export function AdminDateTimePicker({
label,
placeholder,
defaultTime = "23:59:59",
granularity = "datetime",
disabledDate,
onChange,
}: AdminDateTimePickerProps) {
const locale = useLocale();
const tDateRange = useTranslations("common.dateRange");
const selectedDate = parseDatePart(value);
const normalizedTime = selectedDate ? normalizeTimeValue(value, defaultTime) : "";
const dateOnly = granularity === "date";

const handleTimePartChange = (index: number, nextPart: string) => {
if (!selectedDate) return;
Expand All @@ -92,7 +98,7 @@ export function AdminDateTimePicker({
return (
<div className="space-y-1">
<p className="text-xs text-muted-foreground">{label}</p>
<div className="grid gap-5 md:grid-cols-2">
<div className={cn("grid gap-5", !dateOnly && "md:grid-cols-2")}>
<Popover>
<PopoverTrigger asChild>
<Button
Expand All @@ -116,53 +122,70 @@ export function AdminDateTimePicker({
mode="single"
selected={selectedDate}
locale={locale === "zh-CN" ? zhCN : enUS}
onSelect={(date) => onChange(date ? buildDateTimeValue(date, normalizeTimeValue(value, defaultTime), defaultTime) : "")}
onSelect={(date) => onChange(date ? (dateOnly ? format(date, "yyyy-MM-dd") : buildDateTimeValue(date, normalizeTimeValue(value, defaultTime), defaultTime)) : "")}
disabled={disabledDate}
autoFocus
/>
{dateOnly && selectedDate ? (
<div className="px-2 pb-2">
<Button
type="button"
size="sm"
variant="secondary"
className="h-6 w-full text-xs"
disabled={disabled}
onClick={() => onChange("")}
>
{tDateRange("clear")}
</Button>
</div>
) : null}
</PopoverContent>
</Popover>
<div className="grid grid-cols-4 gap-2">
<Input
type="number"
min={0}
max={23}
value={selectedDate ? timePart(normalizedTime, 0, defaultTime) : ""}
placeholder="HH"
disabled={disabled || !selectedDate}
className="px-2 text-center tabular-nums"
onChange={(event) => handleTimePartChange(0, event.target.value)}
/>
<Input
type="number"
min={0}
max={59}
value={selectedDate ? timePart(normalizedTime, 1, defaultTime) : ""}
placeholder="MM"
disabled={disabled || !selectedDate}
className="px-2 text-center tabular-nums"
onChange={(event) => handleTimePartChange(1, event.target.value)}
/>
<Input
type="number"
min={0}
max={59}
value={selectedDate ? timePart(normalizedTime, 2, defaultTime) : ""}
placeholder="SS"
disabled={disabled || !selectedDate}
className="px-2 text-center tabular-nums"
onChange={(event) => handleTimePartChange(2, event.target.value)}
/>
<Button
type="button"
variant="outline"
className="h-8 w-full px-0 text-xs text-muted-foreground"
disabled={disabled || !selectedDate}
onClick={() => onChange("")}
aria-label={tDateRange("clear")}
>
{tDateRange("clear")}
</Button>
</div>
{dateOnly ? null : (
<div className="grid grid-cols-4 gap-2">
<Input
type="number"
min={0}
max={23}
value={selectedDate ? timePart(normalizedTime, 0, defaultTime) : ""}
placeholder="HH"
disabled={disabled || !selectedDate}
className="px-2 text-center tabular-nums"
onChange={(event) => handleTimePartChange(0, event.target.value)}
/>
<Input
type="number"
min={0}
max={59}
value={selectedDate ? timePart(normalizedTime, 1, defaultTime) : ""}
placeholder="MM"
disabled={disabled || !selectedDate}
className="px-2 text-center tabular-nums"
onChange={(event) => handleTimePartChange(1, event.target.value)}
/>
<Input
type="number"
min={0}
max={59}
value={selectedDate ? timePart(normalizedTime, 2, defaultTime) : ""}
placeholder="SS"
disabled={disabled || !selectedDate}
className="px-2 text-center tabular-nums"
onChange={(event) => handleTimePartChange(2, event.target.value)}
/>
<Button
type="button"
variant="outline"
className="h-8 w-full px-0 text-xs text-muted-foreground"
disabled={disabled || !selectedDate}
onClick={() => onChange("")}
aria-label={tDateRange("clear")}
>
{tDateRange("clear")}
</Button>
</div>
)}
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
"use client";

import * as React from "react";
import { format } from "date-fns";
import { CalendarIcon } from "lucide-react";
import { motion } from "motion/react";
import { useLocale, useTranslations } from "next-intl";

Expand Down Expand Up @@ -33,10 +31,8 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Calendar } from "@/components/ui/calendar";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import {
Sheet,
SheetContent,
Expand All @@ -54,7 +50,7 @@ import {
import { resolveAvatarImageSrc } from "@/shared/lib/avatar";
import { TimeZoneSelect } from "@/shared/components/time-zone-select";
import { cn } from "@/lib/utils";
import { ADMIN_DATE_PICKER_TRIGGER_CLASSNAME } from "@/features/admin/components/admin-date-range-filter";
import { AdminDateTimePicker } from "@/features/admin/components/admin-date-time-picker";
import type { UserDTO } from "@/shared/api/auth.types";
import type { AdminUserRole, AdminUserStatus } from "@/features/admin/api/admin.types";
import {
Expand Down Expand Up @@ -134,7 +130,6 @@ type CreateUserDialogProps = {
username: string;
displayName: string;
};
createSubscriptionExpiryDate?: Date;
onOpenCreateAvatarDialog: () => void;
onCreateSubmit: React.FormEventHandler<HTMLFormElement>;
resolveCreateUserInitial: (username: string, displayName: string) => string;
Expand All @@ -150,7 +145,6 @@ export function CreateUserDialog({
billingMode,
billingPlans,
createAvatarSource,
createSubscriptionExpiryDate,
onOpenCreateAvatarDialog,
onCreateSubmit,
resolveCreateUserInitial,
Expand Down Expand Up @@ -252,40 +246,20 @@ export function CreateUserDialog({
</div>

<DialogCollapsible open={createPayload.subscriptionTier !== "free"}>
<div className="space-y-1 pt-0.5">
<p className="text-xs text-muted-foreground">{t("editor.expiryTime")}</p>
<Popover>
<PopoverTrigger asChild>
<Button
type="button"
variant="outline"
className={cn(
ADMIN_DATE_PICKER_TRIGGER_CLASSNAME,
"justify-between",
!createSubscriptionExpiryDate && "text-muted-foreground",
)}
disabled={createPayload.subscriptionTier === "free"}
>
{createSubscriptionExpiryDate ? format(createSubscriptionExpiryDate, "yyyy-MM-dd") : t("editor.selectExpiryDate")}
<CalendarIcon className="size-3.5 opacity-70" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<Calendar
mode="single"
selected={createSubscriptionExpiryDate}
onSelect={(date) =>
setCreatePayload((current) => ({
...current,
subscriptionExpiresAt: date ? format(date, "yyyy-MM-dd") : "",
}))
}
disabled={{ before: new Date() }}
autoFocus
/>
</PopoverContent>
</Popover>
</div>
<AdminDateTimePicker
value={createPayload.subscriptionExpiresAt}
label={t("editor.expiryTime")}
placeholder={t("editor.selectExpiryDate")}
granularity="date"
disabled={createPayload.subscriptionTier === "free"}
disabledDate={{ before: new Date() }}
onChange={(value) =>
setCreatePayload((current) => ({
...current,
subscriptionExpiresAt: value,
}))
}
/>
</DialogCollapsible>
</div>
) : null}
Expand Down Expand Up @@ -313,7 +287,6 @@ type EditUserSheetProps = {
setEditPayload: React.Dispatch<React.SetStateAction<EditUserPayload>>;
billingMode: AdminBillingMode;
billingPlans: AdminBillingPlanDTO[];
editSubscriptionExpiryDate?: Date;
statusChanged: boolean;
timeZoneOptions: string[];
roleOptions: AdminUserRole[];
Expand Down Expand Up @@ -380,7 +353,6 @@ export function EditUserSheet({
setEditPayload,
billingMode,
billingPlans,
editSubscriptionExpiryDate,
statusChanged,
timeZoneOptions,
roleOptions,
Expand Down Expand Up @@ -638,40 +610,20 @@ export function EditUserSheet({
</Combobox>
</div>
<DialogCollapsible open={editPayload.subscriptionTier !== "free"}>
<div className="space-y-1">
<Label className="text-xs text-muted-foreground">{t("editor.expiryTime")}</Label>
<Popover>
<PopoverTrigger asChild>
<Button
type="button"
variant="outline"
className={cn(
ADMIN_DATE_PICKER_TRIGGER_CLASSNAME,
"justify-between",
!editSubscriptionExpiryDate && "text-muted-foreground",
)}
disabled={pending || editPayload.subscriptionTier === "free"}
>
{editSubscriptionExpiryDate ? format(editSubscriptionExpiryDate, "yyyy-MM-dd") : t("editor.selectExpiryDate")}
<CalendarIcon className="size-3.5 opacity-70" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<Calendar
mode="single"
selected={editSubscriptionExpiryDate}
onSelect={(date) =>
setEditPayload((current) => ({
...current,
subscriptionExpiresAt: date ? format(date, "yyyy-MM-dd") : "",
}))
}
disabled={{ before: new Date() }}
autoFocus
/>
</PopoverContent>
</Popover>
</div>
<AdminDateTimePicker
value={editPayload.subscriptionExpiresAt}
label={t("editor.expiryTime")}
placeholder={t("editor.selectExpiryDate")}
granularity="date"
disabled={pending || editPayload.subscriptionTier === "free"}
disabledDate={{ before: new Date() }}
onChange={(value) =>
setEditPayload((current) => ({
...current,
subscriptionExpiresAt: value,
}))
}
/>
</DialogCollapsible>
</div>
) : null}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -448,8 +448,6 @@ export function AccountsUsers({
billingPlans,
createAvatarSource,
avatarDialogPreviewSrc,
createSubscriptionExpiryDate,
editSubscriptionExpiryDate,
editStatusChanged,
pageCount,
filteredItems,
Expand Down Expand Up @@ -806,7 +804,6 @@ export function AccountsUsers({
billingMode={billingMode}
billingPlans={billingPlans}
createAvatarSource={createAvatarSource}
createSubscriptionExpiryDate={createSubscriptionExpiryDate}
onOpenCreateAvatarDialog={handleOpenCreateAvatarDialog}
onCreateSubmit={onCreateUser}
resolveCreateUserInitial={resolveCreateUserInitial}
Expand All @@ -827,7 +824,6 @@ export function AccountsUsers({
setEditPayload={setEditPayload}
billingMode={billingMode}
billingPlans={billingPlans}
editSubscriptionExpiryDate={editSubscriptionExpiryDate}
statusChanged={editStatusChanged}
timeZoneOptions={timeZoneOptions}
roleOptions={roleOptions}
Expand Down
Loading
Loading