Refactor DCA client management: remove CRUD operations for clients and update UI to focus on deposit management. Introduce quick deposit form for existing clients.

This commit is contained in:
padreug 2025-06-17 19:28:53 +02:00
parent 7bafc67370
commit b3332e585a
3 changed files with 103 additions and 174 deletions

View file

@ -37,13 +37,6 @@ window.app = Vue.createApp({
}, },
// Dialog states // Dialog states
clientFormDialog: {
show: false,
data: {
dca_mode: 'flow',
currency: 'GTQ'
}
},
depositFormDialog: { depositFormDialog: {
show: false, show: false,
data: { data: {
@ -56,11 +49,14 @@ window.app = Vue.createApp({
balance: null balance: null
}, },
// Quick deposit form
quickDepositForm: {
client_id: '',
amount: null,
notes: ''
},
// Options // Options
dcaModeOptions: [
{label: 'Flow Mode', value: 'flow'},
{label: 'Fixed Mode', value: 'fixed'}
],
currencyOptions: [ currencyOptions: [
{label: 'GTQ', value: 'GTQ'}, {label: 'GTQ', value: 'GTQ'},
{label: 'USD', value: 'USD'} {label: 'USD', value: 'USD'}
@ -123,43 +119,35 @@ window.app = Vue.createApp({
} }
}, },
async sendClientData() { // Quick Deposit Methods
async sendQuickDeposit() {
try { try {
const data = { const data = {
user_id: this.clientFormDialog.data.user_id, client_id: this.quickDepositForm.client_id,
wallet_id: this.clientFormDialog.data.wallet_id, amount: this.quickDepositForm.amount,
dca_mode: this.clientFormDialog.data.dca_mode, currency: 'GTQ',
fixed_mode_daily_limit: this.clientFormDialog.data.fixed_mode_daily_limit notes: this.quickDepositForm.notes
} }
if (this.clientFormDialog.data.id) { const {data: newDeposit} = await LNbits.api.request(
// Update existing client
const {data: updatedClient} = await LNbits.api.request(
'PUT',
`/myextension/api/v1/dca/clients/${this.clientFormDialog.data.id}`,
this.g.user.wallets[0].adminkey,
data
)
// Update client in array
const index = this.dcaClients.findIndex(c => c.id === updatedClient.id)
if (index !== -1) {
this.dcaClients.splice(index, 1, updatedClient)
}
} else {
// Create new client
const {data: newClient} = await LNbits.api.request(
'POST', 'POST',
'/myextension/api/v1/dca/clients', '/myextension/api/v1/dca/deposits',
this.g.user.wallets[0].adminkey, this.g.user.wallets[0].adminkey,
data data
) )
this.dcaClients.push(newClient)
this.deposits.unshift(newDeposit)
// Reset form
this.quickDepositForm = {
client_id: '',
amount: null,
notes: ''
} }
this.closeClientFormDialog()
this.$q.notify({ this.$q.notify({
type: 'positive', type: 'positive',
message: this.clientFormDialog.data.id ? 'Client updated successfully' : 'Client created successfully', message: 'Deposit created successfully',
timeout: 5000 timeout: 5000
}) })
} catch (error) { } catch (error) {
@ -167,19 +155,6 @@ window.app = Vue.createApp({
} }
}, },
closeClientFormDialog() {
this.clientFormDialog.show = false
this.clientFormDialog.data = {
dca_mode: 'flow',
currency: 'GTQ'
}
},
editClient(client) {
this.clientFormDialog.data = {...client}
this.clientFormDialog.show = true
},
async viewClientDetails(client) { async viewClientDetails(client) {
try { try {
const {data: balance} = await LNbits.api.request( const {data: balance} = await LNbits.api.request(
@ -521,6 +496,13 @@ window.app = Vue.createApp({
}, },
computed: { computed: {
clientOptions() {
return this.dcaClients.map(client => ({
label: `${client.user_id.substring(0, 8)}... (${client.dca_mode})`,
value: client.id
}))
},
calculateTotalDcaBalance() { calculateTotalDcaBalance() {
this.totalDcaBalance = this.deposits this.totalDcaBalance = this.deposits
.filter(d => d.status === 'confirmed') .filter(d => d.status === 'confirmed')

View file

@ -8,17 +8,13 @@
{% endblock %} {% block page %} {% endblock %} {% block page %}
<div class="row q-col-gutter-md" id="dcaAdmin"> <div class="row q-col-gutter-md" id="dcaAdmin">
<div class="col-12 col-md-8 col-lg-7 q-gutter-y-md"> <div class="col-12 col-md-8 col-lg-7 q-gutter-y-md">
<!-- Client Management Section --> <!-- Deposit Management Section -->
<q-card> <q-card>
<q-card-section> <q-card-section>
<div class="row items-center no-wrap q-mb-md"> <div class="row items-center no-wrap q-mb-md">
<div class="col"> <div class="col">
<h5 class="text-subtitle1 q-my-none">DCA Client Management</h5> <h5 class="text-subtitle1 q-my-none">DCA Deposit Management</h5>
</div> <p class="text-caption q-my-none">Manage fiat deposits for existing DCA clients</p>
<div class="col-auto">
<q-btn unelevated color="primary" @click="clientFormDialog.show = true">
Add New Client
</q-btn>
</div> </div>
</div> </div>
</q-card-section> </q-card-section>
@ -29,7 +25,8 @@
<q-card-section> <q-card-section>
<div class="row items-center no-wrap q-mb-md"> <div class="row items-center no-wrap q-mb-md">
<div class="col"> <div class="col">
<h6 class="text-subtitle2 q-my-none">Active DCA Clients</h6> <h6 class="text-subtitle2 q-my-none">Registered DCA Clients</h6>
<p class="text-caption q-my-none">Clients registered via the DCA client extension</p>
</div> </div>
<div class="col-auto"> <div class="col-auto">
<q-btn flat color="grey" @click="exportClientsCSV">Export to CSV</q-btn> <q-btn flat color="grey" @click="exportClientsCSV">Export to CSV</q-btn>
@ -53,7 +50,10 @@
${ col.value } ${ col.value }
</q-badge> </q-badge>
</div> </div>
<div v-else>${ col.value }</div> <div v-else-if="col.field == 'fixed_mode_daily_limit' && col.value">
${ formatCurrency(col.value) }
</div>
<div v-else>${ col.value || '-' }</div>
</q-td> </q-td>
<q-td auto-width> <q-td auto-width>
<q-btn <q-btn
@ -65,17 +65,10 @@
</q-btn> </q-btn>
<q-btn <q-btn
flat dense size="sm" icon="visibility" flat dense size="sm" icon="visibility"
color="blue" class="q-mr-sm" color="blue"
@click="viewClientDetails(props.row)" @click="viewClientDetails(props.row)"
> >
<q-tooltip>View Details</q-tooltip> <q-tooltip>View Balance & Details</q-tooltip>
</q-btn>
<q-btn
flat dense size="sm" icon="edit"
color="orange" class="q-mr-sm"
@click="editClient(props.row)"
>
<q-tooltip>Edit Client</q-tooltip>
</q-btn> </q-btn>
</q-td> </q-td>
</q-tr> </q-tr>
@ -270,68 +263,64 @@
</div> </div>
<!--/////////////////////////////////////////////////--> <!--/////////////////////////////////////////////////-->
<!--//////////////DCA CLIENT FORM DIALOG//////////////--> <!--//////////////QUICK ADD DEPOSIT SECTION//////////-->
<!--/////////////////////////////////////////////////--> <!--/////////////////////////////////////////////////-->
<q-dialog v-model="clientFormDialog.show" position="top" @hide="closeClientFormDialog"> <q-card v-if="dcaClients.length > 0">
<q-card class="q-pa-lg q-pt-xl" style="width: 500px"> <q-card-section>
<q-form @submit="sendClientData" class="q-gutter-md"> <h6 class="text-subtitle2 q-my-none">Quick Add Deposit</h6>
<q-input <p class="text-caption q-my-none">Add a new deposit for an existing client</p>
filled <q-form @submit="sendQuickDeposit" class="q-gutter-md q-mt-md">
dense <div class="row q-gutter-md">
v-model.trim="clientFormDialog.data.user_id" <div class="col">
label="User ID *"
placeholder="LNBits User ID"
></q-input>
<q-select <q-select
filled filled
dense dense
emit-value emit-value
v-model="clientFormDialog.data.wallet_id" v-model="quickDepositForm.client_id"
:options="g.user.walletOptions" :options="clientOptions"
label="DCA Wallet *" label="Select Client *"
hint="Wallet to receive DCA payments" option-label="label"
></q-select> option-value="value"
<q-select
filled
dense
emit-value
v-model="clientFormDialog.data.dca_mode"
:options="dcaModeOptions"
label="DCA Mode *"
></q-select> ></q-select>
</div>
<div class="col">
<q-input <q-input
v-if="clientFormDialog.data.dca_mode === 'fixed'"
filled filled
dense dense
type="number" type="number"
v-model.number="clientFormDialog.data.fixed_mode_daily_limit" v-model.number="quickDepositForm.amount"
label="Daily Limit (GTQ)" label="Amount (GTQ) *"
placeholder="Maximum daily DCA amount" placeholder="Amount in centavos (GTQ * 100)"
hint="Enter amount in centavos"
></q-input> ></q-input>
<div class="row q-mt-lg"> </div>
<div class="col-auto">
<q-btn <q-btn
v-if="clientFormDialog.data.id"
unelevated unelevated
color="primary" color="primary"
type="submit" type="submit"
>Update Client</q-btn :disable="!quickDepositForm.client_id || !quickDepositForm.amount"
> >Add Deposit</q-btn
<q-btn
v-else
unelevated
color="primary"
:disable="!clientFormDialog.data.user_id || !clientFormDialog.data.wallet_id"
type="submit"
>Create Client</q-btn
>
<q-btn v-close-popup flat color="grey" class="q-ml-auto"
>Cancel</q-btn
> >
</div> </div>
</div>
<div class="row">
<div class="col">
<q-input
filled
dense
type="textarea"
v-model.trim="quickDepositForm.notes"
label="Notes (Optional)"
placeholder="Optional notes about this deposit"
rows="2"
></q-input>
</div>
</div>
</q-form> </q-form>
</q-card-section>
</q-card> </q-card>
</q-dialog>
<!--/////////////////////////////////////////////////--> <!--/////////////////////////////////////////////////-->
<!--//////////////DEPOSIT FORM DIALOG////////////////--> <!--//////////////DEPOSIT FORM DIALOG////////////////-->

View file

@ -220,50 +220,8 @@ async def api_get_dca_client(
return client return client
@myextension_api_router.post("/api/v1/dca/clients", status_code=HTTPStatus.CREATED) # Note: Client creation/update/delete will be handled by the DCA client extension
async def api_create_dca_client( # Admin extension only reads existing clients and manages their deposits
data: CreateDcaClientData,
wallet: WalletTypeInfo = Depends(require_admin_key),
) -> DcaClient:
"""Create a new DCA client"""
return await create_dca_client(data)
@myextension_api_router.put("/api/v1/dca/clients/{client_id}")
async def api_update_dca_client(
client_id: str,
data: UpdateDcaClientData,
wallet: WalletTypeInfo = Depends(require_admin_key),
) -> DcaClient:
"""Update a DCA client"""
client = await get_dca_client(client_id)
if not client:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="DCA client not found."
)
updated_client = await update_dca_client(client_id, data)
if not updated_client:
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail="Failed to update client."
)
return updated_client
@myextension_api_router.delete("/api/v1/dca/clients/{client_id}")
async def api_delete_dca_client(
client_id: str,
wallet: WalletTypeInfo = Depends(require_admin_key),
):
"""Delete a DCA client"""
client = await get_dca_client(client_id)
if not client:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="DCA client not found."
)
await delete_dca_client(client_id)
return {"message": "Client deleted successfully"}
@myextension_api_router.get("/api/v1/dca/clients/{client_id}/balance") @myextension_api_router.get("/api/v1/dca/clients/{client_id}/balance")