mirror of
https://github.com/whekin/household-bot.git
synced 2026-03-31 10:14:03 +00:00
feat(miniapp): restore utility categories CRUD in settings
This commit is contained in:
@@ -20,7 +20,9 @@ import {
|
||||
updateMiniAppMemberStatus,
|
||||
promoteMiniAppMember,
|
||||
approveMiniAppPendingMember,
|
||||
rejectMiniAppPendingMember
|
||||
rejectMiniAppPendingMember,
|
||||
upsertMiniAppUtilityCategory,
|
||||
type MiniAppUtilityCategory
|
||||
} from '../miniapp-api'
|
||||
import { minorToMajorString } from '../lib/money'
|
||||
|
||||
@@ -48,6 +50,16 @@ export default function SettingsRoute() {
|
||||
// ── Profile settings ─────────────────────────────
|
||||
const [profileEditorOpen, setProfileEditorOpen] = createSignal(false)
|
||||
|
||||
// ── Utility categories ───────────────────────────
|
||||
const [categoryEditorOpen, setCategoryEditorOpen] = createSignal(false)
|
||||
const [editingCategorySlug, setEditingCategorySlug] = createSignal<string | null>(null)
|
||||
const [savingCategory, setSavingCategory] = createSignal(false)
|
||||
const [categoryForm, setCategoryForm] = createSignal({
|
||||
name: '',
|
||||
sortOrder: 0,
|
||||
isActive: true
|
||||
})
|
||||
|
||||
// ── Billing settings form ────────────────────────
|
||||
const [billingEditorOpen, setBillingEditorOpen] = createSignal(false)
|
||||
const [savingSettings, setSavingSettings] = createSignal(false)
|
||||
@@ -142,6 +154,60 @@ export default function SettingsRoute() {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Utility Category Editing ─────────────────────
|
||||
function openAddCategory() {
|
||||
setEditingCategorySlug(null)
|
||||
setCategoryForm({
|
||||
name: '',
|
||||
sortOrder: adminSettings()?.categories.length ?? 0,
|
||||
isActive: true
|
||||
})
|
||||
setCategoryEditorOpen(true)
|
||||
}
|
||||
|
||||
function openEditCategory(category: MiniAppUtilityCategory) {
|
||||
setEditingCategorySlug(category.slug)
|
||||
setCategoryForm({
|
||||
name: category.name,
|
||||
sortOrder: category.sortOrder,
|
||||
isActive: category.isActive
|
||||
})
|
||||
setCategoryEditorOpen(true)
|
||||
}
|
||||
|
||||
async function handleSaveCategory() {
|
||||
const data = initData()
|
||||
if (!data) return
|
||||
|
||||
setSavingCategory(true)
|
||||
try {
|
||||
const form = categoryForm()
|
||||
const slug = editingCategorySlug()
|
||||
const category = await upsertMiniAppUtilityCategory(data, {
|
||||
...(slug ? { slug } : {}),
|
||||
name: form.name,
|
||||
sortOrder: form.sortOrder,
|
||||
isActive: form.isActive
|
||||
})
|
||||
|
||||
// Update local state
|
||||
setAdminSettings((prev) => {
|
||||
if (!prev) return prev
|
||||
const existing = prev.categories.find((c) => c.slug === category.slug)
|
||||
if (existing) {
|
||||
return {
|
||||
...prev,
|
||||
categories: prev.categories.map((c) => (c.slug === category.slug ? category : c))
|
||||
}
|
||||
}
|
||||
return { ...prev, categories: [...prev.categories, category] }
|
||||
})
|
||||
setCategoryEditorOpen(false)
|
||||
} finally {
|
||||
setSavingCategory(false)
|
||||
}
|
||||
}
|
||||
|
||||
// ── Member Editing ──────────────────────────────
|
||||
const [editMemberId, setEditMemberId] = createSignal<string | null>(null)
|
||||
const [savingMember, setSavingMember] = createSignal(false)
|
||||
@@ -447,6 +513,40 @@ export default function SettingsRoute() {
|
||||
)}
|
||||
</Show>
|
||||
</Collapsible>
|
||||
|
||||
{/* Utility Categories */}
|
||||
<Collapsible title={copy().utilityCategoriesTitle} body={copy().utilityCategoriesBody}>
|
||||
<Show
|
||||
when={adminSettings()?.categories}
|
||||
fallback={<p class="empty-state">{copy().utilityCategoriesBody}</p>}
|
||||
>
|
||||
{(categories) => (
|
||||
<div class="categories-list">
|
||||
<For each={categories()}>
|
||||
{(category) => (
|
||||
<Card>
|
||||
<div
|
||||
class="category-row interactive"
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={() => openEditCategory(category)}
|
||||
>
|
||||
<div class="category-row__info">
|
||||
<strong>{category.name}</strong>
|
||||
<Badge variant={category.isActive ? 'accent' : 'muted'}>
|
||||
{category.isActive ? copy().onLabel : copy().offLabel}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
</For>
|
||||
<Button variant="secondary" size="sm" onClick={() => openAddCategory()}>
|
||||
{copy().addCategoryAction}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</Show>
|
||||
</Collapsible>
|
||||
</Show>
|
||||
|
||||
{/* ── Billing Settings Editor Modal ────────────── */}
|
||||
@@ -862,6 +962,50 @@ export default function SettingsRoute() {
|
||||
</Field>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
{/* ── Category Editor Modal ────────────── */}
|
||||
<Modal
|
||||
open={categoryEditorOpen()}
|
||||
title={editingCategorySlug() ? copy().editCategoryAction : copy().addCategoryAction}
|
||||
description={editingCategorySlug() ? copy().categoryEditorBody : copy().categoryCreateBody}
|
||||
closeLabel={copy().closeEditorAction}
|
||||
onClose={() => setCategoryEditorOpen(false)}
|
||||
footer={
|
||||
<div class="modal-action-row">
|
||||
<Button variant="ghost" onClick={() => setCategoryEditorOpen(false)}>
|
||||
{copy().closeEditorAction}
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
loading={savingCategory()}
|
||||
disabled={categoryForm().name.trim().length < 1}
|
||||
onClick={() => void handleSaveCategory()}
|
||||
>
|
||||
{savingCategory() ? copy().savingCategory : copy().saveCategoryAction}
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div class="editor-grid">
|
||||
<Field label={copy().utilityCategoryName} wide>
|
||||
<Input
|
||||
value={categoryForm().name}
|
||||
onInput={(e) => setCategoryForm((f) => ({ ...f, name: e.currentTarget.value }))}
|
||||
/>
|
||||
</Field>
|
||||
<Field label={copy().utilityCategoryActive}>
|
||||
<Select
|
||||
value={categoryForm().isActive ? 'true' : 'false'}
|
||||
ariaLabel={copy().utilityCategoryActive}
|
||||
options={[
|
||||
{ value: 'true', label: copy().onLabel },
|
||||
{ value: 'false', label: copy().offLabel }
|
||||
]}
|
||||
onChange={(value) => setCategoryForm((f) => ({ ...f, isActive: value === 'true' }))}
|
||||
/>
|
||||
</Field>
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user