admin_nyelvi_javítas
This commit is contained in:
@@ -357,70 +357,139 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Commission Section (L2) -->
|
||||
<!-- Commission Section (L2) - Százalékos Jutalékok -->
|
||||
<div
|
||||
class="bg-slate-700/30 rounded-lg p-4 space-y-4 transition-opacity duration-200"
|
||||
:class="form.rule_type === 'L1_REWARD' ? 'opacity-40 pointer-events-none' : ''"
|
||||
>
|
||||
<div class="text-sm font-medium text-slate-300 mb-2">{{ t('commission_rules.commission_section') }}</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<!-- Commission Percent (Gen1) -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-300 mb-1">{{ t('commission_rules.commission_percent') }}</label>
|
||||
<div class="relative">
|
||||
|
||||
<!-- Two-Column Layout: Gen1 (Left) / Gen2 (Right) -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||
<!-- Left Column: Gen1 - Közvetlen Ajánló -->
|
||||
<div class="space-y-4">
|
||||
<h4 class="text-sm font-semibold text-indigo-300 mb-4 border-b border-slate-600/50 pb-2">{{ t('commission_rules.gen1_title') }}</h4>
|
||||
|
||||
<!-- Commission Percent (Gen1) -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-300 mb-1">{{ t('commission_rules.commission_percent') }}</label>
|
||||
<div class="relative">
|
||||
<input
|
||||
v-model.number="form.commission_percent"
|
||||
type="number"
|
||||
min="0"
|
||||
max="100"
|
||||
step="0.01"
|
||||
placeholder="0.00"
|
||||
:disabled="form.rule_type === 'L1_REWARD'"
|
||||
class="w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500 text-sm pr-8 disabled:cursor-not-allowed"
|
||||
/>
|
||||
<span class="absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 text-sm">%</span>
|
||||
</div>
|
||||
<p class="mt-1 text-xs text-slate-400">{{ t('commission_rules.commission_percent_helper') }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Renewal Commission Percent -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-300 mb-1">{{ t('commission_rules.renewal_commission_percent') }}</label>
|
||||
<div class="relative">
|
||||
<input
|
||||
v-model.number="form.renewal_commission_percent"
|
||||
type="number"
|
||||
min="0"
|
||||
max="100"
|
||||
step="0.01"
|
||||
placeholder="0.00"
|
||||
:disabled="form.rule_type === 'L1_REWARD'"
|
||||
class="w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500 text-sm pr-8 disabled:cursor-not-allowed"
|
||||
/>
|
||||
<span class="absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 text-sm">%</span>
|
||||
</div>
|
||||
<p class="mt-1 text-xs text-slate-400">{{ t('commission_rules.renewal_commission_percent_helper') }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Gen1 Max Amount (Phase 2) -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-300 mb-1">{{ t('commission_rules.gen1_max_amount') }}</label>
|
||||
<input
|
||||
v-model.number="form.commission_percent"
|
||||
v-model.number="form.gen1_max_amount"
|
||||
type="number"
|
||||
min="0"
|
||||
max="100"
|
||||
step="0.01"
|
||||
placeholder="0.00"
|
||||
:disabled="form.rule_type === 'L1_REWARD'"
|
||||
class="w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500 text-sm pr-8 disabled:cursor-not-allowed"
|
||||
class="w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500 text-sm disabled:cursor-not-allowed"
|
||||
/>
|
||||
<span class="absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 text-sm">%</span>
|
||||
<p class="mt-1 text-xs text-slate-400">{{ t('commission_rules.gen1_max_amount_helper') }}</p>
|
||||
</div>
|
||||
<p class="mt-1 text-xs text-slate-400">{{ t('commission_rules.commission_percent_helper') }}</p>
|
||||
</div>
|
||||
<!-- Upline Commission Percent (Gen2) -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-300 mb-1">{{ t('commission_rules.upline_commission_percent') }}</label>
|
||||
<div class="relative">
|
||||
|
||||
<!-- Right Column: Gen2 - Felsővonal -->
|
||||
<div class="space-y-4">
|
||||
<h4 class="text-sm font-semibold text-emerald-300 mb-4 border-b border-slate-600/50 pb-2">{{ t('commission_rules.gen2_title') }}</h4>
|
||||
|
||||
<!-- Upline Commission Percent (Gen2) -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-300 mb-1">{{ t('commission_rules.upline_commission_percent') }}</label>
|
||||
<div class="relative">
|
||||
<input
|
||||
v-model.number="form.upline_commission_percent"
|
||||
type="number"
|
||||
min="0"
|
||||
max="100"
|
||||
step="0.01"
|
||||
placeholder="0.00"
|
||||
:disabled="form.rule_type === 'L1_REWARD'"
|
||||
class="w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500 text-sm pr-8 disabled:cursor-not-allowed"
|
||||
/>
|
||||
<span class="absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 text-sm">%</span>
|
||||
</div>
|
||||
<p class="mt-1 text-xs text-slate-400">{{ t('commission_rules.upline_commission_percent_helper') }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Gen2 Renewal Percent (Phase 2) -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-300 mb-1">{{ t('commission_rules.gen2_renewal_percent') }}</label>
|
||||
<div class="relative">
|
||||
<input
|
||||
v-model.number="form.gen2_renewal_percent"
|
||||
type="number"
|
||||
min="0"
|
||||
max="100"
|
||||
step="0.01"
|
||||
placeholder="0.00"
|
||||
:disabled="form.rule_type === 'L1_REWARD'"
|
||||
class="w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500 text-sm pr-8 disabled:cursor-not-allowed"
|
||||
/>
|
||||
<span class="absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 text-sm">%</span>
|
||||
</div>
|
||||
<p class="mt-1 text-xs text-slate-400">{{ t('commission_rules.gen2_renewal_percent_helper') }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Gen2 Max Amount (Phase 2) -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-300 mb-1">{{ t('commission_rules.gen2_max_amount') }}</label>
|
||||
<input
|
||||
v-model.number="form.upline_commission_percent"
|
||||
v-model.number="form.gen2_max_amount"
|
||||
type="number"
|
||||
min="0"
|
||||
max="100"
|
||||
step="0.01"
|
||||
placeholder="0.00"
|
||||
:disabled="form.rule_type === 'L1_REWARD'"
|
||||
class="w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500 text-sm pr-8 disabled:cursor-not-allowed"
|
||||
class="w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500 text-sm disabled:cursor-not-allowed"
|
||||
/>
|
||||
<span class="absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 text-sm">%</span>
|
||||
<p class="mt-1 text-xs text-slate-400">{{ t('commission_rules.gen2_max_amount_helper') }}</p>
|
||||
</div>
|
||||
<p class="mt-1 text-xs text-slate-400">{{ t('commission_rules.upline_commission_percent_helper') }}</p>
|
||||
</div>
|
||||
<!-- Renewal Commission Percent -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-300 mb-1">{{ t('commission_rules.renewal_commission_percent') }}</label>
|
||||
<div class="relative">
|
||||
<input
|
||||
v-model.number="form.renewal_commission_percent"
|
||||
type="number"
|
||||
min="0"
|
||||
max="100"
|
||||
step="0.01"
|
||||
placeholder="0.00"
|
||||
:disabled="form.rule_type === 'L1_REWARD'"
|
||||
class="w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500 text-sm pr-8 disabled:cursor-not-allowed"
|
||||
/>
|
||||
<span class="absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 text-sm">%</span>
|
||||
</div>
|
||||
<p class="mt-1 text-xs text-slate-400">{{ t('commission_rules.renewal_commission_percent_helper') }}</p>
|
||||
</div>
|
||||
<!-- Commission Max Amount -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-300 mb-1">{{ t('commission_rules.commission_max_amount') }}</label>
|
||||
</div>
|
||||
|
||||
<!-- Legacy Field: Commission Max Amount (Fallback) - full width below columns -->
|
||||
<div class="border-t border-slate-600/30 pt-4 mt-2">
|
||||
<div class="max-w-xs">
|
||||
<label class="block text-sm font-medium text-slate-400 mb-1">
|
||||
{{ t('commission_rules.commission_max_amount') }}
|
||||
<span class="text-xs text-slate-500 ml-1 italic">({{ t('commission_rules.legacy_fallback') }})</span>
|
||||
</label>
|
||||
<input
|
||||
v-model.number="form.commission_max_amount"
|
||||
type="number"
|
||||
@@ -428,9 +497,9 @@
|
||||
step="0.01"
|
||||
placeholder="0.00"
|
||||
:disabled="form.rule_type === 'L1_REWARD'"
|
||||
class="w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500 text-sm disabled:cursor-not-allowed"
|
||||
class="w-full px-3 py-2 bg-slate-700/50 border border-slate-600/50 rounded-lg text-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500 text-sm disabled:cursor-not-allowed"
|
||||
/>
|
||||
<p class="mt-1 text-xs text-slate-400">{{ t('commission_rules.commission_max_amount_helper') }}</p>
|
||||
<p class="mt-1 text-xs text-slate-500">{{ t('commission_rules.commission_max_amount_helper') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -481,6 +550,38 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Conflict Override Confirmation Modal -->
|
||||
<div v-if="showConflictModal" class="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm" @click.self="showConflictModal = false">
|
||||
<div class="bg-slate-800 rounded-xl border border-amber-500/30 p-6 w-full max-w-lg mx-4 shadow-2xl">
|
||||
<div class="flex items-start gap-3 mb-4">
|
||||
<div class="flex-shrink-0 w-10 h-10 rounded-full bg-amber-500/10 flex items-center justify-center">
|
||||
<svg class="w-5 h-5 text-amber-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 16.5c-.77.833.192 2.5 1.732 2.5z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="text-lg font-semibold text-white">{{ t('commission_rules.conflict_title') }}</h2>
|
||||
<p class="text-sm text-slate-400 mt-1">{{ t('commission_rules.conflict_message') }}</p>
|
||||
<div v-if="conflictingRule" class="mt-3 bg-slate-700/30 rounded-lg p-3 text-xs text-slate-300 space-y-1">
|
||||
<div><span class="text-slate-500">ID:</span> {{ conflictingRule.id }}</div>
|
||||
<div><span class="text-slate-500">{{ t('commission_rules.name') }}:</span> {{ conflictingRule.name }}</div>
|
||||
<div><span class="text-slate-500">{{ t('commission_rules.tier') }}:</span> {{ conflictingRule.tier }}</div>
|
||||
<div><span class="text-slate-500">{{ t('commission_rules.region') }}:</span> {{ conflictingRule.region_code }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-end gap-3 pt-2 border-t border-slate-700">
|
||||
<button @click="showConflictModal = false; conflictingRule = null" class="px-4 py-2 text-sm text-slate-400 hover:text-white transition">{{ t('commission_rules.conflict_cancel') }}</button>
|
||||
<button @click="confirmOverride" :disabled="saving" class="px-4 py-2 bg-amber-600 hover:bg-amber-500 disabled:bg-amber-600/50 text-white rounded-lg text-sm font-medium transition flex items-center gap-2">
|
||||
<svg v-if="saving" class="w-4 h-4 animate-spin" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
||||
</svg>
|
||||
{{ saving ? t('commission_rules.saving') : t('commission_rules.conflict_confirm') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Success Toast -->
|
||||
<div v-if="toast" class="fixed bottom-6 right-6 z-50 bg-emerald-600 text-white px-4 py-3 rounded-lg shadow-lg text-sm flex items-center gap-2 animate-slide-up">
|
||||
<svg class="w-4 h-4 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
@@ -511,6 +612,9 @@ interface CommissionRule {
|
||||
upline_commission_percent: number | null
|
||||
renewal_commission_percent: number | null
|
||||
commission_max_amount: number | null
|
||||
gen1_max_amount: number | null
|
||||
gen2_max_amount: number | null
|
||||
gen2_renewal_percent: number | null
|
||||
is_campaign: boolean
|
||||
start_date: string | null
|
||||
end_date: string | null
|
||||
@@ -534,8 +638,11 @@ const error = ref(false)
|
||||
const rules = ref<CommissionRule[]>([])
|
||||
const showModal = ref(false)
|
||||
const showDeactivateModal = ref(false)
|
||||
const showConflictModal = ref(false)
|
||||
const editingRule = ref<CommissionRule | null>(null)
|
||||
const deactivatingRule = ref<CommissionRule | null>(null)
|
||||
const conflictingRule = ref<CommissionRule | null>(null)
|
||||
const pendingPayload = ref<Record<string, any> | null>(null)
|
||||
const saving = ref(false)
|
||||
const formError = ref('')
|
||||
const toast = ref('')
|
||||
@@ -578,6 +685,9 @@ const form = reactive({
|
||||
upline_commission_percent: null as number | null,
|
||||
renewal_commission_percent: null as number | null,
|
||||
commission_max_amount: null as number | null,
|
||||
gen1_max_amount: null as number | null,
|
||||
gen2_max_amount: null as number | null,
|
||||
gen2_renewal_percent: null as number | null,
|
||||
is_active: true,
|
||||
})
|
||||
|
||||
@@ -607,6 +717,9 @@ function resetForm() {
|
||||
form.upline_commission_percent = null
|
||||
form.renewal_commission_percent = null
|
||||
form.commission_max_amount = null
|
||||
form.gen1_max_amount = null
|
||||
form.gen2_max_amount = null
|
||||
form.gen2_renewal_percent = null
|
||||
form.is_active = true
|
||||
formError.value = ''
|
||||
}
|
||||
@@ -633,6 +746,9 @@ function openEditModal(rule: CommissionRule) {
|
||||
form.upline_commission_percent = rule.upline_commission_percent
|
||||
form.renewal_commission_percent = rule.renewal_commission_percent
|
||||
form.commission_max_amount = rule.commission_max_amount
|
||||
form.gen1_max_amount = rule.gen1_max_amount
|
||||
form.gen2_max_amount = rule.gen2_max_amount
|
||||
form.gen2_renewal_percent = rule.gen2_renewal_percent
|
||||
form.is_active = rule.is_active
|
||||
formError.value = ''
|
||||
showModal.value = true
|
||||
@@ -728,6 +844,9 @@ async function saveRule() {
|
||||
payload.upline_commission_percent = form.upline_commission_percent
|
||||
payload.renewal_commission_percent = form.renewal_commission_percent
|
||||
payload.commission_max_amount = form.commission_max_amount
|
||||
payload.gen1_max_amount = form.gen1_max_amount
|
||||
payload.gen2_max_amount = form.gen2_max_amount
|
||||
payload.gen2_renewal_percent = form.gen2_renewal_percent
|
||||
|
||||
if (editingRule.value) {
|
||||
await $fetch(`/api/v1/admin/commission-rules/${editingRule.value.id}`, {
|
||||
@@ -748,7 +867,78 @@ async function saveRule() {
|
||||
await fetchRules()
|
||||
} catch (e: any) {
|
||||
console.error('Failed to save commission rule:', e)
|
||||
formError.value = e?.data?.detail || t('commission_rules.save_error')
|
||||
|
||||
// Phase 3: Handle 409 Conflict — show override confirmation modal
|
||||
if (e?.response?.status === 409 || e?.status === 409) {
|
||||
const detail = e?.data?.detail || e?.response?._data?.detail || {}
|
||||
const conflicting = detail?.conflicting_rule
|
||||
if (conflicting) {
|
||||
conflictingRule.value = conflicting as CommissionRule
|
||||
pendingPayload.value = {
|
||||
name: form.name,
|
||||
description: form.description || null,
|
||||
rule_type: form.rule_type,
|
||||
tier: form.tier,
|
||||
region_code: form.region_code,
|
||||
is_campaign: form.is_campaign,
|
||||
is_active: form.is_active,
|
||||
start_date: form.is_campaign ? (form.start_date || null) : null,
|
||||
end_date: form.is_campaign ? (form.end_date || null) : null,
|
||||
xp_reward: form.xp_reward,
|
||||
credit_reward: form.credit_reward,
|
||||
commission_percent: form.commission_percent,
|
||||
upline_commission_percent: form.upline_commission_percent,
|
||||
renewal_commission_percent: form.renewal_commission_percent,
|
||||
commission_max_amount: form.commission_max_amount,
|
||||
gen1_max_amount: form.gen1_max_amount,
|
||||
gen2_max_amount: form.gen2_max_amount,
|
||||
gen2_renewal_percent: form.gen2_renewal_percent,
|
||||
}
|
||||
showConflictModal.value = true
|
||||
return // Don't clear form error — modal handles it
|
||||
}
|
||||
}
|
||||
|
||||
formError.value = e?.data?.detail || e?.message || t('commission_rules.save_error')
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function confirmOverride() {
|
||||
if (!pendingPayload.value) return
|
||||
saving.value = true
|
||||
formError.value = ''
|
||||
try {
|
||||
// Add force_override flag
|
||||
const payload = { ...pendingPayload.value, force_override: true }
|
||||
|
||||
if (editingRule.value) {
|
||||
await $fetch(`/api/v1/admin/commission-rules/${editingRule.value.id}`, {
|
||||
method: 'PUT',
|
||||
headers: getHeaders(),
|
||||
body: payload,
|
||||
})
|
||||
showToast(t('commission_rules.updated'))
|
||||
} else {
|
||||
await $fetch('/api/v1/admin/commission-rules', {
|
||||
method: 'POST',
|
||||
headers: getHeaders(),
|
||||
body: payload,
|
||||
})
|
||||
showToast(t('commission_rules.created'))
|
||||
}
|
||||
showConflictModal.value = false
|
||||
conflictingRule.value = null
|
||||
pendingPayload.value = null
|
||||
closeModal()
|
||||
await fetchRules()
|
||||
} catch (e: any) {
|
||||
console.error('Failed to save commission rule with override:', e)
|
||||
formError.value = e?.data?.detail || e?.message || t('commission_rules.save_error')
|
||||
showConflictModal.value = false
|
||||
conflictingRule.value = null
|
||||
pendingPayload.value = null
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user