{!formData.is_full_day && (
- setFormData(prev => ({ ...prev, event_time_start: v }))}
/>
- setFormData(prev => ({ ...prev, event_time_end: v }))}
/>
)}
diff --git a/frontend/src/pages/admin/bills/BillEditorPage.tsx b/frontend/src/pages/admin/bills/BillEditorPage.tsx
index 8426117d..3f2d3c1e 100644
--- a/frontend/src/pages/admin/bills/BillEditorPage.tsx
+++ b/frontend/src/pages/admin/bills/BillEditorPage.tsx
@@ -8,7 +8,7 @@ import { useTranslation } from 'react-i18next';
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { ArrowLeft, Eye, Save as SaveIcon } from 'lucide-react';
-import { Button, Card, Loading, Input, LocalizedDateInput } from '../../../components/common';
+import { Button, Card, Loading, Input, LocalizedDateInput, TimeField } from '../../../components/common';
import { billsService, type InvoiceCreatePayload, type InvoiceQrFormat } from '../../../services/bills.service';
import { quotesService } from '../../../services/quotes.service';
import { contractsService } from '../../../services/contracts.service';
@@ -20,7 +20,6 @@ import { customerAdminService } from '../../../services/customerAdmin.service';
import { userManagementService } from '../../../services/userManagement.service';
import { settingsService } from '../../../services/settings.service';
import { useAdminAuth } from '../../../contexts/AdminAuthContext';
-import { useLocalizedDate } from '../../../hooks/useLocalizedDate';
import { toast } from 'react-toastify';
function toMinor(amount: number) {
@@ -29,8 +28,6 @@ function toMinor(amount: number) {
export const BillEditorPage: React.FC = () => {
const { t } = useTranslation();
- const { timeFormat } = useLocalizedDate();
- const timeInputLang = timeFormat === '12h' ? 'en-US' : 'de-DE';
const { id } = useParams<{ id?: string }>();
const [searchParams] = useSearchParams();
const navigate = useNavigate();
@@ -519,10 +516,10 @@ export const BillEditorPage: React.FC = () => {
value={eventName} onChange={(e) => setEventName(e.target.value)} />
-
setEventTimeStart(e.target.value)} />
-
setEventTimeEnd(e.target.value)} />
+
+
diff --git a/frontend/src/pages/admin/clients/HourEntryInlinePopover.tsx b/frontend/src/pages/admin/clients/HourEntryInlinePopover.tsx
index 539dd974..f4290919 100644
--- a/frontend/src/pages/admin/clients/HourEntryInlinePopover.tsx
+++ b/frontend/src/pages/admin/clients/HourEntryInlinePopover.tsx
@@ -27,7 +27,7 @@ import { useTranslation } from 'react-i18next';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { Lock, Trash2 } from 'lucide-react';
import { toast } from 'react-toastify';
-import { Button, Card, Input } from '../../../components/common';
+import { Button, Card, Input, TimeField } from '../../../components/common';
import { customerAdminService } from '../../../services/customerAdmin.service';
import type { CalendarHoursItem } from '../../../services/calendar.service';
@@ -176,23 +176,13 @@ export const HourEntryInlinePopover: React.FC = ({
{t('calendar.hourEntry.startLabel', 'Start')}
- setStartTime(e.target.value)}
- />
+
{t('calendar.hourEntry.endLabel', 'End')}
- setEndTime(e.target.value)}
- />
+
diff --git a/frontend/src/pages/admin/contracts/ContractEditorPage.tsx b/frontend/src/pages/admin/contracts/ContractEditorPage.tsx
index 05aa8114..c06ed00a 100644
--- a/frontend/src/pages/admin/contracts/ContractEditorPage.tsx
+++ b/frontend/src/pages/admin/contracts/ContractEditorPage.tsx
@@ -17,14 +17,13 @@ import { useNavigate, useParams, Link } from 'react-router-dom';
import { useQuery, useMutation } from '@tanstack/react-query';
import { toast } from 'react-toastify';
import { ArrowLeft, Eye, Save } from 'lucide-react';
-import { Button, Card, Input, Loading, LocalizedDateInput } from '../../../components/common';
+import { Button, Card, Input, Loading, LocalizedDateInput, TimeField } from '../../../components/common';
import {
contractsService,
type ContractBlockSection,
CONTRACT_SECTIONS,
} from '../../../services/contracts.service';
import { CustomerPicker } from '../../../components/admin/CustomerPicker';
-import { useLocalizedDate } from '../../../hooks/useLocalizedDate';
interface BlockRow {
blockId: number;
@@ -38,8 +37,6 @@ interface BlockRow {
export const ContractEditorPage: React.FC = () => {
const { t } = useTranslation();
- const { timeFormat } = useLocalizedDate();
- const timeInputLang = timeFormat === '12h' ? 'en-US' : 'de-DE';
const { id } = useParams<{ id?: string }>();
const navigate = useNavigate();
const isEdit = Boolean(id);
@@ -426,13 +423,13 @@ export const ContractEditorPage: React.FC = () => {
{t('contracts.editor.eventTimeStart', 'Start')}
- setEventTimeStart(e.target.value)} />
+
{t('contracts.editor.eventTimeEnd', 'End')}
- setEventTimeEnd(e.target.value)} />
+
diff --git a/frontend/src/pages/admin/quotes/QuoteEditorPage.tsx b/frontend/src/pages/admin/quotes/QuoteEditorPage.tsx
index 22a68839..9f6c380c 100644
--- a/frontend/src/pages/admin/quotes/QuoteEditorPage.tsx
+++ b/frontend/src/pages/admin/quotes/QuoteEditorPage.tsx
@@ -16,7 +16,7 @@ import { useTranslation } from 'react-i18next';
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { ArrowLeft, Eye, Send } from 'lucide-react';
-import { Button, Card, Loading, Input, LocalizedDateInput } from '../../../components/common';
+import { Button, Card, Loading, Input, LocalizedDateInput, TimeField } from '../../../components/common';
import {
quotesService,
type QuoteCreatePayload,
@@ -29,7 +29,6 @@ import { customerAdminService } from '../../../services/customerAdmin.service';
import { userManagementService } from '../../../services/userManagement.service';
import { settingsService } from '../../../services/settings.service';
import { useAdminAuth } from '../../../contexts/AdminAuthContext';
-import { useLocalizedDate } from '../../../hooks/useLocalizedDate';
import { toast } from 'react-toastify';
interface FormState {
@@ -140,8 +139,6 @@ function buildPayload(f: FormState): QuoteCreatePayload {
export const QuoteEditorPage: React.FC = () => {
const { t } = useTranslation();
- const { timeFormat } = useLocalizedDate();
- const timeInputLang = timeFormat === '12h' ? 'en-US' : 'de-DE';
const { id } = useParams<{ id?: string }>();
const [searchParams] = useSearchParams();
const navigate = useNavigate();
@@ -492,10 +489,10 @@ export const QuoteEditorPage: React.FC = () => {
onChange={(e) => setForm((f) => ({ ...f, eventName: e.target.value }))} />
setForm((f) => ({ ...f, eventDate: iso }))} />
- setForm((f) => ({ ...f, eventTimeStart: e.target.value }))} />
- setForm((f) => ({ ...f, eventTimeEnd: e.target.value }))} />
+ setForm((f) => ({ ...f, eventTimeStart: v }))} />
+ setForm((f) => ({ ...f, eventTimeEnd: v }))} />
setForm((f) => ({ ...f, expectedDurationHours: e.target.value }))} />
diff --git a/frontend/src/pages/admin/settings/SettingsBusinessProfilePage.tsx b/frontend/src/pages/admin/settings/SettingsBusinessProfilePage.tsx
index 2e38aef3..cff6045f 100644
--- a/frontend/src/pages/admin/settings/SettingsBusinessProfilePage.tsx
+++ b/frontend/src/pages/admin/settings/SettingsBusinessProfilePage.tsx
@@ -17,9 +17,8 @@ import {
type BusinessHoursBlock,
type QrFormat,
} from '../../../services/businessProfile.service';
-import { Button, Card, Loading, Input, CountrySelect } from '../../../components/common';
+import { Button, Card, Loading, Input, CountrySelect, TimeField } from '../../../components/common';
import { DecimalInput } from '../../../components/common/DecimalInput';
-import { useLocalizedDate } from '../../../hooks/useLocalizedDate';
import { toast } from 'react-toastify';
export const SettingsBusinessProfilePage: React.FC = () => {
@@ -350,84 +349,6 @@ const PdfToggleRow: React.FC = ({ label, description, enabled
*/
const WEEKDAYS = [1, 2, 3, 4, 5, 6, 7];
-/**
- * Parse a free-typed time into the canonical 24h "HH:MM", or null if
- * unparseable. Tolerant of: "13:00", "1300", "9:5", "9", "1:00 PM",
- * "1pm", "12 am". Used to accept input in whichever format the field is
- * displaying (24h or 12h) and normalise it back to storage form.
- */
-const parseTimeToHHMM = (raw: string): string | null => {
- const s = raw.trim().toLowerCase();
- if (!s) return null;
- let ampm: 'am' | 'pm' | null = null;
- let core = s;
- const am = s.match(/(a|p)\.?m?\.?\s*$/);
- if (am) {
- ampm = am[1] === 'p' ? 'pm' : 'am';
- core = s.slice(0, am.index).trim();
- }
- let h: number;
- let mi: number;
- const colon = core.match(/^(\d{1,2})\s*[:.]\s*(\d{1,2})$/);
- if (colon) {
- h = parseInt(colon[1], 10);
- mi = parseInt(colon[2], 10);
- } else {
- const digits = core.replace(/\D/g, '');
- if (!digits) return null;
- if (digits.length <= 2) { h = parseInt(digits, 10); mi = 0; }
- else if (digits.length === 3) { h = parseInt(digits.slice(0, 1), 10); mi = parseInt(digits.slice(1), 10); }
- else { h = parseInt(digits.slice(0, 2), 10); mi = parseInt(digits.slice(2, 4), 10); }
- }
- if (Number.isNaN(h) || Number.isNaN(mi)) return null;
- if (ampm === 'pm' && h < 12) h += 12;
- if (ampm === 'am' && h === 12) h = 0;
- h = Math.min(23, Math.max(0, h));
- mi = Math.min(59, Math.max(0, mi));
- return `${String(h).padStart(2, '0')}:${String(mi).padStart(2, '0')}`;
-};
-
-/**
- * Time field that DISPLAYS in the admin's `general_time_format` (24h →
- * "13:00", 12h → "01:00 PM") but always stores/emits canonical 24h
- * "HH:MM". A plain text input, so the rendered format is identical in
- * every browser (native ignores our setting — its
- * 12h/24h chrome is browser-locale-controlled and Safari ignores the
- * `lang` hint entirely). Free text while typing; parsed + reformatted on
- * blur, reverting to the last good value if unparseable.
- */
-const TimeField: React.FC<{
- value: string;
- onChange: (v: string) => void;
- ariaLabel?: string;
-}> = ({ value, onChange, ariaLabel }) => {
- const { formatTime: fmtTime, timeFormat } = useLocalizedDate();
- const display = (v: string) => (/^\d{1,2}:\d{2}/.test(v) ? fmtTime(v) : (v || ''));
- const [text, setText] = useState(() => display(value));
- useEffect(() => { setText(display(value)); /* eslint-disable-next-line */ }, [value, timeFormat]);
- const commit = () => {
- const parsed = parseTimeToHHMM(text);
- if (parsed) {
- setText(display(parsed));
- if (parsed !== value) onChange(parsed);
- } else {
- setText(display(value));
- }
- };
- return (
- setText(e.target.value)}
- onBlur={commit}
- className="input w-32 shrink-0 tabular-nums"
- />
- );
-};
-
const BusinessHoursEditor: React.FC<{
value: BusinessHours | null;
onChange: (next: BusinessHours) => void;
@@ -508,12 +429,14 @@ const BusinessHoursEditor: React.FC<{
value={block.start}
onChange={(v) => updateBlock(iso, idx, { start: v })}
ariaLabel={t('businessProfile.businessHours.startTime', 'Opening time') as string}
+ className="w-32 shrink-0"
/>
–
updateBlock(iso, idx, { end: v })}
ariaLabel={t('businessProfile.businessHours.endTime', 'Closing time') as string}
+ className="w-32 shrink-0"
/>