mirror of
https://git.meshkee.com/Meshkee/dashboards.git
synced 2026-08-11 22:30:58 +04:30
Add business modules column and selectable home charts.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
a48a24e6f2
commit
0a2358b0c2
@@ -55,9 +55,22 @@ import {
|
||||
getBusinessSettings,
|
||||
updateBusinessBranding,
|
||||
updateBusinessDefaultLocale,
|
||||
updateBusinessModules,
|
||||
updateBusinessPrimaryColor,
|
||||
type DashboardLocale,
|
||||
} from '../services/businessSettingsService'
|
||||
import {
|
||||
BUSINESS_MODULE_IDS,
|
||||
BUSINESS_MODULE_LABELS,
|
||||
DEFAULT_ENABLED_BUSINESS_MODULES,
|
||||
DEFAULT_HOME_CHARTS,
|
||||
HOME_CHART_IDS,
|
||||
HOME_CHART_LABELS,
|
||||
normalizeEnabledBusinessModules,
|
||||
normalizeHomeCharts,
|
||||
type BusinessModuleId,
|
||||
type HomeChartId,
|
||||
} from '../utils/businessModules'
|
||||
import { flattenBusinessCategories } from '../utils/categories'
|
||||
import { Pagination } from '@meshkee/dashboard-ui'
|
||||
import pageStyles from '../components/PageContent.module.css'
|
||||
@@ -70,6 +83,15 @@ function normalizeDefaultLocale(value: unknown): DashboardLocale {
|
||||
return value === 'en' || value === 'fa' ? value : DEFAULT_LOCALE
|
||||
}
|
||||
|
||||
function emptyModuleSelection(
|
||||
enabled: BusinessModuleId[] = DEFAULT_ENABLED_BUSINESS_MODULES,
|
||||
): Record<BusinessModuleId, boolean> {
|
||||
const selected = new Set(enabled)
|
||||
return Object.fromEntries(
|
||||
BUSINESS_MODULE_IDS.map((id) => [id, selected.has(id)]),
|
||||
) as Record<BusinessModuleId, boolean>
|
||||
}
|
||||
|
||||
function formatDate(value: string) {
|
||||
const d = new Date(value)
|
||||
if (Number.isNaN(d.getTime())) return value
|
||||
@@ -227,6 +249,17 @@ export function BusinessesPage() {
|
||||
const [savingColorId, setSavingColorId] = useState<string | null>(null)
|
||||
const [savingLocaleId, setSavingLocaleId] = useState<string | null>(null)
|
||||
|
||||
const [modulesOpen, setModulesOpen] = useState(false)
|
||||
const [modulesBusiness, setModulesBusiness] = useState<BusinessListItem | null>(null)
|
||||
const [modulesSelected, setModulesSelected] = useState<Record<BusinessModuleId, boolean>>(
|
||||
() => emptyModuleSelection(),
|
||||
)
|
||||
const [modulesCharts, setModulesCharts] = useState<[HomeChartId, HomeChartId]>([
|
||||
...DEFAULT_HOME_CHARTS,
|
||||
])
|
||||
const [modulesSubmitting, setModulesSubmitting] = useState(false)
|
||||
const [modulesError, setModulesError] = useState('')
|
||||
|
||||
const [migrateOpen, setMigrateOpen] = useState(false)
|
||||
const [migrateBusiness, setMigrateBusiness] = useState<BusinessListItem | null>(null)
|
||||
const [migrateOldId, setMigrateOldId] = useState('')
|
||||
@@ -465,6 +498,62 @@ export function BusinessesPage() {
|
||||
}
|
||||
}
|
||||
|
||||
function openModules(b: BusinessListItem) {
|
||||
setModulesBusiness(b)
|
||||
setModulesSelected(
|
||||
emptyModuleSelection(
|
||||
normalizeEnabledBusinessModules(b.enabledModules ?? DEFAULT_ENABLED_BUSINESS_MODULES),
|
||||
),
|
||||
)
|
||||
setModulesCharts(normalizeHomeCharts(b.homeCharts ?? DEFAULT_HOME_CHARTS))
|
||||
setModulesError('')
|
||||
setModulesOpen(true)
|
||||
}
|
||||
|
||||
function closeModules() {
|
||||
if (modulesSubmitting) return
|
||||
setModulesOpen(false)
|
||||
setModulesBusiness(null)
|
||||
setModulesError('')
|
||||
}
|
||||
|
||||
async function submitModules() {
|
||||
if (!modulesBusiness) return
|
||||
const enabled = BUSINESS_MODULE_IDS.filter((id) => modulesSelected[id])
|
||||
const charts = normalizeHomeCharts(modulesCharts)
|
||||
setModulesSubmitting(true)
|
||||
setModulesError('')
|
||||
try {
|
||||
await updateBusinessModules(modulesBusiness.id, enabled, charts)
|
||||
setData((prev) =>
|
||||
prev
|
||||
? {
|
||||
...prev,
|
||||
items: prev.items.map((item) =>
|
||||
item.id === modulesBusiness.id
|
||||
? {
|
||||
...item,
|
||||
enabledModules: enabled,
|
||||
moduleCount: enabled.length,
|
||||
homeCharts: charts,
|
||||
}
|
||||
: item,
|
||||
),
|
||||
}
|
||||
: prev,
|
||||
)
|
||||
showToast('Modules updated.', 'success')
|
||||
setModulesOpen(false)
|
||||
setModulesBusiness(null)
|
||||
} catch (err) {
|
||||
setModulesError(
|
||||
err instanceof ApiError ? err.message : 'Unable to update modules.',
|
||||
)
|
||||
} finally {
|
||||
setModulesSubmitting(false)
|
||||
}
|
||||
}
|
||||
|
||||
async function submitEdit() {
|
||||
if (!editBusiness) return
|
||||
setEditSubmitting(true)
|
||||
@@ -903,13 +992,14 @@ export function BusinessesPage() {
|
||||
<th className={styles.th}>Owner</th>
|
||||
<th className={`${styles.th} ${styles.thTheme}`}>Theme</th>
|
||||
<th className={`${styles.th} ${styles.thLocale}`}>Lang</th>
|
||||
<th className={`${styles.th} ${styles.thModules}`}>Modules</th>
|
||||
<th className={`${styles.th} ${styles.thActions}`}>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{loading && (
|
||||
<tr>
|
||||
<td className={styles.td} colSpan={7}>
|
||||
<td className={styles.td} colSpan={8}>
|
||||
Loading...
|
||||
</td>
|
||||
</tr>
|
||||
@@ -917,7 +1007,7 @@ export function BusinessesPage() {
|
||||
|
||||
{!loading && data?.items?.length === 0 && (
|
||||
<tr>
|
||||
<td className={styles.td} colSpan={7}>
|
||||
<td className={styles.td} colSpan={8}>
|
||||
No results found.
|
||||
</td>
|
||||
</tr>
|
||||
@@ -992,6 +1082,21 @@ export function BusinessesPage() {
|
||||
<option value="en">EN</option>
|
||||
</select>
|
||||
</td>
|
||||
<td className={`${styles.td} ${styles.tdModules}`}>
|
||||
<button
|
||||
type="button"
|
||||
className={styles.modulesCountBtn}
|
||||
onClick={() => openModules(b)}
|
||||
title="Edit modules"
|
||||
aria-label={`Edit modules for ${b.name}`}
|
||||
>
|
||||
{typeof b.moduleCount === 'number'
|
||||
? b.moduleCount
|
||||
: normalizeEnabledBusinessModules(
|
||||
b.enabledModules ?? DEFAULT_ENABLED_BUSINESS_MODULES,
|
||||
).length}
|
||||
</button>
|
||||
</td>
|
||||
<td className={`${styles.td} ${styles.tdActions}`}>
|
||||
<div className={styles.rowActions}>
|
||||
<span className={styles.toggleInActions}>
|
||||
@@ -1212,6 +1317,104 @@ export function BusinessesPage() {
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
open={modulesOpen}
|
||||
title={
|
||||
modulesBusiness
|
||||
? `Modules — ${modulesBusiness.name}`
|
||||
: 'Modules'
|
||||
}
|
||||
onClose={closeModules}
|
||||
>
|
||||
{modulesError ? (
|
||||
<p className={styles.alertError} role="alert">
|
||||
{modulesError}
|
||||
</p>
|
||||
) : null}
|
||||
<p className={styles.helperText}>
|
||||
Choose which optional modules this business can use. Customers, website,
|
||||
and other general sections stay available for every business.
|
||||
</p>
|
||||
<div className={styles.modulesChecks}>
|
||||
{BUSINESS_MODULE_IDS.map((id) => (
|
||||
<label key={id} className={styles.entityCheck}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={modulesSelected[id]}
|
||||
disabled={modulesSubmitting}
|
||||
onChange={(e) =>
|
||||
setModulesSelected((prev) => ({
|
||||
...prev,
|
||||
[id]: e.target.checked,
|
||||
}))
|
||||
}
|
||||
/>
|
||||
<span>{BUSINESS_MODULE_LABELS[id]}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
<div className={styles.modulesCharts}>
|
||||
<p className={styles.entityGroupLabel}>Home charts</p>
|
||||
<div className={styles.field}>
|
||||
<label htmlFor="modules-chart-1">Chart 1</label>
|
||||
<select
|
||||
id="modules-chart-1"
|
||||
value={modulesCharts[0]}
|
||||
disabled={modulesSubmitting}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value
|
||||
if (!HOME_CHART_IDS.includes(value as HomeChartId)) return
|
||||
setModulesCharts([value as HomeChartId, modulesCharts[1]])
|
||||
}}
|
||||
>
|
||||
{HOME_CHART_IDS.map((id) => (
|
||||
<option key={id} value={id}>
|
||||
{HOME_CHART_LABELS[id]}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className={styles.field}>
|
||||
<label htmlFor="modules-chart-2">Chart 2</label>
|
||||
<select
|
||||
id="modules-chart-2"
|
||||
value={modulesCharts[1]}
|
||||
disabled={modulesSubmitting}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value
|
||||
if (!HOME_CHART_IDS.includes(value as HomeChartId)) return
|
||||
setModulesCharts([modulesCharts[0], value as HomeChartId])
|
||||
}}
|
||||
>
|
||||
{HOME_CHART_IDS.map((id) => (
|
||||
<option key={id} value={id}>
|
||||
{HOME_CHART_LABELS[id]}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ height: 12 }} />
|
||||
<div className={styles.actionsRow}>
|
||||
<button
|
||||
type="button"
|
||||
className={`${styles.btn} ${styles.btnGhost}`}
|
||||
onClick={closeModules}
|
||||
disabled={modulesSubmitting}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`${styles.btn} ${styles.btnPrimary}`}
|
||||
onClick={() => void submitModules()}
|
||||
disabled={modulesSubmitting}
|
||||
>
|
||||
{modulesSubmitting ? 'Saving…' : 'Save modules'}
|
||||
</button>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
open={migrateOpen}
|
||||
title={
|
||||
|
||||
Reference in New Issue
Block a user