refactor(v1.4.19): apply simplify-review safe suggestions

Mechanical clean-up sweep across five hot spots flagged by the simplify
audit:

- Lift the 11-key auth.* audit-action label map out of the two admin
  components that rebuilt it on every render. The shared
  useAuthActionLabels() hook in _shared.tsx is now the single home for
  new entries (login-overview + recent-audit-preview consume it).
- Drop the dead chipClass branches in IntegrationStatusPill: only the
  connected state actually paints a Dracula-green chip; error and
  disconnected fall back to the badge variant.
- Add thin DateInput / DateTimeInput wrappers under components/ui that
  bake in the lang={locale} hint so 14 callsites across 9 files no
  longer have to remember the prop. A future date input cannot
  silently regress.
- Remove the leading icon-only flex wrappers left behind when the
  duplicate card titles were dropped on /admin/feedback,
  /admin/danger-zone, and the thresholds editor.
- Strip the five "v1.4.19 X removed because…" narration comments from
  insights/page.tsx and the dashboard. git blame already answers what
  changed and the comments aged into history.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Marc-André BombeckandClaude Opus 4.7 committed 2026-05-10 14:24:35 +02:00
1 parent ef74241a68
commit 6b35cad079
19 files changed
+108 -155

No files matched your search

+2 -44
View File
@@ -221,13 +221,6 @@ interface MedicationComplianceDailyResponse {
dailyCompliance: Record<string, MedicationDailyData>;
}
/**
* v1.4.19 A3 — `getRangeColorClass` / `getRangeHint` /
* `RangeDisplayConfig` removed alongside the `<TrendCard>` tile-strip
* deletion. Those helpers were only used by the duplicate-of-dashboard
* tile grid; every other call site lives on the dashboard surface.
*/
/**
* v1.4.16 phase B1b — pick the freshest ISO timestamp from a list so the
* page hero's "Generated …" caption surfaces a single representative
@@ -645,10 +638,6 @@ export default function InsightsPage() {
const sys = analytics?.summaries?.BLOOD_PRESSURE_SYS;
const dia = analytics?.summaries?.BLOOD_PRESSURE_DIA;
const p = analytics?.summaries?.PULSE;
// v1.4.19 A3 — `bf` / `showBodyFatCard` / `moodSummary` /
// `showMoodCard` were bookkeeping for the now-removed duplicate
// tile strip (Marc said those tiles duplicate the dashboard).
// Remaining mood references read `data?.moodSummary` inline below.
const bmiDivisor = user?.heightCm ? (user.heightCm / 100) ** 2 : null;
// bmiAvg30 and bmiSlope30 still used for overallStatus + bmiSectionStatus
const bmiAvg30 = bmiDivisor && w?.avg30 != null ? w.avg30 / bmiDivisor : null;
@@ -853,12 +842,6 @@ export default function InsightsPage() {
return (
<div className="space-y-8">
{/* v1.4.19 A3 — comparison toggle is now mounted INSIDE the hero
meta band (alongside personal-baseline indicator + Generated
timestamp + Regenerate button). The previous standalone band
below the hero made the page read as three stacked cards
with separate header / control rows; folding the toggle into
the hero clusters every page-level control in one place. */}
<InsightsPageHero
updatedAt={advisor.payload?.cachedAt ?? heroUpdatedAt}
onRegenerate={advisor.regenerate}
@@ -869,24 +852,8 @@ export default function InsightsPage() {
{/* v1.4.16 phase D reconcile (CRITICAL C1) — wire the polished
`<InsightAdvisorCard>` (severity-ordered recommendations grid +
per-rec rationale expand + confidence meter + thumbs feedback +
medical-citation footnotes) into the live route. Before this
fix the page rendered only the text-only `<InsightStatusCard>`
per section, hiding all of B5c/d/e/B1b's polish from users.
The per-section status cards stay below as supplemental detail.
v1.4.19 A3 — `onRegenerate` is no longer wired here. The page
hero already owns the single page-level refresh affordance;
the card-level icon button + empty-state "Analyse starten"
button were duplicates that surfaced the same handler three
ways (page hero button, card-header icon button,
empty-state CTA). The per-recommendation Regenerate (inside
`<RecommendationCard>`) is unrelated — it stays under the
v1.4.16 spec. The placeholder `aiOverviewTitle`
("Persönlicher Berater") subtitle is also gone — the
CardTitle ("KI-Gesundheitsanalyse") is enough framing on its
own, and the orphan title above an empty body when the user
has no provider configured was the actual confusion source
Marc reported. */}
medical-citation footnotes) into the live route. The
per-section status cards stay below as supplemental detail. */}
<InsightAdvisorCard
insight={advisor.payload?.insights ?? null}
loading={advisor.isLoading}
@@ -897,15 +864,6 @@ export default function InsightsPage() {
<InsightsSectionNav />
{/* v1.4.19 A3 — duplicate per-metric `<TrendCard>` strip removed.
Up to v1.4.18 the page led with a 5-column grid of weight /
BP-sys / BP-dia / pulse / body-fat / mood tiles — exact
duplicates of the dashboard tile-strip. They added zero
information beyond what's already on the dashboard. The
recommendations grid + per-section detail cards below are
the actual value on `/insights`; the tiles only consumed
scroll height. */}
<section id="section-general" className="scroll-mt-28 space-y-2">
<div className="flex items-center gap-2">
<h2 className="text-lg font-semibold">
+2 -3
View File
@@ -16,6 +16,7 @@ import {
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { DateInput } from "@/components/ui/date-input";
import { Label } from "@/components/ui/label";
import { Logo } from "@/components/ui/logo";
import { MeasurementForm } from "@/components/measurements/measurement-form";
@@ -301,10 +302,8 @@ export default function OnboardingPage() {
<div className="space-y-2">
<Label htmlFor="ob-dob">{t("settings.dateOfBirth")}</Label>
<Input
<DateInput
id="ob-dob"
type="date"
lang={locale}
value={dateOfBirth}
onChange={(e) => setDateOfBirth(e.target.value)}
max={new Date().toISOString().slice(0, 10)}
-11
View File
@@ -491,17 +491,6 @@ export default function DashboardPage() {
</DropdownMenu>
</div>
{/* v1.4.19 A3 — comparison toggle removed from dashboard.
The tile strip is too tight for an extra control band, and
Marc rarely wants Vormonat / Vorjahr context at a glance —
when he does, it's on `/insights` where the chart sits next
to the toggle. The underlying `compareBaseline` value still
flows from `/api/dashboard/widgets` into every chart below
so nothing visual changes when the user has the comparison
on; only the on-surface affordance moves. Settings →
Dashboard remains the power-user backdoor for either
surface. */}
{/* v1.4: Getting-started checklist for brand-new users.
* Self-gates visibility on (onboardingCompletedAt == null
* || measurementCount < 5) and disappears once dismissed
+29
View File
@@ -1,5 +1,6 @@
"use client";
import { useMemo } from "react";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { toast } from "sonner";
import { Switch } from "@/components/ui/switch";
@@ -257,6 +258,34 @@ export async function getApiErrorMessage(response: Response): Promise<string> {
return fallback;
}
/**
* Shared `auth.*` audit-action label map. Two surfaces consume it
* today (`/admin/login-overview` and the dashboard recent-activity
* preview), and any future audit feed will need the same translation
* lookup. New auth actions are added here once instead of being
* copy-pasted into every consumer.
*/
export function useAuthActionLabels(): Record<string, string> {
const { t, locale } = useTranslations();
return useMemo(
() => ({
"auth.register": t("admin.authRegister"),
"auth.login": t("admin.authLogin"),
"auth.login.passkey": t("admin.authLoginPasskey"),
"auth.login.password": t("admin.authLoginPassword"),
"auth.login.failed": t("admin.authLoginFailed"),
"auth.logout": t("admin.authLogout"),
"auth.passkey.register": t("admin.authPasskeyRegister"),
"auth.passkey.delete": t("admin.authPasskeyDelete"),
"auth.token.autoissue.native": t("admin.authTokenAutoissueNative"),
"auth.token.refresh": t("admin.authTokenRefresh"),
"auth.token.revoke": t("admin.authTokenRevoke"),
}),
// eslint-disable-next-line react-hooks/exhaustive-deps
[locale],
);
}
export function useSystemStatus() {
return useQuery({
queryKey: ["admin", "status"],
+4 -6
View File
@@ -70,12 +70,10 @@ export function DangerZoneSection() {
already names the section, so the card-level title was a
duplicate of the page title. The destructive icon stays so
the card still flags itself as dangerous at a glance. */}
<div className="flex items-center gap-2">
<AlertTriangle
className="text-destructive h-5 w-5"
aria-hidden="true"
/>
</div>
<AlertTriangle
className="text-destructive h-5 w-5"
aria-hidden="true"
/>
<div className="mt-4">
<p className="text-sm font-medium">{t("admin.deleteAllData")}</p>
<p className="text-muted-foreground text-xs">
@@ -78,9 +78,7 @@ export function FeedbackInboxSection() {
`/admin/feedback`, so the page header already provides the
title + description — the card-level repetition was just
visual noise. The icon stays as a low-key card affordance. */}
<div className="flex items-center gap-2">
<Inbox className="text-primary h-5 w-5" aria-hidden="true" />
</div>
<Inbox className="text-primary h-5 w-5" aria-hidden="true" />
<Tabs
value={activeStatus}
@@ -23,7 +23,7 @@ import {
import { formatDateTime } from "@/lib/format";
import { useTranslations } from "@/lib/i18n/context";
import { toCSV } from "@/lib/export";
import { type AdminAuditEntry } from "./_shared";
import { type AdminAuditEntry, useAuthActionLabels } from "./_shared";
type DateRangePreset = "all" | "24h" | "7d" | "30d";
type PerPageValue = 25 | 50 | 100;
@@ -67,19 +67,7 @@ export function LoginOverviewSection() {
const [page, setPage] = useState(1);
const [perPage, setPerPage] = useState<PerPageValue>(50);
const AUTH_ACTION_LABELS: Record<string, string> = {
"auth.register": t("admin.authRegister"),
"auth.login": t("admin.authLogin"),
"auth.login.passkey": t("admin.authLoginPasskey"),
"auth.login.password": t("admin.authLoginPassword"),
"auth.login.failed": t("admin.authLoginFailed"),
"auth.logout": t("admin.authLogout"),
"auth.passkey.register": t("admin.authPasskeyRegister"),
"auth.passkey.delete": t("admin.authPasskeyDelete"),
"auth.token.autoissue.native": t("admin.authTokenAutoissueNative"),
"auth.token.refresh": t("admin.authTokenRefresh"),
"auth.token.revoke": t("admin.authTokenRevoke"),
};
const AUTH_ACTION_LABELS = useAuthActionLabels();
// Build the query string once so it's reused by the data-query key, the
// export download, and the next-/prev- buttons.
+2 -14
View File
@@ -21,7 +21,7 @@ import {
import { EmptyState } from "@/components/ui/empty-state";
import { formatDateTime } from "@/lib/format";
import { useTranslations } from "@/lib/i18n/context";
import { type AdminAuditEntry } from "./_shared";
import { type AdminAuditEntry, useAuthActionLabels } from "./_shared";
const PREVIEW_LIMIT = 10;
@@ -47,19 +47,7 @@ export function RecentAuditPreview() {
staleTime: 30_000,
});
const AUTH_ACTION_LABELS: Record<string, string> = {
"auth.register": t("admin.authRegister"),
"auth.login": t("admin.authLogin"),
"auth.login.passkey": t("admin.authLoginPasskey"),
"auth.login.password": t("admin.authLoginPassword"),
"auth.login.failed": t("admin.authLoginFailed"),
"auth.logout": t("admin.authLogout"),
"auth.passkey.register": t("admin.authPasskeyRegister"),
"auth.passkey.delete": t("admin.authPasskeyDelete"),
"auth.token.autoissue.native": t("admin.authTokenAutoissueNative"),
"auth.token.refresh": t("admin.authTokenRefresh"),
"auth.token.revoke": t("admin.authTokenRevoke"),
};
const AUTH_ACTION_LABELS = useAuthActionLabels();
const entries = data?.entries ?? [];
@@ -29,6 +29,7 @@ import {
DialogTitle,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { DateInput } from "@/components/ui/date-input";
import { Label } from "@/components/ui/label";
import { useTranslations } from "@/lib/i18n/context";
@@ -80,7 +81,7 @@ export function DoctorReportDialog({
defaultPracticeName,
onSubmit,
}: DoctorReportDialogProps) {
const { t, locale } = useTranslations();
const { t } = useTranslations();
const [startDate, setStartDate] = useState<string>(defaultStartIso);
const [endDate, setEndDate] = useState<string>(todayIso);
const [practiceName, setPracticeName] = useState<string>("");
@@ -191,10 +192,8 @@ export function DoctorReportDialog({
<Label htmlFor="dr-start">
{t("doctorReport.dialog.startLabel")}
</Label>
<Input
<DateInput
id="dr-start"
type="date"
lang={locale}
value={startDate}
max={endDate || todayIso()}
onChange={(e) => {
@@ -208,10 +207,8 @@ export function DoctorReportDialog({
<Label htmlFor="dr-end">
{t("doctorReport.dialog.endLabel")}
</Label>
<Input
<DateInput
id="dr-end"
type="date"
lang={locale}
value={endDate}
min={startDate}
max={todayIso()}
@@ -10,6 +10,7 @@ import {
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Input } from "@/components/ui/input";
import { DateTimeInput } from "@/components/ui/date-input";
import { Label } from "@/components/ui/label";
import {
Select,
@@ -117,7 +118,7 @@ export function MeasurementForm({
onCancel,
defaultType,
}: MeasurementFormProps) {
const { t, locale } = useTranslations();
const { t } = useTranslations();
const queryClient = useQueryClient();
// Normalize legacy BP types to combined mode
@@ -358,10 +359,8 @@ export function MeasurementForm({
<div className="space-y-2">
<Label htmlFor="measuredAt">{t("measurements.timestamp")}</Label>
<Input
<DateTimeInput
id="measuredAt"
type="datetime-local"
lang={locale}
value={measuredAt}
onChange={(e) => setMeasuredAt(e.target.value)}
required
@@ -62,6 +62,7 @@ import { useState } from "react";
import { formatDateTime } from "@/lib/format";
import { useTranslations, useFormatters } from "@/lib/i18n/context";
import { invalidateKeys, measurementDependentKeys } from "@/lib/query-keys";
import { DateTimeInput } from "@/components/ui/date-input";
import {
MEASUREMENT_TYPE_LABEL_KEYS as TYPE_LABEL_KEYS,
MEASUREMENT_TYPE_ICONS as TYPE_ICONS,
@@ -120,7 +121,7 @@ function formatMeasurementSource(
}
export function MeasurementList({ onEdit, onAddFirst }: MeasurementListProps) {
const { t, locale } = useTranslations();
const { t } = useTranslations();
const fmt = useFormatters();
const { isAuthenticated } = useAuth();
const queryClient = useQueryClient();
@@ -583,10 +584,8 @@ export function MeasurementList({ onEdit, onAddFirst }: MeasurementListProps) {
<Label htmlFor="edit-measuredAt">
{t("measurements.timestamp")}
</Label>
<Input
<DateTimeInput
id="edit-measuredAt"
type="datetime-local"
lang={locale}
value={editMeasuredAt}
onChange={(e) => setEditMeasuredAt(e.target.value)}
required
@@ -28,7 +28,7 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Input } from "@/components/ui/input";
import { DateTimeInput } from "@/components/ui/date-input";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import {
@@ -95,7 +95,7 @@ export function IntakeHistoryList({
}: IntakeHistoryListProps) {
const { isAuthenticated } = useAuth();
const queryClient = useQueryClient();
const { t, locale } = useTranslations();
const { t } = useTranslations();
const sourceLabels: Record<string, string> = {
WEB: t("medications.sourceWeb"),
@@ -542,10 +542,8 @@ export function IntakeHistoryList({
<Label htmlFor="edit-scheduledFor">
{t("medications.intakeScheduledFor")}
</Label>
<Input
<DateTimeInput
id="edit-scheduledFor"
type="datetime-local"
lang={locale}
value={editScheduledFor}
onChange={(e) => setEditScheduledFor(e.target.value)}
required
@@ -556,10 +554,8 @@ export function IntakeHistoryList({
<Label htmlFor="edit-takenAt">
{t("medications.intakeTakenAt")}
</Label>
<Input
<DateTimeInput
id="edit-takenAt"
type="datetime-local"
lang={locale}
value={editTakenAt}
onChange={(e) => setEditTakenAt(e.target.value)}
disabled={editSkipped}
@@ -688,10 +684,8 @@ export function IntakeHistoryList({
<Label htmlFor="create-scheduledFor">
{t("medications.intakeScheduledFor")}
</Label>
<Input
<DateTimeInput
id="create-scheduledFor"
type="datetime-local"
lang={locale}
value={createScheduledFor}
onChange={(e) => setCreateScheduledFor(e.target.value)}
required
@@ -702,10 +696,8 @@ export function IntakeHistoryList({
<Label htmlFor="create-takenAt">
{t("medications.intakeTakenAt")}
</Label>
<Input
<DateTimeInput
id="create-takenAt"
type="datetime-local"
lang={locale}
value={createTakenAt}
onChange={(e) => setCreateTakenAt(e.target.value)}
disabled={createSkipped}
+3 -4
View File
@@ -10,6 +10,7 @@ import {
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Input } from "@/components/ui/input";
import { DateTimeInput } from "@/components/ui/date-input";
import { Label } from "@/components/ui/label";
import { Loader2, MoreHorizontal, Plus, RotateCcw } from "lucide-react";
import { toast } from "sonner";
@@ -37,7 +38,7 @@ function getDefaultMoodLoggedAtValue() {
}
export function MoodForm({ onSuccess, onCancel }: MoodFormProps) {
const { t, locale } = useTranslations();
const { t } = useTranslations();
const queryClient = useQueryClient();
const [mood, setMood] = useState("");
@@ -132,10 +133,8 @@ export function MoodForm({ onSuccess, onCancel }: MoodFormProps) {
<div className="space-y-2">
<Label htmlFor="mood-logged-at">{t("mood.timestamp")}</Label>
<Input
<DateTimeInput
id="mood-logged-at"
type="datetime-local"
lang={locale}
value={moodLoggedAt}
onChange={(e) => setMoodLoggedAt(e.target.value)}
required
+3 -4
View File
@@ -36,6 +36,7 @@ import {
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Input } from "@/components/ui/input";
import { DateTimeInput } from "@/components/ui/date-input";
import { Label } from "@/components/ui/label";
import {
Table,
@@ -117,7 +118,7 @@ interface MoodListProps {
}
export function MoodList({ onAddFirst }: MoodListProps = {}) {
const { t, locale } = useTranslations();
const { t } = useTranslations();
const fmt = useFormatters();
const { isAuthenticated } = useAuth();
const queryClient = useQueryClient();
@@ -569,10 +570,8 @@ export function MoodList({ onAddFirst }: MoodListProps = {}) {
<Label htmlFor="edit-mood-logged-at">
{t("mood.timestamp")}
</Label>
<Input
<DateTimeInput
id="edit-mood-logged-at"
type="datetime-local"
lang={locale}
value={editMoodLoggedAt}
onChange={(e) => setEditMoodLoggedAt(e.target.value)}
required
+2 -3
View File
@@ -35,6 +35,7 @@ import {
DialogTitle,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { DateInput } from "@/components/ui/date-input";
import { Label } from "@/components/ui/label";
import { PasswordInput } from "@/components/settings/password-input";
import { useAuth } from "@/hooks/use-auth";
@@ -383,10 +384,8 @@ export function AccountSection() {
<div className="grid gap-4 sm:grid-cols-2">
<div className="space-y-2">
<Label htmlFor="dob">{t("settings.dateOfBirth")}</Label>
<Input
<DateInput
id="dob"
type="date"
lang={locale}
value={dateOfBirth}
onChange={(e) => setDateOfBirth(e.target.value)}
max={new Date().toISOString().slice(0, 10)}
+4 -8
View File
@@ -31,7 +31,7 @@ import {
} from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { DateInput } from "@/components/ui/date-input";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import {
@@ -253,17 +253,15 @@ function DateRangeFields({
setUntil,
idPrefix,
}: DateRangeFieldsProps) {
const { t, locale } = useTranslations();
const { t } = useTranslations();
return (
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
<div className="space-y-1.5">
<Label htmlFor={`${idPrefix}-since`} className="text-xs">
{t("settings.sections.export.filters.since")}
</Label>
<Input
<DateInput
id={`${idPrefix}-since`}
type="date"
lang={locale}
value={since}
onChange={(e) => setSince(e.target.value)}
max={until || undefined}
@@ -273,10 +271,8 @@ function DateRangeFields({
<Label htmlFor={`${idPrefix}-until`} className="text-xs">
{t("settings.sections.export.filters.until")}
</Label>
<Input
<DateInput
id={`${idPrefix}-until`}
type="date"
lang={locale}
value={until}
onChange={(e) => setUntil(e.target.value)}
min={since || undefined}
@@ -74,26 +74,26 @@ export function IntegrationStatusPill({
}: IntegrationStatusPillProps) {
const { t } = useTranslations();
// chipClass only applies to the `connected` branch — `error` falls back
// to `variant="destructive"` and `disconnected` to `variant="outline"`.
const connectedChipClass =
"border-dracula-green/30 bg-dracula-green/15 text-dracula-green";
let label: string;
let icon: React.ReactNode;
let chipClass: string;
switch (state) {
case "connected":
label = t("settings.integrationPill.connected");
icon = <CheckCircle2 aria-hidden="true" className="h-3 w-3" />;
chipClass =
"border-dracula-green/30 bg-dracula-green/15 text-dracula-green";
break;
case "error":
label = t("settings.integrationPill.errorReconnect");
icon = <AlertTriangle aria-hidden="true" className="h-3 w-3" />;
chipClass = "";
break;
case "disconnected":
label = t("settings.integrationPill.notConnected");
icon = <CircleSlash aria-hidden="true" className="h-3 w-3" />;
chipClass = "";
break;
}
@@ -120,7 +120,7 @@ export function IntegrationStatusPill({
aria-label={t("settings.integrationPill.ariaLabel")}
className={cn(
"max-w-full whitespace-nowrap",
state === "connected" && chipClass,
state === "connected" && connectedChipClass,
className,
)}
>
@@ -129,12 +129,10 @@ export function ThresholdsEditorSection({ id }: { id: string }) {
Keep the icon + reset action so the card still has a visible
control affordance. */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<SlidersHorizontal
className="text-primary h-5 w-5"
aria-hidden="true"
/>
</div>
<SlidersHorizontal
className="text-primary h-5 w-5"
aria-hidden="true"
/>
{data && Object.keys(data.overrides).length > 0 && (
<Button
variant="ghost"
+28
View File
@@ -0,0 +1,28 @@
"use client";
import * as React from "react";
import { Input } from "@/components/ui/input";
import { useTranslations } from "@/lib/i18n/context";
/**
* Native date input that hints the active locale via `lang=`. Browsers
* use this hint to render the calendar widget in dd.MM.yyyy / MM/dd/yyyy
* order, which is the user-visible format we want to follow the app's
* EN/DE preference. Wrapping the prop here keeps every callsite from
* having to pull `locale` out of `useTranslations()` just to feed it
* back into the same JSX prop, and guarantees a future date input
* cannot silently regress by forgetting the hint.
*/
type InputProps = React.ComponentProps<typeof Input>;
type DateInputProps = Omit<InputProps, "type" | "lang">;
export function DateInput(props: DateInputProps) {
const { locale } = useTranslations();
return <Input type="date" lang={locale} {...props} />;
}
export function DateTimeInput(props: DateInputProps) {
const { locale } = useTranslations();
return <Input type="datetime-local" lang={locale} {...props} />;
}