1187 lines
37 KiB
Vue
1187 lines
37 KiB
Vue
<template>
|
|
<q-page class="ris-activity-page">
|
|
<!-- Azioni RIS -->
|
|
<section class="ris-actions-section">
|
|
<div class="ris-actions-grid">
|
|
<q-btn
|
|
unelevated
|
|
class="ris-action-btn send-btn"
|
|
@click="handleSend"
|
|
>
|
|
<q-icon name="arrow_upward" size="24px" />
|
|
<span>Invia RIS</span>
|
|
</q-btn>
|
|
<q-btn
|
|
unelevated
|
|
class="ris-action-btn receive-btn"
|
|
@click="handleReceive"
|
|
>
|
|
<q-icon name="arrow_downward" size="24px" />
|
|
<span>Ricevi RIS</span>
|
|
</q-btn>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Quick Actions Tabs (Desktop) -->
|
|
<section class="quick-actions-section desktop-only">
|
|
<div class="quick-actions-grid">
|
|
<q-btn
|
|
flat
|
|
class="quick-action-btn"
|
|
:class="{ active: selectedTab === 'transactions' }"
|
|
@click="changeTab('transactions')"
|
|
>
|
|
<div class="btn-content">
|
|
<q-icon name="swap_vert" size="28px" />
|
|
<span>Invia Ricevi</span>
|
|
</div>
|
|
</q-btn>
|
|
<q-btn
|
|
flat
|
|
class="quick-action-btn"
|
|
:class="{ active: selectedTab === 'statistics' }"
|
|
@click="changeTab('statistics')"
|
|
>
|
|
<div class="btn-content">
|
|
<q-icon name="bar_chart" size="28px" />
|
|
<span>Statistiche</span>
|
|
</div>
|
|
</q-btn>
|
|
<q-btn
|
|
flat
|
|
class="quick-action-btn"
|
|
:class="{ active: selectedTab === 'circuits' }"
|
|
@click="changeTab('circuits')"
|
|
>
|
|
<div class="btn-content">
|
|
<q-icon name="account_balance" size="28px" />
|
|
<span>Circuiti</span>
|
|
</div>
|
|
</q-btn>
|
|
<q-btn
|
|
flat
|
|
class="quick-action-btn"
|
|
:class="{ active: selectedTab === 'settings' }"
|
|
@click="changeTab('settings')"
|
|
>
|
|
<div class="btn-content">
|
|
<q-icon name="settings" size="28px" />
|
|
<span>Impostazioni</span>
|
|
</div>
|
|
</q-btn>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Tab: Transazioni -->
|
|
<div
|
|
v-show="selectedTab === 'transactions'"
|
|
class="tab-content"
|
|
>
|
|
<!-- Circuiti abilitati -->
|
|
<section class="circuits-section">
|
|
<div class="circuits-header">
|
|
<h2 class="section-title">
|
|
<q-icon name="account_balance_wallet" size="20px" class="title-icon" />
|
|
Circuiti Abilitati
|
|
</h2>
|
|
<span class="scroll-hint">Scorri per vedere altri →</span>
|
|
</div>
|
|
|
|
<!-- Container scrollabile per i circuiti -->
|
|
<div class="circuits-scroll-container">
|
|
<div class="circuits-list">
|
|
<div
|
|
v-for="circuit in userCircuits"
|
|
:key="circuit._id"
|
|
class="circuit-card"
|
|
:class="{ selected: selectedCircuit?._id === circuit._id }"
|
|
@click="selectCircuit(circuit)"
|
|
>
|
|
<div class="circuit-card-content">
|
|
<div class="circuit-left">
|
|
<div class="circuit-header">
|
|
<span class="circuit-name">{{ circuit.name }}</span>
|
|
<q-badge
|
|
:color="circuit.isCircItalia ? 'deep-purple' : 'cyan'"
|
|
class="circuit-badge"
|
|
>
|
|
{{ circuitStore.getTypeCircuit(circuit) }}
|
|
</q-badge>
|
|
</div>
|
|
<div class="circuit-balance">
|
|
{{ circuitStore.getSaldoByCircuitId(circuit._id) }}
|
|
<span class="currency">{{ circuit.symbol }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="circuit-right">
|
|
<div class="limit-row fido">
|
|
<q-icon name="remove_circle_outline" size="16px" />
|
|
<span class="limit-label">Fido:</span>
|
|
<span class="limit-value">{{
|
|
circuitStore.getFidoConcessoByUsername(
|
|
userStore.my,
|
|
circuit._id,
|
|
userStore.my.username,
|
|
''
|
|
) || 0
|
|
}}</span>
|
|
</div>
|
|
<div class="limit-row max">
|
|
<q-icon name="add_circle_outline" size="16px" />
|
|
<span class="limit-label">Max:</span>
|
|
<span class="limit-value">{{
|
|
circuitStore.getMaxAccumuloByUsername(
|
|
userStore.my,
|
|
circuit._id,
|
|
userStore.my.username,
|
|
''
|
|
) || 0
|
|
}}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Indicatore circuito selezionato -->
|
|
<div v-if="selectedCircuit?._id === circuit._id" class="selected-indicator">
|
|
<q-icon name="check_circle" size="18px" />
|
|
<span>Circuito selezionato</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<q-btn
|
|
outline
|
|
class="explore-btn"
|
|
@click="viewCircuits()"
|
|
>
|
|
<q-icon name="explore" size="20px" />
|
|
<span>Esplora altri circuiti</span>
|
|
</q-btn>
|
|
</section>
|
|
|
|
<!-- Statistiche del circuito selezionato -->
|
|
<section class="statistics-section" :key="selectedCircuit?._id">
|
|
<h3 class="stats-title">
|
|
<q-icon name="insights" size="20px" />
|
|
Statistiche {{ selectedCircuit?.name }}
|
|
</h3>
|
|
<div class="stats-grid">
|
|
<div class="stat-card total">
|
|
<q-icon name="repeat" size="28px" />
|
|
<div class="stat-value">{{ currentCircuitData.totTransato }} {{ currentCircuitData.symbol }}</div>
|
|
<div class="stat-label">Transati</div>
|
|
</div>
|
|
<div class="stat-card members">
|
|
<q-icon name="people" size="28px" />
|
|
<div class="stat-value">{{ currentCircuitData.numMembers }}</div>
|
|
<div class="stat-label">Membri</div>
|
|
</div>
|
|
<div class="stat-card sent">
|
|
<q-icon name="arrow_upward" size="28px" />
|
|
<div class="stat-value">{{ currentCircuitData.sentCount }}</div>
|
|
<div class="stat-label">Inviate</div>
|
|
</div>
|
|
<div class="stat-card received">
|
|
<q-icon name="arrow_downward" size="28px" />
|
|
<div class="stat-value">{{ currentCircuitData.receivedCount }}</div>
|
|
<div class="stat-label">Ricevute</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Activity Feed -->
|
|
<section class="activity-section">
|
|
<h3 class="section-title">
|
|
<q-icon name="history" size="20px" class="title-icon" />
|
|
Attività Recente
|
|
</h3>
|
|
|
|
<div class="activity-feed">
|
|
<q-infinite-scroll
|
|
@load="loadMoreTransactions"
|
|
:offset="250"
|
|
>
|
|
<div
|
|
v-for="group in groupedTransactions"
|
|
:key="group.date"
|
|
class="date-group"
|
|
>
|
|
<div class="date-header">{{ group.date }}</div>
|
|
|
|
<div
|
|
v-for="tx in group.transactions"
|
|
:key="tx.id"
|
|
class="activity-card"
|
|
@click="viewTransactionDetail(tx)"
|
|
>
|
|
<div class="activity-icon">
|
|
<q-avatar
|
|
:color="tx.type === 'sent' ? 'red-5' : 'green-5'"
|
|
text-color="white"
|
|
size="48px"
|
|
>
|
|
<q-icon :name="tx.type === 'sent' ? 'arrow_upward' : 'arrow_downward'" size="24px" />
|
|
</q-avatar>
|
|
</div>
|
|
|
|
<div class="activity-content">
|
|
<div class="activity-title">
|
|
{{ tx.type === 'sent' ? 'Inviato a' : 'Ricevuto da' }}
|
|
<strong>{{ tx.otherUser }}</strong>
|
|
</div>
|
|
<div class="activity-meta">
|
|
<span>{{ tx.time }}</span>
|
|
<span class="separator">•</span>
|
|
<span>{{ tx.circuitName }}</span>
|
|
</div>
|
|
<div v-if="tx.description" class="activity-description">
|
|
{{ tx.description }}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="activity-amount" :class="tx.type">
|
|
<div class="amount-value">
|
|
{{ tx.type === 'sent' ? '-' : '+' }}{{ tx.amount }}
|
|
</div>
|
|
<div class="amount-currency">RIS</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<template v-slot:loading>
|
|
<div class="loading-indicator">
|
|
<q-spinner-dots size="40px" color="primary" />
|
|
</div>
|
|
</template>
|
|
</q-infinite-scroll>
|
|
</div>
|
|
|
|
<!-- Empty state -->
|
|
<div v-if="!hasTransactions" class="empty-state">
|
|
<q-icon name="receipt_long" size="64px" color="grey-5" />
|
|
<p>Nessuna transazione ancora</p>
|
|
<q-btn outline color="primary" @click="handleSend()">
|
|
Inizia a scambiare RIS
|
|
</q-btn>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
|
|
<!-- Tab: Statistiche -->
|
|
<div
|
|
v-show="selectedTab === 'statistics'"
|
|
class="tab-content"
|
|
>
|
|
<div class="wallet-card">
|
|
<h2 class="wallet-title">Statistiche Dettagliate</h2>
|
|
|
|
<!-- Balance Bar -->
|
|
<div class="wallet-balances-compact">
|
|
<CRISBalanceBar
|
|
:current-balance="currentCircuitData.account.saldo"
|
|
:min-limit="currentCircuitData.fidoConcesso"
|
|
:max-limit="currentCircuitData.qta_maxConcessa"
|
|
:label="currentCircuitData.name"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Toggle Personali/Community -->
|
|
<div class="recent-transactions">
|
|
<div class="transactions-header">
|
|
<h4 class="transactions-title">Ultimi Scambi</h4>
|
|
<q-btn-toggle
|
|
v-model="transactionsView"
|
|
dense
|
|
no-caps
|
|
unelevated
|
|
rounded
|
|
size="sm"
|
|
toggle-color="white"
|
|
text-color="white"
|
|
class="transactions-toggle"
|
|
:options="[
|
|
{ label: 'Personali', value: 'mine' },
|
|
{ label: 'Community', value: 'all' },
|
|
]"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Tue Transazioni -->
|
|
<div v-if="transactionsView === 'mine'" class="transaction-list">
|
|
<div
|
|
v-for="(tx, idx) in currentCircuitData.recentTransactions.slice(0, 3)"
|
|
:key="idx"
|
|
class="transaction-item"
|
|
@click="openTransaction(tx)"
|
|
>
|
|
<q-avatar
|
|
size="36px"
|
|
:color="tx.amount > 0 ? 'positive' : 'negative'"
|
|
text-color="white"
|
|
>
|
|
{{ tx.userInitial }}
|
|
</q-avatar>
|
|
<div class="transaction-content">
|
|
<span class="transaction-desc">{{ tx.description }}</span>
|
|
<span class="transaction-time">{{ tx.time }}</span>
|
|
</div>
|
|
<span :class="['transaction-amount', tx.amount > 0 ? 'positive' : 'negative']">
|
|
{{ tx.amount > 0 ? '+' : '' }}{{ tx.amount }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Transazioni Community -->
|
|
<div v-if="transactionsView === 'all'" class="transaction-list">
|
|
<div
|
|
v-for="(tx, idx) in allTransactions.slice(0, 3)"
|
|
:key="idx"
|
|
class="transaction-item"
|
|
@click="openTransaction(tx)"
|
|
>
|
|
<div class="transaction-users">
|
|
<q-avatar size="28px" color="primary" text-color="white">
|
|
{{ tx.fromInitial }}
|
|
</q-avatar>
|
|
<q-icon name="arrow_forward" size="14px" color="grey-6" />
|
|
<q-avatar size="28px" color="secondary" text-color="white">
|
|
{{ tx.toInitial }}
|
|
</q-avatar>
|
|
</div>
|
|
<div class="transaction-content">
|
|
<span class="transaction-desc">{{ tx.fromName }} → {{ tx.toName }}</span>
|
|
<span class="transaction-time">{{ tx.time }}</span>
|
|
</div>
|
|
<span class="transaction-amount community">
|
|
{{ tx.amount > 0 ? '+' : '' }}{{ tx.amount }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<q-btn
|
|
unelevated
|
|
class="wallet-detail-btn"
|
|
label="Dettaglio Transazioni"
|
|
icon-right="arrow_forward"
|
|
@click="goToTransactions"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tab: I miei Circuiti -->
|
|
<div
|
|
v-show="selectedTab === 'circuits'"
|
|
class="tab-content"
|
|
>
|
|
<div class="section-header row justify-center">
|
|
<q-btn-toggle
|
|
v-model="selectedCirc"
|
|
dense
|
|
no-caps
|
|
unelevated
|
|
rounded
|
|
size="md"
|
|
toggle-color="primary"
|
|
class="circuits-toggle"
|
|
:options="[
|
|
{ label: 'I miei Circuiti', value: 'mycircuits', icon: 'account_box' },
|
|
{ label: 'Tutti i Circuiti', value: 'allcircuits', icon: 'view_list' },
|
|
]"
|
|
/>
|
|
</div>
|
|
|
|
<div v-show="selectedCirc === 'mycircuits'" class="my-circuits-content">
|
|
<div
|
|
v-for="circuit in userCircuits"
|
|
:key="circuit.id"
|
|
class="circuit-detail-card"
|
|
>
|
|
<div class="circuit-detail-header">
|
|
<div class="circuit-detail-title">
|
|
<h3>{{ circuit.name }}</h3>
|
|
<q-badge :color="circuit.isCircItalia ? 'deep-purple' : 'cyan'">
|
|
{{ circuitStore.getTypeCircuit(circuit) }}
|
|
</q-badge>
|
|
</div>
|
|
<div class="circuit-detail-balance" :style="`color: ${circuit.color || '#667eea'}`">
|
|
{{ circuitStore.getSaldoByCircuitId(circuit._id) }}
|
|
<span class="currency" :style="`background: ${circuit.color || '#667eea'}`">{{ circuit.symbol }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="circuit-detail-body">
|
|
<div class="info-row">
|
|
<q-icon name="handshake" size="20px" />
|
|
<span class="info-label">Fiducia concessa:</span>
|
|
<span class="info-value">
|
|
{{ circuitStore.getFidoConcessoByUsername(userStore.my, circuit._id, userStore.my.username, '') || 0 }} RIS
|
|
</span>
|
|
</div>
|
|
<div class="info-row">
|
|
<q-icon name="account_balance_wallet" size="20px" />
|
|
<span class="info-label">Massimo accumulo:</span>
|
|
<span class="info-value">
|
|
{{ circuitStore.getMaxAccumuloByUsername(userStore.my, circuit._id, userStore.my.username, '') || 0 }} RIS
|
|
</span>
|
|
</div>
|
|
<div class="info-row">
|
|
<q-icon name="people" size="20px" />
|
|
<span class="info-label">Membri totali:</span>
|
|
<span class="info-value">{{ circuit.numMembers || 0 }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="circuit-detail-actions">
|
|
<q-btn
|
|
flat
|
|
class="action-btn"
|
|
icon="visibility"
|
|
label="Dettagli"
|
|
@click="viewCircuitDetails(circuit)"
|
|
/>
|
|
<q-btn
|
|
flat
|
|
class="action-btn"
|
|
icon="settings"
|
|
label="Impostazioni"
|
|
@click="openCircuitSettings(circuit)"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-show="selectedCirc === 'allcircuits'" class="all-circuits-content">
|
|
<div class="section-header">
|
|
<h2 class="section-title">Lista Circuiti</h2>
|
|
</div>
|
|
<div class="circuits-finder">
|
|
<span
|
|
v-for="(circuit, index) in listcircuitsfind"
|
|
:key="index"
|
|
class="circuit-finder-item"
|
|
>
|
|
<CMyCircuit
|
|
:mycircuit="circuit"
|
|
:visu="visu"
|
|
:username="userStore.my.username"
|
|
/>
|
|
</span>
|
|
</div>
|
|
<CFinder
|
|
:ind="ind"
|
|
:table="shared_consts.TABLES_CIRCUITS"
|
|
:noButtAdd="!tools.isManager()"
|
|
:showFilterPersonal="true"
|
|
:showBarSelection="false"
|
|
:labelBtnAddExtra="
|
|
ind >= 0 && tools.isManager()
|
|
? `Aggiungi ` + costanti.MAINCARDS[ind].strsingolo
|
|
: ''
|
|
"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tab: Impostazioni -->
|
|
<div
|
|
v-show="selectedTab === 'settings'"
|
|
class="tab-content"
|
|
>
|
|
<div class="settings-card">
|
|
<h2 class="settings-title">
|
|
<q-icon name="settings" size="24px" />
|
|
Impostazioni
|
|
</h2>
|
|
|
|
<div class="settings-list">
|
|
<div class="settings-item">
|
|
<div class="settings-item-left">
|
|
<q-icon name="notifications" size="24px" />
|
|
<span>Notifiche</span>
|
|
</div>
|
|
<q-toggle v-model="settingsNotifications" color="primary" />
|
|
</div>
|
|
|
|
<div class="settings-item">
|
|
<div class="settings-item-left">
|
|
<q-icon name="dark_mode" size="24px" />
|
|
<span>Tema Scuro</span>
|
|
</div>
|
|
<q-toggle v-model="settingsDarkMode" color="primary" />
|
|
</div>
|
|
|
|
<div class="settings-item clickable" @click="openPrivacy">
|
|
<div class="settings-item-left">
|
|
<q-icon name="security" size="24px" />
|
|
<span>Privacy e Sicurezza</span>
|
|
</div>
|
|
<q-icon name="chevron_right" size="24px" color="grey-6" />
|
|
</div>
|
|
|
|
<div class="settings-item clickable" @click="openHelp">
|
|
<div class="settings-item-left">
|
|
<q-icon name="help" size="24px" />
|
|
<span>Aiuto e Supporto</span>
|
|
</div>
|
|
<q-icon name="chevron_right" size="24px" color="grey-6" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Mobile Bottom Navigation -->
|
|
<nav class="mobile-nav">
|
|
<div class="mobile-nav-grid">
|
|
<q-btn
|
|
flat
|
|
class="mobile-nav-btn"
|
|
:class="{ active: selectedTab === 'transactions' }"
|
|
@click="changeTab('transactions')"
|
|
>
|
|
<q-icon name="swap_vert" size="24px" />
|
|
<span>Scambi</span>
|
|
</q-btn>
|
|
<q-btn
|
|
flat
|
|
class="mobile-nav-btn"
|
|
:class="{ active: selectedTab === 'statistics' }"
|
|
@click="changeTab('statistics')"
|
|
>
|
|
<q-icon name="bar_chart" size="24px" />
|
|
<span>Stats</span>
|
|
</q-btn>
|
|
<q-btn
|
|
flat
|
|
class="mobile-nav-btn"
|
|
:class="{ active: selectedTab === 'circuits' }"
|
|
@click="changeTab('circuits')"
|
|
>
|
|
<q-icon name="account_balance" size="24px" />
|
|
<span>Circuiti</span>
|
|
</q-btn>
|
|
<q-btn
|
|
flat
|
|
class="mobile-nav-btn"
|
|
:class="{ active: selectedTab === 'settings' }"
|
|
@click="changeTab('settings')"
|
|
>
|
|
<q-icon name="settings" size="24px" />
|
|
<span>Altro</span>
|
|
</q-btn>
|
|
</div>
|
|
</nav>
|
|
|
|
<!-- Dialog Invia RIS -->
|
|
<q-dialog
|
|
v-model="showSendDialog"
|
|
persistent
|
|
maximized
|
|
transition-show="slide-up"
|
|
transition-hide="slide-down"
|
|
>
|
|
<q-card class="send-dialog">
|
|
<q-card-section class="dialog-header send">
|
|
<div class="dialog-title">
|
|
<q-icon name="north_east" size="24px" />
|
|
<span>Invia RIS</span>
|
|
</div>
|
|
<q-btn
|
|
flat
|
|
round
|
|
dense
|
|
icon="close"
|
|
color="white"
|
|
@click="closeSendDialog()"
|
|
/>
|
|
</q-card-section>
|
|
|
|
<q-card-section class="dialog-content">
|
|
<q-input
|
|
v-model="sendSearch"
|
|
label="Cerca destinatario..."
|
|
outlined
|
|
dense
|
|
clearable
|
|
class="search-input"
|
|
@update:model-value="searchRecipients"
|
|
>
|
|
<template v-slot:prepend>
|
|
<q-icon name="search" />
|
|
</template>
|
|
</q-input>
|
|
|
|
<!-- Contatti recenti -->
|
|
<div v-if="recentContacts.length && !sendSearch" class="contacts-section">
|
|
<div class="contacts-title">
|
|
<q-icon name="history" size="16px" />
|
|
<span>Usati di recente</span>
|
|
</div>
|
|
<div
|
|
v-for="contact in recentContacts"
|
|
:key="contact.username"
|
|
class="contact-item recent"
|
|
@click="selectRecipient(contact)"
|
|
>
|
|
<q-avatar round size="44px">
|
|
<img :src="userStore.getImgByProfile(contact)" />
|
|
<q-badge
|
|
v-if="tools.isUserOnline(contact)"
|
|
floating
|
|
color="green"
|
|
rounded
|
|
/>
|
|
</q-avatar>
|
|
<div class="contact-info">
|
|
<div v-if="contact.name" class="contact-name">
|
|
{{ contact.name }}
|
|
<span v-if="contact.surname">{{ contact.surname }}</span>
|
|
</div>
|
|
<div class="contact-username">@{{ contact.username }}</div>
|
|
</div>
|
|
<q-icon name="chevron_right" size="20px" color="grey-6" />
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Risultati ricerca -->
|
|
<div v-if="sendSearch && filteredContacts.length" class="contacts-section">
|
|
<div class="contacts-title">
|
|
<q-icon name="people" size="16px" />
|
|
<span>Risultati ricerca</span>
|
|
</div>
|
|
<div class="contacts-scrollable">
|
|
<div
|
|
v-for="contact in filteredContacts"
|
|
:key="contact.id"
|
|
class="contact-item"
|
|
@click="selectRecipient(contact)"
|
|
>
|
|
<q-avatar size="44px" :color="contact.avatarColor" text-color="white">
|
|
{{ contact.initials }}
|
|
</q-avatar>
|
|
<div class="contact-info">
|
|
<div class="contact-name">{{ contact.name }}</div>
|
|
<div class="contact-username">@{{ contact.username }}</div>
|
|
<div class="contact-email">{{ contact.email }}</div>
|
|
</div>
|
|
<q-icon name="chevron_right" size="20px" color="grey-6" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="sendSearch && !filteredContacts.length" class="no-results">
|
|
<q-icon name="search_off" size="56px" color="grey-5" />
|
|
<p>Nessun contatto trovato</p>
|
|
</div>
|
|
</q-card-section>
|
|
</q-card>
|
|
</q-dialog>
|
|
|
|
<!-- Dialog Ricevi RIS -->
|
|
<q-dialog
|
|
v-model="showReceiveDialog"
|
|
persistent
|
|
transition-show="slide-up"
|
|
transition-hide="slide-down"
|
|
>
|
|
<q-card class="receive-dialog">
|
|
<q-card-section class="dialog-header receive">
|
|
<div class="dialog-title">
|
|
<q-icon name="south_west" size="24px" />
|
|
<span>Ricevi RIS</span>
|
|
</div>
|
|
<q-btn
|
|
flat
|
|
round
|
|
dense
|
|
icon="close"
|
|
color="white"
|
|
@click="closeReceiveDialog()"
|
|
/>
|
|
</q-card-section>
|
|
|
|
<q-card-section class="dialog-content">
|
|
<div class="info-banner">
|
|
<q-icon name="info" size="20px" color="info" />
|
|
<p>Scegli da chi vuoi ricevere RIS. Il mittente dovrà confermare la transazione.</p>
|
|
</div>
|
|
|
|
<q-input
|
|
v-model="receiveSearch"
|
|
label="Cerca mittente..."
|
|
outlined
|
|
dense
|
|
clearable
|
|
class="search-input"
|
|
@update:model-value="searchSenders"
|
|
>
|
|
<template v-slot:prepend>
|
|
<q-icon name="search" />
|
|
</template>
|
|
</q-input>
|
|
|
|
<div v-if="filteredSenders.length" class="contacts-section">
|
|
<div class="contacts-title">
|
|
<q-icon name="people" size="16px" />
|
|
<span>{{ receiveSearch ? 'Risultati ricerca' : 'Contatti disponibili' }}</span>
|
|
</div>
|
|
<div class="contacts-scrollable">
|
|
<div
|
|
v-for="contact in filteredSenders"
|
|
:key="contact.id"
|
|
class="contact-item"
|
|
@click="selectSender(contact)"
|
|
>
|
|
<q-avatar size="44px" :color="contact.avatarColor" text-color="white">
|
|
{{ contact.initials }}
|
|
</q-avatar>
|
|
<div class="contact-info">
|
|
<div class="contact-name">{{ contact.name }}</div>
|
|
<div class="contact-username">@{{ contact.username }}</div>
|
|
</div>
|
|
<q-icon name="chevron_right" size="20px" color="grey-6" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="receiveSearch && !filteredSenders.length" class="no-results">
|
|
<q-icon name="search_off" size="56px" color="grey-5" />
|
|
<p>Nessun contatto trovato</p>
|
|
</div>
|
|
</q-card-section>
|
|
</q-card>
|
|
</q-dialog>
|
|
|
|
<!-- Dialog Conferma Invio -->
|
|
<div v-if="showConfirmSendDialog">
|
|
<CSendCoins
|
|
:loadprofile="true"
|
|
:showprop="showConfirmSendDialog"
|
|
:to_user="selectedRecipient"
|
|
@close="showConfirmSendDialog = false"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Dialog Conferma Ricezione -->
|
|
<q-dialog
|
|
v-model="showConfirmReceiveDialog"
|
|
persistent
|
|
>
|
|
<q-card class="confirm-dialog">
|
|
<q-card-section class="dialog-header receive">
|
|
<div class="dialog-title">Richiesta ricezione</div>
|
|
</q-card-section>
|
|
|
|
<q-card-section class="dialog-content">
|
|
<div class="confirm-details">
|
|
<div class="confirm-row">
|
|
<span class="confirm-label">Da:</span>
|
|
<span class="confirm-value">{{ selectedSender?.name }}</span>
|
|
</div>
|
|
<div class="confirm-row">
|
|
<span class="confirm-label">Circuito:</span>
|
|
<span class="confirm-value">{{ receiveCircuit?.name }}</span>
|
|
</div>
|
|
<div class="confirm-row">
|
|
<span class="confirm-label">Saldo attuale:</span>
|
|
<span class="confirm-value">{{ receiveCircuit?.balance }} RIS</span>
|
|
</div>
|
|
</div>
|
|
|
|
<q-input
|
|
v-model="receiveAmount"
|
|
type="number"
|
|
label="Importo RIS da ricevere"
|
|
outlined
|
|
class="amount-input"
|
|
:rules="[(val) => val > 0 || 'Inserisci un importo valido']"
|
|
>
|
|
<template v-slot:append>
|
|
<span class="input-currency">RIS</span>
|
|
</template>
|
|
</q-input>
|
|
|
|
<q-input
|
|
v-model="receiveDescription"
|
|
label="Descrizione (opzionale)"
|
|
outlined
|
|
type="textarea"
|
|
rows="2"
|
|
class="description-input"
|
|
/>
|
|
|
|
<div class="confirm-result">
|
|
<span class="result-label">Nuovo saldo:</span>
|
|
<span class="result-value positive">{{ newReceiveBalance }} RIS</span>
|
|
</div>
|
|
|
|
<div class="receive-note">
|
|
<q-icon name="info" size="18px" />
|
|
<span>Il mittente riceverà una notifica per confermare l'invio</span>
|
|
</div>
|
|
</q-card-section>
|
|
|
|
<q-card-actions align="right" class="dialog-actions">
|
|
<q-btn flat label="Annulla" @click="cancelReceive()" />
|
|
<q-btn
|
|
unelevated
|
|
label="Richiedi"
|
|
color="positive"
|
|
:disable="!receiveAmount || receiveAmount <= 0"
|
|
@click="confirmReceive()"
|
|
/>
|
|
</q-card-actions>
|
|
</q-card>
|
|
</q-dialog>
|
|
</q-page>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { useCircuitStore, useUserStore } from 'app/src/store';
|
|
import { useQuasar } from 'quasar';
|
|
import { ref, computed, onMounted } from 'vue';
|
|
import { useI18n } from 'vue-i18n';
|
|
import { useRouter } from 'vue-router';
|
|
|
|
import { CRISBalanceBar } from '@/components/CRISBalanceBar';
|
|
import { CSendCoins } from '@/components/CSendCoins';
|
|
import { CMyCircuit } from '@/components/CMyCircuit';
|
|
import { CFinder } from '@/components/CFinder';
|
|
import { costanti } from '@costanti';
|
|
import { shared_consts } from 'app/src/common/shared_vuejs';
|
|
import { tools } from 'app/src/store/Modules/tools';
|
|
import { IAccount } from 'app/src/model';
|
|
|
|
const userStore = useUserStore();
|
|
const circuitStore = useCircuitStore();
|
|
const $q = useQuasar();
|
|
const $router = useRouter();
|
|
const { t } = useI18n();
|
|
|
|
const ind = ref(0);
|
|
const filter = ref(costanti.FIND_CIRCUIT);
|
|
const isTest = true;
|
|
|
|
// Settings
|
|
const settingsNotifications = ref(true);
|
|
const settingsDarkMode = ref(true);
|
|
|
|
const allTransactions = ref<any[]>([]);
|
|
const transactionsView = ref<'mine' | 'all'>('mine');
|
|
|
|
const userCircuits = computed(() => {
|
|
return circuitStore.updateListCircuit(costanti.MY_CIRCUITS, true);
|
|
});
|
|
|
|
const selectedCircuit = ref(userCircuits.value[0]);
|
|
const selectedTab = ref('transactions');
|
|
const selectedCirc = ref('mycircuits');
|
|
|
|
const userAccount = ref(<IAccount|null>null)
|
|
|
|
const changeTab = (tab: string) => {
|
|
selectedTab.value = tab;
|
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
};
|
|
|
|
const selectCircuit = (circuit: (typeof userCircuits.value)[0]) => {
|
|
selectedCircuit.value = circuit;
|
|
};
|
|
|
|
const currentCircuitData = computed(() => {
|
|
const circuitId = selectedCircuit.value?._id;
|
|
const txs = allTransactions.value.filter((tx) => tx.circuitId === circuitId);
|
|
|
|
userAccount.value = userStore.getAccountByCircuitId(circuitId)
|
|
|
|
const uniqueMembersSet = new Set(txs.map((tx) => tx.otherUser)).size;
|
|
const sentCount = txs.filter((tx) => tx.type === 'sent').length;
|
|
const receivedCount = txs.filter((tx) => tx.type === 'received').length;
|
|
|
|
return {
|
|
...selectedCircuit.value,
|
|
totalTransactions: txs.length,
|
|
uniqueMembers: uniqueMembersSet,
|
|
sentCount,
|
|
account: userAccount.value,
|
|
receivedCount,
|
|
recentTransactions: [
|
|
{ userInitial: 'A', description: 'Consulenza legale', time: '3 giorni fa', amount: -80 },
|
|
{ userInitial: 'F', description: 'Prodotti artigianali', time: '4 giorni fa', amount: 50 },
|
|
{ userInitial: 'S', description: 'Corso di cucina', time: '5 giorni fa', amount: -40 },
|
|
],
|
|
};
|
|
});
|
|
|
|
const allContacts = computed(() => userStore.getLastRecentUserTransactions());
|
|
const recentContacts = computed(() => userStore.getLastRecentUserTransactions());
|
|
const hasTransactions = computed(() => allTransactions.value.length > 0);
|
|
|
|
const groupedTransactions = computed(() => {
|
|
const groups: { date: string; transactions: typeof allTransactions.value }[] = [];
|
|
const grouped = new Map<string, typeof allTransactions.value>();
|
|
|
|
const validTransactions = allTransactions.value.filter((tx) => tx.timestamp);
|
|
|
|
validTransactions.forEach((tx) => {
|
|
const dateKey = formatDateHeader(tx.timestamp);
|
|
if (!grouped.has(dateKey)) {
|
|
grouped.set(dateKey, []);
|
|
}
|
|
grouped.get(dateKey)!.push(tx);
|
|
});
|
|
|
|
grouped.forEach((transactions, date) => {
|
|
groups.push({ date, transactions });
|
|
});
|
|
|
|
return groups;
|
|
});
|
|
|
|
const formatDateHeader = (date: Date | string | undefined): string => {
|
|
if (!date) return 'Data sconosciuta';
|
|
|
|
const dateObj = typeof date === 'string' ? new Date(date) : date;
|
|
|
|
if (!(dateObj instanceof Date) || isNaN(dateObj.getTime())) {
|
|
return 'Data non valida';
|
|
}
|
|
|
|
const today = new Date();
|
|
const yesterday = new Date(today);
|
|
yesterday.setDate(yesterday.getDate() - 1);
|
|
|
|
if (dateObj.toDateString() === today.toDateString()) return 'Oggi';
|
|
if (dateObj.toDateString() === yesterday.toDateString()) return 'Ieri';
|
|
|
|
return dateObj.toLocaleDateString('it-IT', {
|
|
day: 'numeric',
|
|
month: 'long',
|
|
year: 'numeric',
|
|
});
|
|
};
|
|
|
|
// Dialog states
|
|
const showSendDialog = ref(false);
|
|
const showReceiveDialog = ref(false);
|
|
const showConfirmSendDialog = ref(false);
|
|
const showConfirmReceiveDialog = ref(false);
|
|
|
|
// Send states
|
|
const sendCircuit = ref(userCircuits.value[0]);
|
|
const sendSearch = ref('');
|
|
const selectedRecipient = ref<any>(null);
|
|
const sendAmount = ref<number | null>(null);
|
|
const sendDescription = ref('');
|
|
|
|
const filteredContacts = computed(() => {
|
|
if (!sendSearch.value) return allContacts.value;
|
|
const search = sendSearch.value.toLowerCase();
|
|
return allContacts.value.filter(
|
|
(c) =>
|
|
c.name?.toLowerCase().includes(search) ||
|
|
c.username?.toLowerCase().includes(search) ||
|
|
c.email?.toLowerCase().includes(search)
|
|
);
|
|
});
|
|
|
|
// Receive states
|
|
const receiveCircuit = ref(userCircuits.value[0]);
|
|
const receiveSearch = ref('');
|
|
const selectedSender = ref<any>(null);
|
|
const receiveAmount = ref<number | null>(null);
|
|
const receiveDescription = ref('');
|
|
|
|
const filteredSenders = computed(() => {
|
|
if (!receiveSearch.value) return allContacts.value;
|
|
const search = receiveSearch.value.toLowerCase();
|
|
return allContacts.value.filter(
|
|
(c) =>
|
|
c.name?.toLowerCase().includes(search) ||
|
|
c.username?.toLowerCase().includes(search) ||
|
|
c.email?.toLowerCase().includes(search)
|
|
);
|
|
});
|
|
|
|
const newReceiveBalance = computed(() => {
|
|
if (!receiveAmount.value || !receiveCircuit.value)
|
|
return receiveCircuit.value?.balance || 0;
|
|
return receiveCircuit.value.balance + receiveAmount.value;
|
|
});
|
|
|
|
// Functions
|
|
const handleSend = () => {
|
|
showSendDialog.value = true;
|
|
};
|
|
|
|
const handleReceive = () => {
|
|
showReceiveDialog.value = true;
|
|
receiveCircuit.value = selectedCircuit.value;
|
|
};
|
|
|
|
const closeSendDialog = () => {
|
|
showSendDialog.value = false;
|
|
sendSearch.value = '';
|
|
selectedRecipient.value = null;
|
|
};
|
|
|
|
const closeReceiveDialog = () => {
|
|
showReceiveDialog.value = false;
|
|
receiveSearch.value = '';
|
|
selectedSender.value = null;
|
|
};
|
|
|
|
const searchRecipients = () => {
|
|
console.log('Ricerca destinatari:', sendSearch.value);
|
|
};
|
|
|
|
const searchSenders = () => {
|
|
console.log('Ricerca mittenti:', receiveSearch.value);
|
|
};
|
|
|
|
const selectRecipient = (contact: any) => {
|
|
selectedRecipient.value = contact;
|
|
showSendDialog.value = false;
|
|
showConfirmSendDialog.value = true;
|
|
};
|
|
|
|
const selectSender = (contact: any) => {
|
|
selectedSender.value = contact;
|
|
showReceiveDialog.value = false;
|
|
showConfirmReceiveDialog.value = true;
|
|
};
|
|
|
|
const cancelReceive = () => {
|
|
showConfirmReceiveDialog.value = false;
|
|
receiveAmount.value = null;
|
|
receiveDescription.value = '';
|
|
selectedSender.value = null;
|
|
};
|
|
|
|
const confirmReceive = () => {
|
|
console.log('Ricezione richiesta:', {
|
|
sender: selectedSender.value,
|
|
circuit: receiveCircuit.value,
|
|
amount: receiveAmount.value,
|
|
description: receiveDescription.value,
|
|
});
|
|
showConfirmReceiveDialog.value = false;
|
|
cancelReceive();
|
|
};
|
|
|
|
const loadMoreTransactions = (index: number, done: (stop?: boolean) => void) => {
|
|
setTimeout(() => {
|
|
done(true);
|
|
}, 500);
|
|
};
|
|
|
|
const viewTransactionDetail = (tx: any) => {
|
|
console.log('Visualizza dettaglio transazione:', tx);
|
|
};
|
|
|
|
const viewCircuitDetails = (circuit: any) => {
|
|
console.log('Visualizza dettagli circuito:', circuit);
|
|
};
|
|
|
|
const openCircuitSettings = (circuit: any) => {
|
|
console.log('Apri impostazioni circuito:', circuit);
|
|
};
|
|
|
|
const viewCircuits = () => {
|
|
selectedTab.value = 'circuits';
|
|
selectedCirc.value = 'allcircuits';
|
|
};
|
|
|
|
const openTransaction = (tx: any) => {
|
|
console.log('Apri transazione:', tx);
|
|
};
|
|
|
|
const goToTransactions = () => {
|
|
selectedTab.value = 'transactions';
|
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
};
|
|
|
|
const openPrivacy = () => {
|
|
console.log('Apri privacy');
|
|
};
|
|
|
|
const openHelp = () => {
|
|
console.log('Apri aiuto');
|
|
};
|
|
|
|
const visu = computed(() => ({}));
|
|
|
|
const listcircuitsfind = computed(() => {
|
|
return circuitStore.updateListCircuit(costanti.FIND_CIRCUIT) || [];
|
|
});
|
|
|
|
const initializeTransactions = () => {
|
|
allTransactions.value = [
|
|
{
|
|
id: '1',
|
|
type: 'sent',
|
|
amount: 50,
|
|
otherUser: 'Mario Rossi',
|
|
circuitName: 'RIS Treviso',
|
|
circuitId: '1',
|
|
description: 'Riparazione bicicletta',
|
|
date: '2024-12-02',
|
|
time: '14:30',
|
|
timestamp: new Date('2024-12-02T14:30:00'),
|
|
fromInitial: 'M',
|
|
toInitial: 'L',
|
|
fromName: 'Mario',
|
|
toName: 'Laura',
|
|
},
|
|
{
|
|
id: '2',
|
|
type: 'received',
|
|
amount: 75,
|
|
otherUser: 'Laura Bianchi',
|
|
circuitName: 'RIS Treviso',
|
|
circuitId: '1',
|
|
description: 'Lezione di inglese',
|
|
date: '2024-12-01',
|
|
time: '16:45',
|
|
timestamp: new Date('2024-12-01T16:45:00'),
|
|
fromInitial: 'L',
|
|
toInitial: 'G',
|
|
fromName: 'Laura',
|
|
toName: 'Giuseppe',
|
|
},
|
|
{
|
|
id: '3',
|
|
type: 'sent',
|
|
amount: 30,
|
|
otherUser: 'Giuseppe Verdi',
|
|
circuitName: 'RIS Italia',
|
|
circuitId: '2',
|
|
description: '',
|
|
date: '2024-11-30',
|
|
time: '10:15',
|
|
timestamp: new Date('2024-11-30T10:15:00'),
|
|
fromInitial: 'G',
|
|
toInitial: 'A',
|
|
fromName: 'Giuseppe',
|
|
toName: 'Anna',
|
|
},
|
|
];
|
|
};
|
|
|
|
onMounted(() => {
|
|
initializeTransactions();
|
|
|
|
if (userStore.my.profile.mycircuits.length <= 0) {
|
|
filter.value = costanti.FIND_CIRCUIT;
|
|
}
|
|
|
|
ind.value = tools.getIndMainCardsByTable(shared_consts.TABLES_CIRCUITS);
|
|
});
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
@import './pageris.scss';
|
|
</style>
|