mirror of
https://github.com/whekin/household-bot.git
synced 2026-03-31 15:44:02 +00:00
feat(miniapp): restore utility categories CRUD in settings
This commit is contained in:
@@ -20,7 +20,9 @@ import {
|
|||||||
updateMiniAppMemberStatus,
|
updateMiniAppMemberStatus,
|
||||||
promoteMiniAppMember,
|
promoteMiniAppMember,
|
||||||
approveMiniAppPendingMember,
|
approveMiniAppPendingMember,
|
||||||
rejectMiniAppPendingMember
|
rejectMiniAppPendingMember,
|
||||||
|
upsertMiniAppUtilityCategory,
|
||||||
|
type MiniAppUtilityCategory
|
||||||
} from '../miniapp-api'
|
} from '../miniapp-api'
|
||||||
import { minorToMajorString } from '../lib/money'
|
import { minorToMajorString } from '../lib/money'
|
||||||
|
|
||||||
@@ -48,6 +50,16 @@ export default function SettingsRoute() {
|
|||||||
// ── Profile settings ─────────────────────────────
|
// ── Profile settings ─────────────────────────────
|
||||||
const [profileEditorOpen, setProfileEditorOpen] = createSignal(false)
|
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 ────────────────────────
|
// ── Billing settings form ────────────────────────
|
||||||
const [billingEditorOpen, setBillingEditorOpen] = createSignal(false)
|
const [billingEditorOpen, setBillingEditorOpen] = createSignal(false)
|
||||||
const [savingSettings, setSavingSettings] = 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 ──────────────────────────────
|
// ── Member Editing ──────────────────────────────
|
||||||
const [editMemberId, setEditMemberId] = createSignal<string | null>(null)
|
const [editMemberId, setEditMemberId] = createSignal<string | null>(null)
|
||||||
const [savingMember, setSavingMember] = createSignal(false)
|
const [savingMember, setSavingMember] = createSignal(false)
|
||||||
@@ -447,6 +513,40 @@ export default function SettingsRoute() {
|
|||||||
)}
|
)}
|
||||||
</Show>
|
</Show>
|
||||||
</Collapsible>
|
</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>
|
</Show>
|
||||||
|
|
||||||
{/* ── Billing Settings Editor Modal ────────────── */}
|
{/* ── Billing Settings Editor Modal ────────────── */}
|
||||||
@@ -862,6 +962,50 @@ export default function SettingsRoute() {
|
|||||||
</Field>
|
</Field>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</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>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user