[feat] Payment labels (#3537)

This commit is contained in:
Vlad Stan
2025-11-21 10:33:53 +02:00
committed by GitHub
parent 3ccefb70fa
commit 7c7a04da9d
26 changed files with 930 additions and 39 deletions
+1
View File
@@ -21,6 +21,7 @@ include('components/lnbits-manage-wallet-list.vue') %} {%
include('components/lnbits-language-dropdown.vue') %} {%
include('components/lnbits-payment-list.vue') %} {%
include('components/lnbits-wallet-new.vue') %} {%
include('components/lnbits-label-selector.vue') %} {%
include('components/lnbits-wallet-api-docs.vue') %} {%
include('components/lnbits-wallet-share.vue') %} {%
include('components/lnbits-wallet-charts.vue') %}
@@ -0,0 +1,107 @@
<template id="lnbits-label-selector">
<div v-if="g.user.extra.labels?.length">
<q-item header>
<q-input
v-model="labelFilter"
:label="$t('filter_labels')"
class="full-width"
filled
dense
autofocus
>
</q-input>
</q-item>
<q-separator></q-separator>
<q-scroll-area style="height: 230px; max-width: 300px">
<div v-for="label in g.user.extra.labels" :key="label.name">
<q-item
v-if="
!labelFilter ||
label.name.toLowerCase().includes(labelFilter.toLowerCase())
"
clickable
v-ripple
>
<q-item-section avatar top>
<q-checkbox
:model-value="localLabels.includes(label.name)"
@click="toggleLabel(label)"
dense
>
</q-checkbox>
</q-item-section>
<q-item-section>
<q-item-label lines="1"
><span v-text="label.name"></span
></q-item-label>
<q-item-label caption
><span v-text="label.description"></span
></q-item-label>
</q-item-section>
<q-item-section side>
<q-badge
class="q-pa-sm"
size="xs"
rounded
:style="{
backgroundColor: label.color,
color: 'white'
}"
>
<span v-text="label.color"></span>
</q-badge>
</q-item-section>
</q-item>
</div>
</q-scroll-area>
<q-item footer>
<q-btn
v-close-popup
flat
color="grey"
class="q-ml-none"
icon="highlight_off"
@click="clearLabels"
>
<q-tooltip>
<span v-text="$t('clear')"></span>
</q-tooltip>
</q-btn>
<q-btn
v-close-popup
flat
href="/account#labels"
color="grey"
icon="settings"
class="q-ml-auto float-right"
>
<q-tooltip>
<span v-text="$t('manage_labels')"></span>
</q-tooltip>
</q-btn>
<q-btn
v-close-popup
flat
color="primary"
class="q-ml-none"
:label="$t('ok')"
@click="saveLabels"
>
<q-tooltip>
<span v-text="$t('manage_labels')"></span>
</q-tooltip>
</q-btn>
</q-item>
</div>
<div v-else class="q-pa-md">
<p v-text="$t('no_labels_defined')"></p>
<q-btn
flat
href="/account#labels"
color="primary"
:label="$t('manage_labels')"
></q-btn>
</div>
</template>
@@ -22,7 +22,12 @@
</q-input>
</div>
<div class="gt-sm col-auto">
<q-btn icon="event" flat color="grey">
<q-btn icon="event" flat color="grey" class="q-pa-sm">
<q-badge
v-if="searchDate?.to || searchDate?.from"
color="primary"
floating
></q-badge>
<q-popup-proxy cover transition-show="scale" transition-hide="scale">
<q-date v-model="searchDate" mask="YYYY-MM-DD" range />
<div class="row">
@@ -48,19 +53,30 @@
</div>
</div>
</q-popup-proxy>
<q-badge
v-if="searchDate?.to || searchDate?.from"
class="q-mt-lg q-mr-md"
color="primary"
rounded
floating
style="border-radius: 6px"
></q-badge>
<q-tooltip>
<span v-text="$t('filter_date')"></span>
</q-tooltip>
</q-btn>
<q-btn color="grey" icon="filter_alt" flat>
<q-btn icon="local_offer" class="q-pa-sm" flat color="grey">
<q-badge
v-if="filterLabels.length"
color="primary"
size="xs"
floating
v-text="filterLabels.length"
></q-badge>
<q-tooltip>
<span v-text="$t('label_filter')"></span>
</q-tooltip>
<q-popup-edit class="text-white q-pa-none">
<lnbits-label-selector
:labels="filterLabels"
@update:labels="searchByLabels"
></lnbits-label-selector>
</q-popup-edit>
</q-btn>
<q-btn color="grey" icon="filter_alt" class="q-pa-sm" flat>
<q-menu>
<q-item dense>
<q-checkbox
@@ -109,7 +125,7 @@
persistent
icon="archive"
split
class="q-mr-sm"
class="q-mr-sm q-pa-sm"
color="grey"
@click="exportCSV(false)"
>
@@ -263,6 +279,42 @@
<span class="text-grey-5" v-text="props.row.dateFrom"></span>
<q-tooltip><span v-text="props.row.date"></span></q-tooltip>
</i>
<q-icon
@click="selectedPayment = props.row"
name="local_offer"
color="grey"
class="q-ml-sm cursor-pointer"
size="xs"
>
<q-tooltip>
<span v-text="$t('add_remove_labels')"></span>
</q-tooltip>
<q-popup-edit class="text-white q-pa-none">
<lnbits-label-selector
:labels="props.row.labels"
@update:labels="savePaymentLabels"
></lnbits-label-selector>
</q-popup-edit>
</q-icon>
<template v-for="label in g.user.extra.labels" :key="label.name">
<q-badge
v-if="props.row.labels.includes(label.name)"
@click="searchByLabels([label.name])"
:style="{
backgroundColor: label.color,
color: isLightColor(label.color) ? 'black' : 'white'
}"
class="q-ml-sm cursor-pointer"
size="xs"
dense
rounded
>
<span v-text="label.name"></span>
<q-tooltip>
<span v-text="label.description || label.name"></span>
</q-tooltip>
</q-badge>
</template>
</q-td>
<q-td
auto-width
+188 -5
View File
@@ -6,8 +6,17 @@
<div class="row items-center justify-between q-gutter-xs">
<div class="col">
<q-btn @click="updateAccount" unelevated color="primary">
<q-badge
v-if="isUserTouched"
color="negative"
size="xs"
floating
></q-badge>
<span v-text="$t('update_account')"></span>
</q-btn>
<q-badge v-if="isUserTouched" class="q-ml-sm" color="primary">
<span v-text="$t('must_save')"></span>
</q-badge>
</div>
</div>
</div>
@@ -20,6 +29,7 @@
<q-card>
<q-splitter>
<template v-slot:before>
<!-- todo: small screen as in settings -->
<q-tabs v-model="tab" vertical active-color="primary">
<q-tab
name="user"
@@ -72,6 +82,16 @@
><span v-text="$t('assets')"></span
></q-tooltip>
</q-tab>
<q-tab
name="labels"
icon="local_offer"
:label="$q.screen.gt.sm ? $t('labels') : ''"
@update="val => (tab = val.name)"
>
<q-tooltip v-if="!$q.screen.gt.sm"
><span v-text="$t('labels')"></span
></q-tooltip>
</q-tab>
</q-tabs>
</template>
<template v-slot:after>
@@ -1027,6 +1047,92 @@
</q-table>
</q-card-section>
</q-tab-panel>
<q-tab-panel name="labels">
<q-card-section>
<div class="row">
<div class="col-md-2 col-sm-12">
<q-btn
@click="openAddLabelDialog()"
:label="$t('add_label')"
color="primary"
class="full-width"
></q-btn>
</div>
<div class="col-md-1 col-sm-12"></div>
<div class="col-md-9 col-sm-12">
<q-input
:label="$t('search')"
dense
class="full-width q-pb-xl"
v-model="labelsTable.search"
>
<template v-slot:before>
<q-icon name="search"> </q-icon>
</template>
<template v-slot:append>
<q-icon
v-if="labelsTable.search !== ''"
name="close"
@click="labelsTable.search = ''"
class="cursor-pointer"
>
</q-icon>
</template>
</q-input>
</div>
</div>
</q-card-section>
<q-separator></q-separator>
<q-card-section>
<q-table
:rows="user.extra.labels"
:columns="labelsTable.columns"
v-model:pagination="labelsTable.pagination"
:loading="labelsTable.loading"
row-key="name"
:filter="labelsTable.search"
>
<template v-slot:body="props">
<q-tr :props="props">
<q-td key="actions" :props="props">
<q-btn
@click="openEditLabelDialog(props.row)"
dense
flat
icon="edit"
color="primary"
></q-btn>
<q-btn
@click="deleteUserLabel(props.row)"
dense
flat
icon="delete"
color="negative"
class="q-ml-md"
></q-btn>
</q-td>
<q-td key="name" :props="props">
<span v-text="props.row.name"></span>
</q-td>
<q-td key="description" :props="props">
<span v-text="props.row.description"></span>
</q-td>
<q-td key="color" :props="props">
<q-badge
class="q-pa-sm"
:style="{
backgroundColor: props.row.color,
color: 'white'
}"
>
<span v-text="props.row.color"></span>
</q-badge>
</q-td>
</q-tr>
</template>
</q-table>
</q-card-section>
</q-tab-panel>
</q-tab-panels>
</q-scroll-area>
</template>
@@ -1052,11 +1158,6 @@
</div>
</div>
<div class="row q-mt-lg">
<q-btn
@click="runPasswordGuardedFunction()"
:label="$t('ok')"
color="primary"
></q-btn>
<q-btn
v-close-popup
flat
@@ -1064,6 +1165,11 @@
class="q-ml-auto"
v-text="$t('cancel')"
></q-btn>
<q-btn
@click="runPasswordGuardedFunction()"
:label="$t('ok')"
color="primary"
></q-btn>
</div>
</q-card>
</q-dialog>
@@ -1167,4 +1273,81 @@
</div>
</q-card>
</q-dialog>
<q-dialog v-model="labelsDialog.show" position="top">
<q-card class="q-pa-md q-pt-md lnbits__dialog-card">
<strong v-text="$t('label')"></strong>
<div class="row q-mt-md q-col-gutter-md">
<div class="col-12">
<q-input
v-model="labelsDialog.data.name"
dense
filled
:label="$t('name')"
>
</q-input>
</div>
<div class="col-12">
<q-input
v-model="labelsDialog.data.description"
dense
filled
type="textarea"
rows="3"
:label="$t('description')"
>
</q-input>
</div>
<div class="col-12">
<q-input
v-model="labelsDialog.data.color"
filled
dense
class="my-input"
>
<template v-slot:append>
<q-icon name="colorize" class="cursor-pointer">
<q-popup-proxy
cover
transition-show="scale"
transition-hide="scale"
>
<q-color
v-model="labelsDialog.data.color"
default-view="palette"
/>
</q-popup-proxy>
</q-icon>
</template>
</q-input>
</div>
</div>
<div class="row q-mt-lg">
<q-btn
v-close-popup
flat
color="grey"
class="q-ml-auto"
v-text="$t('cancel')"
></q-btn>
<q-btn
v-if="
user.extra.labels.some(
label => label.name === labelsDialog.data.name
)
"
@click="updateUserLabel()"
:disable="!labelsDialog.data.name || !labelsDialog.data.color"
:label="$t('update_label')"
color="primary"
></q-btn>
<q-btn
v-else
@click="addUserLabel()"
:disable="!labelsDialog.data.name || !labelsDialog.data.color"
:label="$t('add_label')"
color="primary"
></q-btn>
</div>
</q-card>
</q-dialog>
</template>