improvement: rtl direction, solar calendar and persian formatting added for persian users.

This commit is contained in:
2026-07-14 01:27:11 +03:30
parent 27eae25f61
commit ec2b23f4b1
50 changed files with 1410 additions and 528 deletions

View File

@@ -1,6 +1,6 @@
'use client';
import { useTranslations } from 'next-intl';
import { useLocale, useTranslations } from 'next-intl';
import { DialogCloseButton } from '@/components/ui/shared/DialogCloseButton';
import { Card } from '@/components/ui/shared/Card';
import {
@@ -12,12 +12,7 @@ import { CopyInvitationLinkButton } from '@/components/ui/organizations/CopyInvi
import type { OrganizationInvitationHistoryItemDto } from '@/lib/api/organization';
import { Badge } from '@/components/ui/shared/Badge';
import { organizationConnectionStatusVariant } from '@/components/organizations/connectionStatusVariant';
function formatTableDate(value: string): string {
const d = new Date(value);
if (Number.isNaN(d.getTime())) return '—';
return d.toLocaleDateString();
}
import { formatAppTableDate } from '@/lib/i18n/format';
type InvitationHistoryDialogProps = {
open: boolean;
@@ -38,6 +33,7 @@ export function InvitationHistoryDialog({
copyingInvitationId,
onCopy,
}: InvitationHistoryDialogProps) {
const locale = useLocale();
const t = useTranslations('organizations');
function formatInvitationStatusLabel(
@@ -82,7 +78,7 @@ export function InvitationHistoryDialog({
<div className="min-w-0">
<p className="font-medium text-text-primary truncate">{inv.organizationName}</p>
<p className="text-sm text-text-secondary truncate mt-0.5">{inv.ownerEmail}</p>
<p className="text-xs text-text-muted mt-1">{formatTableDate(inv.createdAt)}</p>
<p className="text-xs text-text-muted mt-1">{formatAppTableDate(inv.createdAt, locale)}</p>
</div>
<Badge variant={organizationConnectionStatusVariant(inv.status)} fixedWidth={false}>
{formatInvitationStatusLabel(inv.status)}
@@ -105,19 +101,19 @@ export function InvitationHistoryDialog({
<Table
headers={
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-text-muted uppercase tracking-wider">
<th className="text-xs font-medium text-text-muted uppercase tracking-wider">
{t('tableOrganization')}
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-text-muted uppercase tracking-wider">
<th className="text-xs font-medium text-text-muted uppercase tracking-wider">
{t('tableOwnerEmail')}
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-text-muted uppercase tracking-wider">
<th className="text-xs font-medium text-text-muted uppercase tracking-wider">
{t('tableDate')}
</th>
<th className="px-6 py-3 text-center text-xs font-medium text-text-muted uppercase tracking-wider">
<th className="text-center text-xs font-medium text-text-muted uppercase tracking-wider">
{t('tableStatus')}
</th>
<th className="px-6 py-3 text-right text-xs font-medium text-text-muted uppercase tracking-wider">
<th className="text-end text-xs font-medium text-text-muted uppercase tracking-wider">
{t('tableInvitationLink')}
</th>
</tr>
@@ -126,17 +122,17 @@ export function InvitationHistoryDialog({
<>
{items.map((inv) => (
<tr key={inv.id} className="hover:bg-background-secondary/45">
<td className="px-6 py-1.5 text-sm text-text-primary">{inv.organizationName}</td>
<td className="px-6 py-1.5 text-sm text-text-secondary">{inv.ownerEmail}</td>
<td className="px-6 py-1.5 text-sm text-text-secondary">
{formatTableDate(inv.createdAt)}
<td className="text-sm text-text-primary">{inv.organizationName}</td>
<td className="text-sm text-text-secondary">{inv.ownerEmail}</td>
<td className="text-sm text-text-secondary">
{formatAppTableDate(inv.createdAt, locale)}
</td>
<td className="px-6 py-1.5 text-center align-middle">
<td className="text-center align-middle">
<Badge variant={organizationConnectionStatusVariant(inv.status)} fixedWidth={false}>
{formatInvitationStatusLabel(inv.status)}
</Badge>
</td>
<td className="px-6 py-1.5 text-right align-middle">
<td className="text-end align-middle">
<CopyInvitationLinkButton
invitation={inv}
copied={copiedId === inv.id}

View File

@@ -2,7 +2,7 @@
import { useCallback, useEffect, useState } from 'react';
import { useSearchParams } from 'next/navigation';
import { useTranslations } from 'next-intl';
import { useLocale, useTranslations } from 'next-intl';
import { useToast } from '@/lib/hooks/useToast';
import { Check, Trash2, UserPlus, X } from 'lucide-react';
import { useAuth } from '@/lib/hooks/useAuth';
@@ -26,6 +26,7 @@ import { SearchBar } from '@/components/ui/shared/SearchBar';
import { Table } from '@/components/ui/shared/Table';
import { getUserFacingError } from '@/components/shared/formatApiError';
import { useRouter } from '@/i18n/navigation';
import { formatAppTableDate } from '@/lib/i18n/format';
function formatOrganizationStatusLabel(status: string): string {
if (!status) return status;
@@ -33,15 +34,10 @@ function formatOrganizationStatusLabel(status: string): string {
return lower.charAt(0).toUpperCase() + lower.slice(1);
}
function formatTableDate(value: string): string {
const d = new Date(value);
if (Number.isNaN(d.getTime())) return '\u2014';
return d.toLocaleDateString();
}
type TableMode = 'existing' | 'search';
export function OrganizationsPage() {
const locale = useLocale();
const t = useTranslations('organizations');
const tErrors = useTranslations('errors');
const tNav = useTranslations('nav');
@@ -58,6 +54,11 @@ export function OrganizationsPage() {
[tCommon, tErrors],
);
const formatTableDate = useCallback(
(value: string) => formatAppTableDate(value, locale),
[locale],
);
const formatConnectionStatusLabel = useCallback(
(row: CounterpartItemDto, currentOrganizationId: string): string => {
if (row.status === 'PENDING') {
@@ -389,19 +390,19 @@ export function OrganizationsPage() {
<Table
headers={
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-text-muted uppercase tracking-wider">
<th className="text-xs font-medium text-text-muted uppercase tracking-wider">
{t('tableOrganization')}
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-text-muted uppercase tracking-wider">
<th className="text-xs font-medium text-text-muted uppercase tracking-wider">
{t('tableOwnerEmail')}
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-text-muted uppercase tracking-wider">
<th className="text-xs font-medium text-text-muted uppercase tracking-wider">
{t('tableDate')}
</th>
<th className="px-6 py-3 text-center text-xs font-medium text-text-muted uppercase tracking-wider">
<th className="text-center text-xs font-medium text-text-muted uppercase tracking-wider">
{t('tableStatus')}
</th>
<th className="px-6 py-3 text-right text-xs font-medium text-text-muted uppercase tracking-wider">
<th className="text-end text-xs font-medium text-text-muted uppercase tracking-wider">
{t('tableAction')}
</th>
</tr>
@@ -410,14 +411,14 @@ export function OrganizationsPage() {
<>
{loading || (mode === 'search' && searching) ? (
<tr>
<td colSpan={5} className="px-6 py-8 text-sm text-text-secondary">
<td colSpan={5} className="py-8 text-sm text-text-secondary">
{tCommon('loadingEllipsis')}
</td>
</tr>
) : mode === 'existing' ? (
existingRows.length === 0 ? (
<tr>
<td colSpan={5} className="px-6 py-8 text-sm text-text-secondary">
<td colSpan={5} className="py-8 text-sm text-text-secondary">
{t('emptyConnections')}
</td>
</tr>
@@ -434,19 +435,19 @@ export function OrganizationsPage() {
return (
<tr key={row.id} className="hover:bg-background-secondary/45">
<td className="px-6 py-1.5 text-sm font-medium text-text-primary">
<td className="text-sm font-medium text-text-primary">
{row.organizationName}
</td>
<td className="px-6 py-1.5 text-sm text-text-secondary">{row.ownerEmail}</td>
<td className="px-6 py-1.5 text-sm text-text-secondary">
<td className="text-sm text-text-secondary">{row.ownerEmail}</td>
<td className="text-sm text-text-secondary">
{formatTableDate(row.createdAt)}
</td>
<td className="px-6 py-1.5 text-center align-middle">
<td className="text-center align-middle">
<Badge variant={organizationConnectionStatusVariant(row.status)} fixedWidth={false}>
{formatConnectionStatusLabel(row, currentOrganization.id)}
</Badge>
</td>
<td className="px-6 py-1.5 text-right">
<td className="text-end">
<div className="inline-flex items-center gap-2">
{invitationTarget && (
<CopyInvitationLinkButton
@@ -503,13 +504,13 @@ export function OrganizationsPage() {
) : searchResults.length > 0 ? (
searchResults.map((r) => (
<tr key={r.id} className="hover:bg-background-secondary/45">
<td className="px-6 py-1.5 text-sm font-medium text-text-primary">{r.name}</td>
<td className="px-6 py-1.5 text-sm text-text-secondary">{r.owner.email}</td>
<td className="px-6 py-1.5 text-sm text-text-secondary">{t('statusToday')}</td>
<td className="px-6 py-1.5 text-center align-middle">
<td className="text-sm font-medium text-text-primary">{r.name}</td>
<td className="text-sm text-text-secondary">{r.owner.email}</td>
<td className="text-sm text-text-secondary">{t('statusToday')}</td>
<td className="text-center align-middle">
<Badge variant="default" fixedWidth={false}>{t('statusFound')}</Badge>
</td>
<td className="px-6 py-1.5 text-right">
<td className="text-end">
<button
type="button"
className="p-2 rounded-md text-text-secondary hover:bg-background-card/80 hover:text-text-primary disabled:text-text-muted disabled:opacity-50"
@@ -527,7 +528,7 @@ export function OrganizationsPage() {
))
) : (
<tr>
<td colSpan={5} className="px-6 py-6">
<td colSpan={5} className="py-6">
<div className="flex flex-col gap-3">
<p className="text-sm text-text-secondary">
{t('noDirectoryResults')}