Giant Update
NPM Installation / build (16.x, ubuntu-latest) (push) Has been cancelled
NPM Installation / build (16.x, windows-latest) (push) Has been cancelled
NPM Installation / build (17.x, ubuntu-latest) (push) Has been cancelled
NPM Installation / build (17.x, windows-latest) (push) Has been cancelled
NPM Installation / build (18.x, ubuntu-latest) (push) Has been cancelled
NPM Installation / build (18.x, windows-latest) (push) Has been cancelled
NPM Installation / build (16.x, ubuntu-latest) (push) Has been cancelled
NPM Installation / build (16.x, windows-latest) (push) Has been cancelled
NPM Installation / build (17.x, ubuntu-latest) (push) Has been cancelled
NPM Installation / build (17.x, windows-latest) (push) Has been cancelled
NPM Installation / build (18.x, ubuntu-latest) (push) Has been cancelled
NPM Installation / build (18.x, windows-latest) (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
<script setup>
|
||||
import { computed, onMounted, reactive, ref, watch } from 'vue'
|
||||
import BaseResponsiveDataView from '@/components/base/BaseResponsiveDataView.vue'
|
||||
import { getMyWallets, getWalletLedger, transferWallet, withdrawWallet } from '@/services/walletService'
|
||||
import { showError, showSuccess } from '@/utils/swal'
|
||||
|
||||
const loading = ref(true), wallets = ref([]), activeId = ref(null), ledger = ref([]), modal = ref(''), saving = ref(false)
|
||||
const pagination = ref({ current_page: 1, last_page: 1, per_page: 10, total: 0 }), query = ref({ page: 1, per_page: 10 })
|
||||
const form = reactive({ amount: '', destination_wallet_id: '', bank_code: '', account_number: '', account_name: '' })
|
||||
const active = computed(() => wallets.value.find((x) => x.id === activeId.value) || wallets.value[0])
|
||||
const otherWallets = computed(() => wallets.value.filter((x) => x.id !== active.value?.id))
|
||||
const columns = [{ key: 'date', label: 'Tanggal' }, { key: 'number', label: 'Referensi' }, { key: 'type', label: 'Jenis' }, { key: 'debit', label: 'Debit' }, { key: 'credit', label: 'Kredit' }, { key: 'balance', label: 'Saldo' }]
|
||||
const money = (value) => `Rp ${Number(value || 0).toLocaleString('id-ID')}`
|
||||
async function loadWallets() { loading.value = true; try { wallets.value = (await getMyWallets())?.data || []; activeId.value ||= wallets.value[0]?.id; await loadLedger() } catch (e) { showError(e?.response?.data?.message || 'Gagal memuat dompet.') } finally { loading.value = false } }
|
||||
async function loadLedger(params = query.value) {
|
||||
if (!active.value) return
|
||||
query.value = { ...query.value, ...params }
|
||||
const response = await getWalletLedger(active.value.id, query.value), rows = response?.data?.data || []
|
||||
ledger.value = rows.map((x) => ({ ...x, date: new Date(x.created_at).toLocaleString('id-ID'), number: x.transaction?.transaction_number, type: x.transaction?.type, debit: x.entry_type === 'debit' ? money(x.amount) : '-', credit: x.entry_type === 'credit' ? money(x.amount) : '-', balance: money(x.balance_after) }))
|
||||
pagination.value = { current_page: response?.data?.current_page || 1, last_page: response?.data?.last_page || 1, per_page: response?.data?.per_page || 10, total: response?.data?.total || 0 }
|
||||
}
|
||||
function open(type) { Object.assign(form, { amount: '', destination_wallet_id: otherWallets.value[0]?.id || '', bank_code: '', account_number: '', account_name: '' }); modal.value = type }
|
||||
async function submit() {
|
||||
saving.value = true
|
||||
try {
|
||||
if (modal.value === 'transfer') await transferWallet(active.value.id, { destination_wallet_id: form.destination_wallet_id, amount: form.amount })
|
||||
if (modal.value === 'withdraw') await withdrawWallet(active.value.id, { amount: form.amount, bank_code: form.bank_code, account_number: form.account_number, account_name: form.account_name })
|
||||
modal.value = ''; showSuccess('Permintaan berhasil diproses.'); await loadWallets()
|
||||
} catch (e) { showError(e?.response?.data?.message || 'Transaksi dompet gagal.') } finally { saving.value = false }
|
||||
}
|
||||
watch(activeId, loadLedger)
|
||||
onMounted(loadWallets)
|
||||
</script>
|
||||
<template><div class="brdvx-page-container"><div class="brdvx-page-header"><h5 class="mb-0 fw-semibold">Dompet</h5><small class="text-body-secondary">Saldo user dan tenant dengan pencatatan ledger</small></div><div v-if="loading" class="brdvx-loading-state"><CSpinner color="primary" /><p class="brdvx-loading-text">Memuat data...</p></div><template v-else>
|
||||
<div class="wallet-grid"><button v-for="wallet in wallets" :key="wallet.id" class="wallet-card" :class="{ active: active?.id === wallet.id }" @click="activeId = wallet.id"><span>{{ wallet.wallet_type === 'tenant' ? 'DOMPET TENANT' : 'DOMPET USER' }}</span><strong>{{ money(wallet.available_balance) }}</strong><small>Pending {{ money(wallet.pending_balance) }} · IDR</small></button></div>
|
||||
<div class="wallet-actions">
|
||||
<button type="button" class="wallet-action" disabled>
|
||||
<span class="wallet-action-icon topup"><CIcon icon="cil-data-transfer-down" size="xl" /></span>
|
||||
<span><strong>Isi Saldo</strong><small>Tambahkan saldo melalui payment gateway</small></span>
|
||||
</button>
|
||||
<button type="button" class="wallet-action" :disabled="!active" @click="open('withdraw')">
|
||||
<span class="wallet-action-icon withdraw"><CIcon icon="cil-data-transfer-up" size="xl" /></span>
|
||||
<span><strong>Tarik Saldo</strong><small>Kirim saldo ke rekening bank</small></span>
|
||||
</button>
|
||||
<button type="button" class="wallet-action" :disabled="!otherWallets.length" @click="open('transfer')">
|
||||
<span class="wallet-action-icon transfer"><CIcon icon="cil-transfer" size="xl" /></span>
|
||||
<span><strong>Pindah Saldo</strong><small>Transfer antara dompet user dan tenant</small></span>
|
||||
</button>
|
||||
</div>
|
||||
<h6 class="mt-4">Mutasi {{ active?.wallet_type === 'tenant' ? 'Dompet Tenant' : 'Dompet User' }}</h6><BaseResponsiveDataView :columns="columns" :items="ledger" server-side :pagination="pagination" @query-change="loadLedger" />
|
||||
</template></div>
|
||||
<CModal :visible="Boolean(modal)" @close="modal = ''"><CModalHeader><CModalTitle>{{ modal === 'transfer' ? 'Pindah Dompet' : 'Tarik Saldo' }}</CModalTitle></CModalHeader><CModalBody><CFormLabel>Nominal</CFormLabel><CFormInput v-model.number="form.amount" type="number" min="1" class="mb-3" /><template v-if="modal === 'transfer'"><CFormLabel>Dompet Tujuan</CFormLabel><CFormSelect v-model="form.destination_wallet_id"><option v-for="item in otherWallets" :key="item.id" :value="item.id">{{ item.wallet_type === 'tenant' ? 'Dompet Tenant' : 'Dompet User' }}</option></CFormSelect></template><template v-else><CFormLabel>Bank</CFormLabel><CFormInput v-model="form.bank_code" class="mb-2" /><CFormLabel>Nomor Rekening</CFormLabel><CFormInput v-model="form.account_number" class="mb-2" /><CFormLabel>Nama Pemilik</CFormLabel><CFormInput v-model="form.account_name" /></template></CModalBody><CModalFooter><CButton color="secondary" @click="modal = ''">Batal</CButton><CButton color="primary" :disabled="saving" @click="submit">Proses</CButton></CModalFooter></CModal></template>
|
||||
<style scoped>
|
||||
.wallet-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 400px));
|
||||
gap: 1rem;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.wallet-card {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
min-height: 130px;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding: 1.25rem;
|
||||
color: white;
|
||||
text-align: left;
|
||||
background: linear-gradient(135deg, #f97316, #ff8c00 58%, #f9b115);
|
||||
border: 3px solid transparent;
|
||||
border-radius: 1rem;
|
||||
box-shadow: 0 0.5rem 1rem rgba(249, 115, 22, 0.22);
|
||||
}
|
||||
|
||||
.wallet-card.active {
|
||||
border-color: rgba(255, 255, 255, 0.85);
|
||||
}
|
||||
|
||||
.wallet-card span,
|
||||
.wallet-card small {
|
||||
opacity: 0.82;
|
||||
}
|
||||
|
||||
.wallet-card strong {
|
||||
margin: 0.4rem 0;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.wallet-actions {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(min(100%, 210px), 250px));
|
||||
justify-content: center;
|
||||
gap: 0.75rem;
|
||||
max-width: 800px;
|
||||
margin: 1.25rem auto 0;
|
||||
}
|
||||
|
||||
.wallet-action {
|
||||
display: flex;
|
||||
min-height: 78px;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
padding: 0.75rem;
|
||||
color: var(--cui-body-color);
|
||||
text-align: left;
|
||||
background: var(--cui-body-bg);
|
||||
border: 1px solid var(--cui-border-color);
|
||||
border-radius: 0.85rem;
|
||||
box-shadow: 0 0.25rem 0.75rem rgba(0, 0, 0, 0.06);
|
||||
transition: transform 0.18s ease, border-color 0.18s ease, box-shadow 0.18s ease;
|
||||
}
|
||||
|
||||
.wallet-action:not(:disabled):hover {
|
||||
border-color: #ff8c00;
|
||||
box-shadow: 0 0.4rem 1rem rgba(249, 115, 22, 0.14);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.wallet-action:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
.wallet-action > span:last-child {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.wallet-action strong {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.wallet-action small {
|
||||
margin-top: 0.15rem;
|
||||
color: var(--cui-secondary-color);
|
||||
font-size: 0.7rem;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.wallet-action-icon {
|
||||
display: inline-flex;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
flex: 0 0 44px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
border-radius: 0.75rem;
|
||||
}
|
||||
|
||||
.wallet-action-icon.topup {
|
||||
background: linear-gradient(135deg, #f97316, #f9b115);
|
||||
}
|
||||
|
||||
.wallet-action-icon.withdraw {
|
||||
background: linear-gradient(135deg, #2eb85c, #1f9d55);
|
||||
}
|
||||
|
||||
.wallet-action-icon.transfer {
|
||||
background: linear-gradient(135deg, #3399ff, #5856d6);
|
||||
}
|
||||
|
||||
@media (max-width: 575.98px) {
|
||||
.wallet-action {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user