improvement: rtl direction, solar calendar and persian formatting added for persian users.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { useLocale, useTranslations } from 'next-intl';
|
||||
import { CalendarDays } from 'lucide-react';
|
||||
import { Card } from '@/components/ui/shared/Card';
|
||||
import { ScheduleDayPicker } from '@/components/ui/shared/ScheduleDayPicker';
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
} from '@/components/shared/treatmentTypeDisplay';
|
||||
import type { TreatmentCatalogEntry } from '@/types/treatment-catalog';
|
||||
import type { TreatmentAppointment } from '@/types/treatment';
|
||||
import { APP_DATE, formatAppTimeRange } from '@/lib/i18n/format';
|
||||
|
||||
interface AppointmentsStripProps {
|
||||
stripHidden: boolean;
|
||||
@@ -35,6 +36,7 @@ export function AppointmentsStrip({
|
||||
treatmentCatalog,
|
||||
loading = false,
|
||||
}: AppointmentsStripProps) {
|
||||
const locale = useLocale();
|
||||
const t = useTranslations('treatment');
|
||||
|
||||
if (stripHidden) {
|
||||
@@ -84,15 +86,7 @@ export function AppointmentsStrip({
|
||||
)}
|
||||
{appointments.map((a) => {
|
||||
const sel = a.id === selectedAppointmentId;
|
||||
const start = new Date(a.startAt);
|
||||
const end = new Date(a.endAt);
|
||||
const timeLabel = `${start.toLocaleTimeString(undefined, {
|
||||
hour: 'numeric',
|
||||
minute: '2-digit',
|
||||
})} – ${end.toLocaleTimeString(undefined, {
|
||||
hour: 'numeric',
|
||||
minute: '2-digit',
|
||||
})}`;
|
||||
const timeLabel = formatAppTimeRange(a.startAt, a.endAt, locale);
|
||||
const purposeLabel = treatmentTypeLabelFromCatalog(a.purpose, treatmentCatalog);
|
||||
const purposeIndex = treatmentCatalog.findIndex((e) => e.code === a.purpose);
|
||||
return (
|
||||
@@ -104,7 +98,7 @@ export function AppointmentsStrip({
|
||||
padding="none"
|
||||
style={treatmentTypeBannerStyle(a.purpose, purposeIndex < 0 ? 0 : purposeIndex)}
|
||||
className={`
|
||||
text-left rounded-[var(--radius-sm)] px-3 py-2 w-full sm:w-auto sm:min-w-[200px] sm:max-w-[280px] transition-shadow min-h-[52px]
|
||||
text-start rounded-[var(--radius-sm)] px-3 py-2 w-full sm:w-auto sm:min-w-[200px] sm:max-w-[280px] transition-shadow min-h-[52px]
|
||||
focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/45
|
||||
${sel ? 'ring-2 ring-primary ring-offset-2 ring-offset-background-secondary shadow-[inset_0_1px_0_rgba(255,255,255,0.06)]' : 'hover:brightness-110'}
|
||||
`}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { useLocale, useTranslations } from 'next-intl';
|
||||
import { formatCaseSentLines } from '@/components/treatment/caseSendLabel';
|
||||
import type { LabCaseSendInfo, LinkedOrganizationOption } from '@/types/treatment';
|
||||
|
||||
@@ -16,6 +16,7 @@ interface CaseSentLabelProps {
|
||||
}
|
||||
|
||||
export function CaseSentLabel({ treatmentCase, orgs, className = 'text-xs text-text-muted' }: CaseSentLabelProps) {
|
||||
const locale = useLocale();
|
||||
const t = useTranslations('treatment');
|
||||
const organizationIds =
|
||||
treatmentCase.sendToOrganizationIds ??
|
||||
@@ -24,7 +25,7 @@ export function CaseSentLabel({ treatmentCase, orgs, className = 'text-xs text-t
|
||||
organizationIds,
|
||||
sentAt: treatmentCase.sentAt ?? null,
|
||||
orgs,
|
||||
}, t);
|
||||
}, t, locale);
|
||||
|
||||
if (lines.length === 0) return null;
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useTranslations } from 'next-intl';
|
||||
import { Button } from '@/components/ui/shared/Button';
|
||||
import { Checkbox } from '@/components/ui/shared/Checkbox';
|
||||
import { isDetailReadyForLabDispatch, isLabCaseCompleted } from '@/components/treatment/treatmentDetailRules';
|
||||
import { AppDateInput } from '@/components/ui/shared/AppDateInput';
|
||||
import { toDateInputValue } from '@/components/lab/labCaseDueDateDisplay';
|
||||
import { FORM_SELECT_CLASS } from '@/components/shared/formSelectStyles';
|
||||
import { LinkedOrganizationSearchCombobox } from '@/components/ui/treatment/LinkedOrganizationSearchCombobox';
|
||||
@@ -283,20 +284,17 @@ export function LabCasesDispatchPanel({
|
||||
{t('dueDateLabel')}{' '}
|
||||
<span className="font-normal text-text-muted">({t('dueDateOptional')})</span>
|
||||
</label>
|
||||
<input
|
||||
<AppDateInput
|
||||
id={dueDateInputId}
|
||||
type="date"
|
||||
value={inputValue}
|
||||
disabled={!canEditDueDate}
|
||||
onChange={(e) => {
|
||||
if (!sent) {
|
||||
updateActiveLabCase({ dueDate: e.target.value || null });
|
||||
}
|
||||
onChange={(next) => {
|
||||
updateActiveLabCase({ dueDate: next || null });
|
||||
}}
|
||||
onBlur={(e) => {
|
||||
if (sent) void handleSentDueDateBlur(e.target.value);
|
||||
onBlur={(committed) => {
|
||||
if (sent) void handleSentDueDateBlur(committed);
|
||||
}}
|
||||
className={`${FORM_SELECT_CLASS} w-full max-w-[11rem] rounded-md px-2 py-1.5 text-sm`}
|
||||
className={`${FORM_SELECT_CLASS} w-full max-w-[11rem] rounded-md py-1.5 text-sm`}
|
||||
/>
|
||||
</div>
|
||||
{sent && caseFullyComplete && activeLabCase.dueDate ? (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { useLocale, useTranslations } from 'next-intl';
|
||||
import { AlertTriangle } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/shared/Button';
|
||||
import { DetailLabSendBadge } from '@/components/ui/treatment/DetailLabSendBadge';
|
||||
@@ -9,6 +9,7 @@ import { treatmentTypeLabelFromCatalog } from '@/components/shared/treatmentType
|
||||
import type { LabDispatchAttentionItem } from '@/components/treatment/labDispatchAttention';
|
||||
import type { LinkedOrganizationOption } from '@/types/treatment';
|
||||
import type { TreatmentCatalogEntry } from '@/types/treatment-catalog';
|
||||
import { APP_DATE, formatAppDate } from '@/lib/i18n/format';
|
||||
|
||||
interface LabDispatchAttentionPanelProps {
|
||||
items: LabDispatchAttentionItem[];
|
||||
@@ -27,6 +28,7 @@ export function LabDispatchAttentionPanel({
|
||||
onGoToDispatch,
|
||||
compact = false,
|
||||
}: LabDispatchAttentionPanelProps) {
|
||||
const locale = useLocale();
|
||||
const t = useTranslations('treatment');
|
||||
|
||||
if (items.length === 0) {
|
||||
@@ -54,11 +56,7 @@ export function LabDispatchAttentionPanel({
|
||||
const teeth = item.detail.teeth.length
|
||||
? [...item.detail.teeth].sort().join(', ')
|
||||
: t('teethNone');
|
||||
const dateLabel = new Date(item.treatmentAt).toLocaleDateString(undefined, {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
});
|
||||
const dateLabel = formatAppDate(item.treatmentAt, locale, APP_DATE.short);
|
||||
|
||||
return (
|
||||
<li
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
'use client';
|
||||
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { useLocale, useTranslations } from 'next-intl';
|
||||
import { Button } from '@/components/ui/shared/Button';
|
||||
import { Checkbox } from '@/components/ui/shared/Checkbox';
|
||||
import { FORM_SELECT_CLASS } from '@/components/shared/formSelectStyles';
|
||||
import { TreatmentHistoryDetailLine } from '@/components/ui/treatment/TreatmentHistoryDetailLine';
|
||||
import { DetailLabSendBadge } from '@/components/ui/treatment/DetailLabSendBadge';
|
||||
import { filterTreatmentHistoryItems } from '@/components/treatment/treatmentHistoryFilters';
|
||||
import { AppDateInput } from '@/components/ui/shared/AppDateInput';
|
||||
import { APP_DATE, formatAppDateTime } from '@/lib/i18n/format';
|
||||
import type { LinkedOrganizationOption, PastTreatment } from '@/types/treatment';
|
||||
import type { TreatmentCatalogEntry } from '@/types/treatment-catalog';
|
||||
|
||||
@@ -25,16 +27,8 @@ interface PastTreatmentsPanelProps {
|
||||
compact?: boolean;
|
||||
}
|
||||
|
||||
function formatHistoryTimestamp(iso: string): string {
|
||||
const date = new Date(iso);
|
||||
return date.toLocaleString(undefined, {
|
||||
weekday: 'short',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
hour: 'numeric',
|
||||
minute: '2-digit',
|
||||
});
|
||||
function formatHistoryTimestamp(iso: string, locale: string): string {
|
||||
return formatAppDateTime(iso, locale, APP_DATE.history);
|
||||
}
|
||||
|
||||
export function PastTreatmentsPanel({
|
||||
@@ -50,6 +44,7 @@ export function PastTreatmentsPanel({
|
||||
onSelectTreatment,
|
||||
compact = false,
|
||||
}: PastTreatmentsPanelProps) {
|
||||
const locale = useLocale();
|
||||
const t = useTranslations('treatment');
|
||||
const [notShippedOnly, setNotShippedOnly] = useState(false);
|
||||
const [filterDate, setFilterDate] = useState('');
|
||||
@@ -71,7 +66,7 @@ export function PastTreatmentsPanel({
|
||||
setFilterDate('');
|
||||
}
|
||||
|
||||
const filterInputClass = `${FORM_SELECT_CLASS} rounded-md px-2 py-1.5 text-xs min-w-[9.5rem]`;
|
||||
const filterInputClass = `${FORM_SELECT_CLASS} rounded-md py-1.5 text-xs min-w-[9.5rem]`;
|
||||
|
||||
const filtersBlock = (
|
||||
<div
|
||||
@@ -89,10 +84,9 @@ export function PastTreatmentsPanel({
|
||||
<span className="text-xs font-medium text-text-muted whitespace-nowrap">
|
||||
{t('historyFilterDate')}
|
||||
</span>
|
||||
<input
|
||||
type="date"
|
||||
<AppDateInput
|
||||
value={filterDate}
|
||||
onChange={(e) => setFilterDate(e.target.value)}
|
||||
onChange={setFilterDate}
|
||||
className={filterInputClass}
|
||||
/>
|
||||
</label>
|
||||
@@ -156,7 +150,7 @@ export function PastTreatmentsPanel({
|
||||
className="text-xs font-medium text-text-primary tabular-nums"
|
||||
dateTime={treatment.treatmentAt}
|
||||
>
|
||||
{formatHistoryTimestamp(treatment.treatmentAt)}
|
||||
{formatHistoryTimestamp(treatment.treatmentAt, locale)}
|
||||
</time>
|
||||
{isLiveDraft ? (
|
||||
<span className="text-[10px] font-medium uppercase tracking-wide text-primary">
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
'use client';
|
||||
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { useLocale, useTranslations } from 'next-intl';
|
||||
import { TreatmentDetailSummaryRow } from '@/components/ui/treatment/TreatmentDetailSummaryRow';
|
||||
import { APP_DATE, formatAppDate } from '@/lib/i18n/format';
|
||||
import type { LinkedOrganizationOption, PastTreatment } from '@/types/treatment';
|
||||
import type { TreatmentCatalogEntry } from '@/types/treatment-catalog';
|
||||
|
||||
@@ -23,6 +24,7 @@ export function TreatmentPreviewCard({
|
||||
orgs,
|
||||
embedded = false,
|
||||
}: TreatmentPreviewCardProps) {
|
||||
const locale = useLocale();
|
||||
const t = useTranslations('treatment');
|
||||
|
||||
return (
|
||||
@@ -38,12 +40,7 @@ export function TreatmentPreviewCard({
|
||||
className="text-xs text-text-muted tabular-nums block"
|
||||
dateTime={treatment.treatmentAt}
|
||||
>
|
||||
{new Date(treatment.treatmentAt).toLocaleDateString(undefined, {
|
||||
weekday: 'short',
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
})}
|
||||
{formatAppDate(treatment.treatmentAt, locale, APP_DATE.withWeekday)}
|
||||
</time>
|
||||
) : null}
|
||||
<div className="space-y-2 max-h-[min(280px,40vh)] overflow-y-auto pr-1">
|
||||
|
||||
@@ -28,7 +28,7 @@ export function TreatmentRailSection({
|
||||
: 'surface-card';
|
||||
|
||||
return (
|
||||
<section className={`${shellClass} overflow-hidden`}>
|
||||
<section className={`treatment-rail-section ${shellClass} overflow-hidden`}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setExpanded((open) => !open)}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { useLocale, useTranslations } from 'next-intl';
|
||||
import { useRouter } from '@/i18n/navigation';
|
||||
import { Button } from '@/components/ui/shared/Button';
|
||||
import { Checkbox } from '@/components/ui/shared/Checkbox';
|
||||
@@ -46,6 +46,7 @@ import { scrollWithinMainScrollContainer } from '@/components/shared/scrollWithi
|
||||
import { useMarkTabReadOnVisit, useTabBadgeCounts } from '@/lib/hooks/useTabBadgeCounts';
|
||||
import { tabBadgesChangedEventName } from '@/lib/tabBadgeUtils';
|
||||
import { notifyTabBadgesChanged } from '@/lib/tabBadgeUtils';
|
||||
import { APP_DATE, formatAppDate } from '@/lib/i18n/format';
|
||||
import { useAuth } from '@/lib/hooks/useAuth';
|
||||
import { getUserFacingError } from '@/components/shared/formatApiError';
|
||||
import { useToast } from '@/lib/hooks/useToast';
|
||||
@@ -284,13 +285,13 @@ export function TreatmentWorkspace({
|
||||
currentOrganization,
|
||||
initialAppointmentId = null,
|
||||
}: TreatmentWorkspaceProps) {
|
||||
const locale = useLocale();
|
||||
const t = useTranslations('treatment');
|
||||
const tErrors = useTranslations('errors');
|
||||
const tPatients = useTranslations('patients');
|
||||
const router = useRouter();
|
||||
const { user } = useAuth();
|
||||
const { showError, showSuccess, messages: toastMessages } = useToast();
|
||||
const locale = user?.language ?? 'en';
|
||||
const canView = canViewTreatment(currentOrganization);
|
||||
const canEdit = canEditTreatment(currentOrganization);
|
||||
useMarkTabReadOnVisit();
|
||||
@@ -1571,7 +1572,7 @@ export function TreatmentWorkspace({
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-1 xl:grid-cols-[minmax(300px,380px)_minmax(0,1fr)] gap-4 items-start">
|
||||
<div className="treatment-layout-grid grid grid-cols-1 xl:grid-cols-[minmax(300px,380px)_minmax(0,1fr)] gap-4 items-start">
|
||||
<div className="space-y-3 min-w-0 xl:max-w-[380px]">
|
||||
<div className="surface-card p-3 space-y-3">
|
||||
<PatientSearchCombobox
|
||||
@@ -1619,12 +1620,7 @@ export function TreatmentWorkspace({
|
||||
<div className="rounded-[var(--radius-md)] border border-primary/40 bg-primary/5 px-3 py-2 space-y-2">
|
||||
<p className="text-xs text-text-primary">
|
||||
{t('browseBanner', {
|
||||
date: new Date(previewTreatment.treatmentAt).toLocaleDateString(undefined, {
|
||||
weekday: 'short',
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
}),
|
||||
date: formatAppDate(previewTreatment.treatmentAt, locale, APP_DATE.withWeekday),
|
||||
})}
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
|
||||
Reference in New Issue
Block a user