- verifica email se non è stata verificata (componente)

- altri aggiornamenti grafica PAGERIS.
- OLLAMA AI
This commit is contained in:
Surya Paolo
2025-12-11 18:34:39 +01:00
parent 6fdb101092
commit 89a8d10eae
44 changed files with 7915 additions and 3565 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -13,15 +13,15 @@
<div class="card-icon-wrapper">
<q-icon name="fas fa-sign-in-alt" size="48px" class="card-icon" />
</div>
<div class="card-content">
<h3 class="card-title">Benvenuto su RISO</h3>
<h3 class="card-title">Benvenuto su {{ tools.sitename() }}</h3>
<p class="card-description">
Accedi con le tue credenziali per utilizzare la APP e per unirti
al Circuito di scambio RIS del tuo territorio
ai membri della Community
</p>
</div>
<div class="card-actions">
<q-btn
unelevated
@@ -47,7 +47,7 @@
<span class="telegram-subtitle">Unisciti al gruppo Provinciale</span>
</div>
</div>
<q-btn
rounded
unelevated
@@ -71,7 +71,7 @@
<span class="help-subtitle">Consulta la guida completa</span>
</div>
</div>
<q-btn
rounded
unelevated

View File

@@ -3,7 +3,10 @@
<div class="key-label">
{{ mykey }}
</div>
<div class="value-content" :style="color ? `background-color: ${color}; color: white;` : ''">
<div
class="value-content"
:style="color ? `background-color: ${color}; color: white;` : ''"
>
<span v-if="mydate">
<CDateTime
v-model:value="mydate"
@@ -11,10 +14,17 @@
:canEdit="false"
/>
</span>
<span v-else class="value-text">
<span
v-else
class="value-text"
>
{{ myvalue || '-' }}
</span>
</div>
<div
class="value-content"
:style="color ? `background-color: ${color}; color: white;` : ''"
>
<q-btn
v-if="showSetButton && onSetValue"
rounded
@@ -30,8 +40,7 @@
</div>
</template>
<script lang="ts" src="./CKeyAndValue.ts">
</script>
<script lang="ts" src="./CKeyAndValue.ts"></script>
<style lang="scss" scoped>
@import './CKeyAndValue.scss';

View File

@@ -32,6 +32,7 @@ import { shared_consts } from '@/common/shared_vuejs';
import { LandingFooter } from '@/components/LandingFooter';
import { CMyActivities } from '@/components/CMyActivities';
import { CECommerce } from '@/components/CECommerce';
import { CheckEmail } from '@/components/CheckEmail';
import { HomeRiso } from '@/components/HomeRiso';
import mycircuits from '@/views/user/mycircuits/mycircuits.vue';
import PageRis from '@/components/pageris/pageris.vue';
@@ -118,6 +119,7 @@ export default defineComponent({
CCheckIfIsLogged,
CStatusReg,
CDashboard,
CheckEmail,
CMainView,
CNotifAtTop,
CPresentazione,

View File

@@ -904,14 +904,7 @@
>
Msg di Controllo Verifica Email
</div>
<div class="q-pa-xs q-gutter-md">
<div
v-if="tools.isLogged() && !tools.isVerified()"
class="text-verified"
>
{{ t('components.authentication.email_verification.link_sent') }}
</div>
</div>
<CheckEmail />
</div>
<div v-else-if="myel.type === shared_consts.ELEMTYPE.PRESENTAZIONE">
<div
@@ -1090,10 +1083,10 @@
Check Sito di Test
</div>
<q-banner
v-if="tools.isTest() && false"
v-if="tools.isTest()"
rounded
dense
class="bg-negative text-white"
class="bg-negative text-white q-ma-sm"
color="primary q-title"
style="text-align: center"
>
@@ -1104,7 +1097,7 @@
size="xs"
/>
</template>
<span class="mybanner"> TEST !</span>
<span class="mybanner"> AMBIENTE DI TEST !</span>
</q-banner>
</div>
<div v-else-if="myel.type === shared_consts.ELEMTYPE.CHECKNEWVERSION">

View File

@@ -588,4 +588,48 @@
padding: 8px 12px;
font-weight: 500;
}
}
.annuncio-location {
font-size: 1rem;
color: #718096;
align-items: center;
gap: 4px;
&::before {
content: '📍';
}
}
// Il contenitore padre (q-item o card) deve avere position relative
.q-item,
.event-card {
position: relative;
}
// Bottone overlay fisso a destra
.action-menu-btn-overlay {
position: absolute !important;
top: 8px;
right: 0px;
transform: translateY(-50%);
z-index: 10;
// Sfondo semi-trasparente per visibilità
background: rgba(255, 255, 255, 0.8) !important;
// Ombra leggera
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
&:hover {
background: rgba(255, 255, 255, 1) !important;
}
}
// Alternativa: in alto a destra
.action-menu-btn-overlay--top {
position: absolute !important;
top: 4px;
right: 4px;
z-index: 10;
}

View File

@@ -340,8 +340,9 @@
<span
v-for="(rec, ind) of myrec.mycities"
:key="ind"
class="annuncio-location"
>
<span v-if="ind > 0">, </span>
<span v-if="ind > 0"></span>
<span v-if="table === shared_consts.TABLES_MYHOSPS" class="cities-text-bold">{{ rec.comune }} ({{ rec.prov }})</span>
<span v-else>{{ rec.comune }} ({{ rec.prov }})</span>
</span>
@@ -353,12 +354,11 @@
<q-item-section
v-if="tools.canModifyThisRec(myrec, table) || editOn"
side
top
class="actions-section"
>
<q-btn
round
flat
dense
icon="more_vert"
size="sm"
class="action-menu-btn"

View File

@@ -459,7 +459,7 @@
<q-item-label>
<q-btn rounded icon="fas fa-ellipsis-h">
<q-menu>
<q-list
<!--<q-list
v-if="!userStore.IsMyFriendByUsername(contact.username)"
style="min-width: 200px"
>
@@ -481,7 +481,7 @@
t('friends.ask_friend')
}}</q-item-section>
</q-item>
</q-list>
</q-list>-->
<q-list style="min-width: 200px">
<q-item
clickable
@@ -515,7 +515,7 @@
>
<q-menu>
<q-list style="min-width: 200px">
<q-item
<!--<q-item
v-if="
!userStore.IsMyFriendByUsername(contact.username) &&
!userStore.IsAskedFriendByUsername(contact.username)
@@ -538,9 +538,9 @@
<q-item-section>{{
t('friends.ask_friend')
}}</q-item-section>
</q-item>
</q-item>-->
<q-item
v-else-if="
v-if="
!userStore.IsMyFriendByUsername(contact.username) &&
userStore.IsAskedFriendByUsername(contact.username)
"

View File

@@ -35,6 +35,7 @@ export default defineComponent({
});
const handleInput = (value: string | number) => {
// console.log('value', value)
if (value === '⌫') {
inputValue.value = inputValue.value.slice(0, -1);
} else if (value === '.' && !inputValue.value.includes('.')) {
@@ -43,12 +44,14 @@ export default defineComponent({
inputValue.value += value.toString();
}
// console.log('inputValue.value', inputValue.value)
// Verifica se inputValue contiene più di due cifre decimali
const decimalPattern = /^\d+(\.\d{0,2})?$/; // Regex per validare il numero
const newValue = inputValue.value;
// Se non rispetta il formato, tronca il numero a 2 cifre decimali
if (!decimalPattern.test(newValue)) {
/*if (!decimalPattern.test(newValue)) {
// Se troviamo un punto decimale, manteniamo solo le prime 2 cifre
const parts = newValue.split('.'); // Dividi il numero in parte intera e decimale
if (parts.length > 1) {
@@ -58,7 +61,7 @@ export default defineComponent({
// Nessuna parte decimale, quindi usa solo la parte intera
inputValue.value = parts[0];
}
}
}*/
emit('update:modelValue', inputValue.value);
};

View File

@@ -10,7 +10,7 @@
class="balance-text-saldo"
>Saldo:
</span>
{{ currentBalance > 0 ? '+' : '' }}{{ currentBalance }} RIS
{{ currentBalance > 0 ? '+' : '' }}{{ currentBalance.toFixed(2) }} RIS
</span>
</div>
@@ -47,14 +47,14 @@
:style="{ '--zero-position': zeroPosition + '%' }"
>
<div class="marker min-marker">
<span class="marker-value">{{ minLimit }}</span>
<span class="marker-value">{{ minLimit.toFixed(2) }}</span>
<span class="marker-label">Fido</span>
</div>
<div class="marker zero-marker-label">
<span class="marker-value">0</span>
</div>
<div class="marker max-marker">
<span class="marker-value">+{{ maxLimit }}</span>
<span class="marker-value">+{{ maxLimit.toFixed(2) }}</span>
<span class="marker-label">Max</span>
</div>
</div>
@@ -69,7 +69,7 @@
color="negative"
/>
<span class="availability-text">
Puoi dare ancora: <strong>{{ canGive }} RIS</strong>
Puoi dare ancora: <strong>{{ canGive.toFixed(2) }} RIS</strong>
</span>
</div>
<div class="availability-item">
@@ -79,7 +79,7 @@
color="positive"
/>
<span class="availability-text">
Puoi ricevere: <strong>{{ canReceive }} RIS</strong>
Puoi ricevere: <strong>{{ canReceive.toFixed(2) }} RIS</strong>
</span>
</div>
</div>

View File

@@ -1,341 +1,575 @@
.c-send-coins {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding-bottom: 80px;
// Variables
$primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
$orange-gradient: linear-gradient(135deg, #f97316 0%, #ea580c 100%);
$ris-color: #ff5500;
$border-radius-lg: 16px;
$border-radius-md: 12px;
$border-radius-sm: 8px;
$shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.08);
$shadow-md: 0 4px 16px rgba(0, 0, 0, 0.12);
// Main Dialog
.send-coins-dialog {
border-radius: $border-radius-lg $border-radius-lg 0 0;
overflow: hidden;
max-width: 420px;
width: 100%;
@media (min-width: 600px) {
border-radius: $border-radius-lg;
max-height: 90vh;
}
}
.page-header {
.mobile-fullheight {
height: 100vh;
max-height: 100vh;
display: flex;
flex-direction: column;
border-radius: 0;
}
// Header
.dialog-header {
position: relative;
}
.header-gradient {
background: $primary-gradient;
padding: 12px 16px 14px;
position: relative;
}
.header-top-bar {
display: flex;
align-items: center;
padding: 12px 16px;
background: rgba(255, 255, 255, 0.1);
justify-content: space-between;
margin-bottom: 12px;
}
.close-btn {
background: rgba(255, 255, 255, 0.15);
backdrop-filter: blur(10px);
.back-btn {
color: white;
margin-right: 12px;
}
.header-title {
font-size: 20px;
font-weight: 600;
color: white;
&:hover {
background: rgba(255, 255, 255, 0.25);
}
}
.content-section {
padding: 20px 16px;
}
.section-title {
font-size: 24px;
font-weight: 700;
color: white;
margin-bottom: 20px;
}
.section-subtitle {
font-size: 16px;
font-weight: 600;
color: rgba(255, 255, 255, 0.9);
margin-bottom: 12px;
}
.search-input {
margin-bottom: 16px;
:deep(.q-field__control) {
background: rgba(255, 255, 255, 0.95);
border-radius: 12px;
}
}
.circuit-selector {
margin-bottom: 24px;
}
.circuit-select {
:deep(.q-field__control) {
background: rgba(255, 255, 255, 0.95);
border-radius: 12px;
}
}
.user-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.user-item {
.header-title-wrapper {
display: flex;
align-items: center;
padding: 12px;
background: rgba(255, 255, 255, 0.95);
border-radius: 12px;
cursor: pointer;
transition: all 0.2s;
&:active {
transform: scale(0.98);
background: rgba(255, 255, 255, 0.85);
}
.user-info {
flex: 1;
margin-left: 12px;
}
.user-name {
font-size: 16px;
font-weight: 600;
color: #1a1a1a;
}
.user-username {
font-size: 14px;
color: #666;
}
gap: 10px;
}
.selected-user-card {
display: flex;
align-items: center;
padding: 16px;
background: rgba(255, 255, 255, 0.95);
border-radius: 16px;
margin-bottom: 20px;
.user-info {
margin-left: 12px;
flex: 1;
}
.circuit-badge {
display: inline-block;
padding: 4px 12px;
background: rgba(103, 126, 234, 0.1);
color: #667eea;
border-radius: 12px;
font-size: 12px;
font-weight: 600;
margin-top: 4px;
}
}
.circuit-list {
display: flex;
flex-direction: column;
gap: 12px;
margin-bottom: 24px;
}
.circuit-item {
display: flex;
align-items: center;
padding: 16px;
background: rgba(255, 255, 255, 0.95);
border-radius: 12px;
cursor: pointer;
transition: all 0.2s;
&.selected {
background: rgba(103, 126, 234, 0.15);
border: 2px solid #667eea;
}
&:active {
transform: scale(0.98);
}
.circuit-info {
flex: 1;
}
.circuit-name {
font-size: 16px;
font-weight: 600;
color: #1a1a1a;
margin-bottom: 4px;
}
.circuit-balance {
font-size: 14px;
color: #666;
}
}
.amount-section {
background: rgba(255, 255, 255, 0.95);
border-radius: 16px;
padding: 20px;
margin-bottom: 20px;
}
.amount-display {
.ris-coin-icon {
width: 28px;
height: 28px;
border-radius: 50%;
background: $orange-gradient;
display: flex;
align-items: center;
justify-content: center;
padding: 24px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 16px;
margin-bottom: 16px;
cursor: pointer;
position: relative;
.currency {
font-size: 24px;
font-weight: 600;
color: rgba(255, 255, 255, 0.8);
margin-right: 8px;
box-shadow: 0 2px 8px rgba(255, 107, 53, 0.4);
.ris-logo {
width: 18px;
height: 18px;
object-fit: contain;
}
.amount-value {
font-size: 42px;
.coin-symbol {
color: white;
font-weight: 700;
color: white;
}
.keyboard-btn {
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
color: white;
font-size: 12px;
}
}
.limits-info {
.header-title {
color: white;
font-size: 17px;
font-weight: 600;
}
// Balance Card - Compatto
.balance-card {
background: rgba(255, 255, 255, 0.12);
backdrop-filter: blur(20px);
border-radius: $border-radius-sm;
padding: 10px 14px;
}
.balance-info {
display: flex;
justify-content: space-around;
padding: 12px 0;
margin-bottom: 16px;
border-top: 1px solid rgba(0, 0, 0, 0.1);
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
justify-content: space-between;
align-items: center;
}
.limit-item {
.balance-main {
display: flex;
flex-direction: column;
align-items: center;
.limit-label {
font-size: 12px;
color: #666;
margin-bottom: 4px;
}
.limit-value {
font-size: 16px;
font-weight: 600;
color: #1a1a1a;
gap: 2px;
}
.balance-label {
color: rgba(255, 255, 255, 0.7);
font-size: 11px;
}
.balance-value {
color: white;
font-size: 20px;
font-weight: 700;
line-height: 1.2;
.balance-symbol {
font-size: 14px;
font-weight: 500;
opacity: 0.9;
}
}
.description-input {
:deep(.q-field__control) {
border-radius: 12px;
}
.balance-fido {
text-align: right;
}
.action-btn {
border-radius: 12px;
padding: 12px;
font-size: 16px;
.fido-label {
display: block;
color: rgba(255, 255, 255, 0.7);
font-size: 10px;
}
.fido-value {
color: #4ade80;
font-size: 14px;
font-weight: 600;
text-transform: none;
}
.confirmation-card {
border-radius: 24px;
max-width: 400px;
width: 90vw;
// Content
.dialog-content {
padding: 14px 16px;
flex: 1;
overflow-y: auto;
&::-webkit-scrollbar {
display: none;
}
-ms-overflow-style: none;
scrollbar-width: none;
}
.confirmation-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 20px;
.header-title {
font-size: 20px;
font-weight: 600;
color: white;
// Section Block - Più compatto
.section-block {
margin-bottom: 12px;
}
.section-label {
display: block;
color: #6b7280;
font-size: 12px;
font-weight: 500;
margin-bottom: 6px;
}
// Modern Select
.modern-select {
:deep(.q-field__control) {
border-radius: $border-radius-sm;
background: #f9fafb;
border: 1.5px solid #e5e7eb;
min-height: 40px;
transition: all 0.2s ease;
&:hover {
border-color: #d1d5db;
}
}
:deep(.q-field__control-container) {
}
:deep(.q-field--focused .q-field__control) {
border-color: #667eea;
box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.15);
}
:deep(.q-field__native) {
font-weight: 500;
font-size: 14px;
color: #374151;
}
:deep(.q-field__label) {
font-size: 13px;
}
}
.modern-input {
:deep(.q-field__control) {
border-radius: $border-radius-sm;
background: #f9fafb;
border: 1.5px solid #e5e7eb;
min-height: 40px;
}
}
// Recipient Card - Compatto
.recipient-card {
background: linear-gradient(135deg, #f5f3ff 0%, #ede9fe 100%);
border: 1.5px solid #ddd6fe;
border-radius: $border-radius-md;
padding: 10px 12px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
}
.recipient-content {
flex: 1;
min-width: 0;
}
.recipient-view {
:deep(.q-item) {
padding: 0;
min-height: auto;
}
}
.circuit-badge {
color: white;
font-size: 11px;
font-weight: 600;
padding: 4px 10px;
border-radius: 16px;
flex-shrink: 0;
}
// Amount Input Row - Compatto
.amount-input-row {
cursor: pointer;
}
.amount-input {
:deep(.q-field__control) {
border-radius: $border-radius-sm;
background: linear-gradient(135deg, #1e1b4b 0%, #312e81 100%);
border: none;
min-height: 48px;
padding: 4px 12px;
}
:deep(.q-field__native) {
color: white !important;
font-size: 22px !important;
font-weight: 700 !important;
text-align: center;
}
}
.confirmation-content {
padding: 24px;
text-align: center;
}
:deep(.q-field__prepend) {
padding-right: 0;
}
.amount-label {
font-size: 14px;
color: #666;
margin-bottom: 8px;
}
.amount-display-large {
font-size: 36px;
font-weight: 700;
color: #1a1a1a;
margin-bottom: 24px;
}
.users-row {
display: flex;
align-items: center;
justify-content: center;
gap: 24px;
margin-bottom: 32px;
}
.user-col {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
}
.user-name-small {
font-size: 14px;
color: #666;
}
.verify-title {
font-size: 20px;
font-weight: 600;
color: #1a1a1a;
margin-bottom: 12px;
}
.verify-text {
font-size: 15px;
color: #666;
line-height: 1.5;
.amount-highlight {
color: #667eea;
font-weight: 700;
:deep(.q-field__append) {
padding-left: 0;
}
}
.confirmation-actions {
display: flex;
gap: 12px;
padding: 16px 24px;
.currency-symbol {
color: rgba(255, 255, 255, 0.5);
font-size: 18px;
font-weight: 400;
}
.cancel-btn,
.confirm-btn {
flex: 1;
border-radius: 12px;
padding: 12px;
font-size: 16px;
font-weight: 600;
text-transform: none;
.coin-badge {
width: 32px;
height: 32px;
border-radius: 20%;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: 700;
font-size: 12px;
box-shadow: 0 2px 8px rgba(255, 107, 53, 0.3);
}
.keyboard-btn {
color: rgba(255, 255, 255, 0.7);
&:hover {
color: white;
}
}
// Banners
.warning-banner {
background: $orange-gradient;
color: white;
font-weight: 500;
font-size: 13px;
}
.error-banner {
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
color: white;
font-weight: 500;
font-size: 13px;
}
// Note Input - Compatto
.modern-textarea {
:deep(.q-field__control) {
border-radius: $border-radius-sm;
background: #f9fafb;
border: 1.5px solid #e5e7eb;
transition: all 0.2s ease;
}
:deep(.q-field--focused .q-field__control) {
border-color: #667eea;
box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.15);
}
:deep(.q-field__native) {
resize: none;
font-size: 14px;
}
:deep(.q-field__counter) {
color: #9ca3af;
font-size: 11px;
}
:deep(.q-field__label) {
font-size: 13px;
}
}
// Actions
.dialog-actions {
display: flex;
gap: 10px;
padding: 12px 16px;
background: white;
border-top: 1px solid #f3f4f6;
}
.fixed-bottom-actions {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 100;
box-shadow: 0 -2px 12px rgba(0, 0, 0, 0.08);
padding-bottom: calc(12px + env(safe-area-inset-bottom, 0px));
}
.cancel-btn {
background: rgba(0, 0, 0, 0.05);
flex: 1;
background: #f3f4f6;
color: #6b7280;
font-weight: 600;
font-size: 14px;
padding: 10px 16px;
border-radius: $border-radius-sm;
text-transform: none;
&:hover {
background: #e5e7eb;
}
}
.send-btn {
flex: 2;
background: $orange-gradient;
color: white;
font-weight: 600;
font-size: 14px;
padding: 10px 20px;
border-radius: $border-radius-sm;
text-transform: none;
box-shadow: 0 2px 8px rgba(249, 115, 22, 0.3);
transition: all 0.2s ease;
&:hover:not(.btn-disabled) {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(249, 115, 22, 0.4);
}
&.btn-disabled {
opacity: 0.5;
cursor: not-allowed;
}
}
.send-btn-text {
margin-right: 6px;
}
.send-btn-icon {
width: 20px;
height: 20px;
object-fit: contain;
}
// Keyboard Dialog
.keyboard-dialog {
width: 100%;
max-width: 420px;
border-radius: $border-radius-lg $border-radius-lg 0 0;
margin: 0;
}
.keyboard-header {
background: #f9fafb;
border-bottom: 1px solid #e5e7eb;
padding: 12px 16px;
}
.keyboard-header-content {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.keyboard-title {
color: #6b7280;
font-weight: 500;
font-size: 14px;
}
.done-btn {
font-weight: 600;
font-size: 14px;
}
.keyboard-display {
background: linear-gradient(135deg, #1e1b4b 0%, #312e81 100%);
border-radius: $border-radius-sm;
padding: 14px;
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
}
.keyboard-amount {
color: white;
font-size: 28px;
font-weight: 700;
}
.keyboard-coin-badge {
width: 32px;
height: 32px;
border-radius: 20%;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: 700;
font-size: 12px;
}
.keyboard-section {
padding-bottom: calc(12px + env(safe-area-inset-bottom, 0px)) !important;
}
// Dark mode support
.body--dark {
.send-coins-dialog {
background: #1f2937;
}
.dialog-content {
background: #1f2937;
}
.section-label {
color: #9ca3af;
}
.modern-select,
.modern-input {
:deep(.q-field__control) {
background: #374151;
border-color: #4b5563;
}
:deep(.q-field__native) {
color: #f3f4f6;
}
}
.recipient-card {
background: linear-gradient(135deg, #374151 0%, #4b5563 100%);
border-color: #6b7280;
}
.modern-textarea {
:deep(.q-field__control) {
background: #374151;
border-color: #4b5563;
}
:deep(.q-field__native) {
color: #f3f4f6;
}
}
.dialog-actions {
background: #1f2937;
border-top-color: #374151;
}
.fixed-bottom-actions {
background: #1f2937;
}
.cancel-btn {
background: #374151;
color: #d1d5db;
}
.keyboard-dialog {
background: #1f2937;
}
.keyboard-header {
background: #374151;
border-color: #4b5563;
}
.keyboard-title {
color: #d1d5db;
}
}
// Responsive - Ultra compatto per mobile piccoli
@media (max-width: 360px) {
.header-gradient {
padding: 10px 12px 12px;
}
.balance-value {
font-size: 18px;
}
.dialog-content {
padding: 12px;
}
.section-block {
margin-bottom: 10px;
}
.amount-input {
:deep(.q-field__native) {
font-size: 20px !important;
}
}
}

View File

@@ -1,43 +1,47 @@
import type { PropType } from 'vue';
import { computed, defineComponent, onMounted, ref, watch } from 'vue'
import { computed, defineComponent, onMounted, ref, watch } from 'vue';
import type { IAccount, ICircuit, IMyGroup, ISendCoin, IUserFields } from '../../model';
import { IOperators, ISpecialField } from '../../model'
import { tools } from '@tools'
import { CSaldo } from '@/components/CSaldo'
import { useUserStore } from '@store/UserStore'
import { useCircuitStore } from '@store/CircuitStore'
import { useQuasar } from 'quasar'
import { useI18n } from 'vue-i18n'
import { CNumericKeyboard } from '@/components/CNumericKeyboard'
import { CMyUserOnlyView } from '@/components/CMyUserOnlyView'
import { CMyGroupOnlyView } from '@/components/CMyGroupOnlyView'
import { CCheckCircuitsEnabled } from '@/components/CCheckCircuitsEnabled'
import { costanti } from '@costanti'
import { useRouter } from 'vue-router'
import { shared_consts } from '@/common/shared_vuejs'
import { IOperators, ISpecialField } from '../../model';
import { tools } from '@tools';
import { CSaldo } from '@/components/CSaldo';
import { useUserStore } from '@store/UserStore';
import { useCircuitStore } from '@store/CircuitStore';
import { useQuasar } from 'quasar';
import { useI18n } from 'vue-i18n';
import { CNumericKeyboard } from '@/components/CNumericKeyboard';
import { CMyUserOnlyView } from '@/components/CMyUserOnlyView';
import { CMyGroupOnlyView } from '@/components/CMyGroupOnlyView';
import { CCheckCircuitsEnabled } from '@/components/CCheckCircuitsEnabled';
import { costanti } from '@costanti';
import { useRouter } from 'vue-router';
import { shared_consts } from '@/common/shared_vuejs';
export default defineComponent({
name: 'CSendCoins',
emits: ['close', 'showed'],
props: {
loadprofile: {
type: Boolean,
default: false,
},
showprop: {
type: Boolean,
default: false,
},
circuitname: {
type: String,
default: ''
default: '',
},
qtydefault: {
type: String,
required: false,
default: ''
default: '',
},
to_user: {
type: Object as PropType<IUserFields>,
required: false,
default: null
default: null,
},
to_group: {
type: Object as PropType<IMyGroup>,
@@ -65,99 +69,133 @@ export default defineComponent({
default: '',
},
},
components: { CSaldo, CMyUserOnlyView, CMyGroupOnlyView, CCheckCircuitsEnabled, CNumericKeyboard },
components: {
CSaldo,
CMyUserOnlyView,
CMyGroupOnlyView,
CCheckCircuitsEnabled,
CNumericKeyboard,
},
setup(props, { emit }) {
const $q = useQuasar()
const { t } = useI18n()
const showpage = ref(false)
const userStore = useUserStore()
const circuitStore = useCircuitStore()
const $router = useRouter()
const $q = useQuasar();
const { t } = useI18n();
const showpage = ref(false);
const userStore = useUserStore();
const circuitStore = useCircuitStore();
const $router = useRouter();
const from_username = ref(userStore.my.username)
const from_groupname = ref('')
const from_contocom = ref('')
const circuitsel = ref('')
const qty = ref(<string | number>'')
const causal = ref('')
const loading = ref(false)
const visubanner = ref(true)
const bothcircuits = ref(<any>[])
const to_user_real = ref(<IUserFields>{});
const showProvinceToSelect = ref(false)
const from_username = ref(userStore.my.username);
const from_groupname = ref('');
const from_contocom = ref('');
const circuitsel = ref('');
const qty = ref(<string | number>'');
const causal = ref('');
const loading = ref(false);
const visubanner = ref(true);
const bothcircuits = ref(<any>[]);
const groupSel = ref(<IMyGroup | null | undefined>null)
const showProvinceToSelect = ref(false);
const showKeyboard = ref(false);
const datasaved = ref(<any>null)
const step = ref(0)
const sendCoinDialog = ref(null)
const groupSel = ref(<IMyGroup | null | undefined>null);
const circuittoload = ref(<ICircuit | undefined>undefined)
const circuitloaded = ref(<ICircuit>{})
const circuitdest = ref(<ICircuit | undefined>undefined)
const accountloaded = ref(<IAccount | undefined | null>undefined)
const accountdest = ref(<IAccount | undefined>undefined)
const remainingCoins = ref(0)
const maxsendable = ref(0)
const numstep = ref(0)
const arrTypesAccounts = ref(<any>[])
const tipoConto = ref(shared_consts.AccountType.USER)
const datasaved = ref(<any>null);
const step = ref(0);
const sendCoinDialog = ref(null);
const priceLabel = computed(() => circuitloaded.value ? `${qty.value} ` + circuitloaded.value.symbol : '')
const arrayMarkerLabel = ref(<any>[])
const circuittoload = ref(<ICircuit | undefined>undefined);
const circuitloaded = ref(<ICircuit>{});
const circuitdest = ref(<ICircuit | undefined>undefined);
const accountloaded = ref(<IAccount | undefined | null>undefined);
const accountdest = ref(<IAccount | undefined>undefined);
const remainingCoins = ref(0);
const maxsendable = ref(0);
const numstep = ref(0);
const arrTypesAccounts = ref(<any>[]);
const tipoConto = ref(shared_consts.AccountType.USER);
const qtyRef = ref(<any>null)
const causalRef = ref(<any>null)
const priceLabel = computed(() =>
circuitloaded.value ? `${qty.value} ` + circuitloaded.value.symbol : ''
);
const arrayMarkerLabel = ref(<any>[]);
const groupsListAdmin = ref(<IMyGroup[]>[])
const qtyRef = ref(<any>null);
const causalRef = ref(<any>null);
const arrGroupsList = ref(<any[]>[])
const groupsListAdmin = ref(<IMyGroup[]>[]);
watch(() => circuitsel.value, (newval, oldval) => {
tools.setCookie(tools.CIRCUIT_USE, newval)
aggiorna()
})
const arrGroupsList = ref(<any[]>[]);
watch(() => tipoConto.value, (newval, oldval) => {
watch(
() => circuitsel.value,
(newval, oldval) => {
tools.setCookie(tools.CIRCUIT_USE, newval);
aggiorna(true);
}
);
if (tipoConto.value === shared_consts.AccountType.CONTO_DI_GRUPPO) {
if (arrGroupsList.value.length >= 1)
from_groupname.value = arrGroupsList.value[0].value
watch(
() => tipoConto.value,
(newval, oldval) => {
if (tipoConto.value === shared_consts.AccountType.CONTO_DI_GRUPPO) {
if (arrGroupsList.value.length >= 1)
from_groupname.value = arrGroupsList.value[0].value;
}
tools.setCookie(tools.COOK_TIPOCONTO, tipoConto.value.toString());
aggiorna(true);
}
);
watch(
() => from_groupname.value,
(newval, oldval) => {
aggiorna(true);
}
);
watch(
() => from_username.value,
(newval, oldval) => {
aggiorna(true);
}
);
watch(
() => props.showprop,
(newval, oldval) => {
showpage.value = newval;
}
);
async function aggiorna(load: boolean = false) {
if (load) {
inizio_caricamento();
}
tools.setCookie(tools.COOK_TIPOCONTO, tipoConto.value.toString())
groupSel.value = null;
from_contocom.value = '';
accountloaded.value = null;
aggiorna()
})
watch(() => from_groupname.value, (newval, oldval) => {
aggiorna()
})
watch(() => from_username.value, (newval, oldval) => {
aggiorna()
})
watch(() => props.showprop, (newval, oldval) => {
console.log('props.showprop', props.showprop, newval)
showpage.value = newval
})
async function aggiorna() {
groupSel.value = null
from_contocom.value = ''
accountloaded.value = null
if (!circuittoload.value || (circuittoload.value && circuittoload.value.name !== circuitsel.value)) {
circuittoload.value = circuitStore.listcircuits.find((rec: ICircuit) => rec.name === circuitsel.value)
if (
!circuittoload.value ||
(circuittoload.value && circuittoload.value.name !== circuitsel.value)
) {
circuittoload.value = circuitStore.listcircuits.find(
(rec: ICircuit) => rec.name === circuitsel.value
);
if (circuittoload.value) {
loading.value = true
await userStore.loadCircuit(circuittoload.value.path, '').then(({ data, status }: { data: any, status: number }) => {
datasaved.value = data
})
loading.value = true;
await userStore
.loadCircuit(circuittoload.value.path, '')
.then(({ data, status }: { data: any; status: number }) => {
datasaved.value = data;
});
}
}
@@ -165,201 +203,232 @@ export default defineComponent({
try {
arrTypesAccounts.value = [
{
label: t('circuit.personale') + ' ' + from_username.value,
label: '👤 ' + t('circuit.personale') + ' - ' + from_username.value,
value: shared_consts.AccountType.USER,
},
]
];
if (datasaved.value.circuit) {
circuitloaded.value = datasaved.value.circuit
circuitloaded.value = datasaved.value.circuit;
if (tipoConto.value === shared_consts.AccountType.USER) {
accountloaded.value = userStore.getAccountByCircuitId(circuitloaded.value._id)
accountloaded.value = userStore.getAccountByCircuitId(
circuitloaded.value._id
);
} else if (tipoConto.value === shared_consts.AccountType.CONTO_DI_GRUPPO) {
groupSel.value = userStore.my.profile.manage_mygroups.find((group: IMyGroup) => from_groupname.value === group.groupname)
groupSel.value = userStore.my.profile.manage_mygroups.find(
(group: IMyGroup) => from_groupname.value === group.groupname
);
accountloaded.value = groupSel.value ? groupSel.value.account : null
accountloaded.value = groupSel.value ? groupSel.value.account : null;
} else if (tipoConto.value === shared_consts.AccountType.COMMUNITY_ACCOUNT) {
from_contocom.value = circuitloaded.value.path
accountloaded.value = circuitloaded.value ? circuitloaded.value.account : null
from_contocom.value = circuitloaded.value.path;
accountloaded.value = circuitloaded.value
? circuitloaded.value.account
: null;
}
groupsListAdmin.value = userStore.GroupsListWhereIAmAdminInTheCircuit(circuitloaded.value.name)
// console.log('groupsListAdmin.value', groupsListAdmin.value)
groupsListAdmin.value = userStore.GroupsListWhereIAmAdminInTheCircuit(
circuitloaded.value.name
);
arrGroupsList.value = []
arrGroupsList.value = [];
if (groupsListAdmin.value) {
for (const group of groupsListAdmin.value) {
let aggiungi = true
let aggiungi = true;
if (props.to_group && props.to_group.groupname === group.groupname)
aggiungi = false
aggiungi = false;
if (aggiungi)
arrGroupsList.value.push({ label: group.groupname, value: group.groupname });
arrGroupsList.value.push({
label: group.groupname,
value: group.groupname,
});
}
}
if (arrGroupsList.value.length > 0) {
arrTypesAccounts.value.push(
{
label: t('circuit.conticollettivi'),
value: shared_consts.AccountType.CONTO_DI_GRUPPO,
})
}
if (tools.iCanSendCoinsSuperUserCircuit(circuitsel.value) && (!props.to_contocom)) {
arrTypesAccounts.value.push({
label: t('circuit.contocom'),
value: shared_consts.AccountType.COMMUNITY_ACCOUNT,
})
label: '👥 ' + t('circuit.conticollettivi'),
value: shared_consts.AccountType.CONTO_DI_GRUPPO,
});
}
// if (tools.iAmAdminCircuit(circuitloaded.value.name))
// arrGroupsList.value.push({ label: circuitloaded.value.name, value: circuitloaded.value.path });
if (
tools.iCanSendCoinsSuperUserCircuit(circuitsel.value) &&
!props.to_contocom
) {
arrTypesAccounts.value.push({
label: '🏛️ ' + t('circuit.contocom'),
value: shared_consts.AccountType.COMMUNITY_ACCOUNT,
});
}
// accountdest.value = userStore.getAccountByCircuitId(circuitloaded.value._id)
if (accountloaded.value) {
remainingCoins.value = circuitStore.getRemainingCoinsToSend(accountloaded.value)
remainingCoins.value = circuitStore.getRemainingCoinsToSend(
accountloaded.value
);
if (accountloaded.value.saldo > 0) {
maxsendable.value = accountloaded.value.saldo + accountloaded.value.fidoConcesso
maxsendable.value =
accountloaded.value.saldo + accountloaded.value.fidoConcesso;
} else {
maxsendable.value = accountloaded.value.fidoConcesso
maxsendable.value = accountloaded.value.fidoConcesso;
}
if (remainingCoins.value < 10) {
remainingCoins.value = 10
remainingCoins.value = 10;
}
numstep.value = Math.round(maxsendable.value / 10)
numstep.value = Math.round(maxsendable.value / 10);
if (numstep.value < 1) {
numstep.value = 1
numstep.value = 1;
}
const quanti = [...Array(20).keys()].map(i => i + 1)
const quanti = [...Array(20).keys()].map((i) => i + 1);
for (const ind of quanti) {
const valuenorm = ind * numstep.value
const value = ind * numstep.value
const valuenorm = ind * numstep.value;
const value = ind * numstep.value;
if (value > remainingCoins.value) {
break
break;
} else {
const label = valuenorm.toString()
arrayMarkerLabel.value.push({ value, label })
const label = valuenorm.toString();
arrayMarkerLabel.value.push({ value, label });
}
}
}
}
} finally {
loading.value = false
loading.value = false;
if (load) {
fine_caricamento();
}
}
}
}
function inizio_caricamento() {
$q.loading.show({
message: 'Caricamento in corso...',
spinnerColor: 'white',
backgroundColor: 'primary',
messageColor: 'white',
});
}
function fine_caricamento() {
$q.loading.hide();
}
async function mounted() {
inizio_caricamento();
loading.value = true
to_user_real.value = props.to_user;
loading.value = true;
arrTypesAccounts.value = [
{
label: t('circuit.personale') + ' ' + from_username.value,
label: '👤 ' + t('circuit.personale') + ' - ' + from_username.value,
value: shared_consts.AccountType.USER,
},
]
];
let salvatoprec = tools.getCookie(tools.COOK_TIPOCONTO, -2, true)
let salvatoprec = tools.getCookie(tools.COOK_TIPOCONTO, -2, true);
// ....
if (props.to_user) {
console.log('user', props.to_user)
bothcircuits.value = userStore.getMyCircuitsInCommonByUser(props.to_user)
if (to_user_real.value) {
if (props.loadprofile) {
await userStore
.loadUserProfile({ username: to_user_real.value.username })
.then((ris) => {
to_user_real.value = ris;
});
}
bothcircuits.value = userStore.getMyCircuitsInCommonByUser(to_user_real.value);
if (props.circuitname) {
circuitsel.value = props.circuitname
circuitsel.value = props.circuitname;
} else {
const circcookie = tools.getCookie(tools.CIRCUIT_USE, bothcircuits.value[0])
if (circcookie && bothcircuits.value.findIndex((circ: ICircuit) => circ.name === circcookie) >= 0)
circuitsel.value = circcookie
const circcookie = tools.getCookie(tools.CIRCUIT_USE, bothcircuits.value[0]);
if (
circcookie &&
bothcircuits.value.findIndex((circ: ICircuit) => circ.name === circcookie) >=
0
)
circuitsel.value = circcookie;
}
if (bothcircuits.value && bothcircuits.value.find((name: any) => name !== circuitsel.value)) {
circuitsel.value = bothcircuits.value[0]
if (
bothcircuits.value &&
bothcircuits.value.find((name: any) => name !== circuitsel.value)
) {
circuitsel.value = bothcircuits.value[0];
}
if (props.qtydefault)
qty.value = props.qtydefault
if (props.qtydefault) qty.value = props.qtydefault;
if (props.sendRIS) {
if (props.sendRIS !== '0')
qty.value = props.sendRIS
if (props.sendRIS !== '0') qty.value = props.sendRIS;
}
await aggiorna()
await aggiorna();
// Se quello scelto c'è ancora sulla lista, allora lo imposto
if (arrTypesAccounts.value.findIndex((rec: any) => rec.value === salvatoprec) >= 0) {
tipoConto.value = salvatoprec
if (
arrTypesAccounts.value.findIndex((rec: any) => rec.value === salvatoprec) >= 0
) {
tipoConto.value = salvatoprec;
} else {
tipoConto.value = shared_consts.AccountType.USER
tipoConto.value = shared_consts.AccountType.USER;
}
// tipoConto.value = tools.getCookie(tools.COOK_TIPOCONTO, shared_consts.AccountType.USER, true)
showpage.value = true
showpage.value = true;
fine_caricamento();
}
if (props.to_group) {
console.log('group', props.to_group)
bothcircuits.value = userStore.getMyCircuitsInCommonByGroup(props.to_group)
console.log('bothcircuits', bothcircuits.value)
bothcircuits.value = userStore.getMyCircuitsInCommonByGroup(props.to_group);
if (props.circuitname) {
circuitsel.value = props.circuitname
circuitsel.value = props.circuitname;
} else {
circuitsel.value = tools.getCookie(tools.CIRCUIT_USE, bothcircuits.value[0])
circuitsel.value = tools.getCookie(tools.CIRCUIT_USE, bothcircuits.value[0]);
}
if (!userStore.IsMyCircuitByName(circuitsel.value)) {
circuitsel.value = bothcircuits.value[0]
circuitsel.value = bothcircuits.value[0];
}
await aggiorna()
await aggiorna();
showpage.value = true
showpage.value = true;
}
if (props.to_contocom) {
bothcircuits.value = userStore.getMyCircuits()
bothcircuits.value = userStore.getMyCircuits();
console.log('to_contocom', props.to_contocom)
circuitsel.value = props.circuitname
circuitsel.value = props.circuitname;
if (!userStore.IsMyCircuitByName(circuitsel.value)) {
circuitsel.value = bothcircuits.value[0]
circuitsel.value = bothcircuits.value[0];
}
await aggiorna()
await aggiorna();
showpage.value = true
showpage.value = true;
}
loading.value = false
emit('showed')
loading.value = false;
emit('showed');
}
function hide() {
emit('close', true)
showpage.value = false
emit('close', true);
showpage.value = false;
}
function sendCoin() {
console.log('sendcoin', qty.value, props.to_group ? props.to_group.groupname : (props.to_user ? props.to_user.username : props.to_contocom))
const ok = (props.to_user && props.to_user.username) ||
const ok =
(to_user_real.value && to_user_real.value.username) ||
(props.to_group && props.to_group.groupname) ||
(props.to_contocom)
props.to_contocom;
if (ok && qty.value && circuitloaded.value) {
const myrecsendcoin: ISendCoin = {
@@ -371,122 +440,104 @@ export default defineComponent({
causal: causal.value,
symbol: circuitloaded.value.symbol,
causalDest: props.causalDest,
}
};
myrecsendcoin.groupdest = props.to_group ? props.to_group.groupname : ''
myrecsendcoin.contoComDest = props.to_contocom
myrecsendcoin.grouporig = tipoConto.value === shared_consts.AccountType.CONTO_DI_GRUPPO ? from_groupname.value : ''
myrecsendcoin.contoComOrig = tipoConto.value === shared_consts.AccountType.COMMUNITY_ACCOUNT ? from_contocom.value : ''
myrecsendcoin.dest = props.to_user ? props.to_user.username : ''
myrecsendcoin.groupdest = props.to_group ? props.to_group.groupname : '';
myrecsendcoin.contoComDest = props.to_contocom;
myrecsendcoin.grouporig =
tipoConto.value === shared_consts.AccountType.CONTO_DI_GRUPPO
? from_groupname.value
: '';
myrecsendcoin.contoComOrig =
tipoConto.value === shared_consts.AccountType.COMMUNITY_ACCOUNT
? from_contocom.value
: '';
myrecsendcoin.dest = to_user_real.value ? to_user_real.value.username : '';
if (myrecsendcoin) {
tools.sendCoinsByCircuit($q, $router, circuitloaded.value, myrecsendcoin)
tools
.sendCoinsByCircuit($q, $router, circuitloaded.value, myrecsendcoin)
.then((ris: any) => {
if (ris) {
showpage.value = false
showpage.value = false;
}
})
});
}
}
}
function ifNextCheck(actualstep: number) {
let fase1ok = false
let fase1ok = false;
if (circuitloaded.value && !!circuitloaded.value._id) {
fase1ok = !(
!circuitloaded.value.transactionsEnabled ||
(tipoConto.value === shared_consts.AccountType.USER &&
props.to_user &&
from_username.value === props.to_user.username) ||
(tipoConto.value ===
shared_consts.AccountType.CONTO_DI_GRUPPO &&
to_user_real.value &&
from_username.value === to_user_real.value.username) ||
(tipoConto.value === shared_consts.AccountType.CONTO_DI_GRUPPO &&
!from_groupname.value) ||
(tipoConto.value ===
shared_consts.AccountType.CONTO_DI_GRUPPO &&
(tipoConto.value === shared_consts.AccountType.CONTO_DI_GRUPPO &&
props.to_group &&
from_groupname.value &&
props.to_group.groupname === from_groupname.value) ||
(tipoConto.value ===
shared_consts.AccountType.COMMUNITY_ACCOUNT &&
(tipoConto.value === shared_consts.AccountType.COMMUNITY_ACCOUNT &&
!from_contocom.value)
)
}
if (actualstep === 0) {
return fase1ok
} else if (actualstep === 1) {
return fase1ok && checkRisValid()
} else if (actualstep === 2) {
return fase1ok && checkRisValid()
);
}
return fase1ok && checkRisValid() && causal.value;
}
function checkRisValid() {
return qty.value ? qty.value && getQty() >= 0.01 && accountloaded.value
&& getQty() <= circuitStore.getRemainingCoinsToSend(accountloaded.value) : false
return qty.value
? qty.value &&
getQty() >= 0.01 &&
accountloaded.value &&
getQty() <= circuitStore.getRemainingCoinsToSend(accountloaded.value)
: false;
}
function getQty(): number {
let myqty: number | null = null
let myqty: number | null = null;
try {
if (qty.value) {
myqty = parseFloat(String(qty.value))
myqty = parseFloat(String(qty.value));
}
} catch (e) {
return 0
return 0;
}
return myqty ? myqty : 0
return myqty ? myqty : 0;
}
function getTitle(step: number) {
if (step === 0) {
return 'Circuito'
return 'Circuito';
} else if (step === 1) {
return 'Quantità'
return 'Quantità';
} else if (step === 2) {
return 'Causale'
return 'Causale';
}
}
function getIcon(step: number) {
if (step === 0) {
return 'circuit'
return 'circuit';
} else if (step === 1) {
return 'attach_money'
return 'attach_money';
} else if (step === 2) {
return 'description'
return 'description';
}
}
function clickAvanti(actualstep: number) {
if (actualstep === 0) {
step.value = 1
} else if (actualstep === 1) {
step.value = 2
} else if (actualstep === 2) {
sendCoin()
}
}
function clickIndietro(actualstep: number) {
if (actualstep === 1) {
step.value = 0
} else if (actualstep === 2) {
step.value = 1
} else if (actualstep === 0) {
hide()
}
function setQty(value: string | number) {
qty.value = value;
}
onMounted(mounted)
onMounted(mounted);
return {
t,
@@ -522,12 +573,13 @@ export default defineComponent({
shared_consts,
step,
ifNextCheck,
clickIndietro,
clickAvanti,
getTitle,
getIcon,
sendCoinDialog,
visubanner,
}
showKeyboard,
setQty,
to_user_real,
};
},
})
});

View File

@@ -5,375 +5,351 @@
:maximized="$q.screen.lt.sm"
@hide="hide"
@show="qtyRef ? qtyRef.focus() : ''"
transition-show="slide-up"
transition-hide="slide-down"
>
<q-card
class="dialog_card"
style="display: flex; flex-direction: column; height: 100%"
class="send-coins-dialog"
:class="{ 'mobile-fullheight': $q.screen.lt.sm }"
>
<q-bar class="bg-primary text-white">
{{ $t("circuit.sendcoins") }}
<q-space />
<q-btn flat round color="white" icon="close" v-close-popup></q-btn>
</q-bar>
<!-- Header con gradiente -->
<q-card-section class="dialog-header q-pa-none">
<div class="header-gradient">
<!-- Top bar -->
<div class="header-top-bar">
<div class="header-title-wrapper">
<div class="ris-coin-icon">
<img
v-if="circuitloaded.symbol === 'RIS'"
src="/images/1ris_rosso_100.png"
alt="RIS"
class="ris-logo"
/>
<span v-else class="coin-symbol">{{ circuitloaded.symbol }}</span>
</div>
<span class="header-title">Invia {{ circuitloaded.symbol || 'Crediti' }}</span>
</div>
<q-btn
flat
round
dense
icon="close"
color="white"
class="close-btn"
v-close-popup
/>
</div>
<q-card-section>
<q-stepper
v-model="step"
ref="stepper"
color="primary"
animated
class="mystepper"
<!-- Balance Card compatto -->
<div class="balance-card">
<div class="balance-info">
<div class="balance-main">
<span class="balance-label">Saldo disponibile</span>
<span class="balance-value">
{{ accountloaded ? circuitStore.getRemainingCoinsToSend(accountloaded).toFixed(2) : '0.00' }}
<span class="balance-symbol">{{ circuitloaded.symbol }}</span>
</span>
</div>
<div class="balance-fido" v-if="accountloaded?.fidoConcesso > 0">
<span class="fido-label">Fido</span>
<span class="fido-value">+{{ accountloaded.fidoConcesso.toFixed(2) }}</span>
</div>
</div>
</div>
</div>
</q-card-section>
<!-- Content -->
<q-card-section
class="dialog-content scroll"
:style="$q.screen.lt.sm ? 'padding-bottom: 80px;' : ''"
>
<!-- Circuit Check -->
<CCheckCircuitsEnabled
:to_user="to_user_real"
:to_group="to_group"
/>
<!-- Circuit Selector -->
<div v-if="circuitloaded.symbol && circuitname === ''" class="section-block">
<q-select
v-model="circuitsel"
:options="bothcircuits"
:behavior="$q.platform.is.ios === true ? 'dialog' : 'menu'"
outlined
dense
label="Circuito"
class="modern-select"
popup-content-class="modern-select-popup"
>
<template v-slot:prepend>
<q-icon name="account_balance_wallet" color="primary" />
</template>
</q-select>
</div>
<!-- Province Banner -->
<q-banner
v-if="showProvinceToSelect"
rounded
class="warning-banner q-mb-sm"
>
<q-step
:name="0"
:title="getTitle(0)"
:icon="getIcon(0)"
:done="ifNextCheck(0)"
<template v-slot:avatar>
<q-icon name="warning" color="white" />
</template>
{{ $t('circuit.insertprovince_text') }}
</q-banner>
<!-- Sender Section -->
<div v-if="circuitsel" class="section-block">
<q-select
v-if="arrTypesAccounts.length > 0"
v-model="tipoConto"
:options="arrTypesAccounts"
outlined
dense
emit-value
map-options
label="Mittente"
class="modern-select"
popup-content-class="modern-select-popup"
>
<CCheckCircuitsEnabled :to_user="to_user" :to_group="to_group">
</CCheckCircuitsEnabled>
<template v-slot:prepend>
<q-icon name="person" color="primary" />
</template>
</q-select>
<div v-if="circuitloaded.symbol">
<q-select
v-if="circuitname === ''"
:behavior="$q.platform.is.ios === true ? 'dialog' : 'menu'"
rounded
dense
outlined
v-model="circuitsel"
:options="bothcircuits"
label="Circuito"
>
</q-select>
<div v-else>{{ circuitname }}</div>
</div>
<q-banner
v-if="showProvinceToSelect"
rounded
class="bg-red text-white"
style="text-align: center"
>
<div>
<em style="font-weight: bold">{{
$t("circuit.insertprovince_text")
}}</em>
</div>
<br />
</q-banner>
<br />
<div v-if="circuitsel">
<q-banner
rounded
dense
class="shadow-5 q-my-sm"
color="primary q-title"
style="text-align: center"
>
<div class="mybanner_left q-mb-sm">
<q-select
v-if="arrTypesAccounts.length > 0"
v-model="tipoConto"
class="my-custom-select"
outlined
bg-color="light-blue-2"
emit-value
map-options
:options="arrTypesAccounts"
:label="$t('circuit.sender')"
></q-select>
</div>
<div
v-if="
tipoConto === shared_consts.AccountType.CONTO_DI_GRUPPO
"
>
<q-select
v-model="from_groupname"
:options="arrGroupsList"
:label="$t('circuit.choosecontocom')"
rounded
emit-value
map-options
>
<!-- Mostra i gruppi su cui sei Admin -->
</q-select>
</div>
<div
v-else-if="
tipoConto === shared_consts.AccountType.COMMUNITY_ACCOUNT
"
>
<q-input
v-model="from_contocom"
:label="$t('circuit.contocom')"
readonly
class="q-my-sm"
>
</q-input>
</div>
<CSaldo
v-if="circuitloaded && circuitloaded.symbol"
:symbol="circuitloaded.symbol"
:color="circuitloaded.color"
:saldo="accountloaded ? accountloaded.saldo : 0"
:qtarem="
accountloaded
? circuitStore.getRemainingCoinsToSend(accountloaded)
: 0
"
>
</CSaldo>
</q-banner>
<q-banner
rounded
dense
class="shadow-5 q-my-sm"
color="primary q-title"
>
<div class="mybanner_left bg-green text-white q-mb-sm">
{{ $t("circuit.dest") }}
</div>
<!-- Destination -->
<CMyUserOnlyView
v-if="to_user"
:mycontact="to_user"
:visu="costanti.FIND_PEOPLE"
@setCmd="tools.setCmd"
>
</CMyUserOnlyView>
<CMyGroupOnlyView
v-if="to_group"
:mygrp="to_group"
:visu="costanti.USER_GROUPS"
:circuitname="circuitloaded.name"
>
</CMyGroupOnlyView>
<CMyGroupOnlyView
v-if="circuitloaded && !!circuitloaded._id && to_contocom"
:mygrp="{ groupname: to_contocom }"
:visu="costanti.USER_GROUPS"
:circuitname="circuitloaded.name"
>
</CMyGroupOnlyView>
</q-banner>
</div>
<q-inner-loading id="spinner" :showing="loading">
<q-spinner-tail size="6em" color="primary" />
</q-inner-loading>
</q-step>
<q-step
:name="1"
:title="getTitle(1)"
:icon="getIcon(1)"
:done="ifNextCheck(1)"
<!-- Group Account Selector -->
<q-select
v-if="tipoConto === shared_consts.AccountType.CONTO_DI_GRUPPO"
v-model="from_groupname"
:options="arrGroupsList"
:label="$t('circuit.choosecontocom')"
outlined
dense
emit-value
map-options
class="modern-select q-mt-sm"
>
<div v-if="circuitloaded && !!circuitloaded._id">
<q-banner
v-if="!circuitloaded.transactionsEnabled"
rounded
class="bg-red text-white"
style="text-align: center"
>
<em style="font-weight: bold">{{
$t("circuit.transactionsEnabled_text")
}}</em
><br />
</q-banner>
<template v-slot:prepend>
<q-icon name="groups" color="primary" />
</template>
</q-select>
<q-input
ref="qtyRef"
class="q-py-sm text-h5"
outlined
v-model="qty"
:type="$q.platform.is.mobile ? 'string' : 'number'"
:rules="[
(val) =>
val <=
circuitStore.getRemainingCoinsToSend(accountloaded) ||
t('circuit.qta_remaining_to_send', {
maxqta:
circuitStore.getRemainingCoinsToSend(accountloaded),
symbol: circuitloaded.symbol,
}),
(val) => val > 0 || t('circuit.qta_not_valid'),
]"
:label="
t('movement.amount_to_send', {
qtamax: circuitStore.getRemainingCoinsToSend(accountloaded)
? circuitStore
.getRemainingCoinsToSend(accountloaded)
.toFixed(2)
: 0 + ` ` + circuitloaded.symbol,
})
"
input-class="text-right"
input-style="padding-bottom: 24px !important;"
v-on:keyup.enter="$event.target.nextElementSibling.focus()"
>
<!--val => val > circuitStore.getMaxCoinsToSend(accountloaded) || t('circuit.qta_max_to_send', { maxqta: tools.getRemainingCoinsToSend(accountloaded), symbol: circuitloaded.symbol })]" -->
<template v-slot:append>
<div class="text-h5">
<em
class="q-px-sm text-white rounded-borders"
:style="
`background-color: ` +
(circuitloaded.color ? circuitloaded.color : '#ff5500')
"
>{{ circuitloaded.symbol }}</em
>
</div>
</template>
</q-input>
<div class="q-mt-md">
<CNumericKeyboard v-model="qty" :showInput="false" />
</div>
</div>
</q-step>
<q-step
:name="2"
:title="getTitle(2)"
:icon="getIcon(2)"
:done="ifNextCheck(2)"
<!-- Community Account -->
<q-input
v-if="tipoConto === shared_consts.AccountType.COMMUNITY_ACCOUNT"
v-model="from_contocom"
:label="$t('circuit.contocom')"
outlined
dense
readonly
class="modern-input q-mt-sm"
>
<!-- Destination -->
<CMyUserOnlyView
v-if="to_user"
:mycontact="to_user"
:visu="costanti.FIND_PEOPLE"
@setCmd="tools.setCmd"
>
</CMyUserOnlyView>
<CMyGroupOnlyView
v-if="to_group"
:mygrp="to_group"
:visu="costanti.USER_GROUPS"
:circuitname="circuitloaded.name"
>
</CMyGroupOnlyView>
<CMyGroupOnlyView
v-if="circuitloaded && !!circuitloaded._id && to_contocom"
:mygrp="{ groupname: to_contocom }"
:visu="costanti.USER_GROUPS"
:circuitname="circuitloaded.name"
>
</CMyGroupOnlyView>
<template v-slot:prepend>
<q-icon name="account_balance" color="primary" />
</template>
</q-input>
</div>
<div v-if="causalDest">
<q-field
rounded
filled
:label="t('circuit.descrizione_destinatario')"
stack-label
>
<template v-slot:append>
<q-icon name="description" />
</template>
<template v-slot:control>
<div class="self-center full-width no-outline" tabindex="0">
{{ causalDest }}
</div>
</template>
</q-field>
</div>
<!-- Recipient Section -->
<div v-if="circuitsel" class="section-block">
<label class="section-label">Destinatario</label>
<div class="recipient-card">
<div class="recipient-content">
<!-- User Recipient -->
<CMyUserOnlyView
v-if="to_user_real"
:mycontact="to_user_real"
:visu="costanti.FIND_PEOPLE"
@setCmd="tools.setCmd"
class="recipient-view"
/>
<!--<q-banner v-if="visubanner" rounded class="text-center text-bold">
{{ t('circuit.descr_casuale') }}
<template v-slot:action>
<q-btn label="Chiudi" flat @click="visubanner = false"></q-btn>
</template>
</q-banner>-->
<div class="q-mt-sm text-italic text-blue text-bold">
{{ $t("circuit.descr_casuale") }}
<!-- Group Recipient -->
<CMyGroupOnlyView
v-if="to_group"
:mygrp="to_group"
:visu="costanti.USER_GROUPS"
:circuitname="circuitloaded.name"
class="recipient-view"
/>
<!-- Community Account Recipient -->
<CMyGroupOnlyView
v-if="circuitloaded && !!circuitloaded._id && to_contocom"
:mygrp="{ groupname: to_contocom }"
:visu="costanti.USER_GROUPS"
:circuitname="circuitloaded.name"
class="recipient-view"
/>
</div>
</div>
</div>
<!-- Amount Section - Compatto -->
<div v-if="circuitsel" class="section-block">
<label class="section-label">Importo</label>
<div class="amount-input-row" @click="$q.screen.lt.sm ? showKeyboard = true : qtyRef?.focus()">
<q-input
ref="causalRef"
v-model="causal"
autogrow
rounded
filled
maxlength="200"
counter
:label="$t('circuit.note')"
class="q-my-sm full-width"
ref="qtyRef"
v-model="qty"
:type="$q.screen.lt.sm ? 'text' : 'number'"
outlined
dense
input-class="amount-input-field"
:readonly="$q.screen.lt.sm"
:rules="[
(val) => !isNaN(parseFloat(val)) || t('circuit.qta_not_valid'),
(val) => parseFloat(val) <= circuitStore.getRemainingCoinsToSend(accountloaded) || t('circuit.qta_remaining_to_send', { maxqta: circuitStore.getRemainingCoinsToSend(accountloaded), symbol: circuitloaded.symbol }),
(val) => parseFloat(val) > 0 || t('circuit.qta_not_valid'),
]"
hide-bottom-space
class="amount-input"
@keyup.enter="causalRef?.focus()"
>
<template v-slot:after>
<q-avatar>
<img
:src="
userStore.my.profile
? userStore.getImgByProfile(userStore.my)
: ''
"
:alt="userStore.my.username"
/>
</q-avatar>
</template>
<template v-slot:prepend>
<q-icon name="comment" />
<span class="currency-symbol"></span>
</template>
<template v-slot:append>
<div class="coin-badge" :style="`background: ${circuitloaded.color || '#ff5500'}`">
{{ circuitloaded.symbol }}
</div>
<q-btn
v-if="$q.screen.lt.sm"
flat
round
dense
icon="keyboard"
size="sm"
class="keyboard-btn q-ml-xs"
@click.stop="showKeyboard = true"
/>
</template>
</q-input>
</div>
</div>
<div class="sendris">
{{
$t("circuit.sendcoinsto", {
qty,
coin: circuitloaded.symbol,
dest: to_group
? to_group.groupname
: to_user
? tools.getNomeUtenteByRecUser(to_user)
: to_contocom,
})
}}
sul {{ circuitsel }}
</div>
</q-step>
</q-stepper>
<!-- Transactions Disabled Banner -->
<q-banner
v-if="circuitloaded && !!circuitloaded._id && !circuitloaded.transactionsEnabled"
rounded
class="error-banner q-mb-sm"
>
<template v-slot:avatar>
<q-icon name="error" color="white" />
</template>
{{ $t('circuit.transactionsEnabled_text') }}
</q-banner>
<!-- Note Section - Compatto -->
<div v-if="circuitsel" class="section-block">
<q-input
ref="causalRef"
v-model="causal"
outlined
dense
type="textarea"
rows="3"
maxlength="200"
counter
label="Nota per il destinatario"
placeholder="Scrivi un messaggio..."
class="modern-textarea"
:rules="[
(val) => !!val?.trim() || 'Inserisci un messaggio',
(val) => val.trim().length >= 2 || 'Minimo 2 caratteri',
]"
lazy-rules
>
<template v-slot:prepend>
<q-icon name="message" color="grey-6" class="self-start q-mt-xs" />
</template>
<template v-slot:after>
<q-avatar size="32px">
<img
:src="userStore.my.profile ? userStore.getImgByProfile(userStore.my) : ''"
:alt="userStore.my.username"
/>
</q-avatar>
</template>
</q-input>
</div>
</q-card-section>
<q-card-actions align="center" style="row justify-between">
<!-- Fixed Bottom Actions -->
<q-card-actions
class="dialog-actions"
:class="{ 'fixed-bottom-actions': $q.screen.lt.sm }"
>
<q-btn
class="col"
flat
:label="$t('dialog.indietro')"
color="primary"
icon="navigate_before"
@click="clickIndietro(step)"
></q-btn>
:label="t('dialog.cancel')"
class="cancel-btn"
v-close-popup
/>
<q-btn
class="col"
:disable="!ifNextCheck(step)"
:label="
step === 0 || step === 1
? t('dialog.avanti')
: t('circuit.sendcoins', {
qty,
coin: circuitloaded.symbol,
dest: to_group
? to_group.groupname
: to_user
? tools.getNomeUtenteByRecUser(to_user)
: to_contocom,
})
"
color="positive"
:icon-right="
step === 2 ? 'img: /images/1ris_rosso_100.png' : 'navigate_next'
"
@click="clickAvanti(step)"
></q-btn>
class="send-btn"
:class="{ 'btn-disabled': !ifNextCheck(step) }"
@click="sendCoin"
>
<span class="send-btn-text">Invia {{ qty || 0 }} {{ circuitloaded.symbol }}</span>
<img
v-if="circuitloaded.symbol === 'RIS'"
src="/images/1ris_rosso_100.png"
alt="RIS"
class="send-btn-icon"
/>
<q-icon v-else name="send" class="q-ml-sm" />
</q-btn>
</q-card-actions>
</q-card>
</q-dialog>
<!-- Numeric Keyboard Dialog -->
<q-dialog
v-model="showKeyboard"
position="bottom"
seamless
no-backdrop-dismiss
>
<q-card class="keyboard-dialog">
<q-card-section class="keyboard-header">
<div class="keyboard-header-content">
<span class="keyboard-title">Inserisci importo</span>
<q-btn
flat
dense
label="Fatto"
color="primary"
class="done-btn"
v-close-popup
/>
</div>
<div class="keyboard-display">
<span class="keyboard-amount">{{ qty || '0' }}</span>
<div class="keyboard-coin-badge" :style="`background: ${circuitloaded.color || '#ff5500'}`">
{{ circuitloaded.symbol }}
</div>
</div>
</q-card-section>
<q-card-section class="keyboard-section q-pa-sm">
<CNumericKeyboard
v-model="qty"
:showInput="false"
@update:model-value="setQty"
/>
</q-card-section>
</q-card>
</q-dialog>
</template>
<script lang="ts" src="./CSendCoins.ts">
</script>
<script lang="ts" src="./CSendCoins.ts"></script>
<style lang="scss" scoped>
@import "./CSendCoins.scss";
@import './CSendCoins.scss';
</style>

View File

@@ -0,0 +1,62 @@
.check-email-page {
min-height: 100vh;
display: flex;
flex-direction: column;
}
.check-email-container {
max-width: 500px;
margin: 0 auto;
flex: 1;
}
.email-icon-wrapper {
text-align: center;
margin-bottom: 24px;
.q-icon {
animation: pulse 2s ease-in-out infinite;
}
}
@keyframes pulse {
0%, 100% {
opacity: 1;
transform: scale(1);
}
50% {
opacity: 0.7;
transform: scale(1.05);
}
}
.main-banner,
.info-banner {
.banner-content {
font-size: 15px;
line-height: 1.5;
}
}
.resend-card {
border-radius: 12px;
.countdown-section {
display: flex;
align-items: center;
justify-content: center;
padding: 12px;
background: #f5f5f5;
border-radius: 8px;
strong {
font-family: monospace;
font-size: 18px;
margin-left: 4px;
}
}
}
.helpful-links {
padding: 16px 0;
}

View File

@@ -0,0 +1,140 @@
import { defineComponent, ref, computed, onMounted, onUnmounted } from 'vue';
import { useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { LandingFooter } from '@/components/LandingFooter';
import { useUserStore } from '@store/UserStore';
import { tools } from '@tools';
import { Api } from '@api';
const RESEND_COOLDOWN_MINUTES = 1;
const RESEND_COOLDOWN_MS = RESEND_COOLDOWN_MINUTES * 60 * 1000;
const STORAGE_KEY = 'lastVerificationEmailSent';
export default defineComponent({
name: 'CheckEmail',
components: { LandingFooter },
setup() {
const { t } = useI18n();
const router = useRouter();
const userStore = useUserStore();
// State
const sending = ref(false);
const emailSent = ref(false);
const errorMessage = ref('');
const timeLeft = ref(0);
let countdownInterval: ReturnType<typeof setInterval> | null = null;
// Computed
const canResend = computed(() => timeLeft.value <= 0);
const formattedTimeLeft = computed(() => {
const minutes = Math.floor(timeLeft.value / 60);
const seconds = timeLeft.value % 60;
return `${minutes}:${seconds.toString().padStart(2, '0')}`;
});
// Methods
const getLastSentTime = (): number => {
const stored = localStorage.getItem(STORAGE_KEY);
return stored ? parseInt(stored, 10) : 0;
};
const setLastSentTime = () => {
localStorage.setItem(STORAGE_KEY, Date.now().toString());
};
const calculateTimeLeft = (): number => {
const lastSent = getLastSentTime();
if (!lastSent) return 0;
const elapsed = Date.now() - lastSent;
const remaining = RESEND_COOLDOWN_MS - elapsed;
return remaining > 0 ? Math.ceil(remaining / 1000) : 0;
};
const startCountdown = () => {
timeLeft.value = calculateTimeLeft();
if (timeLeft.value > 0) {
countdownInterval = setInterval(() => {
timeLeft.value = calculateTimeLeft();
if (timeLeft.value <= 0 && countdownInterval) {
clearInterval(countdownInterval);
countdownInterval = null;
}
}, 1000);
}
};
const resendVerificationEmail = async () => {
if (!canResend.value || sending.value) return;
sending.value = true;
emailSent.value = false;
errorMessage.value = '';
try {
await userStore.resendVerificationEmail();
setLastSentTime();
emailSent.value = true;
startCountdown();
// Nascondi messaggio successo dopo 5 secondi
setTimeout(() => {
emailSent.value = false;
}, 5000);
} catch (error: any) {
errorMessage.value =
error?.message ||
t('components.authentication.email_verification.errore_invio');
// Nascondi messaggio errore dopo 5 secondi
setTimeout(() => {
errorMessage.value = '';
}, 5000);
} finally {
sending.value = false;
}
};
const goToChangeEmail = () => {
router.push({ name: 'ChangeEmail' });
};
const logout = async () => {
await userStore.logout();
router.push({ name: 'Home' });
};
// Lifecycle
onMounted(() => {
startCountdown();
});
onUnmounted(() => {
if (countdownInterval) {
clearInterval(countdownInterval);
}
});
return {
t,
tools,
sending,
emailSent,
errorMessage,
canResend,
timeLeft,
formattedTimeLeft,
resendVerificationEmail,
goToChangeEmail,
logout,
};
},
});

View File

@@ -0,0 +1,156 @@
<template>
<q-page
v-if="tools.isLogged() && !tools.isEmailVerified()"
padding
class="check-email-page"
>
<div class="check-email-container">
<!-- Icona principale -->
<div class="email-icon-wrapper">
<q-icon
name="mark_email_unread"
size="80px"
color="warning"
/>
</div>
<!-- Banner principale -->
<q-banner
rounded
class="bg-warning text-black main-banner"
>
<template v-slot:avatar>
<q-icon
name="info"
color="black"
/>
</template>
<div
class="banner-content"
v-html="
$t('components.authentication.email_verification2.link_sent', {
botname: tools.getBotName(),
})
"
></div>
</q-banner>
<!-- Sezione Reinvio Email -->
<q-card
flat
bordered
class="resend-card q-mt-lg"
>
<q-card-section>
<div class="text-subtitle1 text-weight-medium q-mb-sm">
{{ t('components.authentication.email_verification2.non_ricevuta') }}
</div>
<!-- Countdown attivo -->
<div
v-if="!canResend"
class="countdown-section"
>
<q-icon
name="schedule"
size="20px"
color="grey-6"
class="q-mr-sm"
/>
<span class="text-grey-7">
{{ t('components.authentication.email_verification2.attendi') }}
<strong>{{ formattedTimeLeft }}</strong>
</span>
</div>
<!-- Bottone Reinvio -->
<q-btn
v-else
:loading="sending"
:disable="sending"
color="primary"
icon="send"
:label="t('components.authentication.email_verification2.reinvia')"
class="full-width q-mt-sm"
@click="resendVerificationEmail"
/>
<!-- Messaggio successo -->
<q-banner
v-if="emailSent"
rounded
dense
class="bg-positive text-white q-mt-md"
>
<template v-slot:avatar>
<q-icon
name="check_circle"
color="white"
/>
</template>
{{ t('components.authentication.email_verification2.inviata_successo') }}
</q-banner>
<!-- Messaggio errore -->
<q-banner
v-if="errorMessage"
rounded
dense
class="bg-negative text-white q-mt-md"
>
<template v-slot:avatar>
<q-icon
name="error"
color="white"
/>
</template>
{{ errorMessage }}
</q-banner>
</q-card-section>
<!-- Banner istruzioni -->
<q-banner
rounded
class="bg-grey-3 text-black info-banner q-mt-md"
>
<template v-slot:avatar>
<q-icon
name="help_outline"
color="grey-7"
/>
</template>
<div
class="banner-content"
v-html="$t('components.authentication.email_verification2.se_non_ricevo')"
></div>
</q-banner>
</q-card>
<!-- Link utili -->
<div class="helpful-links q-mt-lg text-center">
<q-btn
flat
dense
color="primary"
:label="t('components.authentication.email_verification2.cambia_email')"
@click="goToChangeEmail"
/>
<span class="q-mx-sm text-grey-5">|</span>
<q-btn
flat
dense
color="grey-7"
:label="t('components.authentication.email_verification2.logout')"
@click="logout"
/>
</div>
</div>
<LandingFooter />
</q-page>
</template>
<script lang="ts" src="./CheckEmail.ts"></script>
<style lang="scss" scoped>
@import './CheckEmail.scss';
</style>

View File

@@ -0,0 +1 @@
export {default as CheckEmail} from './checkemail.vue'

View File

@@ -0,0 +1,569 @@
<template>
<q-card
class="ollama-chat"
:style="{ height: height }"
>
<!-- Header -->
<q-card-section class="q-py-sm bg-primary text-white">
<div class="row items-center justify-between">
<div class="row items-center q-gutter-sm">
<q-badge
:color="isConnected ? 'green' : 'red'"
rounded
/>
<span class="text-subtitle1 text-weight-medium"
>{{ title }} - {{ settings.model }}</span
>
</div>
<div class="row q-gutter-xs">
<q-btn
flat
dense
round
icon="settings"
@click="showSettings = true"
>
<q-tooltip>Impostazioni</q-tooltip>
</q-btn>
<q-btn
flat
dense
round
icon="delete_sweep"
@click="clearChat"
>
<q-tooltip>Pulisci Chat</q-tooltip>
</q-btn>
</div>
</div>
</q-card-section>
<!-- Messages -->
<q-card-section
ref="messagesContainer"
class="messages-container q-pa-md"
>
<div
v-if="messages.length === 0"
class="text-center text-grey-6 q-py-xl"
>
<q-icon
name="chat"
size="64px"
color="grey-4"
/>
<p class="q-mt-md">Inizia una conversazione!</p>
</div>
<div
v-for="(msg, index) in messages"
:key="index"
class="message-wrapper q-mb-md"
:class="msg.role === 'user' ? 'text-right' : 'text-left'"
>
<div
class="message-bubble q-pa-sm q-px-md"
:class="msg.role === 'user' ? 'bg-primary text-white' : 'bg-grey-3 text-dark'"
>
<!-- Puntini solo se streaming E contenuto vuoto -->
<div
v-if="msg.role === 'assistant' && msg.isStreaming && !msg.content"
class="typing-indicator"
>
<span></span><span></span><span></span>
</div>
<!-- Contenuto messaggio (anche durante streaming se già arrivato) -->
<div
v-else
class="message-content"
>
<div v-html="formatMessage(msg.content)"></div>
</div>
</div>
<div class="text-caption text-grey-6 q-mt-xs">
{{ msg.role === 'user' ? 'Tu' : modelName }} {{ formatTime(msg.timestamp) }}
</div>
</div>
</q-card-section>
<!-- Input -->
<q-card-section class="q-pa-sm border-top">
<div class="row q-gutter-sm items-end">
<q-input
v-model="inputMessage"
:disable="isGenerating"
outlined
dense
autogrow
class="col"
placeholder="Scrivi un messaggio..."
@keydown.enter.prevent="handleEnter"
>
<template v-slot:append>
<q-btn
v-if="isGenerating"
flat
dense
round
icon="stop"
color="negative"
@click="stopGeneration"
>
<q-tooltip>Ferma generazione</q-tooltip>
</q-btn>
</template>
</q-input>
<q-btn
:loading="isGenerating"
:disable="!inputMessage.trim() || isGenerating"
color="primary"
icon="send"
@click="sendMessage"
>
<q-tooltip>Invia messaggio</q-tooltip>
</q-btn>
</div>
</q-card-section>
<!-- Settings Dialog -->
<q-dialog v-model="showSettings">
<q-card style="min-width: 350px">
<q-card-section class="row items-center q-pb-none">
<div class="text-h6">Impostazioni</div>
<q-space />
<q-btn
icon="close"
flat
round
dense
v-close-popup
/>
</q-card-section>
<q-card-section class="q-gutter-md">
<q-input
v-model="settings.baseUrl"
outlined
label="Ollama URL"
hint="URL del server Ollama"
/>
<q-select
v-model="settings.model"
:options="availableModels"
outlined
label="Modello"
emit-value
map-options
/>
<div>
<div class="text-caption q-mb-sm">
Temperatura: {{ settings.temperature }}
</div>
<q-slider
v-model="settings.temperature"
:min="0"
:max="2"
:step="0.1"
label
color="primary"
/>
</div>
<q-input
v-model="settings.systemPrompt"
outlined
type="textarea"
label="System Prompt (opzionale)"
hint="Istruzioni per il comportamento dell'AI"
/>
</q-card-section>
<q-card-actions align="right">
<q-btn
flat
label="Annulla"
color="grey"
v-close-popup
/>
<q-btn
flat
label="Salva"
color="primary"
@click="saveSettings"
v-close-popup
/>
</q-card-actions>
</q-card>
</q-dialog>
</q-card>
</template>
<script>
import { ref, reactive, computed, onMounted, nextTick, watch } from 'vue';
import OllamaService from './OllamaService.js';
import { tools } from '@tools';
export default {
name: 'OllamaChat',
props: {
title: {
type: String,
default: 'Chat AI',
},
height: {
type: String,
default: '600px',
},
baseUrl: {
type: String,
default: 'http://localhost:11434',
},
model: {
type: String,
default: '',
},
temperature: {
type: Number,
default: 0.7,
},
systemPrompt: {
type: String,
default: '',
},
initialMessages: {
type: Array,
default: () => [],
},
},
emits: ['message-sent', 'response-received', 'error', 'settings-changed'],
setup(props, { emit }) {
// State
const messages = ref([...props.initialMessages]);
const inputMessage = ref('');
const isGenerating = ref(false);
const isConnected = ref(false);
const showSettings = ref(false);
const messagesContainer = ref(null);
const abortController = ref(null);
const availableModels = ref([]);
// Settings
const settings = reactive({
baseUrl: props.baseUrl,
model: props.model,
temperature: props.temperature,
systemPrompt: props.systemPrompt,
});
// Ollama service instance
const ollama = new OllamaService(settings.baseUrl);
// Computed
const modelName = computed(() => settings.model);
// Methods
const scrollToBottom = async () => {
await nextTick();
if (messagesContainer.value) {
const container = messagesContainer.value.$el || messagesContainer.value;
container.scrollTop = container.scrollHeight;
}
};
const formatMessage = (content) => {
if (!content) return '';
return content
.replace(
/```(\w+)?\n([\s\S]*?)```/g,
'<pre class="code-block"><code>$2</code></pre>'
)
.replace(/`([^`]+)`/g, '<code class="inline-code">$1</code>')
.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
.replace(/\*([^*]+)\*/g, '<em>$1</em>')
.replace(/\n/g, '<br>');
};
const formatTime = (timestamp) => {
if (!timestamp) return '';
return new Date(timestamp).toLocaleTimeString('it-IT', {
hour: '2-digit',
minute: '2-digit',
});
};
const handleEnter = (event) => {
if (!event.shiftKey) {
sendMessage();
}
};
const sendMessage = async () => {
const content = inputMessage.value.trim();
if (!content || isGenerating.value) return;
// Add user message
const userMessage = {
role: 'user',
content,
timestamp: new Date(),
};
messages.value.push(userMessage);
inputMessage.value = '';
emit('message-sent', userMessage);
// Add placeholder for assistant
const assistantMessage = {
role: 'assistant',
content: '',
timestamp: new Date(),
isStreaming: true,
};
messages.value.push(assistantMessage);
const assistantIndex = messages.value.length - 1;
isGenerating.value = true;
abortController.value = new AbortController();
try {
ollama.setBaseUrl(settings.baseUrl);
const chatMessages = messages.value
.filter((m) => !m.isStreaming)
.map((m) => ({ role: m.role, content: m.content }));
await ollama.streamChat(
{
model: settings.model,
messages: chatMessages,
temperature: settings.temperature,
system: settings.systemPrompt || undefined,
},
(chunk, full) => {
messages.value[assistantIndex] = {
...messages.value[assistantIndex],
content: full,
isStreaming: true,
};
assistantMessage.isStreaming = true;
scrollToBottom();
},
(fullContent) => {
assistantMessage.content = fullContent;
assistantMessage.isStreaming = false;
assistantMessage.timestamp = new Date();
emit('response-received', assistantMessage);
}
);
isConnected.value = true;
} catch (error) {
assistantMessage.content = `❌ Errore: ${error.message}`;
assistantMessage.isStreaming = false;
isConnected.value = false;
emit('error', error);
}
isGenerating.value = false;
scrollToBottom();
};
const stopGeneration = () => {
if (abortController.value) {
abortController.value.abort();
isGenerating.value = false;
}
};
const clearChat = () => {
messages.value = [];
};
const loadModels = async () => {
try {
ollama.setBaseUrl(settings.baseUrl);
const models = await ollama.listModels();
availableModels.value = models.map((m) => ({
label: m.name,
value: m.name,
}));
isConnected.value = true;
// prende dal cookie salvato
const savedModel = tools.getCookie('ollama_model');
if (savedModel) {
settings.model = savedModel;
}
const modelIndex = availableModels.value.findIndex(
(m) => m.value === settings.model
);
if (modelIndex === -1) {
settings.model = availableModels.value[0].value;
}
scrollToBottom();
} catch (error) {
console.error('Error loadModels', error);
isConnected.value = false;
availableModels.value = [
{ label: 'llama3.2', value: 'llama3.2' },
{ label: 'llama3.1', value: 'llama3.1' },
{ label: 'mistral', value: 'mistral' },
{ label: 'codellama', value: 'codellama' },
];
}
};
const saveSettings = () => {
emit('settings-changed', { ...settings });
};
// Lifecycle
onMounted(() => {
try {
loadModels();
} catch (e) {
console.error('Error Mounted Chat:', e);
}
});
watch(
() => settings.model,
(newModel) => {
tools.setCookie('ollama_model', newModel);
},
{ deep: true }
);
// Watch messages for scroll
watch(messages, scrollToBottom, { deep: true });
return {
messages,
inputMessage,
isGenerating,
isConnected,
showSettings,
messagesContainer,
settings,
availableModels,
modelName,
formatMessage,
formatTime,
handleEnter,
sendMessage,
stopGeneration,
clearChat,
saveSettings,
tools,
};
},
};
</script>
<style scoped>
.ollama-chat {
display: flex;
flex-direction: column;
}
.messages-container {
flex: 1;
overflow-y: auto;
}
.message-bubble {
display: inline-block;
max-width: 80%;
border-radius: 16px;
word-break: break-word;
}
.text-right .message-bubble {
border-bottom-right-radius: 4px;
}
.text-left .message-bubble {
border-bottom-left-radius: 4px;
}
.message-content :deep(pre.code-block) {
background: #1e1e1e;
color: #d4d4d4;
padding: 12px;
border-radius: 8px;
overflow-x: auto;
margin: 8px 0;
}
.message-content :deep(.inline-code) {
background: rgba(0, 0, 0, 0.1);
padding: 2px 6px;
border-radius: 4px;
font-family: monospace;
}
.typing-indicator {
display: flex;
gap: 4px;
padding: 8px 0;
}
.typing-indicator span {
width: 8px;
height: 8px;
background: currentColor;
border-radius: 50%;
animation: typing 1.4s infinite;
}
.typing-indicator span:nth-child(2) {
animation-delay: 0.2s;
}
.typing-indicator span:nth-child(3) {
animation-delay: 0.4s;
}
@keyframes typing {
0%,
60%,
100% {
opacity: 0.3;
transform: scale(0.8);
}
30% {
opacity: 1;
transform: scale(1);
}
}
.border-top {
border-top: 1px solid rgba(0, 0, 0, 0.12);
}
.streaming-cursor {
display: inline-block;
animation: blink-cursor 0.8s infinite;
color: var(--q-primary);
margin-left: 2px;
}
@keyframes blink-cursor {
0%,
50% {
opacity: 1;
}
51%,
100% {
opacity: 0;
}
}
</style>

View File

@@ -0,0 +1,388 @@
/**
* OllamaService - Servizio per interagire con Ollama API
* Usalo in Quasar/Vue.js per tutte le chiamate AI
*/
class OllamaService {
constructor(baseUrl = 'http://localhost:11434') {
this.baseUrl = baseUrl.replace(/\/$/, '');
this.defaultModel = '';
this.defaultTemperature = 0.7;
}
// Configura l'URL base
setBaseUrl(url) {
this.baseUrl = url.replace(/\/$/, '');
}
// Configura il modello di default
setDefaultModel(model) {
this.defaultModel = model;
}
/**
* Chat con cronologia messaggi
* @param {Object} options - Opzioni della chat
* @param {string} options.model - Nome del modello
* @param {Array} options.messages - Array di messaggi [{role: 'user'|'assistant', content: '...'}]
* @param {boolean} options.stream - Abilita streaming
* @param {number} options.temperature - Temperatura (0-2)
* @param {string} options.system - System prompt opzionale
* @returns {Promise<string|Response>}
*/
async chat(options) {
const {
model = this.defaultModel,
messages,
stream = false,
temperature = this.defaultTemperature,
system = null,
maxTokens = null,
topP = null,
topK = null,
} = options;
const payload = {
model,
messages,
stream,
options: {
temperature,
...(maxTokens && { num_predict: maxTokens }),
...(topP && { top_p: topP }),
...(topK && { top_k: topK }),
},
};
if (system) {
payload.system = system;
}
const response = await fetch(`${this.baseUrl}/api2/chat`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
if (!response.ok) {
throw new Error(`Ollama API error: ${response.status} ${response.statusText}`);
}
if (stream) return response;
const data = await response.json();
return data.message.content;
}
/**
* Generazione testo semplice (senza cronologia)
* @param {Object} options - Opzioni di generazione
* @param {string} options.model - Nome del modello
* @param {string} options.prompt - Prompt di input
* @param {boolean} options.stream - Abilita streaming
* @param {number} options.temperature - Temperatura (0-2)
* @param {string} options.system - System prompt opzionale
* @returns {Promise<string|Response>}
*/
async generate(options) {
const {
model = this.defaultModel,
prompt,
stream = false,
temperature = this.defaultTemperature,
system = null,
maxTokens = null,
} = options;
const payload = {
model,
prompt,
stream,
options: {
temperature,
...(maxTokens && { num_predict: maxTokens }),
},
};
if (system) {
payload.system = system;
}
const response = await fetch(`${this.baseUrl}/api2/generate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
if (!response.ok) {
throw new Error(`Ollama API error: ${response.status} ${response.statusText}`);
}
if (stream) return response;
const data = await response.json();
return data.response;
}
/**
* Chat con streaming e callback
* @param {Object} options - Opzioni della chat
* @param {Function} onChunk - Callback per ogni chunk (chunk, fullContent)
* @param {Function} onComplete - Callback al completamento (fullContent)
* @returns {Promise<string>}
*/
async streamChat(options, onChunk, onComplete = null) {
const response = await this.chat({ ...options, stream: true });
const reader = response.body.getReader();
const decoder = new TextDecoder();
let fullContent = '';
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
const lines = chunk.split('\n').filter((line) => line.trim());
for (const line of lines) {
try {
const json = JSON.parse(line);
if (json.message?.content) {
fullContent += json.message.content;
if (onChunk) onChunk(json.message.content, fullContent);
}
} catch (e) {
// Ignora linee non JSON
}
}
}
} finally {
reader.releaseLock();
}
if (onComplete) onComplete(fullContent);
return fullContent;
}
/**
* Generazione con streaming e callback
* @param {Object} options - Opzioni di generazione
* @param {Function} onChunk - Callback per ogni chunk
* @returns {Promise<string>}
*/
async streamGenerate(options, onChunk, onComplete = null) {
const response = await this.generate({ ...options, stream: true });
const reader = response.body.getReader();
const decoder = new TextDecoder();
let fullContent = '';
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
const lines = chunk.split('\n').filter((line) => line.trim());
for (const line of lines) {
try {
const json = JSON.parse(line);
if (json.response) {
fullContent += json.response;
if (onChunk) onChunk(json.response, fullContent);
}
} catch (e) {
// Ignora linee non JSON
}
}
}
} finally {
reader.releaseLock();
}
if (onComplete) onComplete(fullContent);
return fullContent;
}
/**
* Lista modelli disponibili
* @returns {Promise<Array>}
*/
async listModels() {
const response = await fetch(`${this.baseUrl}/api2/models`);
if (!response.ok) {
throw new Error(`Ollama API error: ${response.status}`);
}
const data = await response.json();
return data.models || [];
}
/**
* Informazioni su un modello
* @param {string} model - Nome del modello
* @returns {Promise<Object>}
*/
async showModel(model) {
const response = await fetch(`${this.baseUrl}/api2/show`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: model }),
});
if (!response.ok) {
throw new Error(`Ollama API error: ${response.status}`);
}
return response.json();
}
/**
* Genera embeddings
* @param {string} model - Nome del modello
* @param {string|Array} prompt - Testo o array di testi
* @returns {Promise<Array>}
*/
async embeddings(model, prompt) {
const response = await fetch(`${this.baseUrl}/api2/embeddings`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ model, prompt }),
});
if (!response.ok) {
throw new Error(`Ollama API error: ${response.status}`);
}
const data = await response.json();
return data.embedding;
}
// ============================================
// METODI HELPER PER USI COMUNI
// ============================================
/**
* Genera testo creativo
*/
async generateText(prompt, options = {}) {
return this.generate({
prompt,
temperature: 0.8,
...options,
});
}
/**
* Genera codice
*/
async generateCode(prompt, language = 'javascript', options = {}) {
return this.generate({
prompt: `Scrivi codice ${language} per: ${prompt}\n\nRispondi solo con il codice, senza spiegazioni.`,
temperature: 0.3,
...options,
});
}
/**
* Traduci testo
*/
async translate(text, targetLang = 'english', options = {}) {
return this.generate({
prompt: `Traduci il seguente testo in ${targetLang}. Rispondi solo con la traduzione:\n\n${text}`,
temperature: 0.3,
...options,
});
}
/**
* Riassumi testo
*/
async summarize(text, options = {}) {
return this.generate({
prompt: `Riassumi il seguente testo in modo conciso:\n\n${text}`,
temperature: 0.5,
...options,
});
}
/**
* Rispondi a una domanda basata su un contesto
*/
async answerQuestion(question, context, options = {}) {
return this.generate({
prompt: `Contesto:\n${context}\n\nDomanda: ${question}\n\nRispondi basandoti solo sul contesto fornito.`,
temperature: 0.3,
...options,
});
}
/**
* Estrai informazioni strutturate (JSON)
*/
async extractJSON(text, schema, options = {}) {
const response = await this.generate({
prompt: `Estrai le informazioni dal seguente testo e restituisci un JSON valido con questa struttura: ${JSON.stringify(schema)}\n\nTesto:\n${text}\n\nRispondi SOLO con il JSON, senza altro testo.`,
temperature: 0.1,
...options,
});
try {
// Cerca di estrarre JSON dalla risposta
const jsonMatch = response.match(/\{[\s\S]*\}/);
if (jsonMatch) {
return JSON.parse(jsonMatch[0]);
}
return JSON.parse(response);
} catch (e) {
throw new Error('Failed to parse JSON response: ' + response);
}
}
/**
* Analisi del sentiment
*/
async analyzeSentiment(text, options = {}) {
const response = await this.generate({
prompt: `Analizza il sentiment del seguente testo e rispondi SOLO con un JSON nel formato {"sentiment": "positive"|"negative"|"neutral", "confidence": 0.0-1.0, "explanation": "breve spiegazione"}\n\nTesto: ${text}`,
temperature: 0.1,
...options,
});
try {
const jsonMatch = response.match(/\{[\s\S]*\}/);
if (jsonMatch) {
return JSON.parse(jsonMatch[0]);
}
return JSON.parse(response);
} catch (e) {
return { sentiment: 'unknown', confidence: 0, raw: response };
}
}
/**
* Correggi grammatica
*/
async correctGrammar(text, language = 'italiano', options = {}) {
return this.generate({
prompt: `Correggi gli errori grammaticali nel seguente testo in ${language}. Rispondi solo con il testo corretto:\n\n${text}`,
temperature: 0.2,
...options,
});
}
/**
* Genera lista/bullet points
*/
async generateList(topic, count = 5, options = {}) {
return this.generate({
prompt: `Genera una lista di ${count} elementi su: ${topic}\n\nFormatta come lista puntata.`,
temperature: 0.7,
...options,
});
}
}
// Export per ES6 modules
export default OllamaService;
// Export per CommonJS
if (typeof module !== 'undefined' && module.exports) {
module.exports = OllamaService;
}

View File

@@ -0,0 +1,178 @@
/**
* useOllama - Composable Vue 3 per usare Ollama nelle tue applicazioni Quasar
*
* Esempio d'uso:
*
* import { useOllama } from './useOllama';
*
* const { generate, chat, isLoading, error } = useOllama({
* baseUrl: 'http://localhost:11434',
* model: 'llama3.2'
* });
*
* const result = await generate('Scrivi una poesia');
*/
import { ref, reactive } from 'vue';
import OllamaService from './OllamaService.js';
export function useOllama(options = {}) {
const {
baseUrl = 'http://localhost:11434',
model = 'llama3.2',
temperature = 0.7,
} = options;
// State
const isLoading = ref(false);
const error = ref(null);
const streamingContent = ref('');
const models = ref([]);
// Service instance
const service = new OllamaService(baseUrl);
service.setDefaultModel(model);
/**
* Genera testo
*/
const generate = async (prompt, opts = {}) => {
isLoading.value = true;
error.value = null;
streamingContent.value = '';
try {
const result = await service.generate({
prompt,
temperature,
...opts,
});
return result;
} catch (e) {
error.value = e.message;
throw e;
} finally {
isLoading.value = false;
}
};
/**
* Genera testo con streaming
*/
const generateStream = async (prompt, opts = {}) => {
isLoading.value = true;
error.value = null;
streamingContent.value = '';
try {
const result = await service.streamGenerate(
{ prompt, temperature, ...opts },
(chunk, full) => {
streamingContent.value = full;
}
);
return result;
} catch (e) {
error.value = e.message;
throw e;
} finally {
isLoading.value = false;
}
};
/**
* Chat con messaggi
*/
const chat = async (messages, opts = {}) => {
isLoading.value = true;
error.value = null;
streamingContent.value = '';
try {
const result = await service.chat({
messages,
temperature,
...opts,
});
return result;
} catch (e) {
error.value = e.message;
throw e;
} finally {
isLoading.value = false;
}
};
/**
* Chat con streaming
*/
const chatStream = async (messages, opts = {}) => {
isLoading.value = true;
error.value = null;
streamingContent.value = '';
try {
const result = await service.streamChat(
{ messages, temperature, ...opts },
(chunk, full) => {
streamingContent.value = full;
}
);
return result;
} catch (e) {
error.value = e.message;
throw e;
} finally {
isLoading.value = false;
}
};
/**
* Carica modelli disponibili
*/
const loadModels = async () => {
try {
models.value = await service.listModels();
return models.value;
} catch (e) {
error.value = e.message;
return [];
}
};
// Helper methods
const generateText = (prompt, opts) => service.generateText(prompt, opts);
const generateCode = (prompt, lang, opts) => service.generateCode(prompt, lang, opts);
const translate = (text, lang, opts) => service.translate(text, lang, opts);
const summarize = (text, opts) => service.summarize(text, opts);
const extractJSON = (text, schema, opts) => service.extractJSON(text, schema, opts);
const analyzeSentiment = (text, opts) => service.analyzeSentiment(text, opts);
return {
// State
isLoading,
error,
streamingContent,
models,
// Core methods
generate,
generateStream,
chat,
chatStream,
loadModels,
// Helper methods
generateText,
generateCode,
translate,
summarize,
extractJSON,
analyzeSentiment,
// Service access
service,
};
}
export default useOllama;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -56,6 +56,7 @@ body {
color: #2c3e50;
line-height: 1.7;
/*background: linear-gradient(135deg, #f5f7fa 0%, #f3fff2 100%);*/
background: linear-gradient(180deg, #f1f5f9 0%, #e2e8f0 100%);
//font-size: 1rem;
@media (max-width: 600px) {

View File

@@ -63,6 +63,7 @@ const msg_website_it = {
nextzoom: 'Conferenze',
requestresetpwd: 'Richiesta Reset Password',
vreg: 'Verifica Reg',
reverif_email: 'Riverifica Email',
dashboard: 'Lavagna',
statoattuale: 'Stato Attuale',
posizione_in_programmazione: 'Lista d\'Imbarco',

View File

@@ -1,6 +1,6 @@
import type { IToken } from '@model/other'
import type { ICart, IOrderCart, IShareWithUs } from '@/model/Products'
import type { IAccount, ICatGrp, ICircuit, IImgGallery, IMovement, IMyCircuit } from '@model/GlobalStore';
import type { IAccount, ICatGrp, ICircuit, IImgGallery, IMovement, IMovVisu, IMyCircuit } from '@model/GlobalStore';
import { IGallery } from '@model/GlobalStore'
import type { IBookedEvent } from './Calendar'
@@ -197,7 +197,7 @@ export interface IUserProfile {
refused_circuits: any[]
manage_mycircuits: ICircuit[]
useraccounts: IAccount[]
last_my_transactions: IMovement[]
last_my_transactions: IMovVisu[] // IMovement[]
total_transactions?: number
calc: ICalc
}

View File

@@ -26,6 +26,8 @@ import { shared_consts } from '@/common/shared_vuejs'
import { CPresentazione } from '@/components'
import MixinMetaTags from '@/mixins/mixin-metatags'
import { useI18n } from 'vue-i18n'
import { useQuasar } from 'quasar'
import { watch } from 'vue'
export default defineComponent({
name: 'mainview',
components: {
@@ -39,9 +41,21 @@ export default defineComponent({
const userStore = useUserStore()
const { getValDb } = MixinBase()
const notifStore = useNotifStore()
const $q = useQuasar()
const isfinishLoading = computed(() => globalStore.finishLoading)
watch(() => globalStore.finishLoading, (newValue, oldValue) => {
if (!newValue) {
$q.loading.show({
message: 'Caricamento sito ...',
spinnerColor: 'primary',
})
} else {
$q.loading.hide()
}
})
const { setmeta } = MixinMetaTags()
const { getRefLink } = MixinUsers()

View File

@@ -23,7 +23,9 @@
</CMyPageElem>
</div>
</div>
<LandingFooter></LandingFooter>
<div v-if="isfinishLoading">
<LandingFooter></LandingFooter>
</div>
</div>
<div v-else>
<div v-if="isfinishLoading">

View File

@@ -196,6 +196,15 @@
mykey="Email"
:myvalue="myuser.email"
/>
<CKeyAndValue
mykey="Email Verificata"
:myvalue="myuser.verified_email"
:show-set-button="true"
:on-set-value="userStore.setVerifiedEmail"
:valuetoSet="true"
:param2="myuser._id"
button-tooltip="Verifica Email"
/>
<CKeyAndValue
mykey="Versione"
:myvalue="myuser.profile.version"

View File

@@ -15,6 +15,18 @@ function getRoutesAI(site: ISites) {
level_parent: 0,
level_child: 0.5,
},
{
active: true,
order: 30,
path: '/ai-ollama',
materialIcon: 'fas fa-robot',
name: 'mypages.ollamaVPS',
component: () => import('@/views/toolsAI/ollama/ollama.vue'),
inmenu: true,
submenu: true,
level_parent: 0,
level_child: 0.5,
},
]
const routes_admin_ai: IListRoutes[] = [

View File

@@ -805,6 +805,13 @@ function getRoutesAd(site: ISites) {
name: 'pages.vreg',
component: () => import('@/views/login/vreg/vreg.vue')
},
{
active: true,
order: 1000,
path: '/reverif_email',
name: 'pages.reverif_email',
component: () => import('@/views/login/reverif_email/reverif_email.vue')
},
{
active: true,
order: 1000,

File diff suppressed because it is too large Load Diff

View File

@@ -50,7 +50,7 @@ export const useCircuitStore = defineStore('CircuitStore', {
const account = userStore.my.profile.useraccounts.find(
(rec: IAccount) => rec.circuitId === circuitId
);
if (account) return account.saldo;
if (account) return tools.roundDec2(account.saldo, 2);
else return 0;
},
@@ -164,6 +164,24 @@ export const useCircuitStore = defineStore('CircuitStore', {
return '';
}
},
getMaxAccumuloByUsername(
myuser: IUserFields,
circuitId: string,
username: string,
groupname?: string
): number | string {
if (myuser && myuser.profile.useraccounts) {
const account = myuser.profile.useraccounts.find(
(rec: IAccount) =>
(rec.username === username ||
(rec.groupname === groupname && groupname !== '')) &&
rec.circuitId === circuitId
);
return account ? account.qta_maxConcessa : 0;
} else {
return '';
}
},
SonoDentroAdAlmeno1CircuitoConFido(): boolean {
const userStore = useUserStore();
@@ -334,7 +352,7 @@ export const useCircuitStore = defineStore('CircuitStore', {
return false;
},
updateListCircuit(visu: number) {
updateListCircuit(visu: number, sortbyuser?: boolean) {
const userStore = useUserStore();
let arr: any[] = [];
@@ -367,6 +385,31 @@ export const useCircuitStore = defineStore('CircuitStore', {
}
}
if (sortbyuser) {
// Ordina i circuiti prima per quello provinciale, poi quello Italia, poi tutti gli altri
const circuitiMiaProvincia = this.getCircuitsByProvince(
userStore.my.profile.resid_province
);
const circuitiNazionali = this.getCircuitsNational();
// Crea Set di ID per lookup veloce
const provinciaIds = new Set(circuitiMiaProvincia.map((c) => c._id));
const nazionaliIds = new Set(circuitiNazionali.map((c) => c._id));
// Filtra arr in tre gruppi (mantenendo solo elementi già presenti in arr)
const fromProvincia = arr.filter((rec) => provinciaIds.has(rec._id));
const fromNazionali = arr.filter(
(rec) => nazionaliIds.has(rec._id) && !provinciaIds.has(rec._id)
);
const others = arr.filter(
(rec) => !provinciaIds.has(rec._id) && !nazionaliIds.has(rec._id)
);
// Ricomponi l'array nell'ordine desiderato
arr = [...fromProvincia, ...fromNazionali, ...others];
}
return arr;
},
@@ -1420,5 +1463,15 @@ export const useCircuitStore = defineStore('CircuitStore', {
return str;
},
getTypeCircuit(circuit: ICircuit) {
if (circuit.isCircItalia) {
return 'Nazionale';
} else if (circuit.circuitoIndipendente) {
return 'Indipendente';
} else if (!circuit.circuitiExtraProv) {
return 'Provinciale';
}
},
},
});

View File

@@ -7464,92 +7464,97 @@ export const tools = {
$router: any,
circuit: ICircuit,
sendcoinrec: ISendCoin
) {
): Promise<void> {
const userStore = useUserStore();
const notifStore = useNotifStore();
//T_TOLTO
const { username } = userStore.my;
const username = userStore.my.username;
// Origine e destinazione con fallback chain più leggibile
const orig = sendcoinrec.grouporig || sendcoinrec.contoComOrig || '';
const dest = sendcoinrec.groupdest || sendcoinrec.contoComDest || sendcoinrec.dest;
const orig = sendcoinrec.grouporig
? sendcoinrec.grouporig
: sendcoinrec.contoComOrig
? sendcoinrec.contoComOrig
: '';
const dest = sendcoinrec.groupdest
? sendcoinrec.groupdest
: sendcoinrec.contoComDest
? sendcoinrec.contoComDest
: sendcoinrec.dest;
// Parametri comuni per i messaggi
const msgParams = {
coin: circuit.symbol,
dest,
qty: sendcoinrec.qty,
circuit: circuit.name,
...(orig && { from: orig }), // Aggiunge 'from' solo se esiste
};
let msg = '';
if (orig) {
msg = t('circuit.question_sendcoinsto_from', {
coin: circuit.symbol,
from: orig,
dest,
qty: sendcoinrec.qty,
});
} else {
msg = t('circuit.question_sendcoinsto', {
coin: circuit.symbol,
dest,
qty: sendcoinrec.qty,
});
}
// Seleziona il messaggio appropriato
const messageKey = orig
? 'circuit.question_sendcoinsto_from'
: 'circuit.question_sendcoinsto';
return $q
.dialog({
message: msg,
// Mostra dialog di conferma
return new Promise((resolve, reject) => {
$q.dialog({
title: t('db.domanda'),
message: t(messageKey, msgParams),
ok: {
label: t('dialog.yes'),
color: 'primary',
push: true,
},
cancel: {
label: t('dialog.cancel'),
color: 'secondary',
},
title: t('db.domanda'),
})
.onOk(() => {
return userStore
.setCircuitCmd(
$q,
t,
username,
sendcoinrec.circuitname,
shared_consts.CIRCUITCMD.SENDCOINS_REQ,
true,
sendcoinrec
)
.then((res: any) => {
console.log('setCircuitCmd ', res);
if (res && res.cansend) {
if (res.useraccounts && res.useraccounts.length > 0) {
userStore.my.profile.useraccounts = res.useraccounts;
}
$router.push('/circuits');
tools.showPositiveNotif(
$q,
t('circuit.coins_sent', {
qty: sendcoinrec.qty,
symbol: circuit.symbol,
dest,
})
);
//tools.showPositiveNotif($q, t('circuit.coins_sendrequest_sent'))
} else {
tools.showNegativeNotif($q, res.errormsg);
}
notifStore.updateNotification = true;
tools.updateMyData(res);
});
});
.onOk(async () => {
try {
const res = await userStore.setCircuitCmd(
$q,
t,
username,
sendcoinrec.circuitname,
shared_consts.CIRCUITCMD.SENDCOINS_REQ,
true,
sendcoinrec
);
/* return await new Promise((resolve, reject) => {
console.log('setCircuitCmd', res);
if (!res?.cansend) {
tools.showNegativeNotif($q, res?.errormsg || t('errors.generic'));
return resolve();
}
// Aggiorna gli account utente se presenti
if (res.useraccounts?.length > 0) {
userStore.my.profile.useraccounts = res.useraccounts;
}
// Aggiorna dati e naviga
tools.updateMyData(res);
notifStore.updateNotification = true;
tools.showPositiveNotif(
$q,
t('circuit.coins_sent', {
qty: sendcoinrec.qty,
symbol: circuit.symbol,
dest,
})
);
$router.push('/circuits');
resolve();
} catch (error) {
console.error('Errore invio coins:', error);
tools.showNegativeNotif($q, t('errors.generic'));
reject(error);
}
})
.onCancel(() => resolve())
.onDismiss(() => resolve());
});
},
/* return await new Promise((resolve, reject) => {
resolve(false)
})
*/
},
cancelReqCircuit($q: any, username: string, circuitname: string, groupname?: string) {
const userStore = useUserStore();

View File

@@ -15,6 +15,7 @@ import type {
ISignupIscrizioneConacreisOptions,
IMovQuery,
IGroup,
IMovVisu,
} from '@/model';
import { IFriends, ISettings } from '@/model';
import { tools } from '@tools';
@@ -805,6 +806,10 @@ export const useUserStore = defineStore('UserStore', {
return this.my.profile.teleg_id! > 0 || this.my.profile.teleg_id_old! > 0;
},
isEmailVerified() {
return this.my.verified_email;
},
isUserOk(anchesenonammesso: boolean = false): boolean {
const globalStore = useGlobalStore();
@@ -828,7 +833,7 @@ export const useUserStore = defineStore('UserStore', {
this.isUsernameTelegOk()
);
} else {
return this.my.verified_email!;
return this.my.verified_email! || this.isTelegIdOk();
}
}
@@ -1028,7 +1033,7 @@ export const useUserStore = defineStore('UserStore', {
mydata.password = String(hashedPassword);
return Api.SendReq('/updatepwd', 'POST', mydata, true, false, 1)
.then((res) => {
.then((res: any) => {
return { code: res.data.code, msg: res.data.msg };
})
.catch((error: Types.AxiosError) => {
@@ -1057,7 +1062,7 @@ export const useUserStore = defineStore('UserStore', {
};
return Api.SendReq('/setlang', 'PATCH', { data: mydata })
.then((res) => {
.then((res: any) => {
if (res) {
return res.data.code === serv_constants.RIS_CODE_OK;
}
@@ -1076,8 +1081,8 @@ export const useUserStore = defineStore('UserStore', {
this.setServerCode(tools.CALLING);
return Api.SendReq('/requestnewpwd', 'POST', usertosend)
.then((res) => ({ code: res.data.code, msg: res.data.msg, link: res.data.link }))
.catch((error) => {
.then((res: any) => ({ code: res.data.code, msg: res.data.msg, link: res.data.link }))
.catch((error: any) => {
this.setErrorCatch(error);
return this.getServerCode;
});
@@ -1094,12 +1099,12 @@ export const useUserStore = defineStore('UserStore', {
paramquery.password = String(hashedPassword);
return Api.SendReq('/addNewSite', 'POST', paramquery)
.then((res) => ({
.then((res: any) => ({
code: res.data.code,
msg: res.data.msg,
idapp: res.data.idapp,
}))
.catch((error) => {
.catch((error: any) => {
this.setErrorCatch(error);
return this.getServerCode;
});
@@ -1115,7 +1120,7 @@ export const useUserStore = defineStore('UserStore', {
this.setServerCode(tools.CALLING);
return Api.SendReq('/vreg', 'POST', usertosend)
.then((res) => {
.then((res: any) => {
// console.log("RITORNO 2 ");
// mutations.setServerCode(myres);
if (res.data.code === serv_constants.RIS_CODE_EMAIL_VERIFIED) {
@@ -1126,7 +1131,30 @@ export const useUserStore = defineStore('UserStore', {
}
return { code: res.data.code, msg: res.data.msg };
})
.catch((error) => {
.catch((error: any) => {
this.setErrorCatch(error);
return { code: this.getServerCode, msg: error.getMsgError() };
});
},
async reverif_email(paramquery: ILinkReg) {
const usertosend = {
idlink: paramquery.idlink,
};
console.log(usertosend);
this.setServerCode(tools.CALLING);
return Api.SendReq('/reverif_email', 'POST', usertosend)
.then((res: any) => {
if (res.data.code === serv_constants.RIS_CODE_EMAIL_VERIFIED) {
console.log('VERIFICATO !!');
tools.localStSetItem(toolsext.localStorage.verified_email, String(true));
} else {
console.log('Risultato di vreg: ', res.data.code);
}
return { code: res.data.code, msg: res.data.msg };
})
.catch((error: any) => {
this.setErrorCatch(error);
return { code: this.getServerCode, msg: error.getMsgError() };
});
@@ -1141,7 +1169,7 @@ export const useUserStore = defineStore('UserStore', {
this.setServerCode(tools.CALLING);
return Api.SendReq('/ammetti', 'POST', usertosend)
.then((res) => {
.then((res: any) => {
// console.log("RITORNO 2 ");
// mutations.setServerCode(myres);
if (res.data.code === serv_constants.RIS_CODE_AMMESSO) {
@@ -1151,7 +1179,7 @@ export const useUserStore = defineStore('UserStore', {
}
return { code: res.data.code, msg: res.data.msg };
})
.catch((error) => {
.catch((error: any) => {
this.setErrorCatch(error);
return { code: this.getServerCode, msg: error.getMsgError() };
});
@@ -1170,7 +1198,7 @@ export const useUserStore = defineStore('UserStore', {
this.setServerCode(tools.CALLING);
return Api.SendReq('/abcirc', 'POST', usertosend)
.then((res) => {
.then((res: any) => {
// console.log("RITORNO 2 ");
// mutations.setServerCode(myres);
if (res.data.code === serv_constants.RIS_CODE_AMMESSO) {
@@ -1178,9 +1206,13 @@ export const useUserStore = defineStore('UserStore', {
} else {
console.log('Risultato di abilita: ', res.data.code);
}
return { code: res.data.code, msg: res.data.msg, circuitName: res.data.circuitName };
return {
code: res.data.code,
msg: res.data.msg,
circuitName: res.data.circuitName,
};
})
.catch((error) => {
.catch((error: any) => {
this.setErrorCatch(error);
return { code: this.getServerCode, msg: error.getMsgError() };
});
@@ -1188,7 +1220,7 @@ export const useUserStore = defineStore('UserStore', {
async unsubscribe(paramquery: any) {
return Api.SendReq('/news/unsubscribe', 'POST', paramquery)
.then((res) => {
.then((res: any) => {
// console.log("RITORNO 2 ");
// mutations.setServerCode(myres);
if (res.data.code === serv_constants.RIS_UNSUBSCRIBED_OK) {
@@ -1203,7 +1235,7 @@ export const useUserStore = defineStore('UserStore', {
async unsubscribe_news_on_fielduser(paramquery: any) {
return Api.SendReq('/news/unsubscribe_user', 'POST', paramquery)
.then((res) => {
.then((res: any) => {
if (res.data.code === serv_constants.RIS_UNSUBSCRIBED_OK) {
console.log('DESOTTOSCRITTO ALLA NEWSLETTER !!');
} else {
@@ -1216,16 +1248,16 @@ export const useUserStore = defineStore('UserStore', {
async importemail(paramquery: any) {
return Api.SendReq('/news/import', 'POST', paramquery)
.then((res) => res)
.then((res: any) => res)
.catch((error) => ({ numtot: 0, numadded: 0, numalreadyexisted: 0 }));
},
async importExtraList(paramquery: any) {
return Api.SendReq('/users/import_extralist', 'POST', paramquery)
.then((res) => {
.then((res: any) => {
return res;
})
.catch((error) => {
.catch((error: any) => {
return { numtot: 0, numadded: 0, numalreadyexisted: 0 };
});
},
@@ -1244,22 +1276,22 @@ export const useUserStore = defineStore('UserStore', {
null,
{ timeout: 300000 }
)
.then((res) => {
.then((res: any) => {
return res.data;
})
.catch((error) => {
.catch((error: any) => {
return false;
});
},
async execDbOpUser(paramquery: any) {
return Api.SendReq('/users/dbopuser', 'POST', paramquery)
.then((res) => {
.then((res: any) => {
tools.updateMyData(res.data.ris);
return res.data;
})
.catch((error) => {
.catch((error: any) => {
return false;
});
},
@@ -1327,6 +1359,31 @@ export const useUserStore = defineStore('UserStore', {
}
return await this.execDbOpUser({ mydata });
},
async setVerifiedEmail(val: boolean, userId?: string) {
const mydata = {
_id: userId ? userId : this.my._id,
dbop: 'verifiedemail',
value: val,
};
if (userId) {
} else {
if (this.my.verified_email !== val) {
this.my.verified_email = val;
}
}
return await this.execDbOpUser({ mydata });
},
async resendVerificationEmail() {
const mydata = {
_id: this.my._id,
dbop: 'resendVerificationEmail',
value: this.my.email,
};
return await this.execDbOpUser({ mydata });
},
async setPwdComeQuellaDellAdmin(val: boolean, userId?: string) {
const mydata = {
_id: userId,
@@ -1388,10 +1445,11 @@ export const useUserStore = defineStore('UserStore', {
async getProvincesForMap() {
return Api.SendReq('/users/infomap', 'POST', null)
.then((res) => {
.then((res: any) => {
return res ? res.data.ris : [];
})
.catch((error) => {
.catch((e: any) => {
console.error('Err getProvincesForMap:', e);
return null;
});
},
@@ -1417,29 +1475,29 @@ export const useUserStore = defineStore('UserStore', {
return res.data;
})
.catch((error) => {
.catch((error: any) => {
return null;
});
},
async reportload(paramquery: any) {
return Api.SendReq('/report/load', 'POST', paramquery)
.then((res) => {
.then((res: any) => {
// console.log('res', res)
return res.data;
})
.catch((error) => {
.catch((error: any) => {
return null;
});
},
async newsletter_setactivate(paramquery: any) {
return Api.SendReq('/news/setactivate', 'POST', paramquery)
.then((res) => {
.then((res: any) => {
// console.log('res', res)
return res.data;
})
.catch((error) => {
.catch((error: any) => {
return null;
});
},
@@ -1599,7 +1657,7 @@ export const useUserStore = defineStore('UserStore', {
this.setServerCode(tools.CALLING);
return Api.SendReq('/users', 'POST', authData)
.then((res) => {
.then((res: any) => {
const newuser = res.data;
// console.log('newuser', newuser)
@@ -1652,7 +1710,7 @@ export const useUserStore = defineStore('UserStore', {
return { code: toolsext.ERR_GENERICO, msg: '' };
}
})
.catch((error) => {
.catch((error: any) => {
console.log('Err', error);
this.setErrorCatch(error);
return { code: this.getServerCode, msg: this.getMsg };
@@ -1723,14 +1781,14 @@ export const useUserStore = defineStore('UserStore', {
authData.userId = this.my._id;
return Api.SendReq('/iscritti_conacreis', 'POST', authData)
.then((res) => {
.then((res: any) => {
if (res.status === 200) {
return { code: serv_constants.RIS_ISCRIZIONE_OK, msg: '' };
} else {
return { code: toolsext.ERR_GENERICO, msg: '' };
}
})
.catch((error) => {
.catch((error: any) => {
console.log('Err', error);
this.setErrorCatch(error);
return { code: this.getServerCode, msg: this.getMsg };
@@ -1771,7 +1829,7 @@ export const useUserStore = defineStore('UserStore', {
// console.log('executing login...')
return await Api.SendReq('/users/login', 'POST', usertosend, true, false, 0)
.then((res) => {
.then((res: any) => {
myres = res;
if (myres.status !== 200) {
@@ -1779,7 +1837,7 @@ export const useUserStore = defineStore('UserStore', {
}
return myres;
})
.then((res) => {
.then((res: any) => {
console.log(' Login res', res);
if (res.success) {
@@ -1808,7 +1866,7 @@ export const useUserStore = defineStore('UserStore', {
return code;
}
})
.catch((error) => {
.catch((error: any) => {
console.log('error', error);
this.setErrorCatch(error);
return this.getServerCode;
@@ -1848,11 +1906,11 @@ export const useUserStore = defineStore('UserStore', {
this.clearAuthData();
return await Api.SendReq('/users/me/token', 'DELETE', null)
.then((res) => {
.then((res: any) => {
console.log(res);
})
.then(() => this.clearAuthData())
.catch((error) => {
.catch((error: any) => {
this.setErrorCatch(error);
return this.getServerCode;
});
@@ -1977,14 +2035,14 @@ export const useUserStore = defineStore('UserStore', {
};
return await Api.SendReq('/users/profile', 'POST', data)
.then((ris) => {
.then((ris: any) => {
if (this.my.username === ris.data.user.username) {
// this.updateDataFr(ris.data.friends)
}
return ris.data.user;
})
.catch((error) => {
.catch((error: any) => {
return {};
});
},
@@ -2002,14 +2060,14 @@ export const useUserStore = defineStore('UserStore', {
};
return await Api.SendReq('/users/activities', 'POST', data)
.then((ris) => {
.then((ris: any) => {
if (this.my.username === ris.data.user.username) {
// this.updateDataFr(ris.data.friends)
}
return ris.data.user;
})
.catch((error) => {
.catch((error: any) => {
return {};
});
},
@@ -2020,10 +2078,10 @@ export const useUserStore = defineStore('UserStore', {
};
return Api.SendReq('/users/notifs', 'POST', data)
.then((ris) => {
.then((ris: any) => {
return ris.data;
})
.catch((error) => {
.catch((error: any) => {
return {};
});
},
@@ -2034,11 +2092,11 @@ export const useUserStore = defineStore('UserStore', {
};
return Api.SendReq('/users/panel', 'POST', data)
.then((ris) => {
.then((ris: any) => {
console.log('out:', ris);
return ris.data;
})
.catch((error) => {
.catch((error: any) => {
return {};
});
},
@@ -2050,11 +2108,11 @@ export const useUserStore = defineStore('UserStore', {
};
return Api.SendReq('/users/receiveris', 'POST', data)
.then((ris) => {
.then((ris: any) => {
console.log('out:', ris);
return ris.data;
})
.catch((error) => {
.catch((error: any) => {
return {};
});
},
@@ -2065,11 +2123,11 @@ export const useUserStore = defineStore('UserStore', {
};
return Api.SendReq('/users/listlinkreg', 'POST', data)
.then((ris) => {
.then((ris: any) => {
console.log('out:', ris);
return ris.data;
})
.catch((error) => {
.catch((error: any) => {
return {};
});
},
@@ -2081,10 +2139,10 @@ export const useUserStore = defineStore('UserStore', {
};
return Api.SendReq('/mygroup/load', 'POST', data)
.then((res) => {
.then((res: any) => {
return { data: res.data, status: res.status };
})
.catch((error) => {
.catch((error: any) => {
return { data: null, status: error.status };
});
},
@@ -2098,7 +2156,7 @@ export const useUserStore = defineStore('UserStore', {
};
return Api.SendReq('/circuit/load', 'POST', data)
.then((res) => {
.then((res: any) => {
this.my.profile.last_circuitpath = path;
if (res && res.data.arrrecnotif) {
notifStore.updateArrRecNotifFromServer(res.data.arrrecnotif);
@@ -2111,7 +2169,7 @@ export const useUserStore = defineStore('UserStore', {
}
return { data: res.data, status: res.status };
})
.catch((error) => {
.catch((error: any) => {
return { data: null, status: error.status };
});
},
@@ -2122,11 +2180,11 @@ export const useUserStore = defineStore('UserStore', {
};
return Api.SendReq('/myskills/page', 'POST', data)
.then((res) => {
.then((res: any) => {
console.log('res.data', res);
return res.data;
})
.catch((error) => {
.catch((error: any) => {
return {};
});
},
@@ -2139,10 +2197,10 @@ export const useUserStore = defineStore('UserStore', {
};
return Api.SendReq('/mygen/page', 'POST', data)
.then((res) => {
.then((res: any) => {
return res.data;
})
.catch((error) => {
.catch((error: any) => {
console.error('err', error);
return null;
});
@@ -2163,41 +2221,41 @@ export const useUserStore = defineStore('UserStore', {
async loadFriends() {
return Api.SendReq('/users/friends', 'POST', null)
.then((ris) => {
.then((ris: any) => {
this.updateDataFr(ris.data);
return ris.data;
})
.catch((error) => {
.catch((error: any) => {
return {};
});
},
async loadGroups(username: string) {
return Api.SendReq('/users/groups', 'POST', null)
.then((res) => {
.then((res: any) => {
return res.data;
})
.catch((error) => {
.catch((error: any) => {
return {};
});
},
async loadCircuits(nummovTodownload: number) {
return Api.SendReq('/users/circuits', 'POST', { nummovTodownload })
.then((res) => {
.then((res: any) => {
return res.data;
})
.catch((error) => {
.catch((error: any) => {
return {};
});
},
async loadAllAccounts() {
return Api.SendReq('/account/loadall', 'POST', null)
.then((res) => {
.then((res: any) => {
return res.data;
})
.catch((error) => {
.catch((error: any) => {
return {};
});
},
@@ -2216,11 +2274,11 @@ export const useUserStore = defineStore('UserStore', {
cmd,
value,
})
.then((res) => {
.then((res: any) => {
this.updateTables = true;
return res.data;
})
.catch((error) => {
.catch((error: any) => {
tools.showNegativeNotif($q, t('db.recfailed'));
return {};
});
@@ -2240,11 +2298,11 @@ export const useUserStore = defineStore('UserStore', {
cmd,
value,
})
.then((res) => {
.then((res: any) => {
this.updateTables = true;
return res.data;
})
.catch((error) => {
.catch((error: any) => {
tools.showNegativeNotif($q, t('db.recfailed'));
return {};
});
@@ -2264,14 +2322,14 @@ export const useUserStore = defineStore('UserStore', {
cmd,
value,
})
.then((res) => {
.then((res: any) => {
this.updateTables = true;
// const notifStore = useNotifStore()
// notifStore.updateNotification = true
return res.data;
})
.catch((error) => {
.catch((error: any) => {
tools.showNegativeNotif($q, t('db.recfailed'));
return {};
});
@@ -2294,7 +2352,7 @@ export const useUserStore = defineStore('UserStore', {
false,
0
)
.then((res) => {
.then((res: any) => {
this.updateTables = true;
const notifStore = useNotifStore();
if (res.data.recnotif) {
@@ -2308,7 +2366,7 @@ export const useUserStore = defineStore('UserStore', {
}
return res.data;
})
.catch((error) => {
.catch((error: any) => {
tools.showNegativeNotif($q, t('db.recfailed'));
return {};
});
@@ -2323,7 +2381,7 @@ export const useUserStore = defineStore('UserStore', {
const globalStore = useGlobalStore();
return Api.SendReq('/users/mgt', 'POST', { mydata })
.then((res) => {
.then((res: any) => {
console.log('res', res);
let msgok =
@@ -2354,7 +2412,7 @@ export const useUserStore = defineStore('UserStore', {
}
return false;
})
.catch((error) => {
.catch((error: any) => {
tools.showNegativeNotif($q, t('cal.err_sendmsg'));
return {};
});
@@ -2399,7 +2457,7 @@ export const useUserStore = defineStore('UserStore', {
}
$q.loading.hide();
})
.catch((error) => {
.catch((error: any) => {
$q.loading.hide();
tools.showNegativeNotif($q, t('db.recfailed'));
return {};
@@ -2421,7 +2479,7 @@ export const useUserStore = defineStore('UserStore', {
tab,
value,
})
.then((res) => {
.then((res: any) => {
if (res && res.data.state === 1) {
if (myrec) {
if (!myrec.numfav) {
@@ -2451,7 +2509,7 @@ export const useUserStore = defineStore('UserStore', {
tools.showNegativeNotif($q, t('cmd.favorite_unset'));
}
})
.catch((error) => {
.catch((error: any) => {
tools.showNegativeNotif($q, t('db.recfailed'));
return {};
});
@@ -2472,7 +2530,7 @@ export const useUserStore = defineStore('UserStore', {
tab,
value,
})
.then((res) => {
.then((res: any) => {
if (res && res.data.state === 1) {
if (!myrec.numattend) {
myrec.numattend = 0;
@@ -2498,7 +2556,7 @@ export const useUserStore = defineStore('UserStore', {
tools.showNegativeNotif($q, t('cmd.attend_unset'));
}
})
.catch((error) => {
.catch((error: any) => {
tools.showNegativeNotif($q, t('db.recfailed'));
return {};
});
@@ -2546,7 +2604,7 @@ export const useUserStore = defineStore('UserStore', {
tab,
value,
})
.then((res) => {
.then((res: any) => {
if (res && res.data.state === 1) {
if (!myrec.numbook) {
myrec.numbook = 0;
@@ -2572,7 +2630,7 @@ export const useUserStore = defineStore('UserStore', {
tools.showNegativeNotif($q, t('cmd.bookmark_unset'));
}
})
.catch((error) => {
.catch((error: any) => {
console.error('error', error);
tools.showNegativeNotif($q, t('db.recfailed'));
return {};
@@ -2624,7 +2682,7 @@ export const useUserStore = defineStore('UserStore', {
tab,
value,
})
.then((res) => {
.then((res: any) => {
if (res && res.data.state === 1) {
if (value) {
if (!myrec) {
@@ -2651,7 +2709,7 @@ export const useUserStore = defineStore('UserStore', {
}
return null;
})
.catch((error) => {
.catch((error: any) => {
console.error('error', error);
return null;
});
@@ -2671,7 +2729,7 @@ export const useUserStore = defineStore('UserStore', {
},
async eseguiFunzSulServer(mydata: {}) {
return await this.execDbOp({ mydata }).then((ris) => {
return await this.execDbOp({ mydata }).then((ris: any) => {
return ris?.data;
});
},
@@ -2739,5 +2797,25 @@ export const useUserStore = defineStore('UserStore', {
}
});
},
getLastRecentUserTransactions() {
const arr = [];
for (const rec of this.my.profile.last_my_transactions) {
let myuser = null;
if (rec.userfrom && rec.userfrom.username !== this.my.username) {
myuser = rec.userfrom;
} else if (rec.userto && rec.userto.username !== this.my.username) {
myuser = rec.userto;
}
if (myuser) {
arr.push(myuser);
}
}
const uniques = arr.filter((v, i, a) => a.findIndex(t => (t.username === v.username)) === i);
return uniques;
},
},
});

View File

@@ -0,0 +1,6 @@
.mypanel {
padding: 10px;
margin: 10px;
}

View File

@@ -0,0 +1,77 @@
import { defineComponent, ref } from 'vue'
import { serv_constants } from '../../../store/Modules/serv_constants'
import type { ILinkReg } from '../../../model/other';
import { ICallResult } from '../../../model/other'
import { CSigninNoreg } from '../../../components/CSigninNoreg'
import { useQuasar } from 'quasar'
import { useI18n } from 'vue-i18n'
import { useGlobalStore } from '@store/globalStore'
import { useRoute, useRouter } from 'vue-router'
import { useUserStore } from '@store/UserStore'
import { tools } from '@tools'
export default defineComponent({
name: 'reverif_email',
components: { CSigninNoreg },
setup(props) {
const $q = useQuasar()
const route = useRoute()
const $router = useRouter()
const { t } = useI18n()
const globalStore = useGlobalStore()
const userStore = useUserStore()
const risultato = ref('---')
const riscode = ref(0)
function myrisultato() {
return risultato
}
function giaverificato() {
return riscode.value !== serv_constants.RIS_CODE_EMAIL_VERIFIED
}
function verificatook() {
return riscode.value === serv_constants.RIS_CODE_EMAIL_VERIFIED
}
function load() {
console.log('load REFERIFY')
let param: ILinkReg = { idlink: '' }
if (route.query.idlink)
param = { idlink: route.query.idlink.toString() }
return userStore.reverif_email(param)
.then((ris: any) => {
riscode.value = ris.code
risultato.value = ris.msg
console.log('RIS = ', ris)
if (verificatook()) {
setTimeout(() => {
$router.replace('/signin')
}, 2000)
}
}).catch((err: any) => {
console.log('ERR = ' + err)
})
}
load()
return {
tools,
verificatook,
giaverificato,
myrisultato,
t,
}
},
})

View File

@@ -0,0 +1,58 @@
<template>
<q-page
padding
class="vreg"
>
<div class="q-pa-md q-gutter-sm">
<q-banner
rounded
class="bg-primary text-white"
color="primary q-title"
style="text-align: center"
>
<span class="mybanner">{{ t('reg.title_verif_email') }}</span>
</q-banner>
<br />
<transition
enter-active-class="animated fadeIn"
leave-active-class="animated fadeOut"
appear
>
<div>
<q-banner
rounded
class="bg-warning text-black"
style="text-align: center"
v-if="giaverificato()"
>
<span class="mybanner">{{ myrisultato() }}</span>
</q-banner>
<q-banner
class="bg-positive text-white"
style="text-align: center"
rounded
v-if="verificatook()"
>
<span class="mybanner">{{ myrisultato() }}</span>
</q-banner>
</div>
</transition>
<div class="text-center q-mt-md">
<q-btn
rounded
size="lg"
color="primary"
@click="tools.openrighttoolbar()"
>{{ t('login.enter') }}
</q-btn>
</div>
</div>
</q-page>
</template>
<script lang="ts" src="./reverif_email.ts"></script>
<style lang="scss" scoped>
@import './reverif_email.scss';
</style>

View File

@@ -0,0 +1 @@
export {default as ollama} from './ollama.vue'

View File

@@ -0,0 +1,25 @@
$heightBtn: 100%;
.card .product-image {
height: 300px;
}
.container{
margin-top: 4px;
margin-bottom: 4px;
}
.prod_trov{
font-style: italic;
color: blue;
}
.fixed-group {
position: fixed;
top: 0;
left: 0;
width: 100%;
background-color: #ffffff; /* Customize the background color as needed */
z-index: 1000; /* Adjust the z-index to ensure it's above other elements */
transition: all 1s ease;
}

View File

@@ -0,0 +1,162 @@
import { defineComponent, onMounted, ref, watch, computed, onBeforeUnmount } from 'vue'
import { tools } from '@tools'
import { useUserStore } from '@store/UserStore'
import { useRouter } from 'vue-router'
import { useGlobalStore } from '@store/globalStore'
import { useProducts } from '@store/Products'
import { useI18n } from 'vue-i18n'
import { toolsext } from '@store/Modules/toolsext'
import { useQuasar } from 'quasar'
import { costanti } from '@costanti'
import { shared_consts } from '@/common/shared_vuejs'
import OllamaChat from '@/components/OllamaChat/OllamaChat.vue'
import type { IProduct } from '@/model'
export default defineComponent({
name: 'ollama',
components: { OllamaChat },
props: {},
setup() {
const userStore = useUserStore()
const globalStore = useGlobalStore()
const productStore = useProducts()
const router = useRouter()
const $q = useQuasar()
const { t } = useI18n()
const search = ref('')
const cosa = ref(0)
const cat = ref('')
const loadpage = ref(false)
const refreshpage = ref(false)
const arrProducts = ref<any>([])
// Create a ref for the component to fix
const componentToFixRef = ref(<any>null);
const isFixed = ref(false);
// Register the scroll event on component mount
const handleScroll = () => {
const scrollTop = window.scrollY || document.documentElement.scrollTop;
// Set a threshold value based on how much scroll is needed to fix the components
const threshold = 300;
// Update the isFixed ref based on the scroll position
isFixed.value = scrollTop > threshold;
};
watch(() => cat.value, (newval, oldval) => {
calcArrProducts()
})
watch(() => search.value, (newval, oldval) => {
calcArrProducts()
if (tools.scrollTop() > 300) {
tools.scrollToTopValue(300)
}
})
watch(() => cosa.value, (newval, oldval) => {
tools.setCookie(tools.COOK_COSA_PRODOTTI, cosa.value.toString())
if (cosa.value !== shared_consts.PROD.TUTTI)
cat.value = ''
calcArrProducts()
})
function calcArrProducts() {
refreshpage.value = true
let arrprod = productStore.getProducts(cosa.value)
const catstr = cat.value;
const lowerSearchText = search.value.toLowerCase().trim();
if ((!lowerSearchText || (lowerSearchText && lowerSearchText.length < 2)) && !catstr) {
} else {
arrprod = arrprod.filter((product: IProduct) => {
const lowerName = product.productInfo.name!.toLowerCase();
const hasCategoria = !catstr || (catstr && product.productInfo.idCatProds?.includes(catstr));
// Use a regular expression to match whole words
const codeMatch = new RegExp(`\\b${lowerSearchText}\\b`, 'i');
const nameMatch = new RegExp(`\\b${lowerSearchText}`, 'i');
// Check if any word in lowerName starts with lowerSearchText
const anyWordStartsWithSearch = lowerName.split(/\s+/).some(word => nameMatch.test(word));
return (codeMatch.test(product.productInfo.code) || anyWordStartsWithSearch) && hasCategoria;
});
}
arrProducts.value = arrprod
refreshpage.value = false
}
/*function getProducts() {
let arrprod = productStore.getProducts(cosa.value)
if (!search.value) {
return arrprod
}
let lowerSearchText = search.value.toLowerCase();
let catstr = cat.value;
return arrprod.filter((product: IProduct) => {
let lowerName = product.productInfo.name!.toLowerCase();
const hasCategoria = !catstr || (catstr && product.productInfo.idCatProds?.includes(catstr));
return (product.productInfo.code!.includes(search.value) || lowerName.includes(lowerSearchText)) && hasCategoria
});
}*/
async function mounted() {
loadpage.value = false
await productStore.loadProducts(true)
cosa.value = tools.getCookie(tools.COOK_COSA_PRODOTTI, shared_consts.PROD.TUTTI, true)
// Inizializza
loadpage.value = true
window.addEventListener('scroll', handleScroll);
calcArrProducts()
}
// Remove the event listener on component destroy
onBeforeUnmount(() => {
window.removeEventListener('scroll', handleScroll);
});
function getCatProds() {
const arrcat = productStore.getCatProds(cosa.value)
const riscat: any = [{ label: 'Tutti', value: '', icon: undefined, color: undefined }]
for (const rec of arrcat) {
riscat.push({ label: rec.name, value: rec._id, icon: rec.icon, color: rec.color })
}
return riscat
}
onMounted(mounted)
return {
userStore,
costanti,
tools,
toolsext,
search,
cosa,
shared_consts,
getCatProds,
cat,
productStore,
t,
loadpage,
refreshpage,
componentToFixRef,
isFixed,
arrProducts,
}
}
})

View File

@@ -0,0 +1,18 @@
<template>
<q-page class="column">
<div class="text-center">
<q-spinner v-if="!loadpage" color="primary" size="3em" :thickness="2" />
</div>
<div v-if="loadpage && (tools.isUserOk() && tools.isLogged())">
<OllamaChat :baseUrl="tools.getServerHost()" height="100%"
></OllamaChat>
</div>
</q-page>
</template>
<script lang="ts" src="./ollama.ts">
</script>
<style lang="scss" scoped>
@import './ollama.scss';
</style>

View File

@@ -1,249 +1,295 @@
import { CMyFieldDb } from '@/components/CMyFieldDb'
import { CMyFieldRec } from '@/components/CMyFieldRec'
import { CTitleBanner } from '@/components/CTitleBanner'
import { CProfile } from '@/components/CProfile'
import { CLabel } from '@/components/CLabel'
import { CCopyBtn } from '@/components/CCopyBtn'
import { CSkill } from '@/components/CSkill'
import { CDateTime } from '@/components/CDateTime'
import { CMyGroup } from '@/components/CMyGroup'
import { CUserNote } from '@/components/CUserNote'
import { CMyCircuit } from '@/components/CMyCircuit'
import { CNotifAtTop } from '@/components/CNotifAtTop'
import { CMyActivities } from '@/components/CMyActivities'
import { CSendCoins } from '@/components/CSendCoins'
import { CContactUser } from '@/components/CContactUser'
import { CTimeAgo } from '@/components/CTimeAgo'
import { CMyUser } from '@/components/CMyUser'
import { CUserNonVerif } from '@/components/CUserNonVerif'
import { CCheckIfIsLogged } from '@/components/CCheckIfIsLogged'
import CPageUserNotFound from '@/components/CPageUserNotFound/CPageUserNotFound.vue'
import { tools } from '@tools'
import { computed, defineComponent, onMounted, ref, watch } from 'vue'
import { useUserStore } from '@store/UserStore'
import { useRoute, useRouter } from 'vue-router'
import { useGlobalStore } from '@store/globalStore'
import { useI18n } from 'vue-i18n'
import { toolsext } from '@store/Modules/toolsext'
import { useQuasar } from 'quasar'
import { costanti } from '@costanti'
import { CMyFieldDb } from '@/components/CMyFieldDb';
import { CMyFieldRec } from '@/components/CMyFieldRec';
import { CTitleBanner } from '@/components/CTitleBanner';
import { CProfile } from '@/components/CProfile';
import { CLabel } from '@/components/CLabel';
import { CCopyBtn } from '@/components/CCopyBtn';
import { CSkill } from '@/components/CSkill';
import { CDateTime } from '@/components/CDateTime';
import { CMyGroup } from '@/components/CMyGroup';
import { CUserNote } from '@/components/CUserNote';
import { CMyCircuit } from '@/components/CMyCircuit';
import { CNotifAtTop } from '@/components/CNotifAtTop';
import { CMyActivities } from '@/components/CMyActivities';
import { CSendCoins } from '@/components/CSendCoins';
import { CContactUser } from '@/components/CContactUser';
import { CTimeAgo } from '@/components/CTimeAgo';
import { CMyUser } from '@/components/CMyUser';
import { CUserNonVerif } from '@/components/CUserNonVerif';
import { CCheckIfIsLogged } from '@/components/CCheckIfIsLogged';
import CPageUserNotFound from '@/components/CPageUserNotFound/CPageUserNotFound.vue';
import { tools } from '@tools';
import { computed, defineComponent, onMounted, ref, watch } from 'vue';
import { useUserStore } from '@store/UserStore';
import { useRoute, useRouter } from 'vue-router';
import { useGlobalStore } from '@store/globalStore';
import { useI18n } from 'vue-i18n';
import { toolsext } from '@store/Modules/toolsext';
import { useQuasar } from 'quasar';
import { costanti } from '@costanti';
import type { IMyCircuit, IMyGroup, IUserFields } from 'model';
import { ICircuit, IFriends } from 'model'
import { shared_consts } from '@/common/shared_vuejs'
import { static_data } from '@/db/static_data'
import { fieldsTable } from '@store/Modules/fieldsTable'
import { useNotifStore } from '@store/NotifStore'
import MixinUsers from '@/mixins/mixin-users'
import { ICircuit, IFriends } from 'model';
import { shared_consts } from '@/common/shared_vuejs';
import { static_data } from '@/db/static_data';
import { fieldsTable } from '@store/Modules/fieldsTable';
import { useNotifStore } from '@store/NotifStore';
import MixinUsers from '@/mixins/mixin-users';
export default defineComponent({
name: 'myprofile',
components: {
CProfile, CTitleBanner, CMyFieldDb, CSkill, CDateTime, CCopyBtn, CUserNonVerif, CMyFieldRec, CMyUser,
CMyGroup, CLabel, CMyCircuit, CSendCoins, CNotifAtTop,
CCheckIfIsLogged, CTimeAgo, CContactUser, CMyActivities, CUserNote, CPageUserNotFound,
CProfile,
CTitleBanner,
CMyFieldDb,
CSkill,
CDateTime,
CCopyBtn,
CUserNonVerif,
CMyFieldRec,
CMyUser,
CMyGroup,
CLabel,
CMyCircuit,
CSendCoins,
CNotifAtTop,
CCheckIfIsLogged,
CTimeAgo,
CContactUser,
CMyActivities,
CUserNote,
CPageUserNotFound,
},
props: {},
setup() {
const userStore = useUserStore()
const globalStore = useGlobalStore()
const $route = useRoute()
const $q = useQuasar()
const { t } = useI18n()
const userStore = useUserStore();
const globalStore = useGlobalStore();
const $route = useRoute();
const $q = useQuasar();
const { t } = useI18n();
const site = ref(globalStore.site)
const site = ref(globalStore.site);
const { getRefLink } = MixinUsers()
const { getRefLink } = MixinUsers();
const animation = ref('fade')
const spinner_visible = ref(false)
const shownote = ref(false)
const usersList = ref({ show: false, title: '', list: [] })
const animation = ref('fade');
const shownote = ref(false);
const usersList = ref({ show: false, title: '', list: [] });
const username = computed(() => $route.params.username ? $route.params.username.toString() : userStore.my.username)
const idnotif = computed(() => $route.query.idnotif ? $route.query.idnotif.toString() : '')
const isDebugOn = computed(() => tools.isDebugOn())
const username = computed(() =>
$route.params.username ? $route.params.username.toString() : userStore.my.username
);
const idnotif = computed(() =>
$route.query.idnotif ? $route.query.idnotif.toString() : ''
);
const isDebugOn = computed(() => tools.isDebugOn());
const sendRIS = computed(() => $route.query.sr ? $route.query.sr : '')
const causalDest = computed(() => $route.query.cd ? $route.query.cd : '')
const sendRIS = computed(() => ($route.query.sr ? $route.query.sr : ''));
const causalDest = computed(() => ($route.query.cd ? $route.query.cd : ''));
const $router = useRouter()
const filtroutente = ref(<any[]>[])
const showPic = ref(false)
const caricato = ref(false)
const showsendCoinTo = ref(false)
const showinghand = ref(false)
const $router = useRouter();
const filtroutente = ref(<any[]>[]);
const showPic = ref(false);
const caricato = ref(false);
const showsendCoinTo = ref(false);
const showinghand = ref(false);
const actualcard = ref('mygoods')
const mostranota = ref(false)
const actualcard = ref('mygoods');
const mostranota = ref(false);
const notifStore = useNotifStore()
const notifStore = useNotifStore();
const quantiHandShake = computed(() => (userStore.userprofile.profile.handshake ? userStore.userprofile.profile.handshake.length : 0) + ' ' + t('handshake.strettedimano'))
const handshake_inCommon = computed(() => userStore.getMyHandshakeInCommon(userStore.userprofile))
const quanteHandShakeInCommon = computed(() => (handshake_inCommon.value ? handshake_inCommon.value.length : 0) + ' ' + t('handshake.incommon'))
const quantiHandShake = computed(
() =>
(userStore.userprofile.profile.handshake
? userStore.userprofile.profile.handshake.length
: 0) +
' ' +
t('handshake.strettedimano')
);
const handshake_inCommon = computed(() =>
userStore.getMyHandshakeInCommon(userStore.userprofile)
);
const quanteHandShakeInCommon = computed(
() =>
(handshake_inCommon.value ? handshake_inCommon.value.length : 0) +
' ' +
t('handshake.incommon')
);
const mycards = computed(() => {
return costanti.MAINCARDS.filter((rec: any) => rec.table)
})
return costanti.MAINCARDS.filter((rec: any) => rec.table);
});
const listgroupsfiltered = ref(<IMyGroup[]>[])
const listcircuitsfiltered = ref(<IMyCircuit[]>[])
const listgroupsfiltered = ref(<IMyGroup[]>[]);
const listcircuitsfiltered = ref(<IMyCircuit[]>[]);
const tab = ref('')
const tab = ref('');
function profile() {
return userStore.my.profile
return userStore.my.profile;
}
function myusername() {
return userStore.my.username
return userStore.my.username;
}
async function loadProfile() {
console.log('loadProfile...', username.value)
console.log('loadProfile...', username.value);
try {
caricato.value = false;
caricato.value = false
if (sendRIS.value)
spinner_visible.value = true
if (sendRIS.value) {
$q.loading.show({
message: 'Caricamento in corso...',
spinnerColor: 'white',
backgroundColor: 'black',
messageColor: 'white',
});
}
// Carica il profilo di quest'utente
if (username.value) {
await userStore.loadUserProfile({ username: username.value, idnotif: idnotif.value }).then((ris) => {
console.log('loadUserProfile = ', ris)
userStore.userprofile = ris
if (userStore.userprofile) {
filtroutente.value = [{ userId: userStore.userprofile._id }]
notifStore.setAsRead(idnotif.value)
await userStore
.loadUserProfile({ username: username.value, idnotif: idnotif.value })
.then((ris) => {
console.log('loadUserProfile = ', ris);
userStore.userprofile = ris;
if (userStore.userprofile) {
filtroutente.value = [{ userId: userStore.userprofile._id }];
notifStore.setAsRead(idnotif.value);
try {
if (userStore.userprofile)
listgroupsfiltered.value = globalStore.mygroups.filter(
(grp: IMyGroup) =>
userStore.userprofile.profile.mygroups.findIndex(
(rec: IMyGroup) => rec.groupname === grp.groupname
) >= 0
);
} catch (e) {
listgroupsfiltered.value = [];
}
try {
if (userStore.userprofile)
listgroupsfiltered.value = globalStore.mygroups.filter((grp: IMyGroup) => userStore.userprofile.profile.mygroups.findIndex((rec: IMyGroup) => rec.groupname === grp.groupname) >= 0)
} catch (e) {
listgroupsfiltered.value = []
try {
listcircuitsfiltered.value = userStore.userprofile.profile.mycircuits;
} catch (e) {
listcircuitsfiltered.value = [];
}
}
});
try {
listcircuitsfiltered.value = userStore.userprofile.profile.mycircuits
} catch (e) {
listcircuitsfiltered.value = []
}
}
})
caricato.value = true
caricato.value = true;
}
} catch (e) {
console.error('ERR loadProfile', e)
console.error('ERR loadProfile', e);
}
}
watch(() => username.value, (to: any, from: any) => {
loadProfile()
})
watch(
() => username.value,
(to: any, from: any) => {
loadProfile();
}
);
watch(() => actualcard.value, (to: any, from: any) => {
loadProfile()
})
watch(
() => actualcard.value,
(to: any, from: any) => {
loadProfile();
}
);
function mounted() {
tab.value = tools.isRisoApp() ? 'attivita' : 'info'
loadProfile()
tab.value = tools.isRisoApp() ? 'attivita' : 'info';
loadProfile();
}
function getImgUser() {
if (userStore.userprofile)
return userStore.getImgByProfile(userStore.userprofile)
else
return ''
if (userStore.userprofile) return userStore.getImgByProfile(userStore.userprofile);
else return '';
}
function checkifShow(col: string) {
//++Todo: checkifShow Permessi !
return true
return true;
}
function isMyRecord(username: string) {
return username === userStore.my.username
return username === userStore.my.username;
}
function getLinkWebSite() {
if (userStore.userprofile) {
let mysite = userStore.userprofile.profile.website
let mysite = userStore.userprofile.profile.website;
if (mysite) {
if (!mysite.startsWith('http')) {
mysite = 'https://' + mysite
mysite = 'https://' + mysite;
}
}
return mysite
return mysite;
} else {
return ''
return '';
}
}
onMounted(mounted)
onMounted(mounted);
function gotoPage(link: string) {
$router.push(link)
$router.push(link);
}
function getlinkpage() {
if (userStore.userprofile)
return self.location.host + '/my/' + userStore.userprofile.username
else
return ''
return self.location.host + '/my/' + userStore.userprofile.username;
else return '';
}
function showed() {
spinner_visible.value = false
$q.loading.hide()
}
function salvaUserProv(userprofile: IUserFields) {
if (userprofile)
userStore.userprofile = userprofile
if (userprofile) userStore.userprofile = userprofile;
}
function saveDaContattare() {
const globalStore = useGlobalStore()
const { t } = useI18n()
const globalStore = useGlobalStore();
const { t } = useI18n();
const mydatatosave = {
id: userStore.userprofile._id,
table: 'users',
fieldsvalue: { 'profile.da_contattare': userStore.userprofile.profile.da_contattare }
}
fieldsvalue: {
'profile.da_contattare': userStore.userprofile.profile.da_contattare,
},
};
globalStore.saveFieldValue(mydatatosave).then((esito) => {
if (esito) {
tools.showPositiveNotif($q, t('db.recupdated'))
tools.showPositiveNotif($q, t('db.recupdated'));
} else {
tools.showNegativeNotif($q, t('db.recfailed'))
tools.showNegativeNotif($q, t('db.recfailed'));
}
})
});
}
function savePerm() {
const globalStore = useGlobalStore()
const { t } = useI18n()
const globalStore = useGlobalStore();
const { t } = useI18n();
const mydatatosave = {
id: userStore.userprofile._id,
table: 'users',
fieldsvalue: { 'perm': userStore.userprofile.perm }
}
fieldsvalue: { perm: userStore.userprofile.perm },
};
globalStore.saveFieldValue(mydatatosave).then((esito) => {
if (esito) {
tools.showPositiveNotif($q, t('db.recupdated'))
tools.showPositiveNotif($q, t('db.recupdated'));
} else {
tools.showNegativeNotif($q, t('db.recfailed'))
tools.showNegativeNotif($q, t('db.recfailed'));
}
})
});
}
return {
@@ -285,7 +331,6 @@ export default defineComponent({
gotoPage,
sendRIS,
causalDest,
spinner_visible,
showed,
tab,
shownote,
@@ -293,6 +338,6 @@ export default defineComponent({
salvaUserProv,
saveDaContattare,
savePerm,
}
}
})
};
},
});

View File

@@ -1,10 +1,15 @@
<template>
<div v-if="isDebugOn && false" class="bg-red text-white">
<div
v-if="isDebugOn && false"
class="bg-red text-white"
>
getImgUser(): {{ getImgUser() }}
<span v-if="!!tools.isLogged()">Logged: {{ tools.isLogged() }}</span> -
<span v-if="!!tools.isUserOk()">UserOk: {{ tools.isUserOk() }}</span> -
<span v-if="!!userStore.userprofile.date_reg">Date_Reg: {{ tools.getstrDate(userStore.userprofile.date_reg) }}</span> -
<span>tools.isEmailVerified(): {{ tools.isEmailVerified() }}</span> -
<span v-if="!!userStore.userprofile.date_reg"
>Date_Reg: {{ tools.getstrDate(userStore.userprofile.date_reg) }}</span
>
- <span>tools.isEmailVerified(): {{ tools.isEmailVerified() }}</span> -
<span
>tools.getLinkUserTelegramByUser():
{{ tools.getLinkUserTelegramByUser(userStore.userprofile) }}</span
@@ -18,8 +23,17 @@
v-if="!caricato"
class="fit column no-wrap justify-evenly items-center content-start"
>
<q-skeleton type="QAvatar" size="140px" height="140px" animation="fade" />
<q-card flat bordered style="width: 250px">
<q-skeleton
type="QAvatar"
size="140px"
height="140px"
animation="fade"
/>
<q-card
flat
bordered
style="width: 250px"
>
<div class="text-h6">
<q-skeleton :animation="animation" />
</div>
@@ -62,9 +76,7 @@
>online</q-badge
>
<q-badge
v-if="
userStore.IsHandShakeByUsername(userStore.userprofile.username)
"
v-if="userStore.IsHandShakeByUsername(userStore.userprofile.username)"
align="bottom"
floating
color="red"
@@ -101,9 +113,7 @@
{{ userStore.userprofile.username }}
</div>
<div
v-if="
userStore.userprofile && userStore.userprofile.profile.qualifica
"
v-if="userStore.userprofile && userStore.userprofile.profile.qualifica"
class="col-12 text-h8 q-mt-sm"
>
<span v-if="userStore.userprofile.profile.qualifica">
@@ -115,9 +125,7 @@
</span>
</div>
<div
v-if="
userStore.userprofile && userStore.userprofile.profile.biografia
"
v-if="userStore.userprofile && userStore.userprofile.profile.biografia"
class="col-12 text-h8 q-mt-sm"
>
{{ userStore.userprofile.profile.biografia }}
@@ -185,19 +193,12 @@
</CTitleBanner>
</div>
<div>
<q-inner-loading :showing="spinner_visible">
<q-spinner-tail size="3em" color="primary" />
</q-inner-loading>
</div>
<div v-if="site && site.confpages && site.confpages.showNameSurname">
<div class="text-h6">
<span v-if="checkifShow('name') && userStore.userprofile.name">
{{ userStore.userprofile.name }}</span
>
<span
v-if="checkifShow('surname') && userStore.userprofile.surname"
<span v-if="checkifShow('surname') && userStore.userprofile.surname"
>&nbsp;{{ userStore.userprofile.surname }}</span
>
</div>
@@ -206,10 +207,7 @@
{{ userStore.userprofile.username }}
</div>
<div
v-if="
userStore.userprofile.profile.qualifica &&
userStore.userprofile._id
"
v-if="userStore.userprofile.profile.qualifica && userStore.userprofile._id"
class="col-12 text-h8 q-mt-sm"
>
<span v-if="userStore.userprofile.profile.qualifica">
@@ -258,14 +256,28 @@
"
class="col-12 text-h8 q-mt-sm"
>
<div v-if="!mostranota" class="text-center">
<q-btn label="Note" @click="mostranota = true" color="green">
<q-badge color="red" floating>1</q-badge>
<div
v-if="!mostranota"
class="text-center"
>
<q-btn
label="Note"
@click="mostranota = true"
color="green"
>
<q-badge
color="red"
floating
>1</q-badge
>
</q-btn>
</div>
<div v-else>
<strong>Note del Facilitatore</strong>:<br />
<q-banner rounded class="bg-green-8 text-white">
<q-banner
rounded
class="bg-green-8 text-white"
>
<div class="text-h7 text-center">
{{ userStore.userprofile.profile.note }}
</div>
@@ -329,9 +341,7 @@
<q-chip
v-if="
userStore.IsHandShakeByMe(userStore.userprofile) &&
userStore.IsHandShakeByUsername(
userStore.userprofile.username
)
userStore.IsHandShakeByUsername(userStore.userprofile.username)
"
color="green"
dense
@@ -341,11 +351,9 @@
icon="fas fa-handshake"
>
<span
>&nbsp;<em
class="q-pa-xxs text-white rounded-borders shadow-2"
>
>&nbsp;<em class="q-pa-xxs text-white rounded-borders shadow-2">
{{
$t("db.both_fiducia", {
$t('db.both_fiducia', {
username: userStore.userprofile.username,
})
}}
@@ -355,9 +363,7 @@
<q-chip
v-else-if="
!userStore.IsHandShakeByMe(userStore.userprofile) &&
userStore.IsHandShakeByUsername(
userStore.userprofile.username
)
userStore.IsHandShakeByUsername(userStore.userprofile.username)
"
color="blue"
dense
@@ -367,11 +373,9 @@
icon="fas fa-handshake"
>
<span
>&nbsp;<em
class="q-pa-xxs text-white rounded-borders shadow-2"
>
>&nbsp;<em class="q-pa-xxs text-white rounded-borders shadow-2">
{{
$t("db.handshake_him", {
$t('db.handshake_him', {
username: userStore.userprofile.username,
})
}}
@@ -381,9 +385,7 @@
<q-chip
v-else-if="
userStore.IsHandShakeByMe(userStore.userprofile) &&
!userStore.IsHandShakeByUsername(
userStore.userprofile.username
)
!userStore.IsHandShakeByUsername(userStore.userprofile.username)
"
color="blue"
dense
@@ -393,11 +395,9 @@
icon="fas fa-handshake"
>
<span
>&nbsp;<em
class="q-pa-xxs text-white rounded-borders shadow-2"
>
>&nbsp;<em class="q-pa-xxs text-white rounded-borders shadow-2">
{{
$t("db.handshake_you", {
$t('db.handshake_you', {
username: userStore.userprofile.username,
})
}}
@@ -407,21 +407,13 @@
</div>
<!--HANDSHAKE-->
<div
v-if="
!isMyRecord(userStore.userprofile.username) && tools.isUserOk()
"
>
<div v-if="!isMyRecord(userStore.userprofile.username) && tools.isUserOk()">
<div
class="row centeritems q-pa-sm"
v-if="!userStore.IsHandShakeByMe(userStore.userprofile)"
>
<q-btn
v-if="
userStore.IsHandShakeByUsername(
userStore.userprofile.username
)
"
v-if="userStore.IsHandShakeByUsername(userStore.userprofile.username)"
icon="fas fa-handshake"
color="positive"
dense
@@ -461,9 +453,7 @@
class="row centeritems q-ma-sm q-pa-sm"
v-if="
costanti.ENABLE_FRIENDS &&
userStore.IsReqFriendByUsername(
userStore.userprofile.username
)
userStore.IsReqFriendByUsername(userStore.userprofile.username)
"
>
<q-btn
@@ -497,12 +487,8 @@
<q-btn
v-if="
costanti.ENABLE_FRIENDS &&
!userStore.IsMyFriendByUsername(
userStore.userprofile.username
) &&
!userStore.IsAskedFriendByUsername(
userStore.userprofile.username
)
!userStore.IsMyFriendByUsername(userStore.userprofile.username) &&
!userStore.IsAskedFriendByUsername(userStore.userprofile.username)
"
icon="fas fa-user-plus"
color="primary"
@@ -523,9 +509,8 @@
<div class="row justify-center">
<q-btn
v-if="
userStore.IsMyFriendByUsername(
userStore.userprofile.username
) || userStore.IsHandShakeByMe(userStore.userprofile)
userStore.IsMyFriendByUsername(userStore.userprofile.username) ||
userStore.IsHandShakeByMe(userStore.userprofile)
"
class="text-center"
rounded
@@ -553,15 +538,13 @@
/>
</q-item-section>
<q-item-section>{{
$t("handshake.remove_from_myhandshake")
$t('handshake.remove_from_myhandshake')
}}</q-item-section>
</q-item>
<q-item
v-if="
costanti.ENABLE_FRIENDS &&
userStore.IsMyFriendByUsername(
userStore.userprofile.username
)
userStore.IsMyFriendByUsername(userStore.userprofile.username)
"
clickable
icon="fas fa-user-minus"
@@ -576,17 +559,18 @@
"
>
<q-item-section avatar>
<q-icon color="negative" name="fas fa-user-minus" />
<q-icon
color="negative"
name="fas fa-user-minus"
/>
</q-item-section>
<q-item-section>{{
$t("friends.remove_from_myfriends")
$t('friends.remove_from_myfriends')
}}</q-item-section>
</q-item>
<q-item
v-if="
userStore.IsMyFriendByUsername(
userStore.userprofile.username
)
userStore.IsMyFriendByUsername(userStore.userprofile.username)
"
clickable
icon="fas fa-ban"
@@ -600,17 +584,16 @@
"
>
<q-item-section avatar>
<q-icon color="negative" name="fas fa-ban" />
<q-icon
color="negative"
name="fas fa-ban"
/>
</q-item-section>
<q-item-section>{{
$t("friends.block_user")
}}</q-item-section>
<q-item-section>{{ $t('friends.block_user') }}</q-item-section>
</q-item>
<q-item
v-if="
userStore.IsMyFriendByUsername(
userStore.userprofile.username
)
userStore.IsMyFriendByUsername(userStore.userprofile.username)
"
clickable
v-close-popup
@@ -623,11 +606,12 @@
"
>
<q-item-section avatar>
<q-icon color="negative" name="fas fa-flag" />
<q-icon
color="negative"
name="fas fa-flag"
/>
</q-item-section>
<q-item-section>{{
$t("friends.report_user")
}}</q-item-section>
<q-item-section>{{ $t('friends.report_user') }}</q-item-section>
</q-item>
</q-list>
</q-menu>
@@ -636,12 +620,8 @@
<q-btn
v-if="
costanti.ENABLE_FRIENDS &&
userStore.IsAskedFriendByUsername(
userStore.userprofile.username
) &&
!userStore.IsMyFriendByUsername(
userStore.userprofile.username
)
userStore.IsAskedFriendByUsername(userStore.userprofile.username) &&
!userStore.IsMyFriendByUsername(userStore.userprofile.username)
"
icon="fas fa-user-minus"
outline
@@ -677,7 +657,11 @@
/>
</q-tabs>
<q-tab-panels v-model="tab" animated keep-alive>
<q-tab-panels
v-model="tab"
animated
keep-alive
>
<q-tab-panel name="attivita">
<CMyActivities
:username_prop="userStore.userprofile.username"
@@ -687,8 +671,7 @@
<q-tab-panel name="info">
<div
v-if="
userStore.userprofile._id &&
userStore.userprofile.profile.biografia
userStore.userprofile._id && userStore.userprofile.profile.biografia
"
class="col-12 text-h8 q-mt-sm"
>
@@ -698,9 +681,7 @@
<div
v-if="
userStore.userprofile &&
userStore.userprofile._id &&
tools.isUserOk()
userStore.userprofile && userStore.userprofile._id && tools.isUserOk()
"
>
<div
@@ -713,12 +694,18 @@
v-bind="$attrs"
:copy="false"
:value="
userStore.userprofile.profile.resid_str_comune + (userStore.userprofile.profile.resid_province ? ' (' + userStore.userprofile.profile.resid_province + ')' : '')
userStore.userprofile.profile.resid_str_comune +
(userStore.userprofile.profile.resid_province
? ' (' + userStore.userprofile.profile.resid_province + ')'
: '')
"
label="Comune"
/>
<CLabel
v-if="!!userStore.userprofile.profile.resid_province && !userStore.userprofile.profile.resid_str_comune"
v-if="
!!userStore.userprofile.profile.resid_province &&
!userStore.userprofile.profile.resid_str_comune
"
v-bind="$attrs"
:copy="false"
:value="
@@ -752,9 +739,7 @@
label="Sito Web"
>
<span
v-html="
tools.getlinkhref(getLinkWebSite(), getLinkWebSite())
"
v-html="tools.getlinkhref(getLinkWebSite(), getLinkWebSite())"
/>
</CLabel>
@@ -801,7 +786,10 @@
</q-tab-panels>
</div>
</div>
<q-page-sticky position="top-right" :offset="[18, 18]" class="z-top">
<q-page-sticky
position="top-right"
:offset="[18, 18]"
>
<q-fab
icon="fas fa-ellipsis-v"
color="accent"
@@ -837,14 +825,12 @@
v-if="userStore.isFacilitatore || userStore.isAdmin"
color="green"
:icon="
userStore.userprofile.profile &&
userStore.userprofile.profile.da_contattare
userStore.userprofile.profile && userStore.userprofile.profile.da_contattare
? 'fas fa-user-slash'
: 'fas fa-comment'
"
:label="
userStore.userprofile.profile &&
userStore.userprofile.profile.da_contattare
userStore.userprofile.profile && userStore.userprofile.profile.da_contattare
? t('profile.togli_da_contattare')
: t('profile.da_contattare')
"
@@ -892,8 +878,16 @@
/>
</q-fab>
</q-page-sticky>
<q-dialog v-model="showPic" full-height full-width>
<img :src="getImgUser()" :alt="username" class="full-width" />
<q-dialog
v-model="showPic"
full-height
full-width
>
<img
:src="getImgUser()"
:alt="username"
class="full-width"
/>
</q-dialog>
<div
@@ -909,17 +903,23 @@
style="z-index: 1"
>
<q-menu>
<q-list v-if="true" style="min-width: 200px">
<q-list
v-if="true"
style="min-width: 200px"
>
<q-item
clickable
v-close-popup
@click.stop="gotoPage('/editprofile')"
>
<q-item-section avatar>
<q-icon color="blue" name="fas fa-pencil-alt" />
<q-icon
color="blue"
name="fas fa-pencil-alt"
/>
</q-item-section>
<q-item-section>
{{ $t("shared.edit_profile") }}
{{ $t('shared.edit_profile') }}
</q-item-section>
</q-item>
</q-list>
@@ -950,11 +950,20 @@
<q-toolbar-title>
{{ usersList.title }}
</q-toolbar-title>
<q-btn flat round color="white" icon="close" v-close-popup></q-btn>
<q-btn
flat
round
color="white"
icon="close"
v-close-popup
></q-btn>
</q-toolbar>
<q-card-section class="inset-shadow">
<div v-for="(rec, i) in usersList.list" :key="i">
<div
v-for="(rec, i) in usersList.list"
:key="i"
>
<CMyUser
:mycontact="rec"
:visu="costanti.FIND_PEOPLE"
@@ -975,10 +984,8 @@
</div>
</template>
<script lang="ts" src="./myprofile.ts">
</script>
<script lang="ts" src="./myprofile.ts"></script>
<style lang="scss" scoped>
@import "./myprofile.scss";
@import './myprofile.scss';
</style>