134 lines
7.2 KiB
Vue
134 lines
7.2 KiB
Vue
<script setup>
|
||
import { Head, Link, router, useForm } from '@inertiajs/vue3';
|
||
import { ref } from 'vue';
|
||
import AdminLayout from '../../Layouts/AdminLayout.vue';
|
||
|
||
const props = defineProps({
|
||
site: { type: Object, required: true },
|
||
auth: { type: Object, required: true },
|
||
urls: { type: Object, required: true },
|
||
flash: { type: Object, default: () => ({}) },
|
||
transactions: { type: Array, default: () => [] },
|
||
search: { type: String, default: '' },
|
||
adjustUrl: { type: String, required: true },
|
||
old: { type: Object, default: () => ({}) },
|
||
});
|
||
|
||
const searchValue = ref(props.search);
|
||
const form = useForm({
|
||
account: props.old.account || '',
|
||
direction: props.old.direction || 'credit',
|
||
amount: props.old.amount || '',
|
||
reason: props.old.reason || '',
|
||
});
|
||
|
||
function submit() {
|
||
form.post(props.adjustUrl, {
|
||
preserveScroll: true,
|
||
onSuccess: () => form.reset(),
|
||
});
|
||
}
|
||
|
||
function searchTransactions() {
|
||
router.get(props.urls.admin_balances, {
|
||
search: searchValue.value || undefined,
|
||
}, { preserveState: true, replace: true });
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<Head><title>Слёзы Элуны — {{ site.name }}</title></Head>
|
||
|
||
<AdminLayout :site="site" :auth="auth" :urls="urls" :flash="flash" current="balances">
|
||
<section class="admin-hero wrap">
|
||
<div>
|
||
<span class="eyebrow">Финансы</span>
|
||
<h1 class="h-display">Слёзы Элуны</h1>
|
||
<p class="lead">Ручные корректировки и полный журнал движения виртуальной валюты.</p>
|
||
</div>
|
||
<div class="admin-hero__badge"><small>Операций в выборке</small><strong>{{ transactions.length }}</strong></div>
|
||
</section>
|
||
|
||
<div class="admin-page wrap">
|
||
<section class="admin-section">
|
||
<div class="admin-split admin-split--form">
|
||
<article class="portal-card admin-panel corners">
|
||
<span class="admin-kicker">Новая операция</span>
|
||
<h2 class="h-serif">Изменить баланс</h2>
|
||
<form class="admin-form" @submit.prevent="submit">
|
||
<label class="admin-field">
|
||
<span>ID или логин аккаунта</span>
|
||
<input v-model="form.account" required />
|
||
<small v-if="form.errors.account">{{ form.errors.account }}</small>
|
||
</label>
|
||
<div class="admin-form-grid">
|
||
<label class="admin-field">
|
||
<span>Операция</span>
|
||
<select v-model="form.direction" required>
|
||
<option value="credit">Начислить</option>
|
||
<option value="debit">Снять</option>
|
||
</select>
|
||
<small v-if="form.errors.direction">{{ form.errors.direction }}</small>
|
||
</label>
|
||
<label class="admin-field">
|
||
<span>Количество</span>
|
||
<input v-model="form.amount" type="number" min="0.01" max="1000000" step="0.01" required />
|
||
<small v-if="form.errors.amount">{{ form.errors.amount }}</small>
|
||
</label>
|
||
</div>
|
||
<label class="admin-field">
|
||
<span>Причина</span>
|
||
<input v-model="form.reason" maxlength="255" placeholder="Компенсация, возврат, нарушение…" required />
|
||
<small v-if="form.errors.reason">{{ form.errors.reason }}</small>
|
||
</label>
|
||
<button class="btn btn-primary admin-submit" type="submit" :disabled="form.processing">
|
||
{{ form.processing ? 'Проводим…' : 'Провести операцию' }}
|
||
</button>
|
||
</form>
|
||
</article>
|
||
|
||
<article class="portal-card admin-panel admin-rules">
|
||
<span class="admin-kicker">Контроль</span>
|
||
<h2 class="h-serif">Правила операций</h2>
|
||
<div><span>01</span><p>Баланс аккаунта не может стать отрицательным.</p></div>
|
||
<div><span>02</span><p>В журнале сохраняются администратор, причина и итоговое значение.</p></div>
|
||
<div><span>03</span><p>Ошибочная операция исправляется отдельной обратной корректировкой.</p></div>
|
||
</article>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="admin-section">
|
||
<div class="admin-section__head">
|
||
<div><span class="eyebrow">Аудит</span><h2 class="h-serif">Последние транзакции</h2></div>
|
||
<form class="admin-search" @submit.prevent="searchTransactions">
|
||
<input v-model="searchValue" type="search" placeholder="Логин или ID аккаунта" />
|
||
<button class="btn btn-primary" type="submit">Найти</button>
|
||
<Link v-if="search" class="btn btn-ghost" :href="urls.admin_balances">Сбросить</Link>
|
||
</form>
|
||
</div>
|
||
|
||
<div v-if="transactions.length" class="admin-ledger">
|
||
<article v-for="transaction in transactions" :key="transaction.id" class="portal-card admin-ledger__row">
|
||
<div>
|
||
<strong>{{ transaction.username }}</strong>
|
||
<small>ID #{{ transaction.account_id }} · {{ transaction.created_at }}</small>
|
||
</div>
|
||
<div>
|
||
<strong>{{ transaction.type_label }}</strong>
|
||
<small>{{ transaction.description }}</small>
|
||
<small v-if="transaction.admin">Администратор: {{ transaction.admin.username }} (#{{ transaction.admin.account_id }})</small>
|
||
</div>
|
||
<div class="admin-ledger__amount">
|
||
<strong :class="{ positive: transaction.is_positive }">
|
||
{{ transaction.is_positive ? '+' : '' }}{{ transaction.formatted_amount }}
|
||
</strong>
|
||
<small>Баланс: {{ transaction.formatted_balance_after }}</small>
|
||
</div>
|
||
</article>
|
||
</div>
|
||
<article v-else class="portal-card admin-empty"><p>Транзакции не найдены.</p></article>
|
||
</section>
|
||
</div>
|
||
</AdminLayout>
|
||
</template>
|