refactor: normalize date handling to Tehran timezone
- Updated date handling in BlogSeoFields and ScheduleSection to use Tehran timezone utilities for consistency. - Introduced `toTehranClockTime`, `tehranWallClockToUnix`, and `todayIso` functions for accurate date representation. - Modified various components to utilize these new utilities, ensuring that date strings are correctly formatted and timestamps are accurately converted. - Enhanced API documentation to clarify the handling of date fields, emphasizing the importance of server-local midnight. - Added tests to verify that date overrides and holidays maintain the correct day without shifting due to timezone discrepancies.
This commit is contained in:
@@ -14,7 +14,7 @@ import { WalletChargeLink } from '../components/AppointmentActions';
|
||||
import { useDoctorBookingServices } from '../hooks/useDoctorBookingServices';
|
||||
import ServiceSlotPicker from '../components/appointments/ServiceSlotPicker';
|
||||
import { tehranWallClockToUnix, rialToToman, tomanToRial } from '../lib/utils';
|
||||
import { digitsOnly } from '../lib/utils';
|
||||
import { digitsOnly, todayIso } from '../lib/utils';
|
||||
|
||||
/**
|
||||
* افزودن نوبت — صفحهٔ کامل (بازسازی `CreateTurn.jsx` طرح tauri). از همان endpointهای
|
||||
@@ -43,7 +43,7 @@ export default function AppointmentCreatePage() {
|
||||
const dbUuid = useAuthStore(s => s.dbUuid);
|
||||
const isDoctor = primaryRole === 'doctor';
|
||||
|
||||
const today = new Date().toISOString().slice(0, 10);
|
||||
const today = todayIso();
|
||||
|
||||
// ── پزشک
|
||||
const [doctorUuid, setDoctorUuid] = useState(isDoctor && dbUuid ? dbUuid : (params.get('doctor') ?? ''));
|
||||
|
||||
@@ -9,7 +9,7 @@ import { toast } from 'sonner';
|
||||
import { api } from '../lib/api';
|
||||
import type { PaginatedResponse, ApiResponse } from '../lib/api';
|
||||
import type { Appointment } from '../types';
|
||||
import { formatDate, toGregorianDate, formatTime, digitsOnly, sanitizeMobileInput, rialToToman, tomanToRial, formatRial } from '../lib/utils';
|
||||
import { formatDate, toGregorianDate, todayIso, formatTime, digitsOnly, sanitizeMobileInput, rialToToman, tomanToRial, formatRial } from '../lib/utils';
|
||||
import PriceInput from '../components/ui/PriceInput';
|
||||
import Modal from '../components/ui/Modal';
|
||||
import { useAuthStore } from '../stores/authStore';
|
||||
@@ -507,7 +507,7 @@ export default function AppointmentsPage() {
|
||||
const canCancelAppt = can('appointments', 'cancel');
|
||||
|
||||
const [params] = useSearchParams();
|
||||
const today = new Date().toISOString().slice(0, 10);
|
||||
const today = todayIso();
|
||||
// پس از ویرایش/ثبت، صفحه با ?date=... باز میشود تا همان روز نمایش داده شود.
|
||||
const [selectedDate, setSelectedDate] = useState(params.get('date') || today);
|
||||
const [viewMode, setViewMode] = useState<TurnsViewMode>('timeline');
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useQuery } from '@tanstack/react-query';
|
||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import { ArrowPathIcon } from '@heroicons/react/24/outline';
|
||||
import { api } from '../lib/api';
|
||||
import { formatRial, formatNumber } from '../lib/utils';
|
||||
import { formatRial, formatNumber, toGregorianDate, todayIso } from '../lib/utils';
|
||||
import type { ClaimPatientRow } from '../types';
|
||||
import type { ApiResponse, PaginatedResponse } from '../lib/api';
|
||||
import PageHeader from '../components/ui/PageHeader';
|
||||
@@ -35,9 +35,8 @@ const PAYMENT_OPTIONS = [
|
||||
const isoNDaysAgo = (days: number): string => {
|
||||
const d = new Date();
|
||||
d.setDate(d.getDate() - days);
|
||||
return d.toISOString().slice(0, 10);
|
||||
return toGregorianDate(d);
|
||||
};
|
||||
const todayIso = (): string => new Date().toISOString().slice(0, 10);
|
||||
|
||||
const toUnix = (iso: string): number | null => {
|
||||
if (!iso) return null;
|
||||
|
||||
@@ -7,7 +7,7 @@ import SearchableSelect from '../components/ui/SearchableSelect';
|
||||
import StatCard from '../components/ui/StatCard';
|
||||
import StatusBadge from '../components/ui/StatusBadge';
|
||||
import DataTable, { type Column } from '../components/ui/DataTable';
|
||||
import { formatRial, formatDate, formatNumber, toDate } from '../lib/utils';
|
||||
import { formatRial, formatDate, formatNumber, toDate, toGregorianDate, todayIso } from '../lib/utils';
|
||||
import {
|
||||
usePayments,
|
||||
usePaymentsSummary,
|
||||
@@ -25,9 +25,8 @@ const STATUS_OPTIONS = [
|
||||
const isoNDaysAgo = (days: number): string => {
|
||||
const d = new Date();
|
||||
d.setDate(d.getDate() - days);
|
||||
return d.toISOString().slice(0, 10);
|
||||
return toGregorianDate(d);
|
||||
};
|
||||
const todayIso = (): string => new Date().toISOString().slice(0, 10);
|
||||
|
||||
/** HH:MM (Persian digits) from a unix timestamp. */
|
||||
function formatTime(unix: number): string {
|
||||
|
||||
@@ -13,7 +13,7 @@ import { api } from '../lib/api';
|
||||
import type { ApiResponse } from '../lib/api';
|
||||
import type { PatientRecord } from '../types';
|
||||
import { useAuthStore } from '../stores/authStore';
|
||||
import { formatDate, formatDateTime, formatRial, formatTime, formatNumber } from '../lib/utils';
|
||||
import { formatDate, formatDateTime, formatRial, formatTime, formatNumber, unixToIso } from '../lib/utils';
|
||||
import DataTable, { type Column } from '../components/ui/DataTable';
|
||||
import type { WalletTxn } from '../hooks/usePatientWallet';
|
||||
import type { WalletModalSubmit } from '../components/WalletTransactionModal';
|
||||
@@ -390,7 +390,7 @@ function MedicalRecordsTab({ uuid }: { uuid: string }) {
|
||||
|
||||
const open = (m?: MedicalItem) => {
|
||||
setTitle(m?.title ?? ''); setBody(m?.body ?? '');
|
||||
setDate(m?.recorded_at ? new Date(m.recorded_at * 1000).toISOString().slice(0, 10) : '');
|
||||
setDate(unixToIso(m?.recorded_at));
|
||||
setModal(m ?? 'create');
|
||||
};
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import type { PatientRecord } from '../types';
|
||||
import PersianDateInput from '../components/ui/PersianDateInput';
|
||||
import SearchableSelect from '../components/ui/SearchableSelect';
|
||||
import { numericField } from '../lib/forms';
|
||||
import { iranNationalCodeSchema, iranMobileSchema } from '../lib/utils';
|
||||
import { iranNationalCodeSchema, iranMobileSchema, unixToIso } from '../lib/utils';
|
||||
|
||||
const REFERRAL_OPTIONS = ['اینستاگرام', 'معرفی دوستان و آشنایان', 'جستجوی اینترنتی', 'تابلو مطب', 'سایر'];
|
||||
|
||||
@@ -29,7 +29,7 @@ const schema = z.object({
|
||||
type Form = z.infer<typeof schema>;
|
||||
|
||||
const toEpoch = (iso?: string) => (iso ? Math.floor(new Date(iso).getTime() / 1000) : null);
|
||||
const fromEpoch = (ts?: number | null) => (ts ? new Date(ts * 1000).toISOString().slice(0, 10) : '');
|
||||
const fromEpoch = (ts?: number | null) => unixToIso(ts);
|
||||
|
||||
/** تشکیل/ویرایش پرونده — patient record create & edit form (Figma "تشکیل پرونده"). */
|
||||
export default function PatientRecordFormPage() {
|
||||
|
||||
@@ -114,7 +114,7 @@ export default function PaymentDetailPage() {
|
||||
{payment.refunds.map((r, i) => (
|
||||
<InfoRow
|
||||
key={i}
|
||||
label={formatDateTime(new Date(r.at * 1000).toISOString())}
|
||||
label={formatDateTime(r.at)}
|
||||
value={
|
||||
<span>
|
||||
{formatRial(r.amount)}
|
||||
|
||||
Reference in New Issue
Block a user