Add business workshop management UI with Jalali scheduling and shared dashboard polish.

Includes workshop list/edit/details, registrations modal, category tree, shared EmptyState/RichTextEditor/toasts, and fixes for the Jalali date picker and thumbnail field layout.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-08-22 12:22:27 +03:30
co-authored by Cursor
parent 89269dff04
commit d5aca8bea7
76 changed files with 4719 additions and 974 deletions
@@ -0,0 +1,6 @@
export {
EmptyState,
EmptyStateEmphasis,
EmptyTableRow,
type EmptyStateProps,
} from '@meshkee/dashboard-ui'
@@ -2,6 +2,7 @@ import { useEffect, useState } from 'react'
import { createPortal } from 'react-dom'
import { X } from 'lucide-react'
import { useT } from '../i18n/useT'
import { EmptyState } from './EmptyState'
import type { Order } from '../services/orderService'
import { formatCellForDisplay } from '../lib/cellNumber'
import { formatIrtPrice } from '../utils/irtPrice'
@@ -106,7 +107,7 @@ export function OrderItemsModal({ open, order, onClose }: OrderItemsModalProps)
</div>
{order.items.length === 0 ? (
<p className={styles.empty}>{t('orderItems.empty')}</p>
<EmptyState layout="inline" message={t('orderItems.empty')} />
) : (
<div className={styles.tableBlock}>
<div className={styles.tableHead}>
@@ -2,7 +2,7 @@
position: fixed;
bottom: 24px;
left: 24px;
z-index: 300;
z-index: var(--toast-z-index, 11000);
display: flex;
flex-direction: column;
align-items: flex-start;
@@ -19,12 +19,12 @@
font-weight: 500;
line-height: 1.4;
color: var(--text-primary);
background: var(--elevated-surface);
background: var(--toast-bg, rgba(255, 255, 255, 0.94));
backdrop-filter: blur(var(--blur-glass));
-webkit-backdrop-filter: blur(var(--blur-glass));
border: 1px solid var(--glass-border);
border-radius: var(--radius-sm);
box-shadow: var(--glass-shadow);
box-shadow: var(--toast-shadow, 0 12px 40px rgba(15, 23, 42, 0.14));
}
.toastIn {
+2
View File
@@ -27,6 +27,7 @@
--surface: rgba(255, 255, 255, 0.82);
--card-media-bg: #ffffff;
--elevated-surface: rgba(255, 255, 255, 0.96);
--toast-bg: rgba(255, 255, 255, 0.94);
--icon-bg: color-mix(in srgb, var(--primary) 14%, #ffffff);
--icon-bg-end: color-mix(in srgb, var(--primary) 8%, #f1f5f9);
}
@@ -55,6 +56,7 @@ html[data-theme='dark'] {
--surface: rgba(55, 59, 68, 0.95);
--card-media-bg: #32363f;
--elevated-surface: #3a3e48;
--toast-bg: rgba(58, 62, 72, 0.96);
--card-hover-shadow: 0 16px 48px rgba(0, 0, 0, 0.45);
+7 -4
View File
@@ -3,6 +3,7 @@ import { useCallback, useEffect, useState } from 'react'
import { MapPin, Pencil, Plus, Trash2 } from 'lucide-react'
import { Breadcrumbs, useToast } from '@meshkee/dashboard-ui'
import { AddressFormModal } from '../components/AddressFormModal'
import { EmptyState } from '../components/EmptyState'
import { Tooltip } from '../components/Tooltip'
import { useT } from '../i18n/useT'
import { ApiError, isAbortError } from '../lib/api'
@@ -125,10 +126,12 @@ export function AddressesPage() {
{loading && <p className={styles.status}>{t('addresses.loading')}</p>}
{!loading && addresses.length === 0 && (
<div className={styles.empty}>
<MapPin size={28} />
<p>{t('addresses.empty')}</p>
</div>
<EmptyState
icon={MapPin}
message={t('addresses.empty')}
actionLabel={t('addresses.add')}
onAction={openCreateModal}
/>
)}
{!loading && addresses.length > 0 && (
+2 -4
View File
@@ -2,6 +2,7 @@ import { PageTitle } from '../components/PageTitle'
import { useEffect, useState } from 'react'
import { Heart } from 'lucide-react'
import { Breadcrumbs, Pagination, useToast } from '@meshkee/dashboard-ui'
import { EmptyState } from '../components/EmptyState'
import { FavoriteStoreItemCard } from '../components/FavoriteStoreItemCard'
import { useT } from '../i18n/useT'
import { ApiError, isAbortError } from '../lib/api'
@@ -97,10 +98,7 @@ export function FavoritesPage() {
{loading && <p className={styles.status}>{t('favorites.loading')}</p>}
{!loading && data?.items.length === 0 && (
<div className={styles.empty}>
<Heart size={32} />
<p>{t('favorites.empty')}</p>
</div>
<EmptyState icon={Heart} message={t('favorites.empty')} layout="page" />
)}
{!!data?.items.length && (
+2 -2
View File
@@ -71,10 +71,10 @@
}
.domain {
font-size: 20px;
font-size: 17px;
font-weight: 700;
color: var(--text-primary);
line-height: 1.25;
line-height: 1.3;
}
.appName {
+7 -7
View File
@@ -3,6 +3,7 @@ import { useEffect, useState } from 'react'
import { Link, useNavigate } from 'react-router-dom'
import { Package, Plus } from 'lucide-react'
import { Breadcrumbs, Pagination, useToast } from '@meshkee/dashboard-ui'
import { EmptyState } from '../components/EmptyState'
import { UserProductCard } from '../components/UserProductCard'
import { useT } from '../i18n/useT'
import { ApiError, isAbortError } from '../lib/api'
@@ -141,13 +142,12 @@ export function MyProductsPage() {
{loading ? <p className={styles.status}>{t('myProducts.loading')}</p> : null}
{!loading && !error && products.length === 0 ? (
<div className={styles.empty}>
<Package size={32} />
<p>{t('myProducts.empty')}</p>
<Link to="/my-products/new" className={styles.emptyLink}>
{t('myProducts.add')}
</Link>
</div>
<EmptyState
icon={Package}
message={t('myProducts.empty')}
actionLabel={t('myProducts.add')}
actionTo="/my-products/new"
/>
) : null}
{!loading && products.length > 0 ? (
+2 -5
View File
@@ -1,6 +1,7 @@
import { PageTitle } from '../components/PageTitle'
import { useEffect, useMemo, useState } from 'react'
import { Breadcrumbs, Pagination, useLocale } from '@meshkee/dashboard-ui'
import { EmptyTableRow } from '../components/EmptyState'
import { OrderItemsModal } from '../components/OrderItemsModal'
import { OrderRow } from '../components/OrderRow'
import { useT } from '../i18n/useT'
@@ -131,11 +132,7 @@ export function OrdersPage() {
)}
{!loading && data?.items.length === 0 && (
<tr>
<td className={styles.td} colSpan={COLUMN_COUNT}>
{t('orders.empty')}
</td>
</tr>
<EmptyTableRow colSpan={COLUMN_COUNT} message={t('orders.empty')} />
)}
{!loading &&