Add Hamkar UI reset-to-default snapshot and restore button.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,16 +1,18 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { Modal, message } from 'ant-design-vue'
|
||||
import { ArrowRightOutlined } from '@ant-design/icons-vue'
|
||||
import api from '@/api/http'
|
||||
import {
|
||||
cloneHamkarSnapshot,
|
||||
createDefaultHamkarUiConfig,
|
||||
HAMKAR_ORDER_UI_OPTIONS,
|
||||
HAMKAR_PRICE_FONT_SIZE_OPTIONS,
|
||||
HAMKAR_PRICING_BOX_THEME_OPTIONS,
|
||||
hamkarUiToApiPayload,
|
||||
normalizeHamkarUiConfig,
|
||||
registryModeFromNotes,
|
||||
type HamkarUiConfig,
|
||||
} from '@/config/defaultHamkarUiConfig'
|
||||
import { resolveHamkarEffectiveTheme } from '@/config/hamkarEffectiveTheme'
|
||||
@@ -19,6 +21,7 @@ const route = useRoute()
|
||||
const router = useRouter()
|
||||
const loading = ref(true)
|
||||
const saving = ref(false)
|
||||
const resetting = ref(false)
|
||||
const tenantName = ref('')
|
||||
const domain = ref('')
|
||||
|
||||
@@ -40,6 +43,9 @@ const fontOptions = computed(() =>
|
||||
HAMKAR_PRICE_FONT_SIZE_OPTIONS.map((o) => ({ value: o.value, label: o.label })),
|
||||
)
|
||||
|
||||
const hasDefaultSnapshot = computed(() => Boolean(config.defaultSnapshot))
|
||||
const defaultModeLabel = computed(() => config.defaultSnapshot?.registryMode || '—')
|
||||
|
||||
const NAV_ITEMS = ['معامله', 'دارایی', 'سفارشات', 'گزارشدهی', 'مشتریان']
|
||||
|
||||
function swatchHex(value: string, fallback = '#cbd5e1') {
|
||||
@@ -62,7 +68,6 @@ function textOn(bg: string) {
|
||||
return (r * 299 + g * 587 + b * 114) / 1000 > 128 ? '#0f172a' : '#ffffff'
|
||||
}
|
||||
|
||||
/** Live palette driving the preview (= what save will apply via dynamicMode) */
|
||||
const palette = computed(() => ({
|
||||
header: swatchHex(config.brand.primaryColor, '#ffffff'),
|
||||
accent: swatchHex(config.brand.secondaryColor, '#2563EB'),
|
||||
@@ -96,7 +101,6 @@ function seedEditableColors(normalized: HamkarUiConfig) {
|
||||
secondaryColor: normalized.brand.secondaryColor,
|
||||
backgroundColor: normalized.brand.backgroundColor,
|
||||
})
|
||||
// Named CSS modes ignore registry colors — seed pickers with what the app actually paints
|
||||
if (!effective.isDynamic) {
|
||||
normalized.brand.primaryColor = effective.headerBg
|
||||
normalized.brand.secondaryColor = effective.accent
|
||||
@@ -108,6 +112,47 @@ function seedEditableColors(normalized: HamkarUiConfig) {
|
||||
}
|
||||
}
|
||||
|
||||
function ensureDefaultSnapshot(
|
||||
normalized: HamkarUiConfig,
|
||||
opts: {
|
||||
notes?: string | null
|
||||
primaryColor?: string | null
|
||||
secondaryColor?: string | null
|
||||
backgroundColor?: string | null
|
||||
},
|
||||
) {
|
||||
if (normalized.defaultSnapshot) return
|
||||
|
||||
const noteMode = registryModeFromNotes(opts.notes)
|
||||
const currentMode = String(normalized.registryMode || '').trim()
|
||||
const registryMode =
|
||||
(noteMode && noteMode !== 'dynamicMode' ? noteMode : null) ||
|
||||
(currentMode && currentMode !== 'dynamicMode' ? currentMode : null) ||
|
||||
noteMode ||
|
||||
currentMode ||
|
||||
'dynamicMode'
|
||||
|
||||
normalized.defaultSnapshot = cloneHamkarSnapshot({
|
||||
registryMode,
|
||||
brand: {
|
||||
primaryColor: String(opts.primaryColor || normalized.brand.primaryColor || ''),
|
||||
secondaryColor: String(opts.secondaryColor || normalized.brand.secondaryColor || ''),
|
||||
backgroundColor: String(opts.backgroundColor || normalized.brand.backgroundColor || ''),
|
||||
},
|
||||
settings: { ...normalized.settings },
|
||||
})
|
||||
}
|
||||
|
||||
async function persist(payload: ReturnType<typeof hamkarUiToApiPayload>) {
|
||||
await api.patch(`/tenants/${route.params.id}`, {
|
||||
appUiConfig: payload,
|
||||
primaryColor: payload.brand.primaryColor,
|
||||
secondaryColor: payload.brand.secondaryColor,
|
||||
backgroundColor: payload.brand.backgroundColor,
|
||||
})
|
||||
Object.assign(config, normalizeHamkarUiConfig(payload))
|
||||
}
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
try {
|
||||
@@ -128,7 +173,7 @@ async function load() {
|
||||
settingsManaged: false,
|
||||
registryMode:
|
||||
data.appUiConfig?.registryMode ||
|
||||
data.notes?.match(/registryMode:([^\s|]+)/)?.[1] ||
|
||||
registryModeFromNotes(data.notes) ||
|
||||
'dynamicMode',
|
||||
brand: {
|
||||
primaryColor: data.primaryColor,
|
||||
@@ -148,8 +193,20 @@ async function load() {
|
||||
if (!normalized.brand.backgroundColor && data.backgroundColor) {
|
||||
normalized.brand.backgroundColor = String(data.backgroundColor)
|
||||
}
|
||||
|
||||
ensureDefaultSnapshot(normalized, {
|
||||
notes: data.notes,
|
||||
primaryColor: data.primaryColor,
|
||||
secondaryColor: data.secondaryColor,
|
||||
backgroundColor: data.backgroundColor,
|
||||
})
|
||||
|
||||
seedEditableColors(normalized)
|
||||
Object.assign(config, normalized)
|
||||
|
||||
if (!rawUi.defaultSnapshot && normalized.defaultSnapshot) {
|
||||
await persist(hamkarUiToApiPayload(normalized))
|
||||
}
|
||||
} catch (e: any) {
|
||||
message.error(e?.response?.data?.message || 'خطا در بارگذاری ظاهر همکار')
|
||||
} finally {
|
||||
@@ -160,16 +217,16 @@ async function load() {
|
||||
async function save() {
|
||||
saving.value = true
|
||||
try {
|
||||
// Colors only paint the app in dynamicMode (Hamkar apply-theme)
|
||||
config.registryMode = 'dynamicMode'
|
||||
const payload = hamkarUiToApiPayload(config)
|
||||
await api.patch(`/tenants/${route.params.id}`, {
|
||||
appUiConfig: payload,
|
||||
primaryColor: payload.brand.primaryColor,
|
||||
secondaryColor: payload.brand.secondaryColor,
|
||||
backgroundColor: payload.brand.backgroundColor,
|
||||
})
|
||||
Object.assign(config, normalizeHamkarUiConfig(payload))
|
||||
if (!config.defaultSnapshot) {
|
||||
ensureDefaultSnapshot(config, {
|
||||
notes: null,
|
||||
primaryColor: config.brand.primaryColor,
|
||||
secondaryColor: config.brand.secondaryColor,
|
||||
backgroundColor: config.brand.backgroundColor,
|
||||
})
|
||||
}
|
||||
await persist(hamkarUiToApiPayload(config))
|
||||
message.success('ظاهر همکار ذخیره شد')
|
||||
} catch (e: any) {
|
||||
const msg = e?.response?.data?.message
|
||||
@@ -179,6 +236,40 @@ async function save() {
|
||||
}
|
||||
}
|
||||
|
||||
function resetToDefault() {
|
||||
if (!config.defaultSnapshot) {
|
||||
message.warning('ظاهر پیشفرض برای این نسخه ثبت نشده است')
|
||||
return
|
||||
}
|
||||
Modal.confirm({
|
||||
title: 'بازگشت به ظاهر اولیه؟',
|
||||
content: `حالت پیشفرض «${config.defaultSnapshot.registryMode}» اعمال و ذخیره میشود.`,
|
||||
okText: 'بازگشت به پیشفرض',
|
||||
cancelText: 'انصراف',
|
||||
okButtonProps: { danger: true },
|
||||
async onOk() {
|
||||
resetting.value = true
|
||||
try {
|
||||
const snap = config.defaultSnapshot!
|
||||
config.registryMode = snap.registryMode
|
||||
config.brand.primaryColor = snap.brand.primaryColor
|
||||
config.brand.secondaryColor = snap.brand.secondaryColor
|
||||
config.brand.backgroundColor = snap.brand.backgroundColor
|
||||
config.settings = { ...snap.settings }
|
||||
await persist(hamkarUiToApiPayload(config))
|
||||
seedEditableColors(config)
|
||||
message.success('ظاهر اولیه بازیابی شد')
|
||||
} catch (e: any) {
|
||||
const msg = e?.response?.data?.message
|
||||
message.error(Array.isArray(msg) ? msg.join('، ') : msg || 'بازیابی ناموفق')
|
||||
throw e
|
||||
} finally {
|
||||
resetting.value = false
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
</script>
|
||||
|
||||
@@ -232,6 +323,9 @@ onMounted(load)
|
||||
|
||||
<section class="form-section">
|
||||
<h3>رنگها</h3>
|
||||
<p v-if="hasDefaultSnapshot" class="hint">
|
||||
ظاهر پیشفرض ثبتشده: <strong>{{ defaultModeLabel }}</strong>
|
||||
</p>
|
||||
<div class="palette-grid">
|
||||
<label class="palette-card">
|
||||
<span class="palette-title">هدر</span>
|
||||
@@ -325,6 +419,15 @@ onMounted(load)
|
||||
</section>
|
||||
|
||||
<div class="sticky-actions">
|
||||
<a-button
|
||||
size="large"
|
||||
block
|
||||
:loading="resetting"
|
||||
:disabled="!hasDefaultSnapshot"
|
||||
@click="resetToDefault"
|
||||
>
|
||||
بازگشت به ظاهر اولیه
|
||||
</a-button>
|
||||
<a-button type="primary" size="large" block :loading="saving" @click="save">
|
||||
ذخیره ظاهر همکار
|
||||
</a-button>
|
||||
@@ -340,6 +443,12 @@ onMounted(load)
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.hint {
|
||||
margin: 0 0 10px;
|
||||
color: #64748b;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.preview {
|
||||
padding: 16px;
|
||||
}
|
||||
@@ -466,4 +575,9 @@ onMounted(load)
|
||||
.palette-hex {
|
||||
font-family: ui-monospace, monospace;
|
||||
}
|
||||
|
||||
.sticky-actions {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user