Allows users to configure their own wallet ID, enabling the system to track expenses and receivables on a per-user basis. Introduces new database table, models, API endpoints, and UI elements to manage user-specific wallet settings.
366 lines
12 KiB
HTML
366 lines
12 KiB
HTML
{% extends "base.html" %}
|
|
{% from "macros.jinja" import window_vars with context %}
|
|
|
|
{% block scripts %}
|
|
{{ window_vars(user) }}
|
|
<script src="{{ static_url_for('castle/static', path='js/index.js') }}"></script>
|
|
{% endblock %}
|
|
|
|
{% block page %}
|
|
<div class="row q-col-gutter-md">
|
|
<div class="col-12 col-md-8 col-lg-7 q-gutter-y-md">
|
|
<q-card>
|
|
<q-card-section>
|
|
<div class="row items-center no-wrap">
|
|
<div class="col">
|
|
<h5 class="q-my-none">🏰 Castle Accounting</h5>
|
|
<p class="q-mb-none">Track expenses, receivables, and balances for the collective</p>
|
|
</div>
|
|
<div class="col-auto">
|
|
<q-btn flat round icon="account_balance_wallet" @click="showUserWalletDialog">
|
|
<q-tooltip>Configure Your Wallet</q-tooltip>
|
|
</q-btn>
|
|
<q-btn v-if="isSuperUser" flat round icon="settings" @click="showSettingsDialog">
|
|
<q-tooltip>Castle Settings (Super User Only)</q-tooltip>
|
|
</q-btn>
|
|
</div>
|
|
</div>
|
|
</q-card-section>
|
|
</q-card>
|
|
|
|
<!-- Setup Warning -->
|
|
<q-banner v-if="!castleWalletConfigured && isSuperUser" class="bg-warning text-white" rounded>
|
|
<template v-slot:avatar>
|
|
<q-icon name="warning" color="white"></q-icon>
|
|
</template>
|
|
<div>
|
|
<strong>Setup Required:</strong> Castle Wallet ID must be configured before the extension can function.
|
|
</div>
|
|
<template v-slot:action>
|
|
<q-btn flat color="white" label="Configure Now" @click="showSettingsDialog"></q-btn>
|
|
</template>
|
|
</q-banner>
|
|
|
|
<q-banner v-if="!castleWalletConfigured && !isSuperUser" class="bg-info text-white" rounded>
|
|
<template v-slot:avatar>
|
|
<q-icon name="info" color="white"></q-icon>
|
|
</template>
|
|
<div>
|
|
<strong>Setup Required:</strong> This extension requires configuration by the super user before it can be used.
|
|
</div>
|
|
</q-banner>
|
|
|
|
<q-banner v-if="castleWalletConfigured && !userWalletConfigured" class="bg-orange text-white" rounded>
|
|
<template v-slot:avatar>
|
|
<q-icon name="account_balance_wallet" color="white"></q-icon>
|
|
</template>
|
|
<div>
|
|
<strong>Wallet Setup Required:</strong> You must configure your wallet before using this extension.
|
|
</div>
|
|
<template v-slot:action>
|
|
<q-btn flat color="white" label="Configure Wallet" @click="showUserWalletDialog"></q-btn>
|
|
</template>
|
|
</q-banner>
|
|
|
|
<!-- User Balance Card -->
|
|
<q-card>
|
|
<q-card-section>
|
|
<div class="row items-center no-wrap q-mb-sm">
|
|
<div class="col">
|
|
<h6 class="q-my-none">Your Balance</h6>
|
|
</div>
|
|
<div class="col-auto">
|
|
<q-btn flat round icon="refresh" @click="loadBalance">
|
|
<q-tooltip>Refresh balance</q-tooltip>
|
|
</q-btn>
|
|
</div>
|
|
</div>
|
|
<div v-if="balance !== null">
|
|
<div class="text-h4" :class="balance.balance >= 0 ? 'text-positive' : 'text-negative'">
|
|
{% raw %}{{ formatSats(Math.abs(balance.balance)) }} sats{% endraw %}
|
|
</div>
|
|
<div class="text-subtitle2">
|
|
{% raw %}{{ balance.balance >= 0 ? 'Castle owes you' : 'You owe Castle' }}{% endraw %}
|
|
</div>
|
|
<q-btn
|
|
v-if="balance.balance < 0"
|
|
color="primary"
|
|
class="q-mt-md"
|
|
@click="showPayBalanceDialog"
|
|
>
|
|
Pay Balance
|
|
</q-btn>
|
|
</div>
|
|
<div v-else>
|
|
<q-spinner color="primary" size="md"></q-spinner>
|
|
Loading balance...
|
|
</div>
|
|
</q-card-section>
|
|
</q-card>
|
|
|
|
<!-- Quick Actions -->
|
|
<q-card>
|
|
<q-card-section>
|
|
<h6 class="q-my-none q-mb-md">Quick Actions</h6>
|
|
<div class="row q-gutter-sm">
|
|
<q-btn
|
|
color="primary"
|
|
@click="expenseDialog.show = true"
|
|
:disable="!castleWalletConfigured || !userWalletConfigured"
|
|
>
|
|
Add Expense
|
|
<q-tooltip v-if="!castleWalletConfigured">
|
|
Castle wallet must be configured first
|
|
</q-tooltip>
|
|
<q-tooltip v-if="castleWalletConfigured && !userWalletConfigured">
|
|
You must configure your wallet first
|
|
</q-tooltip>
|
|
</q-btn>
|
|
<q-btn color="secondary" @click="loadTransactions">
|
|
View Transactions
|
|
</q-btn>
|
|
</div>
|
|
</q-card-section>
|
|
</q-card>
|
|
|
|
<!-- Recent Transactions -->
|
|
<q-card>
|
|
<q-card-section>
|
|
<div class="row items-center no-wrap q-mb-sm">
|
|
<div class="col">
|
|
<h6 class="q-my-none">Recent Transactions</h6>
|
|
</div>
|
|
<div class="col-auto">
|
|
<q-btn flat round icon="refresh" @click="loadTransactions">
|
|
<q-tooltip>Refresh transactions</q-tooltip>
|
|
</q-btn>
|
|
</div>
|
|
</div>
|
|
<q-list v-if="transactions.length > 0" separator>
|
|
<q-item v-for="entry in transactions" :key="entry.id">
|
|
<q-item-section>
|
|
<q-item-label>{% raw %}{{ entry.description }}{% endraw %}</q-item-label>
|
|
<q-item-label caption>
|
|
{% raw %}{{ formatDate(entry.entry_date) }}{% endraw %}
|
|
</q-item-label>
|
|
</q-item-section>
|
|
<q-item-section side>
|
|
<q-item-label>{% raw %}{{ formatSats(getTotalAmount(entry)) }} sats{% endraw %}</q-item-label>
|
|
</q-item-section>
|
|
</q-item>
|
|
</q-list>
|
|
<div v-else class="text-center q-pa-md text-grey">
|
|
No transactions yet
|
|
</div>
|
|
</q-card-section>
|
|
</q-card>
|
|
</div>
|
|
|
|
<div class="col-12 col-md-4 col-lg-5 q-gutter-y-md">
|
|
<!-- Chart of Accounts -->
|
|
<q-card>
|
|
<q-card-section>
|
|
<h6 class="q-my-none q-mb-md">Chart of Accounts</h6>
|
|
<q-list dense v-if="accounts.length > 0">
|
|
<q-item v-for="account in accounts" :key="account.id">
|
|
<q-item-section>
|
|
<q-item-label>{% raw %}{{ account.name }}{% endraw %}</q-item-label>
|
|
<q-item-label caption>{% raw %}{{ account.account_type }}{% endraw %}</q-item-label>
|
|
</q-item-section>
|
|
</q-item>
|
|
</q-list>
|
|
<div v-else>
|
|
<q-spinner color="primary" size="sm"></q-spinner>
|
|
Loading accounts...
|
|
</div>
|
|
</q-card-section>
|
|
</q-card>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Add Expense Dialog -->
|
|
<q-dialog v-model="expenseDialog.show" position="top">
|
|
<q-card v-if="expenseDialog.show" class="q-pa-lg q-pt-xl lnbits__dialog-card">
|
|
<q-form @submit="submitExpense" class="q-gutter-md">
|
|
<div class="text-h6 q-mb-md">Add Expense</div>
|
|
|
|
<q-input
|
|
filled
|
|
dense
|
|
v-model.trim="expenseDialog.description"
|
|
label="Description *"
|
|
placeholder="e.g., Groceries for the house"
|
|
></q-input>
|
|
|
|
<q-select
|
|
filled
|
|
dense
|
|
v-model="expenseDialog.currency"
|
|
:options="currencyOptions"
|
|
option-label="label"
|
|
option-value="value"
|
|
emit-value
|
|
map-options
|
|
label="Currency"
|
|
></q-select>
|
|
|
|
<q-input
|
|
filled
|
|
dense
|
|
v-model.number="expenseDialog.amount"
|
|
type="number"
|
|
:label="amountLabel"
|
|
min="0.01"
|
|
step="0.01"
|
|
></q-input>
|
|
|
|
<q-select
|
|
filled
|
|
dense
|
|
v-model="expenseDialog.expenseAccount"
|
|
:options="expenseAccounts"
|
|
option-label="name"
|
|
option-value="id"
|
|
emit-value
|
|
map-options
|
|
label="Expense Category *"
|
|
></q-select>
|
|
|
|
<q-select
|
|
filled
|
|
dense
|
|
v-model="expenseDialog.isEquity"
|
|
:options="[
|
|
{label: 'Liability (Castle owes me)', value: false},
|
|
{label: 'Equity (My contribution)', value: true}
|
|
]"
|
|
option-label="label"
|
|
option-value="value"
|
|
emit-value
|
|
map-options
|
|
label="Type *"
|
|
></q-select>
|
|
|
|
<q-input
|
|
filled
|
|
dense
|
|
v-model.trim="expenseDialog.reference"
|
|
label="Reference (optional)"
|
|
placeholder="e.g., Receipt #123"
|
|
></q-input>
|
|
|
|
<div class="row q-mt-lg">
|
|
<q-btn unelevated color="primary" type="submit" :loading="expenseDialog.loading">
|
|
Submit Expense
|
|
</q-btn>
|
|
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Cancel</q-btn>
|
|
</div>
|
|
</q-form>
|
|
</q-card>
|
|
</q-dialog>
|
|
|
|
<!-- Pay Balance Dialog -->
|
|
<q-dialog v-model="payDialog.show" position="top">
|
|
<q-card v-if="payDialog.show" class="q-pa-lg q-pt-xl lnbits__dialog-card">
|
|
<q-form @submit="submitPayment" class="q-gutter-md">
|
|
<div class="text-h6 q-mb-md">Pay Balance</div>
|
|
|
|
<div v-if="balance" class="q-mb-md">
|
|
Amount owed: <strong>{% raw %}{{ formatSats(Math.abs(balance.balance)) }}{% endraw %} sats</strong>
|
|
</div>
|
|
|
|
<q-input
|
|
filled
|
|
dense
|
|
v-model.number="payDialog.amount"
|
|
type="number"
|
|
label="Amount to pay (sats) *"
|
|
min="1"
|
|
:max="balance ? Math.abs(balance.balance) : 0"
|
|
></q-input>
|
|
|
|
<div class="row q-mt-lg">
|
|
<q-btn unelevated color="primary" type="submit" :loading="payDialog.loading">
|
|
Generate Invoice
|
|
</q-btn>
|
|
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Cancel</q-btn>
|
|
</div>
|
|
</q-form>
|
|
</q-card>
|
|
</q-dialog>
|
|
|
|
<!-- Settings Dialog -->
|
|
<q-dialog v-model="settingsDialog.show" position="top">
|
|
<q-card v-if="settingsDialog.show" class="q-pa-lg q-pt-xl lnbits__dialog-card">
|
|
<q-form @submit="submitSettings" class="q-gutter-md">
|
|
<div class="text-h6 q-mb-md">Castle Settings</div>
|
|
|
|
<q-banner v-if="!isSuperUser" class="bg-warning text-dark q-mb-md" dense rounded>
|
|
<template v-slot:avatar>
|
|
<q-icon name="lock" color="orange"></q-icon>
|
|
</template>
|
|
<div class="text-caption">
|
|
<strong>Super User Only:</strong> Only the super user can modify these settings.
|
|
</div>
|
|
</q-banner>
|
|
|
|
<q-input
|
|
filled
|
|
dense
|
|
v-model.trim="settingsDialog.castleWalletId"
|
|
label="Castle Wallet ID *"
|
|
placeholder="The wallet ID that represents the Castle"
|
|
:readonly="!isSuperUser"
|
|
></q-input>
|
|
|
|
<div class="text-caption text-grey">
|
|
This wallet will be used for Castle operations and transactions.
|
|
</div>
|
|
|
|
<div class="row q-mt-lg">
|
|
<q-btn
|
|
v-if="isSuperUser"
|
|
unelevated
|
|
color="primary"
|
|
type="submit"
|
|
:loading="settingsDialog.loading"
|
|
>
|
|
Save Settings
|
|
</q-btn>
|
|
<q-btn v-close-popup flat color="grey" :class="isSuperUser ? 'q-ml-auto' : ''">
|
|
{% raw %}{{ isSuperUser ? 'Cancel' : 'Close' }}{% endraw %}
|
|
</q-btn>
|
|
</div>
|
|
</q-form>
|
|
</q-card>
|
|
</q-dialog>
|
|
|
|
<!-- User Wallet Dialog -->
|
|
<q-dialog v-model="userWalletDialog.show" position="top">
|
|
<q-card v-if="userWalletDialog.show" class="q-pa-lg q-pt-xl lnbits__dialog-card">
|
|
<q-form @submit="submitUserWallet" class="q-gutter-md">
|
|
<div class="text-h6 q-mb-md">Configure Your Wallet</div>
|
|
|
|
<q-input
|
|
filled
|
|
dense
|
|
v-model.trim="userWalletDialog.userWalletId"
|
|
label="Your Wallet ID *"
|
|
placeholder="Enter your wallet ID"
|
|
></q-input>
|
|
|
|
<div class="text-caption text-grey">
|
|
This is the wallet you'll use for Castle transactions. You can find your wallet ID in the LNbits wallet settings.
|
|
</div>
|
|
|
|
<div class="row q-mt-lg">
|
|
<q-btn unelevated color="primary" type="submit" :loading="userWalletDialog.loading">
|
|
Save Wallet
|
|
</q-btn>
|
|
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Cancel</q-btn>
|
|
</div>
|
|
</q-form>
|
|
</q-card>
|
|
</q-dialog>
|
|
|
|
{% endblock %}
|