fix(settings): every settings page renders the settings shell
Resources, branches, price lists, holidays and the new categories page sat in the settings menu but rendered bare, so clicking one made the settings sidebar disappear — the subscription page was the only one that kept it. Eleven pages now wrap in SettingsLayout with the key of the menu entry they belong to, and the four resource pages (list, types, skills, pools) share one menu entry plus a sub-nav between them, rather than four entries that would make the menu a third longer without making anything clearer. .seg accepts `a` as well as `button`, and treats `active` as an alias of `on`. Both were needed: cross-page tabs must be real links, and the pages already using `active` (service detail, clinic appointment settings) had no visible highlight at all. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ import SearchableSelect from '../components/ui/SearchableSelect';
|
||||
import { usePermissions } from '../hooks/usePermissions';
|
||||
import { useBranchWorkingHours, useBranches } from '../hooks/useBranches';
|
||||
import type { WorkingHourRange } from '../types';
|
||||
import SettingsLayout from '../components/layout/SettingsLayout';
|
||||
|
||||
/** ۰ = شنبه — همان قرارداد محاسبهٔ اسلات در بکاند. */
|
||||
const DAY_LABELS = ['شنبه', 'یکشنبه', 'دوشنبه', 'سهشنبه', 'چهارشنبه', 'پنجشنبه', 'جمعه'];
|
||||
@@ -116,127 +117,129 @@ export default function BranchWorkingHoursPage() {
|
||||
const totalRanges = Object.values(draft).reduce((sum, ranges) => sum + ranges.length, 0);
|
||||
|
||||
return (
|
||||
<div className="fade-in">
|
||||
<PageHeader
|
||||
title={`ساعت کاری ${branch?.name ?? 'شعبه'}`}
|
||||
description="روز بدون بازه یعنی شعبه آن روز بسته است. شعبهٔ بدون هیچ ساعتی «تعریفنشده» است، نه همیشهباز."
|
||||
backTo="/admin/branches"
|
||||
breadcrumbs={[
|
||||
{ label: 'شعبهها', to: '/admin/branches' },
|
||||
{ label: 'ساعت کاری' },
|
||||
]}
|
||||
action={
|
||||
canUpdate ? (
|
||||
<button type="button" className="btn primary" disabled={save.isPending} onClick={submit}>
|
||||
{save.isPending ? 'در حال ذخیره...' : 'ذخیرهٔ هفته'}
|
||||
</button>
|
||||
) : undefined
|
||||
}
|
||||
/>
|
||||
<SettingsLayout active="branches">
|
||||
<div className="fade-in">
|
||||
<PageHeader
|
||||
title={`ساعت کاری ${branch?.name ?? 'شعبه'}`}
|
||||
description="روز بدون بازه یعنی شعبه آن روز بسته است. شعبهٔ بدون هیچ ساعتی «تعریفنشده» است، نه همیشهباز."
|
||||
backTo="/admin/branches"
|
||||
breadcrumbs={[
|
||||
{ label: 'شعبهها', to: '/admin/branches' },
|
||||
{ label: 'ساعت کاری' },
|
||||
]}
|
||||
action={
|
||||
canUpdate ? (
|
||||
<button type="button" className="btn primary" disabled={save.isPending} onClick={submit}>
|
||||
{save.isPending ? 'در حال ذخیره...' : 'ذخیرهٔ هفته'}
|
||||
</button>
|
||||
) : undefined
|
||||
}
|
||||
/>
|
||||
|
||||
{error && (
|
||||
<div
|
||||
className="card"
|
||||
style={{ padding: '12px 16px', marginBottom: 16, color: 'var(--danger)', background: 'var(--danger-bg)', fontSize: 13 }}
|
||||
>
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{branch && canUpdate && (
|
||||
<div className="card" style={{ padding: 16, marginBottom: 16, display: 'flex', alignItems: 'center', gap: 12, flexWrap: 'wrap' }}>
|
||||
<span style={{ fontSize: 13, color: 'var(--text-2)' }}>منطقهٔ زمانی شعبه</span>
|
||||
<div style={{ minWidth: 240 }}>
|
||||
<TimezoneSelect branchUuid={branch.uuid} value={branch.timezone} />
|
||||
{error && (
|
||||
<div
|
||||
className="card"
|
||||
style={{ padding: '12px 16px', marginBottom: 16, color: 'var(--danger)', background: 'var(--danger-bg)', fontSize: 13 }}
|
||||
>
|
||||
{error}
|
||||
</div>
|
||||
<span style={{ fontSize: 12, color: 'var(--text-3)' }}>
|
||||
{totalRanges === 0 ? 'هیچ بازهای تعریف نشده' : `${totalRanges} بازه در هفته`}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
|
||||
{loading ? (
|
||||
<div style={{ padding: 16, color: 'var(--text-3)', fontSize: 13 }}>در حال بارگذاری...</div>
|
||||
) : (
|
||||
<div style={{ display: 'grid', gap: 12 }}>
|
||||
{DAY_LABELS.map((label, day) => {
|
||||
const ranges = draft[day] ?? [];
|
||||
return (
|
||||
<div key={day} className="card" style={{ padding: 16 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, marginBottom: ranges.length ? 12 : 0 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
|
||||
<span style={{ fontWeight: 700, fontSize: 14 }}>{label}</span>
|
||||
{ranges.length === 0 && (
|
||||
<span style={{ fontSize: 12, color: 'var(--text-3)' }}>بسته</span>
|
||||
{branch && canUpdate && (
|
||||
<div className="card" style={{ padding: 16, marginBottom: 16, display: 'flex', alignItems: 'center', gap: 12, flexWrap: 'wrap' }}>
|
||||
<span style={{ fontSize: 13, color: 'var(--text-2)' }}>منطقهٔ زمانی شعبه</span>
|
||||
<div style={{ minWidth: 240 }}>
|
||||
<TimezoneSelect branchUuid={branch.uuid} value={branch.timezone} />
|
||||
</div>
|
||||
<span style={{ fontSize: 12, color: 'var(--text-3)' }}>
|
||||
{totalRanges === 0 ? 'هیچ بازهای تعریف نشده' : `${totalRanges} بازه در هفته`}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{loading ? (
|
||||
<div style={{ padding: 16, color: 'var(--text-3)', fontSize: 13 }}>در حال بارگذاری...</div>
|
||||
) : (
|
||||
<div style={{ display: 'grid', gap: 12 }}>
|
||||
{DAY_LABELS.map((label, day) => {
|
||||
const ranges = draft[day] ?? [];
|
||||
return (
|
||||
<div key={day} className="card" style={{ padding: 16 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, marginBottom: ranges.length ? 12 : 0 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
|
||||
<span style={{ fontWeight: 700, fontSize: 14 }}>{label}</span>
|
||||
{ranges.length === 0 && (
|
||||
<span style={{ fontSize: 12, color: 'var(--text-3)' }}>بسته</span>
|
||||
)}
|
||||
</div>
|
||||
{canUpdate && (
|
||||
<div style={{ display: 'flex', gap: 6 }}>
|
||||
{ranges.length > 0 && (
|
||||
<button type="button" className="btn secondary sm" onClick={() => copyToWholeWeek(day)}>
|
||||
اعمال روی همهٔ روزها
|
||||
</button>
|
||||
)}
|
||||
<button type="button" className="btn secondary sm" onClick={() => addRange(day)}>
|
||||
<PlusIcon style={{ width: 15 }} /> بازه
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{canUpdate && (
|
||||
<div style={{ display: 'flex', gap: 6 }}>
|
||||
{ranges.length > 0 && (
|
||||
<button type="button" className="btn secondary sm" onClick={() => copyToWholeWeek(day)}>
|
||||
اعمال روی همهٔ روزها
|
||||
</button>
|
||||
)}
|
||||
<button type="button" className="btn secondary sm" onClick={() => addRange(day)}>
|
||||
<PlusIcon style={{ width: 15 }} /> بازه
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'grid', gap: 8 }}>
|
||||
{ranges.map((range, index) => (
|
||||
<div key={index} style={{ display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' }}>
|
||||
<label style={{ fontSize: 12, color: 'var(--text-3)' }}>از</label>
|
||||
<input
|
||||
type="time"
|
||||
className="field"
|
||||
value={range.start}
|
||||
disabled={!canUpdate}
|
||||
onChange={(e) => editRange(day, index, { start: e.target.value })}
|
||||
style={{ width: 120 }}
|
||||
/>
|
||||
<label style={{ fontSize: 12, color: 'var(--text-3)' }}>تا</label>
|
||||
{range.endOfDay ? (
|
||||
<span className="field" style={{ width: 120, color: 'var(--text-2)' }}>۲۴:۰۰</span>
|
||||
) : (
|
||||
<div style={{ display: 'grid', gap: 8 }}>
|
||||
{ranges.map((range, index) => (
|
||||
<div key={index} style={{ display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' }}>
|
||||
<label style={{ fontSize: 12, color: 'var(--text-3)' }}>از</label>
|
||||
<input
|
||||
type="time"
|
||||
className="field"
|
||||
value={range.end}
|
||||
value={range.start}
|
||||
disabled={!canUpdate}
|
||||
onChange={(e) => editRange(day, index, { end: e.target.value })}
|
||||
onChange={(e) => editRange(day, index, { start: e.target.value })}
|
||||
style={{ width: 120 }}
|
||||
/>
|
||||
)}
|
||||
<label style={{ display: 'flex', alignItems: 'center', gap: 5, fontSize: 12, color: 'var(--text-3)' }}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={range.endOfDay}
|
||||
disabled={!canUpdate}
|
||||
onChange={(e) => editRange(day, index, { endOfDay: e.target.checked })}
|
||||
/>
|
||||
تا پایان روز
|
||||
</label>
|
||||
{canUpdate && (
|
||||
<button
|
||||
type="button"
|
||||
className="btn secondary sm"
|
||||
onClick={() => removeRange(day, index)}
|
||||
aria-label="حذف بازه"
|
||||
>
|
||||
<TrashIcon style={{ width: 15 }} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
<label style={{ fontSize: 12, color: 'var(--text-3)' }}>تا</label>
|
||||
{range.endOfDay ? (
|
||||
<span className="field" style={{ width: 120, color: 'var(--text-2)' }}>۲۴:۰۰</span>
|
||||
) : (
|
||||
<input
|
||||
type="time"
|
||||
className="field"
|
||||
value={range.end}
|
||||
disabled={!canUpdate}
|
||||
onChange={(e) => editRange(day, index, { end: e.target.value })}
|
||||
style={{ width: 120 }}
|
||||
/>
|
||||
)}
|
||||
<label style={{ display: 'flex', alignItems: 'center', gap: 5, fontSize: 12, color: 'var(--text-3)' }}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={range.endOfDay}
|
||||
disabled={!canUpdate}
|
||||
onChange={(e) => editRange(day, index, { endOfDay: e.target.checked })}
|
||||
/>
|
||||
تا پایان روز
|
||||
</label>
|
||||
{canUpdate && (
|
||||
<button
|
||||
type="button"
|
||||
className="btn secondary sm"
|
||||
onClick={() => removeRange(day, index)}
|
||||
aria-label="حذف بازه"
|
||||
>
|
||||
<TrashIcon style={{ width: 15 }} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</SettingsLayout>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user