- aggiornamenti guida RIS, FAQ

- Editor HTML aggiunto CSS e Script
- Statistiche
- CRISBalanceBar
- Inizio Sync... (ma disattivato)
This commit is contained in:
Surya Paolo
2025-12-02 22:16:24 +01:00
parent 8b6a636a96
commit a51bc5a8a2
53 changed files with 8041 additions and 1177 deletions

2291
_LIMBO/risospiegazione.html Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -117,8 +117,62 @@ self.addEventListener('activate', (event) => {
); );
}) })
); );
self.clients.claim(); // SERVE? OPPURE NO ?
}); });
const USASYNC = false;
// PER ATTIVARE IL SYNC TOGLI L'AREA COMMENTATA DI 'FETCH' QUI SOTTO ....
/*
// Strategia fetch
self.addEventListener('fetch', (event) => {
const { request } = event;
const url = new URL(request.url);
// ============================================
// IMPORTANTE: NON cachare API /sync o /loadsite
// ============================================
if (url.pathname.includes('/api/') ||
url.pathname.includes('/sync') ||
url.pathname.includes('/loadsite')) {
// Lascia passare normalmente - IndexedDB gestisce cache
return;
}
// ============================================
// Cache Strategy per assets statici
// ============================================
if (request.method === 'GET') {
event.respondWith(
caches.match(request).then((cachedResponse) => {
if (cachedResponse) {
return cachedResponse;
}
return fetch(request).then((response) => {
// Non cachare se non è successo
if (!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
// Clona e salva in cache
const responseToCache = response.clone();
caches.open(CACHE_NAME).then((cache) => {
cache.put(request, responseToCache);
});
return response;
}).catch(() => {
// Offline fallback
if (request.destination === 'document') {
return caches.match('/offline.html');
}
});
})
);
}
});
*/
console.log( console.log(
' [ VER-' + ' [ VER-' +
VITE_APP_VERSION + VITE_APP_VERSION +

View File

@@ -5,6 +5,8 @@ $card-radius-mobile: 14px;
$transition-smooth: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); $transition-smooth: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
$transition-base: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); $transition-base: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
$gradient-primary: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
.stat-card { .stat-card {
position: relative; position: relative;
width: 100%; width: 100%;
@@ -158,26 +160,26 @@ $transition-base: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
} }
.stat-title { .stat-title {
font-size: 0.75rem; font-size: 1rem;
font-weight: 600; font-weight: 600;
letter-spacing: 0.03em; letter-spacing: 0.03em;
line-height: 1.3; line-height: 1.3;
opacity: 0.85; opacity: 0.85;
text-transform: uppercase; text-transform: uppercase;
margin: 4px 0; margin: 4px 0;
color: #424242; color: #718096;
@media (max-width: 960px) { @media (max-width: 960px) {
font-size: 0.7rem; font-size: 0.9rem;
} }
@media (max-width: 718px) { @media (max-width: 718px) {
font-size: 0.65rem; font-size: 0.8rem;
margin: 2px 0; margin: 2px 0;
} }
@media (max-width: 480px) { @media (max-width: 480px) {
font-size: 0.6rem; font-size: 0.8rem;
letter-spacing: 0.02em; letter-spacing: 0.02em;
} }
} }
@@ -195,11 +197,14 @@ $transition-base: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
} }
.stat-value { .stat-value {
font-size: 1.5rem;
font-weight: 700;
line-height: 1.1;
letter-spacing: -0.03em; letter-spacing: -0.03em;
color: #1a1a1a; font-size: 2rem;
font-weight: 800;
background: $gradient-primary;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
line-height: 1;
padding-bottom: 2px;
@media (max-width: 960px) { @media (max-width: 960px) {
font-size: 1.375rem; font-size: 1.375rem;
@@ -276,12 +281,10 @@ $transition-base: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
left: -50%; left: -50%;
width: 200%; width: 200%;
height: 200%; height: 200%;
background: linear-gradient( background: linear-gradient(45deg,
45deg,
transparent 30%, transparent 30%,
rgba(255, 255, 255, 0.4) 50%, rgba(255, 255, 255, 0.4) 50%,
transparent 70% transparent 70%);
);
transform: translateX(-100%) translateY(-100%); transform: translateX(-100%) translateY(-100%);
transition: transform 0.8s ease; transition: transform 0.8s ease;
pointer-events: none; pointer-events: none;
@@ -307,10 +310,13 @@ $transition-base: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
// Animations // Animations
@keyframes pulse { @keyframes pulse {
0%, 100% {
0%,
100% {
opacity: 0.3; opacity: 0.3;
transform: scale(1); transform: scale(1);
} }
50% { 50% {
opacity: 0.5; opacity: 0.5;
transform: scale(1.05); transform: scale(1.05);
@@ -318,9 +324,12 @@ $transition-base: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
} }
@keyframes bounce-subtle { @keyframes bounce-subtle {
0%, 100% {
0%,
100% {
transform: translateY(0); transform: translateY(0);
} }
50% { 50% {
transform: translateY(-2px); transform: translateY(-2px);
} }
@@ -339,9 +348,11 @@ $transition-base: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
opacity: 0; opacity: 0;
transform: scale(0.5) translateY(10px); transform: scale(0.5) translateY(10px);
} }
60% { 60% {
transform: scale(1.1) translateY(-2px); transform: scale(1.1) translateY(-2px);
} }
100% { 100% {
opacity: 1; opacity: 1;
transform: scale(1) translateY(0); transform: scale(1) translateY(0);

View File

@@ -6,18 +6,16 @@
<div :class="['stat-content', classColor]"> <div :class="['stat-content', classColor]">
<!-- Icon con effetto hover e glow --> <!-- Icon con effetto hover e glow -->
<div class="stat-icon-wrapper"> <div class="stat-icon-wrapper">
<div class="stat-icon-glow" :style="{ backgroundColor: getGlowColor(classColor) }"></div> <div
class="stat-icon-glow"
:style="{ backgroundColor: getGlowColor(classColor) }"
></div>
<q-icon <q-icon
:name="icon" :name="icon"
:class="['stat-icon', classColor]" :class="['stat-icon', classColor]"
/> />
</div> </div>
<!-- Title -->
<div class="stat-title">
{{ title }}
</div>
<!-- Value con tipografia gerarchica --> <!-- Value con tipografia gerarchica -->
<div class="stat-value-container"> <div class="stat-value-container">
<div class="stat-value"> <div class="stat-value">
@@ -32,11 +30,19 @@
:style="{ backgroundColor: colBack }" :style="{ backgroundColor: colBack }"
> >
<div class="stat-badge-pulse"></div> <div class="stat-badge-pulse"></div>
<q-icon name="trending_up" size="12px" class="stat-badge-icon" /> <q-icon
name="trending_up"
size="12px"
class="stat-badge-icon"
/>
<span class="stat-badge-text">+{{ value_today }} oggi</span> <span class="stat-badge-text">+{{ value_today }} oggi</span>
</div> </div>
</transition> </transition>
</div> </div>
<!-- Title -->
<div class="stat-title">
{{ title }}
</div>
</div> </div>
<!-- Decorative elements --> <!-- Decorative elements -->
@@ -45,9 +51,8 @@
</q-card> </q-card>
</template> </template>
<script lang="ts" src="./CElemStat.ts"> <script lang="ts" src="./CElemStat.ts"></script>
</script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "./CElemStat.scss"; @import './CElemStat.scss';
</style> </style>

View File

@@ -6,6 +6,9 @@ $border-radius: 10px;
$transition-speed: 0.3s; $transition-speed: 0.3s;
$mobile-breakpoint: 768px; $mobile-breakpoint: 768px;
@use 'sass:color';
$shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.06); $shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.06);
$shadow-md: 0 2px 6px rgba(0, 0, 0, 0.08); $shadow-md: 0 2px 6px rgba(0, 0, 0, 0.08);
$shadow-hover: 0 4px 12px rgba(25, 118, 210, 0.15); $shadow-hover: 0 4px 12px rgba(25, 118, 210, 0.15);
@@ -219,9 +222,16 @@ $shadow-hover: 0 4px 12px rgba(25, 118, 210, 0.15);
} }
.grid-card-item { .grid-card-item {
width: 100%; width: 100%;
background: rgba(255, 255, 255, 0.7);
backdrop-filter: blur(8px);
border-radius: 12px;
border: 1px solid rgba(255, 255, 255, 0.9);
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
padding: 8px; // Spazio tra carousel e card
@media (max-width: $mobile-breakpoint) { @media (max-width: $mobile-breakpoint) {
width: 100%; width: 100%;
padding: 4px; // Ridotto su mobile
} }
} }

View File

@@ -1451,6 +1451,7 @@ export default defineComponent({
} }
function onRequest(myprops: any) { function onRequest(myprops: any) {
try {
const { page, rowsPerPage, rowsNumber, sortBy, descending } = myprops.pagination; const { page, rowsPerPage, rowsNumber, sortBy, descending } = myprops.pagination;
const myfilternow = myfilter.value; const myfilternow = myfilter.value;
const myfilterandnow = myfilterand.value; const myfilterandnow = myfilterand.value;
@@ -1481,7 +1482,14 @@ export default defineComponent({
console.log('onRequest: startRow', startRow, 'endRow', endRow); console.log('onRequest: startRow', startRow, 'endRow', endRow);
serverData.value = []; if (page > 1) {
// Aggiungi senza duplicati
const existingIds = new Set(serverData.value.map((rec) => rec._id));
const toadd = returnedData.value.filter((elem) => !existingIds.has(elem._id));
serverData.value = [...serverData.value, ...toadd];
} else {
serverData.value = [...returnedData.value];
}
// fetch data from "server" // fetch data from "server"
return fetchFromServer( return fetchFromServer(
@@ -1529,6 +1537,9 @@ export default defineComponent({
checkScrollPosition(); checkScrollPosition();
}); });
} catch (e) {
console.error('Error onrequest', e);
}
} }
function onUpdateData(index: number, myprops: any, done: any) { function onUpdateData(index: number, myprops: any, done: any) {
@@ -1768,6 +1779,20 @@ export default defineComponent({
} }
); );
watch(
() => searchList.value,
(to, from) => {
if (
searchList.value &&
!changetable.value &&
!startsearch.value &&
!loading.value
) {
refresh();
}
}
);
/*watch(() => myfilterand.value, (newval, oldval) => { /*watch(() => myfilterand.value, (newval, oldval) => {
refresh() refresh()
})*/ })*/

View File

@@ -513,7 +513,6 @@
</q-carousel> </q-carousel>
<q-infinite-scroll <q-infinite-scroll
ref="myinfscroll" ref="myinfscroll"
v-if="!loading"
:initial-index="0" :initial-index="0"
@load="loadMore" @load="loadMore"
:offset="350" :offset="350"
@@ -526,14 +525,12 @@
ref="myinfscroll" ref="myinfscroll"
v-else-if=" v-else-if="
shared_consts.VERTIC_SHOW_GRID.includes(myvertical) && shared_consts.VERTIC_SHOW_GRID.includes(myvertical) &&
!loading &&
alreadymounting alreadymounting
" "
:initial-index="0" :initial-index="0"
@load="onLoadScroll" @load="onLoadScroll"
:offset="350" :offset="350"
debounce="300" debounce="300"
scroll-target=".carousel-scroll-container"
> >
<div v-if="showHeaderCol"> <div v-if="showHeaderCol">
<div <div
@@ -559,7 +556,7 @@
> >
<div <div
v-for="(row, indexrow) in serverData" v-for="(row, indexrow) in serverData"
:key="indexrow" :key="row._id || indexrow"
:class="{ :class="{
row: opt.rowclass, row: opt.rowclass,
'items-stretch': opt.rowclass, 'items-stretch': opt.rowclass,
@@ -703,7 +700,6 @@
<q-table <q-table
v-else-if=" v-else-if="
!shared_consts.VERTIC_SHOW_GRID.includes(myvertical) && !shared_consts.VERTIC_SHOW_GRID.includes(myvertical) &&
!loading &&
serverData && serverData &&
mycolumns mycolumns
" "

View File

@@ -10,6 +10,8 @@ $negative-color: #c10015;
$info-color: #31ccec; $info-color: #31ccec;
$warning-color: #f2c037; $warning-color: #f2c037;
@use 'sass:color';
$grey-text: #555; $grey-text: #555;
$grey-light: #999; $grey-light: #999;
$grey-dark: #333; $grey-dark: #333;
@@ -658,7 +660,7 @@ $gradient-hover: linear-gradient(135deg, rgba(102, 126, 234, 0.05) 0%, rgba(118,
.accom_maxosp { .accom_maxosp {
font-weight: 600; font-weight: 600;
background: linear-gradient(135deg, $primary-color, color-lighten($primary-color, 15%)); background: linear-gradient(135deg, $primary-color, color.adjust($primary-color, $lightness: 15%));
-webkit-background-clip: text; -webkit-background-clip: text;
-webkit-text-fill-color: transparent; -webkit-text-fill-color: transparent;
background-clip: text; background-clip: text;

View File

@@ -33,6 +33,8 @@ import translate from '@src/globalroutines/util'
import { useMessageStore } from '@src/store/MessageStore' import { useMessageStore } from '@src/store/MessageStore'
import mixinEvents from '@src/mixins/mixin-events' import mixinEvents from '@src/mixins/mixin-events'
import { colmyUserPeople } from '@store/Modules/fieldsTable';
export default defineComponent({ export default defineComponent({
name: 'CMyCardService', name: 'CMyCardService',
emits: ['showInMap'], emits: ['showInMap'],
@@ -429,7 +431,7 @@ export default defineComponent({
return ris return ris
} }
function saveBookEvent(myevent: IEvents) { function saveBookEvent() {
if (true) { if (true) {
// close the dialog // close the dialog
@@ -543,9 +545,7 @@ export default defineComponent({
} }
function isAlreadyBooked() { function isAlreadyBooked() {
// const calendarStore = useCalendarStore() return arrbookings.value.some((recbook: IBookedEvent) => recbook.userId === userStore.my._id && recbook.booked);
// return calendarStore.findEventBooked(myrec.value._id, true)
arrbookings.value.find((recbook: IBookedEvent) => recbook.userId === userStore.my._id && recbook.booked)
} }
function extraparams() { function extraparams() {
@@ -734,6 +734,7 @@ export default defineComponent({
ismounted, ismounted,
updatePart, updatePart,
numpart, numpart,
colmyUserPeople,
} }
} }
}) })

View File

@@ -1041,7 +1041,7 @@
<span v-if="myrec.createdBy" <span v-if="myrec.createdBy"
><br />{{ $t('services.createdBy') }} ><br />{{ $t('services.createdBy') }}
<span class="text-bold" <span class="text-bold"
><a :href="'my/' + createdBy">{{ createdBy }}</a></span ><a :href="'my/' + myrec.createdBy">{{ myrec.createdBy }}</a></span
></span ></span
> >
</q-item-label> </q-item-label>
@@ -1522,7 +1522,7 @@
v-else-if="bookEventForm.booked" v-else-if="bookEventForm.booked"
:label="getTitleBtnBooking()" :label="getTitleBtnBooking()"
color="primary" color="primary"
@click="saveBookEvent()" @click="saveBookEvent"
:disable="!(bookEventpage.state === EState.Creating || hasModifiedBooking)" :disable="!(bookEventpage.state === EState.Creating || hasModifiedBooking)"
></q-btn> ></q-btn>

View File

@@ -35,7 +35,7 @@ $mobile-breakpoint: 768px;
// ======================================== // ========================================
.text-subtitle2 { .text-subtitle2 {
&.text-primary { &.text-primary {
background: linear-gradient(135deg, $primary-color, lighten($primary-color, 15%)); background: linear-gradient(135deg, $primary-color, color.adjust($primary-color, $lightness: 15%));
-webkit-background-clip: text; -webkit-background-clip: text;
-webkit-text-fill-color: transparent; -webkit-text-fill-color: transparent;
background-clip: text; background-clip: text;
@@ -116,7 +116,7 @@ $mobile-breakpoint: 768px;
// Colori con gradienti // Colori con gradienti
&[class*='bg-primary'], &[class*='bg-primary'],
&[class*='bg-blue'] { &[class*='bg-blue'] {
background: linear-gradient(135deg, $primary-color, lighten($primary-color, 10%)) !important; background: linear-gradient(135deg, $primary-color, color.adjust($primary-color, $lightness: 10%)) !important;
box-shadow: 0 2px 8px rgba($primary-color, 0.3); box-shadow: 0 2px 8px rgba($primary-color, 0.3);
&:hover { &:hover {
@@ -127,7 +127,7 @@ $mobile-breakpoint: 768px;
&[class*='bg-secondary'], &[class*='bg-secondary'],
&[class*='bg-teal'] { &[class*='bg-teal'] {
background: linear-gradient(135deg, $secondary-color, lighten($secondary-color, 10%)) !important; background: linear-gradient(135deg, $secondary-color, color.adjust($secondary-color, $lightness: 10%)) !important;
box-shadow: 0 2px 8px rgba($secondary-color, 0.3); box-shadow: 0 2px 8px rgba($secondary-color, 0.3);
&:hover { &:hover {
@@ -138,7 +138,7 @@ $mobile-breakpoint: 768px;
&[class*='bg-positive'], &[class*='bg-positive'],
&[class*='bg-green'] { &[class*='bg-green'] {
background: linear-gradient(135deg, $positive-color, lighten($positive-color, 10%)) !important; background: linear-gradient(135deg, $positive-color, color.adjust($positive-color, $lightness: 10%)) !important;
box-shadow: 0 2px 8px rgba($positive-color, 0.3); box-shadow: 0 2px 8px rgba($positive-color, 0.3);
&:hover { &:hover {
@@ -161,7 +161,7 @@ $mobile-breakpoint: 768px;
&[class*='bg-warning'], &[class*='bg-warning'],
&[class*='bg-orange'], &[class*='bg-orange'],
&[class*='bg-amber'] { &[class*='bg-amber'] {
background: linear-gradient(135deg, $warning-color, lighten($warning-color, 10%)) !important; background: linear-gradient(135deg, $warning-color, color.adjust($warning-color, $lightness: 10%)) !important;
box-shadow: 0 2px 8px rgba($warning-color, 0.3); box-shadow: 0 2px 8px rgba($warning-color, 0.3);
&:hover { &:hover {
@@ -182,7 +182,7 @@ $mobile-breakpoint: 768px;
} }
&[class*='bg-purple'] { &[class*='bg-purple'] {
background: linear-gradient(135deg, #9c27b0, lighten(#9c27b0, 10%)) !important; background: linear-gradient(135deg, #9c27b0, color.adjust(#9c27b0, $lightness: 10%)) !important;
box-shadow: 0 2px 8px rgba(#9c27b0, 0.3); box-shadow: 0 2px 8px rgba(#9c27b0, 0.3);
&:hover { &:hover {
@@ -192,7 +192,7 @@ $mobile-breakpoint: 768px;
} }
&[class*='bg-pink'] { &[class*='bg-pink'] {
background: linear-gradient(135deg, #e91e63, lighten(#e91e63, 10%)) !important; background: linear-gradient(135deg, #e91e63, color.adjust(#e91e63, $lightness: 10%)) !important;
box-shadow: 0 2px 8px rgba(#e91e63, 0.3); box-shadow: 0 2px 8px rgba(#e91e63, 0.3);
&:hover { &:hover {
@@ -203,7 +203,7 @@ $mobile-breakpoint: 768px;
&[class*='bg-grey'], &[class*='bg-grey'],
&[class*='bg-gray'] { &[class*='bg-gray'] {
background: linear-gradient(135deg, #757575, lighten(#757575, 10%)) !important; background: linear-gradient(135deg, #757575, color.adjust(#757575, $lightness: 10%)) !important;
box-shadow: 0 2px 8px rgba(#757575, 0.3); box-shadow: 0 2px 8px rgba(#757575, 0.3);
&:hover { &:hover {

View File

@@ -3,113 +3,703 @@
flex: 1; flex: 1;
} }
.regulation-container { // CSS MODERNO PER REGOLAMENTO
:deep(.regulation-content) {
max-width: 900px;
margin: 0 auto;
// Variabili
$s-xs: 4px;
$s-sm: 8px;
$s-md: 12px;
$s-lg: 16px;
$s-xl: 24px;
$r-sm: 8px;
$r-md: 12px;
$r-lg: 16px;
$gradient-primary: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
$gradient-success: linear-gradient(135deg, #10b981 0%, #059669 100%);
$gradient-warning: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
.regulation-content {
max-width: 800px;
margin: 0 auto;
padding: $s-lg;
background: linear-gradient(135deg, #f5f7fa 0%, #e8ecf3 100%);
min-height: 100vh;
}
// Header
.reg-header { .reg-header {
background: $gradient-primary;
color: white;
padding: $s-xl;
border-radius: $r-lg;
text-align: center; text-align: center;
margin-bottom: 2rem; margin-bottom: $s-xl;
padding-bottom: 1.5rem; box-shadow: 0 8px 24px rgba(102, 126, 234, 0.3);
border-bottom: 2px solid #6b8e23; position: relative;
overflow: hidden;
&::before {
content: '';
position: absolute;
top: -50%;
right: -50%;
width: 200%;
height: 200%;
background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 70%);
}
.header-icon {
margin-bottom: $s-md;
svg {
width: 48px;
height: 48px;
color: white;
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.2));
}
}
h1 { h1 {
font-size: 2rem;
font-weight: 700;
color: #6b8e23;
margin: 0; margin: 0;
font-size: 2rem;
font-weight: 800;
text-shadow: 0 2px 8px rgba(0,0,0,0.2);
}
.circuit-badge {
display: inline-block;
background: rgba(255,255,255,0.2);
backdrop-filter: blur(10px);
padding: $s-sm $s-lg;
border-radius: 20px;
margin-top: $s-md;
font-weight: 600;
border: 1px solid rgba(255,255,255,0.3);
} }
} }
// Intro card
.intro-card {
background: white;
border-radius: $r-lg;
padding: $s-lg;
margin-bottom: $s-xl;
box-shadow: 0 4px 16px rgba(0,0,0,0.1);
display: flex;
gap: $s-lg;
align-items: flex-start;
.intro-icon {
font-size: 2rem;
flex-shrink: 0;
}
.intro-content {
h3 {
margin: 0 0 $s-sm 0;
color: #667eea;
font-size: 1.2rem;
}
p {
margin: 0;
color: #64748b;
line-height: 1.6;
}
}
}
// Sezioni
.reg-section { .reg-section {
margin-bottom: 2.5rem; background: white;
border-radius: $r-lg;
padding: $s-xl;
margin-bottom: $s-lg;
box-shadow: 0 4px 16px rgba(0,0,0,0.08);
position: relative;
transition: all 0.3s ease;
&:hover {
box-shadow: 0 6px 20px rgba(0,0,0,0.12);
transform: translateY(-2px);
}
.section-number {
position: absolute;
top: $s-lg;
right: $s-lg;
font-size: 3rem;
font-weight: 900;
color: rgba(102, 126, 234, 0.1);
line-height: 1;
}
.section-header {
display: flex;
align-items: center;
gap: $s-md;
margin-bottom: $s-lg;
padding-bottom: $s-md;
border-bottom: 3px solid #f1f5f9;
.section-icon {
font-size: 2rem;
flex-shrink: 0;
}
.section-title { .section-title {
margin: 0;
font-size: 1.5rem; font-size: 1.5rem;
font-weight: 600; font-weight: 700;
color: #556b2f; color: #1e293b;
margin-bottom: 1rem; }
padding-bottom: 0.5rem;
border-bottom: 1px solid rgba(107, 142, 35, 0.3);
} }
.section-body {
.section-content { .section-content {
font-size: 1rem; line-height: 1.8;
line-height: 1.7; color: #475569;
color: #333; margin-bottom: $s-lg;
text-align: justify;
margin-bottom: 1rem;
strong { strong {
color: #6b8e23; color: #667eea;
font-weight: 600; font-weight: 600;
} }
} }
}
}
.section-list { // Info boxes
list-style: none; .info-box {
padding-left: 0; display: flex;
gap: $s-md;
padding: $s-lg;
border-radius: $r-md;
margin: $s-lg 0;
align-items: flex-start;
scssli { &.primary {
padding: 0.75rem 0 0.75rem 3rem; // aumenta da 2rem a 3rem background: linear-gradient(135deg, rgba(102, 126, 234, 0.1) 0%, rgba(118, 75, 162, 0.1) 100%);
position: relative; border-left: 4px solid #667eea;
line-height: 1.6; }
&:before { &.success {
content: ""; background: linear-gradient(135deg, rgba(16, 185, 129, 0.1) 0%, rgba(5, 150, 105, 0.1) 100%);
position: absolute; border-left: 4px solid #10b981;
left: 1rem; // aumenta da 0.5rem a 1rem }
color: #6b8e23;
&.warning {
background: linear-gradient(135deg, rgba(245, 158, 11, 0.1) 0%, rgba(217, 119, 6, 0.1) 100%);
border-left: 4px solid #f59e0b;
}
&.neutral {
background: rgba(100, 116, 139, 0.08);
border-left: 4px solid #64748b;
}
.info-icon {
font-size: 1.5rem; font-size: 1.5rem;
line-height: 1.6; flex-shrink: 0;
}
}
} }
.highlight-box { .info-text {
background: rgba(107, 142, 35, 0.08); flex: 1;
border-left: 4px solid #6b8e23; line-height: 1.6;
padding: 1rem 1.5rem; color: #475569;
margin: 1rem 0;
border-radius: 4px;
strong { strong {
display: block; display: block;
margin-bottom: 0.5rem; margin-bottom: $s-xs;
color: #1e293b;
} }
} }
} }
.reg-footer { // Timeline
margin-top: 3rem; .timeline {
padding-top: 1.5rem; margin: $s-xl 0;
border-top: 1px solid rgba(107, 142, 35, 0.3); position: relative;
font-size: 0.95rem;
color: #666; &::before {
content: '';
position: absolute;
left: 20px;
top: 0;
bottom: 0;
width: 2px;
background: linear-gradient(180deg, #667eea 0%, #764ba2 100%);
} }
.timeline-item {
display: flex;
gap: $s-lg;
margin-bottom: $s-lg;
position: relative;
.timeline-dot {
width: 40px;
height: 40px;
border-radius: 50%;
background: $gradient-primary;
color: white;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
flex-shrink: 0;
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
position: relative;
z-index: 1;
}
.timeline-content {
flex: 1;
background: #f8fafc;
padding: $s-md;
border-radius: $r-md;
border: 1px solid #e2e8f0;
strong {
display: block;
color: #667eea;
margin-bottom: $s-xs;
font-size: 0.95rem;
}
p {
margin: 0;
color: #64748b;
font-size: 0.9rem;
line-height: 1.5;
}
}
}
}
// Principle box
.principle-box {
background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);
border: 2px solid #0ea5e9;
border-radius: $r-lg;
padding: $s-xl;
margin: $s-xl 0;
text-align: center;
.principle-icon {
font-size: 3rem;
margin-bottom: $s-md;
}
h4 {
margin: 0 0 $s-sm 0;
color: #0369a1;
font-size: 1.2rem;
}
p {
margin: 0 0 $s-lg 0;
color: #0c4a6e;
}
.balance-visual {
display: flex;
justify-content: center;
align-items: center;
gap: $s-xl;
margin-top: $s-lg;
.balance-side {
display: flex;
flex-direction: column;
gap: $s-xs;
&.positive .balance-value {
color: #10b981;
font-size: 3rem;
}
&.negative .balance-value {
color: #ef4444;
font-size: 3rem;
}
.balance-label {
font-size: 0.85rem;
font-weight: 600;
color: #64748b;
}
.balance-value {
font-weight: 900;
}
}
.balance-equal {
font-size: 2rem;
font-weight: 700;
color: #0369a1;
}
}
}
// Cases grid
.cases-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: $s-md;
margin: $s-lg 0;
.case-card {
background: #f8fafc;
border: 2px solid #e2e8f0;
border-radius: $r-md;
padding: $s-lg;
text-align: center;
transition: all 0.3s ease;
&:hover {
border-color: #667eea;
transform: translateY(-4px);
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.2);
}
.case-icon {
font-size: 2rem;
margin-bottom: $s-sm;
}
h5 {
margin: 0 0 $s-xs 0;
color: #1e293b;
font-size: 1rem;
}
p {
margin: 0;
font-size: 0.85rem;
color: #64748b;
line-height: 1.4;
}
}
}
// Highlight box
.highlight-box {
display: flex;
gap: $s-md;
padding: $s-lg;
border-radius: $r-md;
margin: $s-lg 0;
align-items: flex-start;
&.warning {
background: linear-gradient(135deg, rgba(245, 158, 11, 0.15) 0%, rgba(217, 119, 6, 0.15) 100%);
border: 2px solid #f59e0b;
}
.highlight-icon {
font-size: 1.5rem;
flex-shrink: 0;
}
.highlight-content {
flex: 1;
line-height: 1.6;
color: #78350f;
strong {
display: block;
margin-bottom: $s-xs;
color: #92400e;
}
}
}
// Role box
.role-box {
background: linear-gradient(135deg, #fefce8 0%, #fef3c7 100%);
border: 2px solid #fbbf24;
border-radius: $r-lg;
padding: $s-lg;
margin: $s-lg 0;
.role-header {
display: flex;
align-items: center;
gap: $s-md;
margin-bottom: $s-lg;
padding-bottom: $s-md;
border-bottom: 2px solid #fde68a;
.role-icon {
font-size: 1.5rem;
}
h4 {
margin: 0;
color: #92400e;
font-size: 1.1rem;
}
}
.tasks-list {
display: flex;
flex-direction: column;
gap: $s-sm;
.task-item {
display: flex;
align-items: center;
gap: $s-md;
padding: $s-sm;
background: rgba(255,255,255,0.5);
border-radius: $r-sm;
transition: all 0.3s ease;
&:hover {
background: rgba(255,255,255,0.8);
transform: translateX(4px);
}
.task-bullet {
color: #10b981;
font-weight: 700;
font-size: 1.1rem;
flex-shrink: 0;
}
span:last-child {
color: #78350f;
line-height: 1.4;
}
}
}
}
// Platform card
.platform-card {
background: linear-gradient(135deg, rgba(102, 126, 234, 0.05) 0%, rgba(118, 75, 162, 0.05) 100%);
border: 2px solid #667eea;
border-radius: $r-lg;
padding: $s-xl;
margin: $s-lg 0;
text-align: center;
.platform-logo {
margin-bottom: $s-lg;
.logo-text {
font-size: 2.5rem;
font-weight: 900;
background: $gradient-primary;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.logo-domain {
font-size: 1.5rem;
color: #667eea;
font-weight: 600;
}
}
p {
color: #475569;
margin-bottom: $s-lg;
line-height: 1.6;
}
.platform-features {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: $s-md;
.feature {
display: flex;
flex-direction: column;
align-items: center;
gap: $s-xs;
padding: $s-md;
background: white;
border-radius: $r-md;
font-size: 0.85rem;
color: #64748b;
.feature-icon {
font-size: 1.5rem;
}
}
}
}
// Accounts grid
.accounts-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: $s-lg;
margin: $s-lg 0;
.account-type {
padding: $s-xl;
border-radius: $r-lg;
text-align: center;
border: 3px solid;
&.positive {
background: linear-gradient(135deg, rgba(16, 185, 129, 0.1) 0%, rgba(5, 150, 105, 0.1) 100%);
border-color: #10b981;
}
&.negative {
background: linear-gradient(135deg, rgba(59, 130, 246, 0.1) 0%, rgba(37, 99, 235, 0.1) 100%);
border-color: #3b82f6;
}
.account-icon {
font-size: 2.5rem;
margin-bottom: $s-md;
}
h5 {
margin: 0 0 $s-sm 0;
color: #1e293b;
font-size: 1.1rem;
}
p {
margin: 0;
color: #64748b;
font-size: 0.9rem;
line-height: 1.5;
}
}
}
// Reference card
.reference-card {
background: linear-gradient(135deg, #ede9fe 0%, #ddd6fe 100%);
border: 2px solid #8b5cf6;
border-radius: $r-lg;
padding: $s-xl;
display: flex;
gap: $s-lg;
align-items: flex-start;
.reference-icon {
font-size: 2rem;
flex-shrink: 0;
}
.reference-content {
flex: 1;
h4 {
margin: 0 0 $s-sm 0;
color: #6b21a8;
font-size: 1.2rem;
}
p {
margin: 0 0 $s-md 0;
color: #7c3aed;
}
.reference-link {
display: inline-flex;
align-items: center;
gap: $s-sm;
padding: $s-md $s-lg;
background: white;
color: #8b5cf6;
text-decoration: none;
border-radius: $r-md;
font-weight: 600;
transition: all 0.3s ease;
box-shadow: 0 2px 8px rgba(139, 92, 246, 0.2);
&:hover {
transform: translateY(-2px);
box-shadow: 0 4px 16px rgba(139, 92, 246, 0.3);
}
.link-arrow {
font-size: 1.2rem;
transition: transform 0.3s ease;
}
&:hover .link-arrow {
transform: translateX(4px);
}
}
}
}
// Footer
.reg-footer {
background: $gradient-primary;
color: white;
padding: $s-xl;
border-radius: $r-lg;
text-align: center;
margin-top: $s-xl;
box-shadow: 0 8px 24px rgba(102, 126, 234, 0.3);
.footer-icon {
font-size: 2rem;
margin-bottom: $s-md;
}
p {
margin: 0;
font-size: 1.1rem;
font-weight: 600;
opacity: 0.95;
} }
} }
// Responsive // Responsive
@media (max-width: 768px) { @media (max-width: 600px) {
.regulation-container :deep(.regulation-content) { .regulation-content {
padding: $s-sm;
}
.reg-header h1 { .reg-header h1 {
font-size: 1.5rem; font-size: 1.5rem;
} }
.reg-section { .reg-section {
.section-title { padding: $s-lg;
font-size: 1.25rem;
.section-number {
font-size: 2rem;
top: $s-sm;
right: $s-sm;
} }
.section-content { .section-header .section-title {
text-align: left; font-size: 1.2rem;
}
} }
.section-list li { .timeline::before {
padding-left: 1.5rem; left: 15px;
} }
}
.timeline-item .timeline-dot {
width: 30px;
height: 30px;
font-size: 0.85rem;
}
.cases-grid {
grid-template-columns: 1fr;
}
.balance-visual {
flex-direction: column;
gap: $s-md !important;
} }
} }

View File

@@ -72,7 +72,10 @@ export default defineComponent({
const circuit = ref(<IMyCircuit | ICircuit | null>null); const circuit = ref(<IMyCircuit | ICircuit | null>null);
const account = computed(() => { const account = computed(() => {
if (groupnameSel.value) { if (groupnameSel.value) {
return userStore.getAccountGroupByCircuitId(circuit.value._id, groupnameSel.value); return userStore.getAccountGroupByCircuitId(
circuit.value._id,
groupnameSel.value
);
} else { } else {
return circuit.value ? userStore.getAccountByCircuitId(circuit.value._id) : null; return circuit.value ? userStore.getAccountByCircuitId(circuit.value._id) : null;
} }
@@ -164,133 +167,9 @@ export default defineComponent({
} }
// Trasforma il vecchio HTML in uno moderno // Trasforma il vecchio HTML in uno moderno
return transformRegulationHTML(name); return circuitStore.transformRegulationHTML(name);
} }
function transformRegulationHTML(circuitName: string): string {
return `
<div class="regulation-content">
<div class="reg-header">
<h1>${circuitName}</h1>
</div>
<div class="reg-section">
<h2 class="section-title">Costituzione e scopo Comunitario</h2>
<p class="section-content">
La Comunità Territoriale "RIS ${circuitName}" spontanea (da ora "la Comunità") costituisce un circuito di scambio
tra i partecipanti, ciascuno dei quali dovrà indicare i beni e servizi che offre alla Comunità stessa in RIS.
Il circuito funziona come dettagliato qui di seguito.
</p>
</div>
<div class="reg-section">
<h2 class="section-title">Circuito di scambio</h2>
<p class="section-content">
La Comunità ha avviato un sistema di scambio di beni e servizi tra utenti, in cui il valore delle transazioni
si misura utilizzando una unità di conto denominata <strong>RIS</strong>, convenzionalmente considerata pari a 1 euro.
</p>
<p class="section-content">
Gli scambi tra utenti avvengono liberamente, dopo aver concordato il valore tra le parti.
</p>
<p class="section-content">
Tutti gli utenti partecipanti al circuito iniziano con un conto vuoto, cioè pari a 0 RIS.
</p>
<p class="section-content">
L'utente pagante che ha un conto RIS vuoto o con quantità non sufficiente a perfezionare lo scambio può utilizzare
la Fiducia Concessa andando in debito fino al limite massimo a lui permesso, denominato "fiducia concessa".
Un equivalente valore in RIS a credito sarà conseguentemente iscritto sul conto del ricevente.
</p>
<p class="section-content">
In ogni momento nel circuito la somma di tutte le esposizioni in positivo saranno perfettamente bilanciate dalla
somma di tutte le esposizioni in negativo, dimostrando così che ciascun detentore di un saldo positivo abbia
garantita la solvibilità del proprio credito. Ciascun detentore di un saldo negativo concorda che la sua
esposizione funge da garanzia della quantità di RIS equivalenti circolanti nel circuito e che può essere chiamato
a regolarizzare con equivalente pagamento in euro *, entro un lasso di tempo ragionevolmente adeguato, nel caso in cui:
</p>
<ul class="section-list">
<li>la Comunità, in accordo con il Gruppo Tecnico, deliberi il rientro di alcune posizioni con decisione motivata;</li>
<li>l'utente decida di uscire per motivi personali;</li>
<li>il circuito chiuda, per decisione deliberata o per motivi di forza maggiore.</li>
</ul>
<div class="highlight-box">
<strong>* Nota importante:</strong>
La regolarizzazione può avvenire con equivalente valore in beni e servizi, oppure, in ultima istanza,
con pagamento in euro.
</div>
</div>
<div class="reg-section">
<h2 class="section-title">Il Gruppo Tecnico</h2>
<p class="section-content">
La Comunità costituisce il Gruppo Tecnico di Gestione degli Scambi (da ora "Gruppo Tecnico"), partecipante esso
stesso agli scambi tramite uno o più delegati autorizzati. Il Gruppo Tecnico è l'organo che supporta la comunità
nella gestione degli scambi e quant'altro sia necessario all'ottimale svolgimento dello scopo comunitario.
</p>
<p class="section-content"><strong>In particolare ha il compito di:</strong></p>
<ul class="section-list">
<li>approvare le richieste di adesione, dopo aver verificato che il richiedente faccia parte della Comunità;</li>
<li>tenere aggiornato l'elenco dei partecipanti al circuito;</li>
<li>stabilire la soglia di massimo scoperto e massimo attivo, collettivo e individuale;</li>
<li>valutare la sostenibilità di eventuali stanziamenti di finanziamenti;</li>
<li>introdurre tassazioni sulle transazioni o una tantum;</li>
<li>adottare o meno il Deperimento dei conti inoperosi (riferimento sistema Si.Cre.Na.C.C.) e modularne i parametri di applicazione;</li>
<li>deliberare la chiusura del circuito;</li>
<li>deliberare la destinazione delle riserve comunitarie depositate sui Conti della Comunità per: finanziare opere varie;
corrispondere salari o agire con interventi di sussistenza a partecipanti in stato di necessità; rilevare lo stato
debitorio all'interno del circuito di utenti in difficoltà economica, irreperibili o venuti a mancare; attuare
interventi ritenuti utili, necessari o meritevoli;</li>
<li>redigere, emendare ed adeguare alle eventuali necessità il presente Regolamento.</li>
</ul>
<p class="section-content">
Il Gruppo Tecnico è formato da candidati scelti dalla Comunità sulla base delle competenze e dovrà sottoporre
all'approvazione della Comunità stessa resoconti periodici delle sue attività.
</p>
</div>
<div class="reg-section">
<h2 class="section-title">Sistemi di contabilizzazione</h2>
<p class="section-content">
I conti RIS e le transazioni conseguenti agli scambi sono registrati attraverso la piattaforma <strong>riso.app</strong>,
dove i partecipanti comunicano i beni e servizi proposti alla Comunità. Tutti i partecipanti possono visualizzare
in ogni momento la situazione del circuito.
</p>
<p class="section-content">
La Comunità, in accordo con il Gruppo Tecnico, può decidere di utilizzare altri strumenti di contabilizzazione,
che dovranno, in ogni caso, permettere l'inserimento delle singole transazioni effettuate tra gli utenti e la
visualizzazione da parte di ogni partecipante. Saranno dunque inseriti nel nuovo strumento tutti gli utenti
partecipanti con l'ultimo saldo rilevato al momento della transizione.
</p>
</div>
<div class="reg-section">
<h2 class="section-title">Conto Comunitario</h2>
<p class="section-content">
La Comunità può decidere di aprire uno o più Conti all'interno del circuito, che saranno gestiti dal Gruppo Tecnico,
allo scopo di interagire con i conti degli altri utenti e quindi recepire eventuali proventi da tassazioni o
finanziare iniziative autorizzate. I Conti Comunitari sono i conti della Comunità.
</p>
<p class="section-content">
Un conto a credito rappresenta una riserva monetaria da re-immettere nel circuito per finanziare opere o attività
di beneficio comune; un conto a debito rappresenta il "debito pubblico" della Comunità.
</p>
<p class="section-content">
I conti comunitari sono sottoposti alle stesse regole degli altri conti (conto vuoto, possibilità di scoperto di conto, ecc.).
Ogni operazione straordinaria viene deliberata appositamente, mentre l'ordinaria amministrazione può essere autorizzata
in via continuativa.
</p>
</div>
<div class="reg-section">
<h2 class="section-title">Riferimenti</h2>
<p class="section-content">
Per una migliore comprensione dei meccanismi che sottostanno allo scambio nel qui presentato circuito, si rimanda
alla lettura del testo disponibile gratuitamente su <strong>https://sicrenacc.info</strong>, da cui è stata tratta
libera ispirazione e condivisione dei principi generali del Sistema di Credito Naturale.
</p>
</div>
</div>
`;
}
onMounted(mounted); onMounted(mounted);
return { return {

View File

@@ -460,3 +460,9 @@ h1 {
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.scroll-input {
:deep(textarea) {
max-height: 400px;
overflow-y: auto;
}
}

View File

@@ -931,7 +931,6 @@
@update:model-value="modifElem" @update:model-value="modifElem"
> >
</q-toggle> </q-toggle>
<CMyEditor <CMyEditor
v-model:value="myel.containerHtml" v-model:value="myel.containerHtml"
title="" title=""
@@ -941,9 +940,35 @@
@update:value="modifElem" @update:value="modifElem"
@showandsave="saveElem" @showandsave="saveElem"
:start-in-code-mode="myel.parambool2" :start-in-code-mode="myel.parambool2"
:custom-styles="myel.container2"
> >
</CMyEditor> </CMyEditor>
<q-card-section>
<q-input
label="Stile Pagina <style>...</style>:"
v-model="myel.container2"
filled
style="max-height: 200px"
dense
class="scroll-input"
v-on:keyup.enter="saveElem"
@update:model-value="modifElem"
/>
</q-card-section>
<q-card-section>
<q-input
label="Script:"
v-model="myel.container3"
autogrow
filled
dense
style="max-height: 200px"
class="scroll-input"
v-on:keyup.enter="saveElem"
@update:model-value="modifElem"
/>
</q-card-section>
</div> </div>
</div> </div>
<div v-else-if="myel.type === shared_consts.ELEMTYPE.IMAGE"> <div v-else-if="myel.type === shared_consts.ELEMTYPE.IMAGE">

View File

@@ -2,3 +2,36 @@
display: flex; display: flex;
flex: 1; flex: 1;
} }
// Wrapper per stili custom dell'editor
.cmyeditor.custom-styled {
// Gli stili verranno iniettati dinamicamente
:deep(.q-editor__content) {
// Override di default se necessario
transition: all 0.3s ease;
}
}
// Stili specifici per contenuto custom
[data-custom-editor] {
:deep(.q-editor__content) {
// Base styles che possono essere override
line-height: 1.6;
}
}
.cmyeditor-wrapper.has-custom-styles {
:deep(.q-editor__content) {
// Force override di tutti gli stili di default
all: unset;
display: block;
width: 100%;
min-height: 10rem;
padding: 12px;
outline: none;
white-space: pre-wrap;
word-wrap: break-word;
}
}

View File

@@ -1,7 +1,7 @@
import { tools } from '@tools'; import { tools } from '@tools';
import { CTitleBanner } from '../CTitleBanner'; import { CTitleBanner } from '../CTitleBanner';
import { defineComponent, onMounted, ref, toRef, watch } from 'vue'; import { defineComponent, onMounted, onUnmounted, ref, toRef, watch } from 'vue';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
@@ -24,6 +24,11 @@ export default defineComponent({
required: false, required: false,
default: '', default: '',
}, },
customStyles: {
type: String,
required: false,
default: '',
},
showButtons: { showButtons: {
type: Boolean, type: Boolean,
required: false, required: false,
@@ -62,6 +67,21 @@ export default defineComponent({
const myvalue = ref(''); const myvalue = ref('');
const mycolor = ref(''); const mycolor = ref('');
const editorId = ref(
`editor-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
);
const styleElement = ref<HTMLStyleElement | null>(null);
// Watch per applicare stili personalizzati
watch(
() => props.customStyles,
(newStyles) => {
if (newStyles && editorRef.value) {
applyCustomStyles(newStyles);
}
}
);
const myfonts = ref({ const myfonts = ref({
arial: 'Arial', arial: 'Arial',
arial_black: 'Arial Black', arial_black: 'Arial Black',
@@ -127,6 +147,45 @@ export default defineComponent({
} }
); );
// Funzione per applicare stili custom
// ALTERNATIVA: Se vuoi applicare gli stili solo al contenuto dell'editor (più sicuro)
function applyCustomStyles(styles: string) {
// console.log('Applying custom styles:', styles);
// Rimuovi style precedente
if (styleElement.value && styleElement.value.parentNode) {
styleElement.value.parentNode.removeChild(styleElement.value);
}
if (!styles) return;
// Crea style element nel HEAD
styleElement.value = document.createElement('style');
styleElement.value.setAttribute('type', 'text/css');
styleElement.value.setAttribute('data-editor-styles', editorId.value);
// CSS con selettori super specifici usando l'ID univoco
const css = `
[data-editor-id="${editorId.value}"] .q-editor__content {
${styles}
}
[data-editor-id="${editorId.value}"] .q-editor__content * {
${styles}
}
/* Selettori per classi custom nel contenuto */
[data-editor-id="${editorId.value}"] .q-editor__content .prova1 {
color: red !important;
}
`;
styleElement.value.textContent = css;
document.head.appendChild(styleElement.value);
// console.log('Style injected:', css);
}
function getTextLength(html: string) { function getTextLength(html: string) {
// Crea un elemento temporaneo per convertire HTML in testo // Crea un elemento temporaneo per convertire HTML in testo
const div = document.createElement('div'); const div = document.createElement('div');
@@ -197,6 +256,12 @@ export default defineComponent({
characterCount.value = getTextLength(myvalue.value); characterCount.value = getTextLength(myvalue.value);
if (props.customStyles) {
setTimeout(() => {
applyCustomStyles(props.customStyles);
}, 300);
}
if (props.startInCodeMode) { if (props.startInCodeMode) {
// Attiva modalità codice di default // Attiva modalità codice di default
setTimeout(() => { setTimeout(() => {
@@ -231,6 +296,13 @@ export default defineComponent({
onMounted(mounted); onMounted(mounted);
// Cleanup quando componente viene distrutto
onUnmounted(() => {
if (styleElement.value && styleElement.value.parentNode) {
styleElement.value.parentNode.removeChild(styleElement.value);
}
});
return { return {
myfonts, myfonts,
toolbarcomp, toolbarcomp,
@@ -248,6 +320,8 @@ export default defineComponent({
showtools, showtools,
characterCount, characterCount,
t, t,
applyCustomStyles,
editorId,
}; };
}, },
}); });

View File

@@ -15,9 +15,19 @@
v-close-popup v-close-popup
></q-btn> ></q-btn>
</q-toolbar> </q-toolbar>
<q-card-section class="inset-shadow" style="padding: 4px !important"> <q-card-section
<CTitleBanner v-if="title" :title="title"></CTitleBanner> class="inset-shadow"
<form autocapitalize="off" autocomplete="off" spellcheck="false"> style="padding: 4px !important"
>
<CTitleBanner
v-if="title"
:title="title"
></CTitleBanner>
<form
autocapitalize="off"
autocomplete="off"
spellcheck="false"
>
<q-toggle <q-toggle
v-if="!hideTools" v-if="!hideTools"
v-model="showtools" v-model="showtools"
@@ -25,34 +35,53 @@
@click="tools.setCookie('showtools', showtools ? '1' : '0')" @click="tools.setCookie('showtools', showtools ? '1' : '0')"
></q-toggle> ></q-toggle>
<br /> <br />
<q-btn v-if="showtools && !hideTools" rounded size="sm" color="primary"> <q-btn
<q-icon name="colorize" class="cursor-pointer"> v-if="showtools && !hideTools"
rounded
size="sm"
color="primary"
>
<q-icon
name="colorize"
class="cursor-pointer"
>
<q-popup-proxy> <q-popup-proxy>
<q-color v-model="mycolor" @change="setcolor"></q-color> <q-color
v-model="mycolor"
@change="setcolor"
></q-color>
</q-popup-proxy> </q-popup-proxy>
</q-icon> </q-icon>
</q-btn> </q-btn>
<div
class="cmyeditor-wrapper"
:data-editor-id="editorId"
>
<q-editor <q-editor
ref="editorRef" ref="editorRef"
content-class="styled-content"
toolbar-text-color="white"
toolbar-toggle-color="yellow-8"
toolbar-bg="primary"
:readonly="!canModify"
:toolbar="showtools && !hideTools ? toolbarcomp : []"
:fonts="myfonts"
@update:model-value="changeval"
@paste="onPaste"
@keyup.esc.stop="visueditor = false"
@keyup.enter.stop
v-model="myvalue" v-model="myvalue"
:class="['cmyeditor', myclass]"
@update:model-value="changeval"
@paste.prevent="onPaste"
:min-height="'10rem'"
:max-height="'70vh'"
:toolbar="toolbarcomp"
:fonts="myfonts"
/>
</div>
<div
v-if="maxlength"
class="text-gray text-italic"
> >
</q-editor> Caratteri: {{ characterCount }} / {{ maxlength }}
<div v-if="maxlength" class="text-gray text-italic">Caratteri: {{ characterCount }} / {{ maxlength }}</div> </div>
</form> </form>
</q-card-section> </q-card-section>
<q-card-actions v-if="showButtons" align="center"> <q-card-actions
v-if="showButtons"
align="center"
>
<q-btn <q-btn
v-if="canModify" v-if="canModify"
:label="$t('dialog.ok')" :label="$t('dialog.ok')"
@@ -78,8 +107,7 @@
</div> </div>
</template> </template>
<script lang="ts" src="./CMyEditor.ts"> <script lang="ts" src="./CMyEditor.ts"></script>
</script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import './CMyEditor.scss'; @import './CMyEditor.scss';

View File

@@ -8,6 +8,7 @@ import {
watch, watch,
nextTick, nextTick,
onUnmounted, onUnmounted,
onUpdated,
} from 'vue'; } from 'vue';
import type { IOptCatalogo, ICoordGPS, IMyElem, ISocial } from '@src/model'; import type { IOptCatalogo, ICoordGPS, IMyElem, ISocial } from '@src/model';
@@ -206,6 +207,8 @@ export default defineComponent({
const enableAdd = ref(true); const enableAdd = ref(true);
const visushare = ref(false); const visushare = ref(false);
const htmlContainer = ref(null);
const tabcatalogo = ref('griglia'); const tabcatalogo = ref('griglia');
const enablePwa = computed(() => globalStore.site.confpages?.enablePwa); const enablePwa = computed(() => globalStore.site.confpages?.enablePwa);
@@ -338,6 +341,10 @@ export default defineComponent({
canShowVersion.value = true; canShowVersion.value = true;
}, 60000); }, 60000);
setTimeout(() => {
executeScript();
}, 500);
if (props.myelem) newtype.value = props.myelem.type; if (props.myelem) newtype.value = props.myelem.type;
nextTick(() => { nextTick(() => {
@@ -442,6 +449,23 @@ export default defineComponent({
console.log('Invio via Telegram...'); console.log('Invio via Telegram...');
}; };
const executeScript = () => {
if (myel.value.container3 && htmlContainer.value) {
try {
console.log('Script da eseguire:', myel.value.container3);
const fn = new Function('element', myel.value.container3);
fn(htmlContainer.value);
} catch (e) {
console.error('Script execution error:', e);
}
}
};
watch(() => myel.value.container3, executeScript, { flush: 'post' });
onUpdated(executeScript);
return { return {
onInvitoInviato, onInvitoInviato,
onTelegramClick, onTelegramClick,
@@ -494,6 +518,7 @@ export default defineComponent({
enablePwa, enablePwa,
mostraInviti, mostraInviti,
nascondiBottone, nascondiBottone,
htmlContainer,
}; };
}, },
}); });

View File

@@ -153,6 +153,7 @@
> >
<div> <div>
<div <div
ref="htmlContainer"
:class=" :class="
myel.class + myel.class +
(editOn ? ` clEdit` : ``) + (editOn ? ` clEdit` : ``) +
@@ -161,7 +162,13 @@
tools.getClassAnim(myel.anim) tools.getClassAnim(myel.anim)
" "
@click="clickOnElem" @click="clickOnElem"
v-html="tools.convertHTMLForElement(myel.containerHtml, myel.parambool2)" v-html="
tools.convertHTMLForElement(
myel.containerHtml,
myel.parambool2,
myel.container2
)
"
></div> ></div>
</div> </div>
</div> </div>

View File

@@ -11,6 +11,8 @@ $negative-color: #c10015;
$info-color: #31ccec; $info-color: #31ccec;
$warning-color: #f2c037; $warning-color: #f2c037;
@use 'sass:color';
$grey-text: #555; $grey-text: #555;
$grey-light: #999; $grey-light: #999;
@@ -24,8 +26,8 @@ $shadow-lg: 0 4px 16px rgba(0, 0, 0, 0.15);
$mobile-breakpoint: 768px; $mobile-breakpoint: 768px;
// Gradiente speciale per gruppi // Gradiente speciale per gruppi
$gradient-group-primary: linear-gradient(135deg, $secondary-color 0%, lighten($secondary-color, 15%) 100%); $gradient-group-primary: linear-gradient(135deg, $secondary-color 0%, color.adjust($secondary-color, $lightness: 15%) 100%);
$gradient-group-hover: linear-gradient(135deg, darken($secondary-color, 5%) 0%, $secondary-color 100%); $gradient-group-hover: linear-gradient(135deg, color.adjust($secondary-color, $lightness: -5%) 0%, $secondary-color 100%);
// ======================================== // ========================================
// Q-ITEM CONTAINER (con gradiente group-style) // Q-ITEM CONTAINER (con gradiente group-style)
@@ -250,11 +252,11 @@ $gradient-group-hover: linear-gradient(135deg, darken($secondary-color, 5%) 0%,
// Send coins button (special gradient) // Send coins button (special gradient)
&[color='green'] { &[color='green'] {
background: linear-gradient(135deg, $positive-color, lighten($positive-color, 10%)) !important; background: linear-gradient(135deg, $positive-color, color.adjust($positive-color, $lightness: 10%)) !important;
box-shadow: 0 2px 8px rgba($positive-color, 0.3); box-shadow: 0 2px 8px rgba($positive-color, 0.3);
&:hover { &:hover {
background: linear-gradient(135deg, darken($positive-color, 5%), $positive-color) !important; background: linear-gradient(135deg, color.adjust($positive-color, $lightness: -5%), $positive-color) !important;
box-shadow: 0 4px 12px rgba($positive-color, 0.4); box-shadow: 0 4px 12px rgba($positive-color, 0.4);
transform: translateY(-2px); transform: translateY(-2px);
} }
@@ -513,7 +515,7 @@ $gradient-group-hover: linear-gradient(135deg, darken($secondary-color, 5%) 0%,
border-radius: 12px; border-radius: 12px;
background: linear-gradient(135deg, rgba($info-color, 0.1), rgba($info-color, 0.05)); background: linear-gradient(135deg, rgba($info-color, 0.1), rgba($info-color, 0.05));
border: 1px solid rgba($info-color, 0.3); border: 1px solid rgba($info-color, 0.3);
color: darken($info-color, 15%); color: color.adjust($info-color, $lightness: 15%);
font-size: 0.75rem; font-size: 0.75rem;
font-weight: 600; font-weight: 600;
margin-left: 8px; margin-left: 8px;

View File

@@ -1,3 +1,5 @@
@use "sass:color";
// ======================================== // ========================================
// VARIABILI // VARIABILI
// ======================================== // ========================================
@@ -31,14 +33,14 @@ $mobile-breakpoint: 768px;
&.is-even { &.is-even {
.modern-rec-card { .modern-rec-card {
background: linear-gradient(135deg, rgba(49, 154, 239, 0.08) 0%, rgba(25, 118, 210, 0.03) 100%); background: linear-gradient(135deg, rgba(49, 153, 239, 0.249) 0%, rgba(25, 118, 210, 0.03) 100%);
border-color: rgba(66, 165, 245, 0.12); border-color: rgba(66, 165, 245, 0.12);
} }
} }
&.is-odd { &.is-odd {
.modern-rec-card { .modern-rec-card {
background: linear-gradient(135deg, rgba(38, 197, 218, 0.08) 0%, rgba(0, 150, 136, 0.03) 100%); background: linear-gradient(135deg, rgba(38, 197, 218, 0.344) 0%, rgba(0, 150, 136, 0.03) 100%);
border-color: rgba(38, 198, 218, 0.12); border-color: rgba(38, 198, 218, 0.12);
} }
} }
@@ -280,26 +282,29 @@ $mobile-breakpoint: 768px;
} }
.tag-chip { .tag-chip {
height: 19px; height: 20px;
font-size: 0.6875rem; font-size: 0.9rem;
padding: 0 5px; padding: 0 px;
border-radius: 4px; border-radius: 4px;
box-shadow: none; box-shadow: none;
font-weight: 500; font-weight: 500;
&.subsector { // Categoria principale - più scura e intensa
background: linear-gradient(135deg, $positive-color, #26a69a); &.sector {
background: linear-gradient(135deg, $primary-color, #1976d2);
color: white; color: white;
} }
&.sector { // Sottocategoria - stessa base ma più chiara
background: linear-gradient(135deg, $primary-color, #42a5f5); &.subsector {
background: linear-gradient(135deg, color.adjust($primary-color, $lightness: 10%), #42a5f5);
color: white; color: white;
opacity: 0.9; // Opzionale: leggera trasparenza
} }
@media (max-width: $mobile-breakpoint) { @media (max-width: $mobile-breakpoint) {
height: 17px; height: 19px;
font-size: 0.625rem; font-size: 0.8rem;
padding: 0 4px; padding: 0 4px;
} }
@@ -506,3 +511,20 @@ $mobile-breakpoint: 768px;
margin: 2px 0; margin: 2px 0;
} }
} }
.category-hierarchy {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 2px;
.hierarchy-arrow {
opacity: 0.5;
font-size: 16px;
}
@media (max-width: $mobile-breakpoint) {
gap: 0px;
}
}

View File

@@ -60,6 +60,12 @@ export default defineComponent({
const disabilita = computed(() => { const disabilita = computed(() => {
return props.table === shared_consts.TABLES_MYBACHECAS; return props.table === shared_consts.TABLES_MYBACHECAS;
}); });
const arrSubSector = computed(() => {
return tools.getArrSubSector(props.table, myrec.value)
});
const arrSector = computed(() => {
return tools.getArrSector(props.table, myrec.value)
});
watch( watch(
() => props.prop_myrec, () => props.prop_myrec,
@@ -168,6 +174,8 @@ export default defineComponent({
globalStore, globalStore,
computedWidth, computedWidth,
computedEventImageHeight, computedEventImageHeight,
arrSubSector,
arrSector,
}; };
}, },
}); });

View File

@@ -153,22 +153,47 @@
> >
<!-- Tags e Chips --> <!-- Tags e Chips -->
<q-item-label class="tags-row"> <q-item-label class="tags-row">
<div class="category-hierarchy">
<q-chip <q-chip
v-for="(rec, ind) of tools.getArrSubSector(table, myrec)" v-for="(rec, ind) of arrSector"
:key="'sub-' + ind"
dense
class="tag-chip subsector"
>
{{ rec.descr }}
</q-chip>
<q-chip
v-for="(rec, ind) of tools.getArrSector(table, myrec)"
:key="'sec-' + ind" :key="'sec-' + ind"
dense
class="tag-chip sector" class="tag-chip sector"
:style="{
background: `linear-gradient(135deg, ${rec.color}, ${rec.color})`,
}"
> >
<q-icon
v-if="rec.icon"
:name="rec.icon"
size="xs"
class="q-mr-xs"
/>
{{ rec.descr }} {{ rec.descr }}
</q-chip> </q-chip>
<q-icon
v-if="arrSubSector.length > 0"
name="chevron_right"
size="sm"
class="hierarchy-arrow"
/>
<q-chip
v-for="(rec, ind) of arrSubSector"
:key="'sub-' + ind"
class="tag-chip subsector"
:style="{
background: `linear-gradient(135deg, ${rec.color}, ${rec.color})`,
opacity: 0.8,
}"
>
<q-icon
v-if="rec.icon"
:name="rec.icon"
size="xs"
class="q-mr-xs"
/>
{{ rec.descr }}
</q-chip>
</div>
<template v-for="(recstatus, index) in myrec.idStatusSkill"> <template v-for="(recstatus, index) in myrec.idStatusSkill">
<q-badge <q-badge
v-if=" v-if="

View File

@@ -11,6 +11,8 @@ $negative-color: #c10015;
$info-color: #31ccec; $info-color: #31ccec;
$warning-color: #f2c037; $warning-color: #f2c037;
@use 'sass:color';
$grey-text: #555; $grey-text: #555;
$grey-light: #999; $grey-light: #999;
@@ -165,7 +167,8 @@ $mobile-breakpoint: 768px;
border-radius: 6px; border-radius: 6px;
background: linear-gradient(135deg, rgba($info-color, 0.1), rgba($info-color, 0.05)); background: linear-gradient(135deg, rgba($info-color, 0.1), rgba($info-color, 0.05));
border: 1px solid rgba($info-color, 0.3); border: 1px solid rgba($info-color, 0.3);
color: color-darken($info-color, 15%); color: color.adjust($info-color, $lightness: -15%);
color: ($info-color, 15%);
transition: all $transition-speed ease; transition: all $transition-speed ease;
&:hover { &:hover {

View File

@@ -15,6 +15,12 @@ $r-md: 10px;
border: 1px solid rgba(255, 255, 255, 0.2); border: 1px solid rgba(255, 255, 255, 0.2);
} }
.balance-text-saldo{
color: white;
font-size: 1rem;
font-weight: normal;
}
// Header con label e valore corrente // Header con label e valore corrente
.balance-header { .balance-header {
display: flex; display: flex;
@@ -30,7 +36,7 @@ $r-md: 10px;
} }
.balance-current { .balance-current {
font-size: 1.1rem; font-size: 1.2rem;
font-weight: 800; font-weight: 800;
&.negative { &.negative {
@@ -166,13 +172,21 @@ $r-md: 10px;
padding-top: $s-md; padding-top: $s-md;
border-top: 1px solid rgba(255, 255, 255, 0.2); border-top: 1px solid rgba(255, 255, 255, 0.2);
// Layout inline quando !small
&.inline-layout {
display: flex;
justify-content: space-around;
align-items: center;
gap: $s-lg;
}
.availability-item { .availability-item {
display: flex; display: flex;
align-items: center; align-items: center;
gap: $s-xs; gap: $s-xs;
.availability-text { .availability-text {
font-size: 1rem; font-size: 0.8rem;
opacity: 0.95; opacity: 0.95;
line-height: 1.3; line-height: 1.3;
@@ -182,6 +196,13 @@ $r-md: 10px;
} }
} }
} }
// Override per inline layout
&.inline-layout .availability-item {
.availability-text strong {
display: inline; // Inline invece di block
}
}
} }
// Responsive // Responsive
@@ -192,7 +213,7 @@ $r-md: 10px;
} }
.balance-current { .balance-current {
font-size: 1rem; font-size: 1.1rem;
} }
} }

View File

@@ -26,6 +26,10 @@ export default defineComponent({
type: String, type: String,
default: 'Range disponibile', default: 'Range disponibile',
}, },
small: {
type: Boolean,
default: false,
}
}, },
setup(props) { setup(props) {
// Range totale // Range totale

View File

@@ -3,7 +3,13 @@
<!-- Label e valore corrente --> <!-- Label e valore corrente -->
<div class="balance-header"> <div class="balance-header">
<span class="balance-label">{{ label }}</span> <span class="balance-label">{{ label }}</span>
<span :class="['balance-current', balanceClass]"> <span :class="['balance-current', balanceClass]">
<span
v-if="!small"
class="balance-text-saldo"
>Saldo:
</span>
{{ currentBalance > 0 ? '+' : '' }}{{ currentBalance }} RIS {{ currentBalance > 0 ? '+' : '' }}{{ currentBalance }} RIS
</span> </span>
</div> </div>
@@ -55,7 +61,7 @@
</div> </div>
<!-- Info disponibilità --> <!-- Info disponibilità -->
<div class="availability-info"> <div :class="['availability-info', { 'inline-layout': !small }]">
<div class="availability-item"> <div class="availability-item">
<q-icon <q-icon
name="arrow_downward" name="arrow_downward"

View File

@@ -9,6 +9,8 @@ $positive-color: #21ba45;
$negative-color: #c10015; $negative-color: #c10015;
$warning-color: #f2c037; $warning-color: #f2c037;
@use 'sass:color';
$border-radius: 16px; $border-radius: 16px;
$border-radius-sm: 12px; $border-radius-sm: 12px;
$border-radius-lg: 24px; $border-radius-lg: 24px;
@@ -1040,7 +1042,7 @@ $mobile-footer-height: 80px;
background: linear-gradient(135deg, $primary-color, $primary-dark); background: linear-gradient(135deg, $primary-color, $primary-dark);
&:hover { &:hover {
background: linear-gradient(135deg, darken($primary-color, 5%), $primary-color); background: linear-gradient(135deg, color.adjust($primary-color, $lightness: -5%), $primary-color);
box-shadow: 0 4px 16px rgba($primary-color, 0.4); box-shadow: 0 4px 16px rgba($primary-color, 0.4);
} }
} }

View File

@@ -30,7 +30,7 @@ export default defineComponent({
props: {}, props: {},
components: { components: {
CTitleBanner, CElemStat, CTitleBanner, CElemStat,
CCardState, CCardStat, CLineChart, CMyFieldRec, CTimeAgo CLineChart, CMyFieldRec, CTimeAgo
}, },
setup(props, { attrs, slots, emit }) { setup(props, { attrs, slots, emit }) {
const { t } = useI18n() const { t } = useI18n()

View File

@@ -1,336 +1,230 @@
<template> <template>
<div> <div class="status-reg-modern">
<div v-if="visustat"> <div v-if="visustat">
<CTitleBanner class="q-pa-xs" :title="$t('pages.status')" bgcolor="bg-primary" clcolor="text-white" mystyle="" <CTitleBanner class="title-banner" :title="t('pages.status')" bgcolor="bg-primary"
myclass="sfondo_gradiente_blu myshad" :canopen="true"> clcolor="text-white" mystyle="" myclass="sfondo_gradiente_blu myshad" :canopen="true">
<div class="flex flex-center flex-wrap">
<CElemStat myclass="fixed-size" :title="$t('statusreg.reg')" icon="fas fa-users"
:value_today="datastat.num_reg_today" :mytextval="datastat.activeusers + ' su ' + tools.numtostr(datastat.num_reg)
" classColor="text-red" colBack="green">
</CElemStat>
<CElemStat v-if="true" myclass="fixed-size" :title="$t('statusreg.online_today')" icon="fas fa-wifi"
:mytextval="tools.numtostr(datastat.online_today)" classColor="text-orange" colBack="yellow">
</CElemStat>
<CElemStat myclass="fixed-size" v-if="datastat.num_annunci > 0" :title="$t('statusreg.num_annunci')"
icon="fas fa-tshirt" :value_today="0" :mytextval="tools.numtostr(datastat.num_annunci)"
classColor="text-green" colBack="orange">
</CElemStat>
<CElemStat myclass="fixed-size" v-if="datastat.num_circuiti_attivi > 0"
:title="$t('statusreg.num_circuiti_attivi')" icon="fas fa-map-marker-alt" :value_today="0" :mytextval="datastat.num_circuiti_attivi + ' su ' + datastat.num_circuiti
" classColor="text-blue" colBack="red">
</CElemStat>
<CElemStat myclass="fixed-size" v-if="datastat.num_transaz_tot > 0" :title="$t('statusreg.numtransazioni')"
icon="fas fa-sync-alt" :value_today="0" :mytextval="tools.numtostr(datastat.num_transaz_tot)"
classColor="text-indigo" colBack="green">
</CElemStat>
<CElemStat myclass="fixed-size" v-if="datastat.tot_RIS_transati > 0" :title="$t('statusreg.totristransati')"
icon="img: /images/1ris_rosso_100.png" :value_today="0"
:mytextval="tools.numtostr(datastat.tot_RIS_transati) + ' RIS'" classColor="text-blueviolet"
colBack="green">
</CElemStat>
<!--<CCardState :mytext="$t('statusreg.autorizzare')" :myval="datastat.num_autorizzare" <div class="stats-wrapper">
mycolor="yellow" :myperc="(datastat.num_autorizzare / datastat.num_teleg_attivo) * 100"></CCardState> <CElemStat myclass="stat-card" :title="t('statusreg.reg')" icon="fas fa-users"
--> :value_today="datastat.num_reg_today"
:mytextval="datastat.activeusers + ' su ' + tools.numtostr(datastat.num_reg)"
classColor="text-red" colBack="green" />
<div class="q-pa-xs" v-if="datastat.num_part_accepted > 1"> <CElemStat myclass="stat-card" :title="t('statusreg.online_today')" icon="fas fa-wifi"
<CCardStat :mytext="$t('stat.accepted')" :myval="datastat.num_part_accepted"></CCardStat> :mytextval="tools.numtostr(datastat.online_today)"
classColor="text-orange" colBack="yellow" />
<CElemStat myclass="stat-card" v-if="datastat.num_annunci > 0"
:title="t('statusreg.num_annunci')" icon="fas fa-tshirt" :value_today="0"
:mytextval="tools.numtostr(datastat.num_annunci)"
classColor="text-green" colBack="orange" />
<CElemStat myclass="stat-card" v-if="datastat.num_circuiti_attivi > 0"
:title="t('statusreg.num_circuiti_attivi')" icon="fas fa-map-marker-alt"
:value_today="0" :mytextval="datastat.num_circuiti_attivi + ' su ' + datastat.num_circuiti"
classColor="text-blue" colBack="red" />
<CElemStat myclass="stat-card" v-if="datastat.num_transaz_tot > 0"
:title="t('statusreg.numtransazioni')" icon="fas fa-sync-alt" :value_today="0"
:mytextval="tools.numtostr(datastat.num_transaz_tot)"
classColor="text-indigo" colBack="green" />
<CElemStat myclass="stat-card" v-if="datastat.tot_RIS_transati > 0"
:title="t('statusreg.totristransati')" icon="img: /images/1ris_rosso_100.png"
:value_today="0" :mytextval="tools.numtostr(datastat.tot_RIS_transati) + ' RIS'"
classColor="text-blueviolet" colBack="green" />
</div> </div>
<q-list bordered v-if="tools.isLogged()"> <q-list bordered class="expansion-list">
<q-expansion-item group="somegroup" icon="fas fa-user-plus" :label="$t('statusreg.newreg')" default-opened <q-expansion-item group="somegroup" icon="fas fa-user-plus" :label="t('statusreg.newreg')"
header-class="text-primary"> default-opened header-class="text-primary expansion-header">
<q-card> <q-card class="expansion-card">
<q-card-section> <q-card-section class="card-content">
<div class="q-pa-md" style="max-width: 350px; margin: auto"> <q-item v-for="(user, index) in lastsreg" :key="index"
<TransitionGroup name="fade" appear enter-active-class="animazione fadeIn" class="user-item" clickable v-ripple @click="gotoPage(`/my/${user.username}`)">
leave-active-class="animazione fadeOut">
<q-item v-for="(user, index) in lastsreg" :key="index" class="animated chip_shadow q-ma-sm"
v-ripple clickable @click="gotoPage(`/my/${user.username}`)">
<q-item-section avatar> <q-item-section avatar>
<q-avatar round size="48px"> <q-avatar round size="44px">
<img :src="userStore.getImgByProfile(user)" /> <img :src="userStore.getImgByProfile(user)" />
<q-badge v-if="tools.isUserOnline(user)" align="top" floating color="green">online</q-badge> <q-badge v-if="tools.isUserOnline(user)" align="top" floating color="green">online</q-badge>
</q-avatar> </q-avatar>
</q-item-section> </q-item-section>
<q-item-section class=""> <q-item-section>
<q-item-label> <q-item-label class="user-name">
<span v-html="tools.getNameToShow(user, null, { <span v-html="tools.getNameToShow(user, null, { showprov: true, html: true })"></span>
showprov: true,
html: true,
})
"></span>
</q-item-label> </q-item-label>
<q-item-label caption> <q-item-label caption class="username-caption">
<span v-html="tools.getUserNameOnlyIfToShow(user, null, { <span v-html="tools.getUserNameOnlyIfToShow(user, null, { showprov: false, html: true })"></span>
showprov: false,
html: true,
})"></span>
</q-item-label> </q-item-label>
<q-item-label class="iscritto_da"> <q-item-label class="iscritto_da">
{{ t("statusreg.invite_by") }}: {{ t("statusreg.invite_by") }}:
<span class="iscritto_da_name">{{ <span class="iscritto_da_name">{{ tools.getNameToShow(user.user_aportador) }}</span>
tools.getNameToShow(user.user_aportador)
}}</span>
</q-item-label> </q-item-label>
</q-item-section> </q-item-section>
<q-item-section side> <q-item-section side>
<q-item-label style="color: white">{{ <q-item-label class="date-label">{{ tools.getstrshortDate(user.date_reg) }}</q-item-label>
tools.getstrshortDate(user.date_reg)
}}</q-item-label>
</q-item-section> </q-item-section>
</q-item> </q-item>
</TransitionGroup>
</div>
</q-card-section> </q-card-section>
</q-card> </q-card>
</q-expansion-item> </q-expansion-item>
<q-separator /> <q-separator />
<q-expansion-item expand-separator group="somegroup" icon="fas fa-medal" <q-expansion-item group="somegroup" icon="fas fa-medal"
:label="$t('statusreg.lastsharedlink')" header-class="text-purple"> :label="t('statusreg.lastsharedlink')" header-class="text-purple expansion-header">
<div> <q-card class="expansion-card">
<div class="text-center text-bold text-h6">Unisciti a {{tools.sitename()}}</div> <q-card-section class="card-content">
<div class="text-center"> <div class="info-section">
Se ancora non sei registrato a {{tools.sitename()}}, scegli un invitante che <div class="info-title">Unisciti a {{tools.sitename()}}</div>
conosci. Questa persona dovrà ammetterti per permetterti di <div class="info-text">
accedere alle funzionalità. Se ancora non sei registrato a {{tools.sitename()}}, scegli un invitante che conosci.
</div> </div>
</div> </div>
<q-item v-for="(user, index) in lastssharedlink" :key="index" class="animated chip_shadow q-ma-sm" <q-item v-for="(user, index) in lastssharedlink" :key="index"
clickable v-ripple @click="gotoPage(`/registrati/${user.user_aportador.username}`)"> class="user-item" clickable v-ripple
@click="gotoPage(`/registrati/${user.user_aportador.username}`)">
<q-item-section avatar> <q-item-section avatar>
<q-avatar round size="48px"> <q-avatar round size="44px">
<img :src="userStore.getImgByProfile(user.user_aportador)" /> <img :src="userStore.getImgByProfile(user.user_aportador)" />
</q-avatar> </q-avatar>
</q-item-section> </q-item-section>
<q-item-section class=""> <q-item-section>
<q-item-label> <q-item-label class="user-name">
<span v-html="tools.getNameToShow(user.user_aportador, null, { <span v-html="tools.getNameToShow(user.user_aportador, null, { showprov: true, html: true })"></span>
showprov: true, </q-item-label>
html: true, <q-item-label caption class="username-caption">
}) <span v-html="tools.getUserNameOnlyIfToShow(user.user_aportador, null, { showprov: false, html: true })"></span>
"></span>
</q-item-label> </q-item-label>
<q-item-label caption>
<span v-html="tools.getUserNameOnlyIfToShow(user.user_aportador, null, {
showprov: false,
html: true,
})">
</span></q-item-label>
<q-item-label class="iscritto_da"> <q-item-label class="iscritto_da">
{{ t("statusreg.has_invited") }}: {{ t("statusreg.has_invited") }}:
<span class="iscritto_da_name">{{ <span class="iscritto_da_name">{{ tools.getNameToShow(user) }}</span>
tools.getNameToShow(user)
}}</span>
</q-item-label> </q-item-label>
</q-item-section> </q-item-section>
<q-item-section side><span class="text-h6 q-mr-sm"></span></q-item-section>
</q-item> </q-item>
</q-expansion-item>
<q-expansion-item group="somegroup" icon="fas fa-wifi" :label="$t('statusreg.onlineusers', { today: datastat.online_today })
" header-class="text-teal">
<q-card>
<q-card-section>
<div class="q-pa-md" style="max-width: 350px; margin: auto">
<q-list bordered>
<TransitionGroup name="fade" appear enter-active-class="animazione fadeIn"
leave-active-class="animazione fadeOut">
<q-item v-for="(user, index) in lastsonline" :key="index" class="animated chip_shadow q-ma-sm"
clickable v-ripple @click="gotoPage(`/my/${user.username}`)">
<q-item-section avatar>
<q-avatar round size="48px">
<img :src="userStore.getImgByProfile(user)" />
<q-badge v-if="tools.isUserOnline(user)" align="top" floating
color="green">online</q-badge>
</q-avatar>
</q-item-section>
<q-item-section class="">
<q-item-label>
<span v-html="tools.getNameToShow(user, null, {
showprov: true,
html: true,
})
"></span>
</q-item-label>
<q-item-label caption>{{
tools.getUserNameOnlyIfToShow(user, null, {
showprov: false,
html: true,
})
}}</q-item-label>
</q-item-section>
<q-item-section side>
<div :class="`text-h6 q-mr-sm text-bold ` + $q.dark.isActive
? `text-white`
: `text-black`
">
<CTimeAgo :datetime="user.lasttimeonline" />
</div>
</q-item-section>
</q-item>
</TransitionGroup>
</q-list>
</div>
</q-card-section> </q-card-section>
</q-card> </q-card>
</q-expansion-item> </q-expansion-item>
<q-separator /> <q-separator />
<q-expansion-item v-if="false" expand-separator group="somegroup" icon="fas fa-medal" <q-expansion-item group="somegroup" icon="fas fa-wifi"
:label="$t('statusreg.diffusori')" header-class="text-purple"> :label="t('statusreg.onlineusers', { today: datastat.online_today })"
<q-card> header-class="text-teal expansion-header">
<q-card-section> <q-card class="expansion-card">
<div class="q-pa-md" style="max-width: 350px; margin: auto"> <q-card-section class="card-content">
<div class="text-center text-bold text-h6"> <q-item v-for="(user, index) in lastsonline" :key="index"
Aiuta {{tools.sitename()}} a crescere class="user-item" clickable v-ripple @click="gotoPage(`/my/${user.username}`)">
</div>
<div class="text-center">
Condividi il tuo link d'invito, alimentando nuovi ingressi
alla RETE Solidale di {{tools.sitename()}}.
</div>
<q-list bordered>
<TransitionGroup name="fade" appear enter-active-class="animazione fadeIn"
leave-active-class="animazione fadeOut">
<q-item v-for="(user, index) in diffusorilist" :key="index" class="animated chip_shadow q-ma-sm"
clickable v-ripple @click="gotoPage(`/my/${user.username}`)">
<q-item-section avatar> <q-item-section avatar>
<q-avatar round size="48px"> <q-avatar round size="44px">
<img :src="userStore.getImgByProfile(user)" /> <img :src="userStore.getImgByProfile(user)" />
<q-badge v-if="tools.isUserOnline(user)" align="top" floating <q-badge v-if="tools.isUserOnline(user)" align="top" floating color="green">online</q-badge>
color="green">online</q-badge>
</q-avatar> </q-avatar>
</q-item-section> </q-item-section>
<q-item-section class=""> <q-item-section>
<q-item-label class="user-name">
<span v-html="tools.getNameToShow(user, null, { showprov: true, html: true })"></span>
</q-item-label>
<q-item-label caption class="username-caption">
<span v-html="tools.getUserNameOnlyIfToShow(user, null, { showprov: false, html: true })"></span>
</q-item-label>
<q-item-label caption class="access-time">
{{ tools.getstrDateTimeLong(user.lastaccess) }}
</q-item-label>
</q-item-section>
</q-item>
</q-card-section>
</q-card>
</q-expansion-item>
<q-separator />
<q-expansion-item group="somegroup" icon="fas fa-gift"
:label="t('statusreg.diffusori')" header-class="text-green expansion-header">
<q-card class="expansion-card">
<q-card-section class="card-content">
<div class="info-section">
<div class="info-title">Aiuta {{tools.sitename()}} a crescere</div>
<div class="info-text">
Condividi il tuo link d'invito, alimentando nuovi ingressi alla RETE Solidale.
</div>
</div>
<q-item v-for="(user, index) in diffusorilist" :key="index"
class="user-item ranking-item" clickable v-ripple @click="gotoPage(`/my/${user.username}`)">
<q-item-section avatar>
<q-avatar round size="44px">
<img :src="userStore.getImgByProfile(user)" />
<q-badge v-if="tools.isUserOnline(user)" align="top" floating color="green">online</q-badge>
</q-avatar>
</q-item-section>
<q-item-section>
<q-item-label overline> <q-item-label overline>
<div class="index_diffusore"> <div class="index_diffusore">{{ index + 1 }}°</div>
{{ index + 1 }}°
</div>
</q-item-label> </q-item-label>
<q-item-label> <q-item-label class="user-name">
<span v-html="tools.getNameToShow(user, null, { <span v-html="tools.getNameToShow(user, null, { showprov: true, html: true })"></span>
showprov: true, </q-item-label>
html: true, <q-item-label caption class="username-caption">
}) {{ tools.getUserNameOnlyIfToShow(user, null, { showprov: false, html: true }) }}
"></span>
</q-item-label> </q-item-label>
<q-item-label caption>{{
tools.getUserNameOnlyIfToShow(user, null, {
showprov: false,
html: true,
})
}}</q-item-label>
</q-item-section> </q-item-section>
<q-item-section side> <q-item-section side>
<div :class="`text-h6 q-mx-sm q-px-sm text-bold ` + <div class="ranking-count">{{ user.count }}</div>
($q.dark.isActive ? `text-white` : `text-black`)
">
{{ user.count }}
</div>
</q-item-section> </q-item-section>
</q-item> </q-item>
</TransitionGroup>
</q-list>
</div>
</q-card-section> </q-card-section>
</q-card> </q-card>
</q-expansion-item> </q-expansion-item>
<q-separator /> <q-separator />
<q-expansion-item group="somegroup" icon="fas fa-handshake" :label="$t('handshake.last_strettedimano')" <q-expansion-item group="somegroup" icon="fas fa-handshake"
header-class="bg-teal text-white" expand-icon-class="text-white"> :label="t('handshake.last_strettedimano')"
<q-card class="bg-teal-2"> header-class="bg-teal text-white expansion-header" expand-icon-class="text-white">
<q-card-section> <q-card class="expansion-card bg-teal-1">
<div class="q-pa-md" style="max-width: 350px; margin: auto"> <q-card-section class="card-content">
<div class="text-center text-bold text-h6"> <div class="info-section">
Strette di Mano <div class="info-title">Strette di Mano</div>
<div class="info-text">
Più persone conoscerai di persona e maggiore aumenterà la Rete di fiducia.
</div> </div>
<div class="text-center">
Più persone conoscerai di persona e maggiore aumenterà la
Rete di fiducia.
</div> </div>
<q-list bordered>
<TransitionGroup name="fade" appear enter-active-class="animazione fadeIn" <q-item v-for="(user, index) in strettelist" :key="index"
leave-active-class="animazione fadeOut"> class="user-item handshake-item" clickable v-ripple>
<q-item v-for="(user, index) in strettelist" :key="index" class="animated chip_shadow q-ma-sm" <q-item-section avatar @click="gotoPage(`/my/${user.username}`)">
clickable v-ripple> <q-avatar round size="44px">
<q-item-section avatar>
<q-avatar round size="48px" @click="gotoPage(`/my/${user.username}`)">
<img :src="userStore.getImgByProfile(user)" /> <img :src="userStore.getImgByProfile(user)" />
<q-badge v-if="tools.isUserOnline(user)" align="top" floating <q-badge v-if="tools.isUserOnline(user)" align="top" floating color="green">online</q-badge>
color="green">online</q-badge>
</q-avatar> </q-avatar>
</q-item-section> </q-item-section>
<q-item-section class=""> <q-item-section>
<q-item-label class="clDateStrette"> <q-item-label class="clDateStrette">
{{ {{ tools.getstrDateTimeLong(user.profile.handshake.date) }}
tools.getstrDateTimeLong(
user.profile.handshake.date
)
}}</q-item-label>
<q-item-label>
<span v-html="tools.getNameToShow(user, null, {
showprov: true,
html: true,
})
"></span>
</q-item-label> </q-item-label>
<q-item-label class="user-name">
<q-item-label style="text-align: right"> <span v-html="tools.getNameToShow(user, null, { showprov: true, html: true })"></span>
<span v-html="tools.getNameToShow(user.userfriend, null, { </q-item-label>
showprov: true, <q-item-label class="handshake-friend">
html: true, <span v-html="tools.getNameToShow(user.userfriend, null, { showprov: true, html: true })"></span>
})
"></span>
</q-item-label> </q-item-label>
</q-item-section> </q-item-section>
<q-item-section side> <q-item-section side @click="gotoPage(`/my/${user.userfriend.username}`)">
<q-avatar round size="48px" @click=" <q-avatar round size="44px">
gotoPage(`/my/${user.userfriend.username}`) <img :src="userStore.getImgByProfile(user.userfriend)" />
"> <q-badge v-if="tools.isUserOnline(user.userfriend)" align="top" floating color="green">online</q-badge>
<img :src="userStore.getImgByProfile(user.userfriend)
" />
<q-badge v-if="tools.isUserOnline(user.userfriend)" align="top" floating
color="green">online</q-badge>
</q-avatar> </q-avatar>
</q-item-section> </q-item-section>
</q-item> </q-item>
</TransitionGroup>
</q-list>
</div>
</q-card-section> </q-card-section>
</q-card> </q-card>
</q-expansion-item> </q-expansion-item>
</q-list> </q-list>
<!--<CGeoChart :mydata="datastat.arr_nations"> <div class="chart-wrapper">
<CLineChart :mydata="datastat.reg_daily" :title="t('stat.reg_daily')"
</CGeoChart>--> color="blue" bordercolor="blue" :sum="true" :showMedia="true" />
<div class="row q-pa-sm text-center justify-center">
<!--
<div class="clBorderZoom">
<CListNationality :mydata="datastat.arr_nations">
</CListNationality>
</div>-->
<div class="clBorderTutor">
<CLineChart :mydata="datastat.reg_daily" :title="$t('stat.reg_daily')" color="blue" bordercolor="blue"
:sum="true" :showMedia="true">
</CLineChart>
<!--<CLineChart :mydata="datastat.reg_daily" :title="$t('stat.reg_total')"
:offset="datastat.numreg_untilday" :sum="true"
:mycolors="['#0b0', '#666']">
</CLineChart>-->
</div>
</div>
</div> </div>
</CTitleBanner> </CTitleBanner>
</div> </div>
@@ -341,5 +235,5 @@
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "./CStatusReg.scss"; @import "./CStatusReg_Modern.scss";
</style> </style>

View File

@@ -0,0 +1,243 @@
.status-reg-modern {
padding: 4px;
@media (min-width: 1024px) {
padding: 8px;
}
}
.title-banner {
padding: 4px;
@media (min-width: 1024px) {
padding: 8px;
}
}
// Stats wrapper
.stats-wrapper {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 8px;
padding: 8px;
@media (min-width: 1024px) {
gap: 12px;
padding: 12px;
}
.stat-card {
min-width: 140px;
@media (min-width: 768px) {
min-width: 160px;
}
}
}
// Expansion list
.expansion-list {
background: transparent;
border: none;
margin: 8px 0;
@media (min-width: 1024px) {
margin: 12px 0;
}
.expansion-header {
padding: 8px 12px;
font-weight: 600;
@media (min-width: 1024px) {
padding: 10px 16px;
}
}
}
// Expansion card
.expansion-card {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
.card-content {
padding: 8px;
@media (min-width: 1024px) {
padding: 12px;
}
}
}
// Info section
.info-section {
background: linear-gradient(135deg, rgba(102, 126, 234, 0.1) 0%, rgba(118, 75, 162, 0.1) 100%);
border-radius: 8px;
padding: 12px;
margin-bottom: 8px;
text-align: center;
@media (min-width: 1024px) {
padding: 16px;
margin-bottom: 12px;
}
.info-title {
font-weight: 700;
font-size: 16px;
color: #333;
margin-bottom: 4px;
}
.info-text {
font-size: 13px;
color: #666;
line-height: 1.4;
@media (min-width: 1024px) {
font-size: 14px;
}
}
}
// User items
.user-item {
background: rgba(255, 255, 255, 0.8);
border-radius: 8px;
margin-bottom: 6px;
padding: 8px;
transition: all 0.2s ease;
@media (min-width: 1024px) {
margin-bottom: 8px;
padding: 10px;
}
&:hover {
background: rgba(102, 126, 234, 0.15);
transform: translateX(4px);
}
&:last-child {
margin-bottom: 0;
}
.user-name {
font-weight: 600;
font-size: 14px;
color: #333;
@media (min-width: 1024px) {
font-size: 15px;
}
}
.username-caption {
font-size: 12px;
color: #888;
@media (min-width: 1024px) {
font-size: 13px;
}
}
.date-label {
color: #888;
font-size: 12px;
@media (min-width: 1024px) {
font-size: 13px;
}
}
.access-time {
font-size: 11px;
color: #999;
font-style: italic;
@media (min-width: 1024px) {
font-size: 12px;
}
}
}
// Ranking items
.ranking-item {
.index_diffusore {
font-size: 14px;
font-weight: 700;
color: #667eea;
@media (min-width: 1024px) {
font-size: 16px;
}
}
.ranking-count {
font-size: 20px;
font-weight: 700;
color: #333;
@media (min-width: 1024px) {
font-size: 24px;
}
}
}
// Handshake items
.handshake-item {
display: flex;
align-items: center;
.clDateStrette {
color: #888;
font-style: italic;
font-size: 11px;
margin-bottom: 4px;
@media (min-width: 1024px) {
font-size: 12px;
}
}
.handshake-friend {
text-align: right;
font-size: 13px;
color: #666;
@media (min-width: 1024px) {
font-size: 14px;
}
}
}
// Original styles preserved
.iscritto_da {
font-style: italic;
font-size: 11px;
color: #aaa;
@media (min-width: 1024px) {
font-size: 12px;
}
}
.iscritto_da_name {
color: #667eea !important;
font-weight: 600;
}
// Chart wrapper
.chart-wrapper {
margin-top: 12px;
padding: 8px;
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(10px);
border-radius: 12px;
@media (min-width: 1024px) {
margin-top: 16px;
padding: 12px;
}
}

View File

@@ -42,7 +42,7 @@
} }
.img_in_tab{ .img_in_tab{
width: 52px; width: 32px;
height: 52px; height: 32px;
margin: 4px !important; margin: 4px !important;
} }

View File

@@ -1,45 +1,87 @@
<template> <template>
<q-footer v-if="tools.isUserOk() && site && site.confpages" <q-footer
:class="($q.dark.isActive ? `bg-black` : `bg-white`) + ` small-screen-only`" bordered> v-if="tools.isUserOk() && site && site.confpages"
:class="($q.dark.isActive ? `bg-black` : `bg-white`) + ` small-screen-only`"
bordered
>
<q-toolbar> <q-toolbar>
<q-toolbar-title> <q-toolbar-title>
<q-tabs dense :class="($q.dark.isActive <q-tabs
? `text-white bg-black` dense
: `text-grey-10 bg-white`) + ` mylabfooter mysmalltabs` :class="
" style="padding: 0px !important" content-class="mysmalltabs" active-color="white" active-bg-color="blue" ($q.dark.isActive ? `text-white bg-black` : `text-grey-10 bg-white`) +
no-caps indicator-color="transparent"> ` mylabfooter mysmalltabs`
<q-route-tab v-if="site.confpages?.showButtHome" to="/" class="mylabfooter" :label="$t('tabdown.home')" "
icon="fas fa-home" /> style="padding: 0px !important"
<!--<q-route-tab content-class="mysmalltabs"
v-if="site.confpages?.showViewGroups" active-color="white"
active-bg-color="blue"
no-caps
indicator-color="transparent"
>
<q-route-tab
v-if="site.confpages?.showButtHome"
to="/"
class="mylabfooter" class="mylabfooter"
:label="$t('tabdown.groups')" :label="$t('tabdown.home')"
to="/groups" icon="fas fa-home"
icon="fas fa-users" />
/>--> <q-route-tab
<q-route-tab v-if="site.confpages?.showViewEventi" class="mylabfooter" :label="$t('tabdown.eventi')" v-if="site.confpages?.showViewEventi"
to="/events" icon="fas fa-calendar-week" /> class="mylabfooter"
<q-img v-if="site.confpages?.showViewCircuits" src="/images/1ris_rosso_100.png" round flat :label="$t('tabdown.eventi')"
:style="currentPath === '/circuits' ? `background-color: lightblue` : ''" class="img_in_tab" to="/events"
@click="naviga(tools.updateLink('/circuits'))"></q-img> icon="fas fa-calendar-week"
<q-route-tab v-if="site.confpages?.showViewUsers" class="mylabfooter" :label="$t('tabdown.friends')" />
to="/friends" icon="fas fa-user-friends" /> <q-route-tab
<q-route-tab v-if="site.confpages?.showViewBooking" class="mylabfooter" :label="$t('tabdown.bookings')" v-if="site.confpages?.showViewCircuits"
to="/admin/eventlist" icon="fas fa-calendar-plus" /> class="mylabfooter"
<q-route-tab v-if="site.confpages?.showViewCart" class="mylabfooter" :label="$t('tabdown.showViewCart')" :label="$t('tabdown.circuits')"
to="/checkout" icon="fas fa-shopping-cart" /> to="/circuits"
<q-route-tab v-if="site.confpages?.showViewOrders" class="mylabfooter" :label="$t('tabdown.showViewOrders')" icon="img:/images/1ris_rosso_100.png"
to="/orderinfo" icon="fas fa-clipboard-list" /> />
<q-route-tab v-if="site.confpages?.showViewProfile" class="mylabfooter" :label="$t('tabdown.profile')" <q-route-tab
@click="globalStore.rightDrawerOpen = true" :icon="getMyImg() ? `img: ` + getMyImg() : `fas fa-user`" /> v-if="site.confpages?.showViewUsers"
class="mylabfooter"
:label="$t('tabdown.friends')"
to="/friends"
icon="fas fa-user-friends"
/>
<q-route-tab
v-if="site.confpages?.showViewBooking"
class="mylabfooter"
:label="$t('tabdown.bookings')"
to="/admin/eventlist"
icon="fas fa-calendar-plus"
/>
<q-route-tab
v-if="site.confpages?.showViewCart"
class="mylabfooter"
:label="$t('tabdown.showViewCart')"
to="/checkout"
icon="fas fa-shopping-cart"
/>
<q-route-tab
v-if="site.confpages?.showViewOrders"
class="mylabfooter"
:label="$t('tabdown.showViewOrders')"
to="/orderinfo"
icon="fas fa-clipboard-list"
/>
<q-route-tab
v-if="site.confpages?.showViewProfile"
class="mylabfooter"
:label="$t('tabdown.profile')"
@click="globalStore.rightDrawerOpen = true"
:icon="getMyImg() ? `img: ` + getMyImg() : `fas fa-user`"
/>
</q-tabs> </q-tabs>
</q-toolbar-title> </q-toolbar-title>
</q-toolbar> </q-toolbar>
</q-footer> </q-footer>
</template> </template>
<script lang="ts" src="./MyFooter.ts"> <script lang="ts" src="./MyFooter.ts"></script>
</script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import './MyFooter.scss'; @import './MyFooter.scss';

View File

@@ -227,10 +227,18 @@ $gradient-teal: linear-gradient(135deg, #14b8a6 0%, #06b6d4 100%);
margin-bottom: $s-lg; margin-bottom: $s-lg;
} }
.avviso-card {
background: $gradient-orange;
border-radius: $r-md;
padding: $s-md;
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.3);
color: white;
}
.wallet-card { .wallet-card {
background: $gradient-primary; background: $gradient-primary;
border-radius: $r-lg; border-radius: $r-md;
padding: $s-lg; padding: $s-md;
color: white; color: white;
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.3); box-shadow: 0 6px 20px rgba(102, 126, 234, 0.3);
@@ -832,13 +840,13 @@ $gradient-teal: linear-gradient(135deg, #14b8a6 0%, #06b6d4 100%);
} }
.dialog-content { .dialog-content {
padding: $s-lg; padding: $s-md;
} }
.annunci-options-mobile { .annunci-options-mobile {
display: grid; display: grid;
grid-template-columns: repeat(2, 1fr); grid-template-columns: repeat(2, 1fr);
gap: $s-md; gap: $s-sm;
} }
.annuncio-option { .annuncio-option {
@@ -846,7 +854,7 @@ $gradient-teal: linear-gradient(135deg, #14b8a6 0%, #06b6d4 100%);
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
gap: $s-sm; gap: $s-sm;
padding: $s-lg; padding: $s-md;
border-radius: $r-lg; border-radius: $r-lg;
cursor: pointer; cursor: pointer;
transition: all 0.3s ease; transition: all 0.3s ease;
@@ -886,14 +894,14 @@ $gradient-teal: linear-gradient(135deg, #14b8a6 0%, #06b6d4 100%);
} }
.option-title { .option-title {
font-size: 1.1rem; font-size: 1.5rem;
font-weight: 700; font-weight: 700;
text-align: center; text-align: center;
line-height: 1.2; line-height: 1.2;
} }
.option-subtitle { .option-subtitle {
font-size: 0.9rem; font-size: 1rem;
opacity: 0.9; opacity: 0.9;
text-align: center; text-align: center;
line-height: 1.3; line-height: 1.3;
@@ -926,11 +934,11 @@ $gradient-teal: linear-gradient(135deg, #14b8a6 0%, #06b6d4 100%);
} }
.option-title { .option-title {
font-size: 1rem; font-size: 1.3rem;
} }
.option-subtitle { .option-subtitle {
font-size: 0.7rem; font-size: 0.9rem;
} }
} }
} }
@@ -1284,6 +1292,24 @@ section {
.circuit-selector { .circuit-selector {
margin-bottom: $s-lg; margin-bottom: $s-lg;
.selector-label {
display: flex;
align-items: center;
gap: $s-xs;
margin-bottom: $s-sm;
.selector-emoji {
font-size: 1.2rem;
animation: pointRight 1.5s ease-in-out infinite;
}
.selector-text {
font-size: 0.85rem;
font-weight: 600;
opacity: 0.9;
}
}
.circuit-toggle { .circuit-toggle {
width: 100%; width: 100%;
@@ -1299,6 +1325,19 @@ section {
} }
} }
// Animazione puntamento
@keyframes pointRight {
0%,
100% {
transform: translateX(0);
}
50% {
transform: translateX(4px);
}
}
// Overview saldi circuiti // Overview saldi circuiti
.circuits-balance-overview { .circuits-balance-overview {
display: grid; display: grid;
@@ -1493,3 +1532,41 @@ section {
left: 16px; left: 16px;
} }
} }
.close-btn {
position: absolute;
top: 8px;
right: 8px;
color: rgba(255, 255, 255, 0.7);
&:hover {
color: white;
}
}
// Header transazioni con toggle
.transactions-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: $s-md;
.transactions-title {
margin: 0;
font-size: 0.95rem;
font-weight: 700;
opacity: 0.95;
}
}
// Transazioni community con 2 avatar
.transaction-users {
display: flex;
align-items: center;
gap: 4px;
}
.transaction-amount.community {
color: #a78bfa; // Viola per transazioni community
font-weight: 700;
}

View File

@@ -1,6 +1,8 @@
import { defineComponent, ref, computed } from 'vue'; import { defineComponent, ref, computed, watch, onMounted } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { CRISBalanceBar } from '@src/components/CRISBalanceBar'; import { CRISBalanceBar } from '@src/components/CRISBalanceBar';
import { tools } from '@tools';
import { useUserStore } from 'app/src/store';
const isTest = true; // Cambia a false in produzione const isTest = true; // Cambia a false in produzione
@@ -12,10 +14,16 @@ export default defineComponent({
// State // State
const showAnnunciDialog = ref(false); const showAnnunciDialog = ref(false);
const showBannScambio = ref(true);
const selectedCircuit = ref<'provinciale' | 'italia'>('provinciale'); const selectedCircuit = ref<'provinciale' | 'italia'>('provinciale');
const handshakesView = ref<'mine' | 'all'>('mine'); const handshakesView = ref<'mine' | 'all'>('mine');
const transactionsView = ref<'mine' | 'all'>('mine');
// Transazioni globali community
const allTransactions = ref<any[]>([]);
// Dati wallet (da collegare allo store) // Dati wallet (da collegare allo store)
const availableBalance = ref(0); // Saldo utilizzabile (include fido) const availableBalance = ref(0); // Saldo utilizzabile (include fido)
const realBalance = ref(0); // Saldo reale effettivo const realBalance = ref(0); // Saldo reale effettivo
@@ -51,6 +59,8 @@ export default defineComponent({
const uniqueMembers = ref(0); const uniqueMembers = ref(0);
const lastTradeTime = ref('Mai'); const lastTradeTime = ref('Mai');
const userStore = useUserStore()
// Organizzazioni // Organizzazioni
const organizations = ref<any[]>([ const organizations = ref<any[]>([
// Esempio struttura: // Esempio struttura:
@@ -73,26 +83,22 @@ export default defineComponent({
// Dialog Annunci // Dialog Annunci
const openAnnunciDialog = () => { const openAnnunciDialog = () => {
showAnnunciDialog.value = true; showAnnunciDialog.value = true;
// TODO: implementare logica apertura dialog
}; };
// Navigazione Annunci // Navigazione Annunci
const goToGoods = () => { const goToGoods = () => {
showAnnunciDialog.value = false; showAnnunciDialog.value = false;
// TODO: navigare a /goods $router.push('/goods')
// $router.push('/goods')
}; };
const goToServices = () => { const goToServices = () => {
showAnnunciDialog.value = false; showAnnunciDialog.value = false;
// TODO: navigare a /services $router.push('/services')
// $router.push('/services')
}; };
const goToHospitality = () => { const goToHospitality = () => {
showAnnunciDialog.value = false; showAnnunciDialog.value = false;
// TODO: navigare a /hosps $router.push('/hosps')
// $router.push('/hosps')
}; };
const goToTransport = () => { const goToTransport = () => {
@@ -109,17 +115,17 @@ export default defineComponent({
const goToCircuits = () => { const goToCircuits = () => {
// TODO: navigare ai circuiti RIS // TODO: navigare ai circuiti RIS
// $router.push('/circuits') $router.push('/circuits')
}; };
const goToEvents = () => { const goToEvents = () => {
// TODO: navigare alla lista eventi // TODO: navigare alla lista eventi
// $router.push('/events') $router.push('/events')
}; };
const goToProfile = () => { const goToProfile = () => {
// TODO: navigare al profilo utente // TODO: navigare al profilo utente
// $router.push('/profile') $router.push('/my/' + userStore.my.username)
}; };
// Azioni Rapide // Azioni Rapide
@@ -153,7 +159,7 @@ export default defineComponent({
// Eventi // Eventi
const goToAllEvents = () => { const goToAllEvents = () => {
// TODO: navigare a tutti gli eventi // TODO: navigare a tutti gli eventi
// $router.push('/events') $router.push('/events')
}; };
const openEvent = (event: any) => { const openEvent = (event: any) => {
@@ -192,18 +198,18 @@ export default defineComponent({
const goToAllOrganizations = () => { const goToAllOrganizations = () => {
// TODO: navigare a lista completa organizzazioni // TODO: navigare a lista completa organizzazioni
// $router.push('/groups') $router.push('/groups')
}; };
// Footer // Footer
const openFAQ = () => { const openFAQ = () => {
// TODO: navigare a FAQ // TODO: navigare a FAQ
// $router.push('/faq') $router.push('/faq_ris')
}; };
const openGuide = () => { const openGuide = () => {
// TODO: navigare a guida // TODO: navigare a guida
// $router.push('/guide') $router.push('/guida')
}; };
const openInfo = () => { const openInfo = () => {
@@ -277,6 +283,33 @@ export default defineComponent({
], ],
}; };
// Transazioni community (tutti)
allTransactions.value = [
{
fromInitial: 'M',
toInitial: 'L',
fromName: 'Mario',
toName: 'Laura',
amount: 25,
time: '1h fa',
},
{
fromInitial: 'G',
toInitial: 'C',
fromName: 'Giovanni',
toName: 'Chiara',
amount: 40,
time: '2h fa',
},
{
fromInitial: 'P',
toInitial: 'A',
fromName: 'Paolo',
toName: 'Anna',
amount: 15,
time: '3h fa',
},
];
// Community // Community
invitesSent.value = 7; invitesSent.value = 7;
@@ -504,15 +537,16 @@ export default defineComponent({
loadTestData(); loadTestData();
// ========== LIFECYCLE (da implementare) ========== onMounted(() => {
// onMounted(() => { showBannScambio.value = tools.getCookieBool('bann_scambio', true);
// loadWalletData() // loadWalletData()
// loadRecentEvents() // loadRecentEvents()
// loadRecentActivity() // loadRecentActivity()
// loadStats() // loadStats()
// loadOrganizations() // loadOrganizations()
// loadTelegramLinks() // loadTelegramLinks()
// }) });
return { return {
// State // State
@@ -583,6 +617,11 @@ export default defineComponent({
openTransaction, openTransaction,
openHandshake, openHandshake,
goToAllCircuits, goToAllCircuits,
showBannScambio,
tools,
transactionsView,
allTransactions,
userStore,
}; };
}, },
}); });

View File

@@ -50,8 +50,8 @@
name="account_circle" name="account_circle"
size="2.5rem" size="2.5rem"
/> />
<span class="card-title">Profilo</span> <span class="card-title">Il mio Profilo</span>
<span class="card-subtitle">Gestisci account</span> <span class="card-subtitle text-italic">({{ userStore.my.username }})</span>
</div> </div>
</section> </section>
@@ -83,7 +83,7 @@
<div class="wallet-header"> <div class="wallet-header">
<h2 class="section-title-white"> <h2 class="section-title-white">
<q-icon name="fas fa-coins" /> <q-icon name="fas fa-coins" />
I Tuoi RIS - Circuiti di Scambio I Tuoi Scambi in RIS
</h2> </h2>
<q-btn <q-btn
flat flat
@@ -103,6 +103,7 @@
:min-limit="circuits.provinciale.trustLimit" :min-limit="circuits.provinciale.trustLimit"
:max-limit="circuits.provinciale.maxAccumulation" :max-limit="circuits.provinciale.maxAccumulation"
:label="circuits.provinciale.title" :label="circuits.provinciale.title"
small
/> />
</div> </div>
<div class="circuit-balance-mini"> <div class="circuit-balance-mini">
@@ -111,12 +112,17 @@
:min-limit="circuits.italia.trustLimit" :min-limit="circuits.italia.trustLimit"
:max-limit="circuits.italia.maxAccumulation" :max-limit="circuits.italia.maxAccumulation"
:label="circuits.italia.title" :label="circuits.italia.title"
small
/> />
</div> </div>
</div> </div>
<!-- Selettore Circuito --> <!-- Selettore Circuito -->
<div class="circuit-selector"> <div class="circuit-selector">
<div class="selector-label">
<span class="selector-emoji">👉🏻</span>
<span class="selector-text">Seleziona il Circuito</span>
</div>
<q-btn-toggle <q-btn-toggle
v-model="selectedCircuit" v-model="selectedCircuit"
dense dense
@@ -143,6 +149,14 @@
<span class="stat-number">{{ currentCircuitData.totalTransactions }}</span> <span class="stat-number">{{ currentCircuitData.totalTransactions }}</span>
<span class="stat-label">Transazioni</span> <span class="stat-label">Transazioni</span>
</div> </div>
<div class="transaction-stat members">
<q-icon
name="people"
size="sm"
/>
<span class="stat-number">{{ currentCircuitData.uniqueMembers }}</span>
<span class="stat-label">Membri coinvolti</span>
</div>
<div class="transaction-stat sent"> <div class="transaction-stat sent">
<q-icon <q-icon
name="arrow_upward" name="arrow_upward"
@@ -159,25 +173,10 @@
<span class="stat-number">{{ currentCircuitData.receivedCount }}</span> <span class="stat-number">{{ currentCircuitData.receivedCount }}</span>
<span class="stat-label">Ricevute</span> <span class="stat-label">Ricevute</span>
</div> </div>
<div class="transaction-stat members">
<q-icon
name="people"
size="sm"
/>
<span class="stat-number">{{ currentCircuitData.uniqueMembers }}</span>
<span class="stat-label">Membri coinvolti</span>
</div>
</div> </div>
<!-- Saldi compatti circuito selezionato --> <!-- Saldi compatti circuito selezionato -->
<div class="wallet-balances-compact"> <div class="wallet-balances-compact">
<div class="balance-row">
<span class="balance-label-compact">Saldo:</span>
<span class="balance-value-compact"
>{{ currentCircuitData.realBalance }} RIS</span
>
</div>
<CRISBalanceBar <CRISBalanceBar
:current-balance="currentCircuitData.realBalance" :current-balance="currentCircuitData.realBalance"
:min-limit="currentCircuitData.trustLimit" :min-limit="currentCircuitData.trustLimit"
@@ -187,9 +186,31 @@
</div> </div>
<!-- Ultime 3 transazioni del circuito selezionato --> <!-- Ultime 3 transazioni del circuito selezionato -->
<!-- SOSTITUISCI la sezione recent-transactions -->
<div class="recent-transactions"> <div class="recent-transactions">
<h4 class="transactions-title">Ultime Tue Transazioni</h4> <div class="transactions-header">
<div class="transaction-list"> <h4 class="transactions-title">Ultimi Scambi</h4>
<q-btn-toggle
v-model="transactionsView"
dense
no-caps
unelevated
rounded
size="md"
toggle-color="primary"
:options="[
{ label: 'Personali', value: 'mine' },
{ label: 'Della Community', value: 'all' },
]"
/>
</div>
<!-- Tue Transazioni -->
<div
v-if="transactionsView === 'mine'"
class="transaction-list"
>
<div <div
v-for="(tx, idx) in currentCircuitData.recentTransactions.slice(0, 3)" v-for="(tx, idx) in currentCircuitData.recentTransactions.slice(0, 3)"
:key="idx" :key="idx"
@@ -214,27 +235,51 @@
</span> </span>
</div> </div>
</div> </div>
<!-- Transazioni Community -->
<div
v-if="transactionsView === 'all'"
class="transaction-list"
>
<div
v-for="(tx, idx) in allTransactions.slice(0, 3)"
:key="idx"
class="transaction-item"
@click="openTransaction(tx)"
>
<div class="transaction-users">
<q-avatar
size="28px"
color="primary"
text-color="white"
>
{{ tx.fromInitial }}
</q-avatar>
<q-icon
name="arrow_forward"
size="xs"
color="grey-6"
/>
<q-avatar
size="28px"
color="secondary"
text-color="white"
>
{{ tx.toInitial }}
</q-avatar>
</div>
<div class="transaction-content">
<span class="transaction-desc">{{ tx.fromName }} {{ tx.toName }}</span>
<span class="transaction-time">{{ tx.time }}</span>
</div>
<span class="transaction-amount community">
{{ tx.amount > 0 ? '+' : '' }}{{ tx.amount }}
</span>
</div>
</div>
</div> </div>
<!-- Focus su attività di scambio --> <!-- Focus su attività di scambio -->
<div class="wallet-activity-focus">
<div class="activity-message">
<q-icon
name="swap_horiz"
size="lg"
/>
<span class="activity-text"
>Continua a scambiare con più persone per far crescere la comunità!</span
>
</div>
<div class="last-trade">
<q-icon
name="schedule"
size="sm"
/>
<span>Ultimo scambio: {{ currentCircuitData.lastTradeTime }}</span>
</div>
</div>
<q-btn <q-btn
unelevated unelevated
rounded rounded
@@ -244,6 +289,34 @@
@click="goToTransactions" @click="goToTransactions"
/> />
</div> </div>
<div
v-if="showBannScambio"
class="avviso-card"
>
<div class="wallet-activity-focus">
<q-btn
flat
dense
round
icon="close"
class="close-btn"
@click="
showBannScambio = false;
tools.setCookie('bann_scambio', '0');
"
/>
<div class="activity-message">
<q-icon
name="people_outline"
size="lg"
/>
<span class="activity-text"
>Continua a scambiare con più persone per far crescere la tua
comunità!</span
>
</div>
</div>
</div>
</section> </section>
<!-- Prossimi Eventi --> <!-- Prossimi Eventi -->
<section <section
@@ -300,6 +373,87 @@
</div> </div>
</section> </section>
<!-- Organizzazioni -->
<section
v-if="hasOrganizations"
class="content-section"
>
<div class="section-header">
<h2 class="section-title">
<q-icon name="fas fa-users" />
Organizzazioni
</h2>
<q-btn
flat
dense
round
icon="arrow_forward"
@click="goToAllOrganizations"
/>
</div>
<div class="org-scroll">
<div
v-for="(org, idx) in organizations.slice(0, 10)"
:key="idx"
class="org-card-scroll"
@click="openOrganization(org)"
>
<q-avatar
size="50px"
color="blue-6"
text-color="white"
>
{{ org.initial }}
</q-avatar>
<span class="org-name">{{ org.name }}</span>
<span class="org-members">{{ org.membersCount }} membri</span>
</div>
</div>
</section>
<!-- Circuiti RIS Overview -->
<!--<section class="circuits-overview-section">
<div class="section-header">
<h2 class="section-title">
<q-icon name="account_balance" />
Circuiti RIS
</h2>
</div>
<div class="circuits-cards">
<div class="circuit-info-card">
<q-icon
name="check_circle"
color="positive"
size="lg"
/>
<span class="circuit-stat-number">{{ activeCircuitsCount }}</span>
<span class="circuit-stat-label">Circuiti Attivi</span>
</div>
<div class="circuit-info-card">
<q-icon
name="public"
color="primary"
size="lg"
/>
<span class="circuit-stat-number">{{ totalCircuitsAvailable }}</span>
<span class="circuit-stat-label">Circuiti Disponibili</span>
</div>
</div>
<q-btn
unelevated
rounded
class="view-all-btn"
label="Esplora tutti i Circuiti"
icon-right="arrow_forward"
color="primary"
@click="goToAllCircuits"
/>
</section>
-->
<!-- Community Actions --> <!-- Community Actions -->
<section class="community-actions-section"> <section class="community-actions-section">
<h2 class="section-title"> <h2 class="section-title">
@@ -348,126 +502,9 @@
</div> </div>
</section> </section>
<!-- Organizzazioni -->
<section
v-if="hasOrganizations"
class="content-section"
>
<div class="section-header">
<h2 class="section-title">
<q-icon name="fas fa-users" />
Organizzazioni
</h2>
<q-btn
flat
dense
round
icon="arrow_forward"
@click="goToAllOrganizations"
/>
</div>
<div class="org-scroll"> <!-- Attività Community
<div
v-for="(org, idx) in organizations.slice(0, 10)"
:key="idx"
class="org-card-scroll"
@click="openOrganization(org)"
>
<q-avatar
size="50px"
color="blue-6"
text-color="white"
>
{{ org.initial }}
</q-avatar>
<span class="org-name">{{ org.name }}</span>
<span class="org-members">{{ org.membersCount }} membri</span>
</div>
</div>
</section>
<!-- Statistiche Generali -->
<section class="stats-section">
<h2 class="section-title-center">
<q-icon name="analytics" />
Statistiche RISO
</h2>
<div class="stats-grid">
<div class="stat-card">
<q-icon
name="people"
color="primary"
size="lg"
/>
<span class="stat-number">{{ totalMembers }}</span>
<span class="stat-text">Membri</span>
</div>
<div class="stat-card">
<q-icon
name="swap_horiz"
color="positive"
size="lg"
/>
<span class="stat-number">{{ totalTrades }}</span>
<span class="stat-text">Scambi</span>
</div>
<div class="stat-card">
<q-icon
name="event"
color="accent"
size="lg"
/>
<span class="stat-number">{{ totalEvents }}</span>
<span class="stat-text">Eventi</span>
</div>
</div>
</section>
<!-- Circuiti RIS Overview -->
<section class="circuits-overview-section">
<div class="section-header">
<h2 class="section-title">
<q-icon name="account_balance" />
Circuiti RIS
</h2>
</div>
<div class="circuits-cards">
<div class="circuit-info-card">
<q-icon
name="check_circle"
color="positive"
size="lg"
/>
<span class="circuit-stat-number">{{ activeCircuitsCount }}</span>
<span class="circuit-stat-label">Circuiti Attivi</span>
</div>
<div class="circuit-info-card">
<q-icon
name="public"
color="primary"
size="lg"
/>
<span class="circuit-stat-number">{{ totalCircuitsAvailable }}</span>
<span class="circuit-stat-label">Circuiti Disponibili</span>
</div>
</div>
<q-btn
unelevated
rounded
class="view-all-btn"
label="Esplora tutti i Circuiti"
icon-right="arrow_forward"
color="primary"
@click="goToAllCircuits"
/>
</section>
<!-- Attività Community -->
<section class="community-section"> <section class="community-section">
<!-- Strette di Mano -->
<div class="community-card"> <div class="community-card">
<div class="community-header"> <div class="community-header">
<h3 class="community-title"> <h3 class="community-title">
@@ -557,9 +594,10 @@
</div> </div>
</div> </div>
</div> </div>
-->
<!-- Ultimi Scambi --> <!-- Ultimi Scambi -->
<div class="community-card"> <!--<div class="community-card">
<div class="community-header"> <div class="community-header">
<h3 class="community-title"> <h3 class="community-title">
<q-icon name="swap_horiz" /> <q-icon name="swap_horiz" />
@@ -599,10 +637,10 @@
</div> </div>
</div> </div>
</div> </div>
</section> </section>-->
<!-- Canali Telegram --> <!-- Canali Telegram -->
<section <!--<section
v-if="hasTelegramLinks" v-if="hasTelegramLinks"
class="content-section" class="content-section"
> >
@@ -655,6 +693,7 @@
</a> </a>
</div> </div>
</section> </section>
-->
<!-- Footer Links --> <!-- Footer Links -->
<section class="footer-section-modern"> <section class="footer-section-modern">
@@ -745,7 +784,6 @@
<div <div
class="annuncio-option gradient-teal" class="annuncio-option gradient-teal"
@click="goToTransport"
> >
<q-icon <q-icon
name="directions_car" name="directions_car"
@@ -753,6 +791,7 @@
/> />
<span class="option-title">Trasporti</span> <span class="option-title">Trasporti</span>
<span class="option-subtitle">Condivisione viaggi</span> <span class="option-subtitle">Condivisione viaggi</span>
<span class="option-subtitle"> (IN ARRIVO...)</span>
</div> </div>
</div> </div>
</q-card-section> </q-card-section>

View File

@@ -1,4 +1,4 @@
import { costanti } from '@costanti' import { costanti } from '@costanti';
export let idbKeyval = (() => { export let idbKeyval = (() => {
let db; let db;
@@ -17,18 +17,52 @@ export let idbKeyval = (() => {
openreq.onupgradeneeded = () => { openreq.onupgradeneeded = () => {
// First time setup: create an empty object store // First time setup: create an empty object store
for (const mytab of costanti.MainTables) { for (const mytab of costanti.MainTables) {
openreq.result.createObjectStore(mytab, { keyPath: 'BOMID', autoIncrement: true }); openreq.result.createObjectStore(mytab, {
keyPath: 'BOMID',
autoIncrement: true,
});
for (const mymeth of costanti.allMethod) { for (const mymeth of costanti.allMethod) {
const tab = mymeth + mytab const tab = mymeth + mytab;
openreq.result.createObjectStore(tab, { keyPath: 'BOMID', autoIncrement: true }); openreq.result.createObjectStore(tab, {
keyPath: 'BOMID',
autoIncrement: true,
});
} }
} }
for (const mytab of costanti.OtherTables) { for (const mytab of costanti.OtherTables) {
// console.log('mytab', mytab); // console.log('mytab', mytab);
openreq.result.createObjectStore(mytab, { keyPath: 'BOMID', autoIncrement: true }); openreq.result.createObjectStore(mytab, {
keyPath: 'BOMID',
autoIncrement: true,
});
} }
}; };
const USASYNC = false;
if (USASYNC) {
// ============================================
// NUOVO: Tabelle per sincronizzazione
// ============================================
const syncTables = [
'resps',
'workers',
'groups',
'mygroups',
'products',
'cart',
'orderscart',
'metadata', // Per timestamp
];
for (const table of syncTables) {
if (!db.objectStoreNames.contains(table)) {
db.createObjectStore(table, { keyPath: 'BOMID', autoIncrement: true });
console.log(`Created sync table: ${table}`);
}
}
}
openreq.onsuccess = () => { openreq.onsuccess = () => {
resolve(openreq.result); resolve(openreq.result);
}; };
@@ -50,7 +84,7 @@ export let idbKeyval = (() => {
return { return {
async get(key) { async get(key) {
let req; let req;
await withStore('readonly', 'keyval', store => { await withStore('readonly', 'keyval', (store) => {
req = store.get(key); req = store.get(key);
}); });
return req.result; return req.result;
@@ -62,15 +96,14 @@ export let idbKeyval = (() => {
async getdata(table, key) { async getdata(table, key) {
let req; let req;
await withStore('readonly', table, (store) => {
await withStore('readonly', table, store => {
// console.log('getdata', table, key) // console.log('getdata', table, key)
req = store.get(key); req = store.get(key);
// console.log(' req', req) // console.log(' req', req)
}); });
if (req) { if (req) {
console.log('getdata', table, key, req.result) console.log('getdata', table, key, req.result);
return req.result; return req.result;
} else { } else {
return null; return null;
@@ -79,7 +112,7 @@ export let idbKeyval = (() => {
async getalldata(table) { async getalldata(table) {
try { try {
let result; let result;
await withStore('readonly', table, store => { await withStore('readonly', table, (store) => {
const req = store.getAll(); const req = store.getAll();
req.onsuccess = () => (result = req.result); req.onsuccess = () => (result = req.result);
}); });
@@ -92,7 +125,7 @@ export let idbKeyval = (() => {
async count(table) { async count(table) {
let req; let req;
await withStore('readonly', table, store => { await withStore('readonly', table, (store) => {
req = store.count(); req = store.count();
}); });
@@ -106,7 +139,7 @@ export let idbKeyval = (() => {
}, },
async set(key, value) { async set(key, value) {
let req; let req;
await withStore('readwrite', 'keyval', store => { await withStore('readwrite', 'keyval', (store) => {
req = store.put(value, key); req = store.put(value, key);
}); });
return req.result; return req.result;
@@ -115,24 +148,24 @@ export let idbKeyval = (() => {
let req; let req;
// console.log('setdata', table, value) // console.log('setdata', table, value)
await withStore('readwrite', table, store => { await withStore('readwrite', table, (store) => {
req = store.put(value); req = store.put(value);
}); });
return req.result; return req.result;
}, },
async delete(key) { async delete(key) {
return withStore('readwrite', 'keyval', store => { return withStore('readwrite', 'keyval', (store) => {
store.delete(key); store.delete(key);
}); });
}, },
async deletedata(table, key) { async deletedata(table, key) {
return withStore('readwrite', table, store => { return withStore('readwrite', table, (store) => {
store.delete(key); store.delete(key);
}); });
}, },
async clearalldata(table) { async clearalldata(table) {
// console.log('clearalldata', table) // console.log('clearalldata', table)
return withStore('readwrite', table, store => { return withStore('readwrite', table, (store) => {
store.clear(); store.clear();
}); });
}, },
@@ -142,10 +175,10 @@ export let idbKeyval = (() => {
// iOS add-to-homescreen is missing IDB, or at least it used to. // iOS add-to-homescreen is missing IDB, or at least it used to.
// I haven't tested this in a while. // I haven't tested this in a while.
if (!self.indexedDB) { if (!self.indexedDB) {
console.log('NO indexedDB') console.log('NO indexedDB');
idbKeyval = { idbKeyval = {
get: key => Promise.resolve(localStorage.getItem(key)), get: (key) => Promise.resolve(localStorage.getItem(key)),
set: (key, val) => Promise.resolve(localStorage.setItem(key, val)), set: (key, val) => Promise.resolve(localStorage.setItem(key, val)),
delete: key => Promise.resolve(localStorage.removeItem(key)), delete: (key) => Promise.resolve(localStorage.removeItem(key)),
}; };
} }

289
src/pages/provaris1.vue Normal file
View File

@@ -0,0 +1,289 @@
<template>
<q-page class="ris-wallet-page">
<!-- Header con riepilogo totale -->
<div class="wallet-header">
<div class="header-content">
<h1 class="page-title">I tuoi RIS</h1>
<div class="total-balance">
<span class="balance-label">Totale circuiti attivi</span>
<span class="balance-value">{{ totalCircuits }}</span>
</div>
</div>
</div>
<!-- Azioni rapide globali -->
<div class="quick-actions">
<q-btn flat class="action-btn" @click="handleSendRIS()">
<q-icon name="send" />
<span>Invia</span>
</q-btn>
<q-btn flat class="action-btn" @click="handleReceiveRIS()">
<q-icon name="download" />
<span>Ricevi</span>
</q-btn>
</div>
<!-- Card per ogni circuito -->
<div class="circuits-grid">
<div
v-for="circuit in userCircuits"
:key="circuit.id"
class="circuit-card"
@click="selectCircuit(circuit)"
>
<div class="circuit-header">
<div class="circuit-name">{{ circuit.name }}</div>
<q-badge :color="circuit.isNational ? 'primary' : 'secondary'">
{{ circuit.isNational ? 'Nazionale' : 'Provinciale' }}
</q-badge>
</div>
<div class="circuit-balance">
<div class="balance-amount">{{ formatBalance(circuit.balance) }}</div>
<div class="balance-currency">RIS</div>
</div>
<div class="circuit-limits">
<span>Fido: {{ circuit.fido }}</span>
<span>Min: {{ circuit.minBalance }}</span>
</div>
<div class="circuit-actions">
<q-btn size="sm" flat @click.stop="sendFromCircuit(circuit)">Invia</q-btn>
<q-btn size="sm" flat @click.stop="receiveToCircuit(circuit)">Ricevi</q-btn>
</div>
</div>
</div>
<!-- Transazioni recenti -->
<div class="recent-transactions">
<div class="section-header">
<h2>Transazioni recenti</h2>
<q-btn flat size="sm" @click="viewAllTransactions()">Vedi tutte</q-btn>
</div>
<div class="transaction-list">
<div
v-for="tx in recentTransactions"
:key="tx.id"
class="transaction-item"
@click="viewTransaction(tx)"
>
<q-icon
:name="tx.type === 'sent' ? 'north_east' : 'south_west'"
:class="['tx-icon', tx.type]"
/>
<div class="tx-details">
<div class="tx-user">{{ tx.otherUser }}</div>
<div class="tx-circuit">{{ tx.circuitName }}</div>
</div>
<div class="tx-amount" :class="tx.type">
{{ tx.type === 'sent' ? '-' : '+' }}{{ tx.amount }} RIS
</div>
</div>
</div>
</div>
<!-- Esplora altri circuiti -->
<div class="explore-section">
<q-btn
outline
class="explore-btn"
@click="exploreCircuits()"
>
<q-icon name="explore" />
Esplora altri circuiti
</q-btn>
</div>
</q-page>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const totalCircuits = ref(2);
const userCircuits = ref([]);
const recentTransactions = ref([]);
// Funzioni vuote
const handleSendRIS = () => {};
const handleReceiveRIS = () => {};
const selectCircuit = (circuit: any) => {};
const sendFromCircuit = (circuit: any) => {};
const receiveToCircuit = (circuit: any) => {};
const formatBalance = (balance: number) => balance;
const viewAllTransactions = () => {};
const viewTransaction = (tx: any) => {};
const exploreCircuits = () => {};
</script>
<style lang="scss" scoped>
.ris-wallet-page {
padding: 12px;
}
.wallet-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 16px;
padding: 20px;
margin-bottom: 16px;
color: white;
.page-title {
margin: 0 0 8px 0;
font-size: 24px;
font-weight: 600;
}
.total-balance {
display: flex;
gap: 8px;
font-size: 14px;
opacity: 0.9;
}
}
.quick-actions {
display: flex;
gap: 12px;
margin-bottom: 16px;
.action-btn {
flex: 1;
background: rgba(255,255,255,0.1);
backdrop-filter: blur(10px);
border-radius: 12px;
padding: 16px;
display: flex;
flex-direction: column;
gap: 4px;
}
}
.circuits-grid {
display: grid;
gap: 12px;
margin-bottom: 20px;
@media (min-width: 768px) {
grid-template-columns: repeat(2, 1fr);
}
}
.circuit-card {
background: rgba(255,255,255,0.05);
backdrop-filter: blur(10px);
border: 1px solid rgba(255,255,255,0.1);
border-radius: 16px;
padding: 16px;
cursor: pointer;
transition: all 0.2s;
&:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.circuit-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.circuit-balance {
display: flex;
align-items: baseline;
gap: 4px;
margin-bottom: 8px;
.balance-amount {
font-size: 28px;
font-weight: 700;
}
.balance-currency {
font-size: 16px;
opacity: 0.7;
}
}
.circuit-limits {
display: flex;
gap: 12px;
font-size: 12px;
opacity: 0.7;
margin-bottom: 12px;
}
.circuit-actions {
display: flex;
gap: 8px;
padding-top: 12px;
border-top: 1px solid rgba(255,255,255,0.1);
}
}
.recent-transactions {
margin-bottom: 20px;
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
h2 {
margin: 0;
font-size: 18px;
}
}
}
.transaction-item {
display: flex;
align-items: center;
gap: 12px;
padding: 12px;
background: rgba(255,255,255,0.03);
border-radius: 12px;
margin-bottom: 8px;
cursor: pointer;
.tx-icon {
font-size: 24px;
&.sent { color: #f87171; }
&.received { color: #34d399; }
}
.tx-details {
flex: 1;
.tx-user {
font-weight: 500;
}
.tx-circuit {
font-size: 12px;
opacity: 0.7;
}
}
.tx-amount {
font-weight: 600;
&.sent { color: #f87171; }
&.received { color: #34d399; }
}
}
.explore-section {
text-align: center;
.explore-btn {
width: 100%;
padding: 12px;
}
}
</style>

1118
src/pages/provaris2.vue Normal file

File diff suppressed because it is too large Load Diff

676
src/pages/provaris3.vue Normal file
View File

@@ -0,0 +1,676 @@
<template>
<q-page class="ris-tabs-page">
<q-tabs
v-model="currentTab"
dense
class="ris-tabs"
active-color="primary"
indicator-color="primary"
align="justify"
>
<q-tab name="home" icon="home" label="Home" />
<q-tab name="send" icon="north_east" label="Invia" />
<q-tab name="receive" icon="south_west" label="Ricevi" />
<q-tab name="transactions" icon="receipt" label="Movimenti" />
<q-tab name="circuits" icon="hub" label="Circuiti" />
</q-tabs>
<q-tab-panels v-model="currentTab" animated class="ris-panels">
<!-- Tab Home/Dashboard -->
<q-tab-panel name="home">
<div class="home-summary">
<div class="summary-cards">
<div
v-for="circuit in userCircuits"
:key="circuit.id"
class="summary-card"
@click="goToCircuit(circuit)"
>
<div class="card-badge">{{ circuit.type }}</div>
<div class="card-name">{{ circuit.name }}</div>
<div class="card-balance">
{{ circuit.balance }} <span>RIS</span>
</div>
<div class="card-footer">
<span>Fido: {{ circuit.fido }}</span>
</div>
</div>
</div>
<div class="quick-stats">
<div class="stat-item">
<q-icon name="trending_up" />
<div class="stat-value">{{ totalSent }}</div>
<div class="stat-label">Inviati</div>
</div>
<div class="stat-item">
<q-icon name="trending_down" />
<div class="stat-value">{{ totalReceived }}</div>
<div class="stat-label">Ricevuti</div>
</div>
<div class="stat-item">
<q-icon name="people" />
<div class="stat-value">{{ totalContacts }}</div>
<div class="stat-label">Contatti</div>
</div>
</div>
<div class="recent-activity-preview">
<h3>Ultime transazioni</h3>
<div
v-for="tx in recentTxPreview"
:key="tx.id"
class="preview-item"
@click="goToTransaction(tx)"
>
<q-icon :name="tx.type === 'sent' ? 'north_east' : 'south_west'" />
<span class="preview-user">{{ tx.otherUser }}</span>
<span class="preview-amount" :class="tx.type">
{{ tx.type === 'sent' ? '-' : '+' }}{{ tx.amount }}
</span>
</div>
</div>
</div>
</q-tab-panel>
<!-- Tab Invia -->
<q-tab-panel name="send">
<div class="send-panel">
<div class="panel-header">
<h2>Invia RIS</h2>
</div>
<q-select
v-model="sendCircuit"
:options="userCircuits"
option-label="name"
label="Da circuito"
outlined
class="circuit-select"
/>
<q-input
v-model="sendSearch"
label="Cerca destinatario"
outlined
clearable
@input="searchRecipients"
>
<template v-slot:prepend>
<q-icon name="search" />
</template>
</q-input>
<div class="recipients-section">
<div v-if="recentContacts.length" class="recent-contacts">
<div class="section-title">Usati di recente</div>
<div
v-for="contact in recentContacts"
:key="contact.id"
class="contact-item recent"
@click="selectRecipient(contact)"
>
<q-avatar size="40px" color="primary" text-color="white">
{{ contact.initials }}
</q-avatar>
<div class="contact-info">
<div class="contact-name">{{ contact.name }}</div>
<div class="contact-username">@{{ contact.username }}</div>
</div>
</div>
</div>
<div class="all-contacts">
<div class="section-title">Tutti i contatti</div>
<div
v-for="contact in filteredContacts"
:key="contact.id"
class="contact-item"
@click="selectRecipient(contact)"
>
<q-avatar size="40px" color="secondary" text-color="white">
{{ contact.initials }}
</q-avatar>
<div class="contact-info">
<div class="contact-name">{{ contact.name }}</div>
<div class="contact-username">@{{ contact.username }}</div>
</div>
</div>
</div>
</div>
</div>
</q-tab-panel>
<!-- Tab Ricevi -->
<q-tab-panel name="receive">
<div class="receive-panel">
<div class="panel-header">
<h2>Ricevi RIS</h2>
</div>
<q-select
v-model="receiveCircuit"
:options="userCircuits"
option-label="name"
label="Sul circuito"
outlined
class="circuit-select"
/>
<div class="receive-info">
<q-icon name="info" />
<p>Scegli da chi vuoi ricevere RIS. Il mittente dovrà confermare la transazione.</p>
</div>
<q-input
v-model="receiveSearch"
label="Cerca mittente"
outlined
clearable
@input="searchSenders"
>
<template v-slot:prepend>
<q-icon name="search" />
</template>
</q-input>
<div class="senders-list">
<div
v-for="contact in filteredSenders"
:key="contact.id"
class="contact-item"
@click="selectSender(contact)"
>
<q-avatar size="40px" color="accent" text-color="white">
{{ contact.initials }}
</q-avatar>
<div class="contact-info">
<div class="contact-name">{{ contact.name }}</div>
<div class="contact-username">@{{ contact.username }}</div>
</div>
</div>
</div>
</div>
</q-tab-panel>
<!-- Tab Transazioni -->
<q-tab-panel name="transactions">
<div class="transactions-panel">
<div class="panel-header">
<h2>Tutte le transazioni</h2>
</div>
<div class="filter-bar">
<q-select
v-model="filterCircuit"
:options="[{ id: 'all', name: 'Tutti i circuiti' }, ...userCircuits]"
option-label="name"
outlined
dense
/>
<q-select
v-model="filterType"
:options="filterTypeOptions"
outlined
dense
/>
</div>
<div class="transactions-list">
<div
v-for="tx in allTransactions"
:key="tx.id"
class="transaction-row"
@click="viewTransactionDetail(tx)"
>
<div class="tx-date">{{ tx.date }}</div>
<div class="tx-main">
<q-icon
:name="tx.type === 'sent' ? 'north_east' : 'south_west'"
:class="tx.type"
/>
<div class="tx-info">
<div class="tx-user">{{ tx.otherUser }}</div>
<div class="tx-circuit">{{ tx.circuitName }}</div>
</div>
<div class="tx-amount" :class="tx.type">
{{ tx.type === 'sent' ? '-' : '+' }}{{ tx.amount }} RIS
</div>
</div>
</div>
</div>
</div>
</q-tab-panel>
<!-- Tab Circuiti -->
<q-tab-panel name="circuits">
<div class="circuits-panel">
<div class="panel-header">
<h2>I miei circuiti</h2>
</div>
<div class="my-circuits">
<div
v-for="circuit in userCircuits"
:key="circuit.id"
class="circuit-detail-card"
@click="viewCircuitDetail(circuit)"
>
<div class="circuit-top">
<div class="circuit-name">{{ circuit.name }}</div>
<q-badge :color="circuit.isNational ? 'primary' : 'secondary'">
{{ circuit.type }}
</q-badge>
</div>
<div class="circuit-balance-line">
<span class="balance-label">Saldo:</span>
<span class="balance-value">{{ circuit.balance }} RIS</span>
</div>
<div class="circuit-info-grid">
<div class="info-item">
<span class="info-label">Fido</span>
<span class="info-value">{{ circuit.fido }}</span>
</div>
<div class="info-item">
<span class="info-label">Minimo</span>
<span class="info-value">{{ circuit.minBalance }}</span>
</div>
<div class="info-item">
<span class="info-label">Membri</span>
<span class="info-value">{{ circuit.members }}</span>
</div>
</div>
</div>
</div>
<div class="panel-header">
<h2>Altri circuiti disponibili</h2>
</div>
<div class="available-circuits">
<div
v-for="circuit in availableCircuits"
:key="circuit.id"
class="available-circuit-card"
@click="viewAvailableCircuit(circuit)"
>
<div class="circuit-name">{{ circuit.name }}</div>
<div class="circuit-description">{{ circuit.description }}</div>
<div class="circuit-stats">
<span><q-icon name="people" /> {{ circuit.members }} membri</span>
<span><q-icon name="place" /> {{ circuit.location }}</span>
</div>
<q-btn outline size="sm" @click.stop="requestJoin(circuit)">
Richiedi accesso
</q-btn>
</div>
</div>
</div>
</q-tab-panel>
</q-tab-panels>
</q-page>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const currentTab = ref('home');
const userCircuits = ref([]);
const sendCircuit = ref(null);
const receiveCircuit = ref(null);
const sendSearch = ref('');
const receiveSearch = ref('');
const recentContacts = ref([]);
const filteredContacts = ref([]);
const filteredSenders = ref([]);
const filterCircuit = ref(null);
const filterType = ref('all');
const filterTypeOptions = ref(['Tutte', 'Inviate', 'Ricevute']);
const allTransactions = ref([]);
const recentTxPreview = ref([]);
const totalSent = ref(0);
const totalReceived = ref(0);
const totalContacts = ref(0);
const availableCircuits = ref([]);
// Funzioni vuote
const goToCircuit = (circuit: any) => {};
const goToTransaction = (tx: any) => {};
const searchRecipients = () => {};
const selectRecipient = (contact: any) => {};
const searchSenders = () => {};
const selectSender = (contact: any) => {};
const viewTransactionDetail = (tx: any) => {};
const viewCircuitDetail = (circuit: any) => {};
const viewAvailableCircuit = (circuit: any) => {};
const requestJoin = (circuit: any) => {};
</script>
<style lang="scss" scoped>
.ris-tabs-page {
.ris-tabs {
position: sticky;
top: 0;
z-index: 10;
background: rgba(0,0,0,0.5);
backdrop-filter: blur(10px);
}
.ris-panels {
background: transparent;
}
}
.home-summary {
.summary-cards {
display: grid;
gap: 12px;
margin-bottom: 20px;
@media (min-width: 768px) {
grid-template-columns: repeat(2, 1fr);
}
}
.summary-card {
background: linear-gradient(135deg, rgba(102, 126, 234, 0.2), rgba(118, 75, 162, 0.2));
border: 1px solid rgba(255,255,255,0.1);
border-radius: 16px;
padding: 16px;
cursor: pointer;
.card-badge {
font-size: 11px;
text-transform: uppercase;
opacity: 0.7;
margin-bottom: 8px;
}
.card-name {
font-size: 16px;
font-weight: 600;
margin-bottom: 12px;
}
.card-balance {
font-size: 28px;
font-weight: 700;
margin-bottom: 8px;
span {
font-size: 14px;
opacity: 0.7;
}
}
.card-footer {
font-size: 12px;
opacity: 0.6;
}
}
.quick-stats {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
margin-bottom: 20px;
.stat-item {
text-align: center;
padding: 12px;
background: rgba(255,255,255,0.03);
border-radius: 12px;
.stat-value {
font-size: 20px;
font-weight: 700;
margin: 4px 0;
}
.stat-label {
font-size: 11px;
opacity: 0.6;
}
}
}
.recent-activity-preview {
h3 {
font-size: 16px;
margin-bottom: 12px;
}
.preview-item {
display: flex;
align-items: center;
gap: 12px;
padding: 10px;
background: rgba(255,255,255,0.03);
border-radius: 8px;
margin-bottom: 6px;
.preview-user {
flex: 1;
font-size: 14px;
}
.preview-amount {
font-weight: 600;
&.sent { color: #f87171; }
&.received { color: #34d399; }
}
}
}
}
.send-panel, .receive-panel {
.panel-header {
margin-bottom: 16px;
h2 {
font-size: 20px;
margin: 0;
}
}
.circuit-select {
margin-bottom: 16px;
}
.receive-info {
display: flex;
gap: 12px;
padding: 12px;
background: rgba(33, 150, 243, 0.1);
border-radius: 8px;
margin-bottom: 16px;
font-size: 13px;
}
.recipients-section, .senders-list {
.section-title {
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
opacity: 0.6;
margin: 16px 0 8px;
}
}
.contact-item {
display: flex;
gap: 12px;
align-items: center;
padding: 12px;
background: rgba(255,255,255,0.03);
border-radius: 12px;
margin-bottom: 8px;
cursor: pointer;
&:hover {
background: rgba(255,255,255,0.06);
}
&.recent {
background: rgba(102, 126, 234, 0.1);
}
.contact-info {
flex: 1;
.contact-name {
font-weight: 500;
margin-bottom: 2px;
}
.contact-username {
font-size: 12px;
opacity: 0.6;
}
}
}
}
.transactions-panel {
.filter-bar {
display: flex;
gap: 12px;
margin-bottom: 16px;
}
.transaction-row {
background: rgba(255,255,255,0.03);
border-radius: 12px;
padding: 12px;
margin-bottom: 8px;
.tx-date {
font-size: 11px;
opacity: 0.5;
margin-bottom: 6px;
}
.tx-main {
display: flex;
align-items: center;
gap: 12px;
.tx-info {
flex: 1;
.tx-user {
font-weight: 500;
}
.tx-circuit {
font-size: 12px;
opacity: 0.6;
}
}
.tx-amount {
font-weight: 600;
&.sent { color: #f87171; }
&.received { color: #34d399; }
}
}
}
}
.circuits-panel {
.my-circuits {
margin-bottom: 24px;
}
.circuit-detail-card {
background: rgba(255,255,255,0.05);
border: 1px solid rgba(255,255,255,0.1);
border-radius: 16px;
padding: 16px;
margin-bottom: 12px;
.circuit-top {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
.circuit-name {
font-size: 18px;
font-weight: 600;
}
}
.circuit-balance-line {
display: flex;
justify-content: space-between;
padding: 12px;
background: rgba(255,255,255,0.03);
border-radius: 8px;
margin-bottom: 12px;
.balance-value {
font-size: 20px;
font-weight: 700;
}
}
.circuit-info-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
.info-item {
text-align: center;
.info-label {
display: block;
font-size: 11px;
opacity: 0.6;
margin-bottom: 4px;
}
.info-value {
font-weight: 600;
}
}
}
}
.available-circuit-card {
background: rgba(255,255,255,0.03);
border: 1px solid rgba(255,255,255,0.08);
border-radius: 12px;
padding: 16px;
margin-bottom: 12px;
.circuit-name {
font-size: 16px;
font-weight: 600;
margin-bottom: 8px;
}
.circuit-description {
font-size: 13px;
opacity: 0.8;
margin-bottom: 12px;
}
.circuit-stats {
display: flex;
gap: 16px;
font-size: 12px;
opacity: 0.6;
margin-bottom: 12px;
span {
display: flex;
align-items: center;
gap: 4px;
}
}
}
}
</style>

View File

@@ -5,6 +5,8 @@ $text-color: #111827;
$gray-dark: #4b5563; $gray-dark: #4b5563;
$bg-light: #f9fafb; $bg-light: #f9fafb;
@use 'sass:color';
@mixin center-flex { @mixin center-flex {
display: flex; display: flex;
justify-content: center; justify-content: center;
@@ -64,7 +66,7 @@ section.relative.overflow-hidden {
color: white; color: white;
&:hover { &:hover {
background: darken($primary-color, 10%); background: color.adjust($primary-color, $lightness: -10%);
} }
} }

View File

@@ -561,6 +561,39 @@ function getRoutesAd(site: ISites) {
inmenu: false, inmenu: false,
infooter: false, infooter: false,
}, },
{
active: true,
order: 400,
path: '/provaris1',
materialIcon: 'fas fa-test',
name: 'mypages.provaris1',
component: () => import('@src/pages/provaris1.vue'),
meta: { },
inmenu: false,
infooter: false,
},
{
active: true,
order: 400,
path: '/provaris2',
materialIcon: 'fas fa-test',
name: 'mypages.provaris2',
component: () => import('@src/pages/provaris2.vue'),
meta: { },
inmenu: false,
infooter: false,
},
{
active: true,
order: 400,
path: '/provaris3',
materialIcon: 'fas fa-test',
name: 'mypages.provaris3',
component: () => import('@src/pages/provaris3.vue'),
meta: { },
inmenu: false,
infooter: false,
},
{ {
active: true, active: true,
order: 2000, order: 2000,

View File

@@ -0,0 +1,64 @@
// src/services/SyncService.js
import syncIDB from './idb'; // Usa il wrapper sopra
import Api from './Api';
class SyncService {
constructor() {
this.syncTables = ['resps', 'workers', 'groups', 'mygroups', 'products', 'cart', 'orderscart'];
}
async syncAll(idapp, tablesToSync, forceRefresh = false) {
const syncRequest = {};
for (const table of tablesToSync) {
const lastSync = forceRefresh ? 0 : await syncIDB.getLastSync(table);
syncRequest[table] = { lastSync };
}
console.log('🔄 Sync request:', syncRequest);
try {
const response = await Api.SendReq(
`/sync/${idapp}`,
'POST',
{ tables: syncRequest }
);
if (response.status === 200 && response.data.success) {
await this.processServerData(response.data.data);
return { success: true };
}
} catch (error) {
console.error('Sync failed:', error);
return { success: false, error };
}
}
async processServerData(data) {
for (const [tableName, result] of Object.entries(data)) {
if (result.error) {
console.error(`${tableName}:`, result.error);
continue;
}
const { data: records, timestamp, fullSync } = result;
if (fullSync) {
await syncIDB.replaceTable(tableName, records);
} else {
await syncIDB.mergeRecords(tableName, records);
}
await syncIDB.setLastSync(tableName, timestamp);
console.log(`${tableName}: ${records.length} records (${fullSync ? 'full' : 'incremental'})`);
}
}
async loadFromCache(tableName) {
return syncIDB.getTable(tableName);
}
}
export default new SyncService();

68
src/services/idb.js Normal file
View File

@@ -0,0 +1,68 @@
// src/services/idb.js (nuovo, usa storage.js esistente)
import { idbKeyval } from '@/storage';
class SyncIDB {
constructor() {
this.tables = ['resps', 'workers', 'groups', 'mygroups', 'products', 'cart', 'orderscart'];
}
// Sostituisci intera tabella
async replaceTable(tableName, records) {
await idbKeyval.clearalldata(tableName);
for (const record of records) {
await idbKeyval.setdata(tableName, record);
}
}
// Merge records modificati
async mergeRecords(tableName, records) {
for (const record of records) {
if (record.deleted === true) {
// Trova BOMID e cancella
const existing = await this.findByMongoId(tableName, record._id);
if (existing) {
await idbKeyval.deletedata(tableName, existing.BOMID);
}
} else {
// Controlla se esiste già
const existing = await this.findByMongoId(tableName, record._id);
if (existing) {
// Aggiorna (mantieni BOMID)
record.BOMID = existing.BOMID;
await idbKeyval.setdata(tableName, record);
} else {
// Inserisci nuovo
await idbKeyval.setdata(tableName, record);
}
}
}
}
// Trova record per _id MongoDB
async findByMongoId(tableName, mongoId) {
const allRecords = await idbKeyval.getalldata(tableName);
return allRecords.find(r => r._id === mongoId);
}
// Ottieni tutti i record
async getTable(tableName) {
return await idbKeyval.getalldata(tableName);
}
// Timestamp ultima sync
async getLastSync(tableName) {
const meta = await idbKeyval.getdata('metadata', `${tableName}_lastSync`);
return meta?.value || 0;
}
async setLastSync(tableName, timestamp) {
await idbKeyval.setdata('metadata', {
BOMID: `${tableName}_lastSync`,
value: timestamp
});
}
}
export default new SyncIDB();

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -2324,6 +2324,7 @@ export const colmyUserGroup = [
required: false, required: false,
showWhen: costanti.showWhen.NewRec + costanti.showWhen.InEdit, showWhen: costanti.showWhen.NewRec + costanti.showWhen.InEdit,
visible: false, visible: false,
sortable: false,
}), }),
AddCol({ AddCol({
name: 'visibility', name: 'visibility',
@@ -2508,6 +2509,7 @@ export const colmyGoods = [
required: false, required: false,
showWhen: costanti.showWhen.NewRec + costanti.showWhen.InEdit, showWhen: costanti.showWhen.NewRec + costanti.showWhen.InEdit,
visible: false, visible: false,
sortable: false,
}), }),
AddCol({ AddCol({
name: 'idShipping', name: 'idShipping',

View File

@@ -4232,7 +4232,7 @@ export const tools = {
}, },
sitename() { sitename() {
return this.getappname() return this.getappname();
}, },
getproc() { getproc() {
@@ -4681,9 +4681,9 @@ export const tools = {
return def; return def;
} }
}, },
getCookieBool(mytok: any, def?: any) { getCookieBool(mytok: any, def?: any): boolean {
const ris = Cookies.get(mytok); const ris = Cookies.get(mytok);
if (ris === 'null') return def; if (ris === 'null' || ris === undefined || ris === null) return def;
return ris === '1' ? true : false; return ris === '1' ? true : false;
}, },
@@ -11430,14 +11430,29 @@ export const tools = {
.replace(/[ \t]{2,}/g, ' '); // 2+ spazi → 1 spazio (MA non newline) }, .replace(/[ \t]{2,}/g, ' '); // 2+ spazi → 1 spazio (MA non newline) },
}, },
// Nel tuo file tools.ts o dove hai le utility // Nel tuo file tools.ts o dove hai le utility
convertHTMLForElement(htmlText: string, usacomeHTML?: boolean): string { convertHTMLForElement(
htmlText: string,
usacomeHTML?: boolean,
customCss?: string
): string {
if (!htmlText) return ''; if (!htmlText) return '';
if (!usacomeHTML) { if (!usacomeHTML) {
return htmlText; return htmlText;
} }
return this.converteSpaziMultipliIn1solo(htmlText); let str = this.converteSpaziMultipliIn1solo(htmlText);
if (customCss) {
str = `
<style scoped>
${customCss}
</style>
${str}
`;
}
return str;
}, },
convertinbspInSpazi(str: string) { convertinbspInSpazi(str: string) {
@@ -11459,7 +11474,7 @@ export const tools = {
}, },
isGruppoMacro() { isGruppoMacro() {
return this.getIdApp() === this.IDAPP_MACRO return this.getIdApp() === this.IDAPP_MACRO;
}, },
// FINE ! // FINE !

View File

@@ -56,6 +56,8 @@ import { useCatalogStore } from './CatalogStore';
const stateConnDefault = 'online'; const stateConnDefault = 'online';
const USASYNC = false;
async function getConfig(id: any) { async function getConfig(id: any) {
return globalroutines('read', 'config', null, id); return globalroutines('read', 'config', null, id);
} }
@@ -1271,8 +1273,10 @@ export const useGlobalStore = defineStore('GlobalStore', {
// console.log('loadAfterLogin') // console.log('loadAfterLogin')
this.clearDataAfterLoginOnlyIfActiveConnection(); this.clearDataAfterLoginOnlyIfActiveConnection();
if (tools.isGruppoMacro()) {
await userStore.loadListaEditori(); await userStore.loadListaEditori();
await userStore.loadListaReferenti(); await userStore.loadListaReferenti();
}
await globalroutines('readall', 'config', null); await globalroutines('readall', 'config', null);
@@ -2245,6 +2249,21 @@ export const useGlobalStore = defineStore('GlobalStore', {
// User not exist !! // User not exist !!
} }
if (USASYNC) {
// ============================================
// 3. CARICA TABELLE SYNC DA CACHE
// ============================================
await this.loadSyncTablesFromCache();
// ============================================
// 4. SYNC IN BACKGROUND (non blocca UI)
// ============================================
if (res.data._syncTables && res.data._syncTables.length > 0) {
this.syncTablesInBackground(res.data._syncTables);
}
}
Products.init(); Products.init();
// const isLogged = localStorage.getItem(toolsext.localStorage.username) // const isLogged = localStorage.getItem(toolsext.localStorage.username)
@@ -2306,6 +2325,49 @@ export const useGlobalStore = defineStore('GlobalStore', {
return { ris: false, status }; return { ris: false, status };
}, },
// ============================================
// NUOVI METODI PER SYNC
// ============================================
async loadSyncTablesFromCache() {
const SyncService = (await import('@src/services/SyncService')).default;
// Carica da cache locale (veloce, mostra subito)
this.resps = (await SyncService.loadFromCache('resps')) || [];
this.workers = (await SyncService.loadFromCache('workers')) || [];
this.groups = (await SyncService.loadFromCache('groups')) || [];
this.mygroups = (await SyncService.loadFromCache('mygroups')) || [];
const Products = useProducts();
Products.products = (await SyncService.loadFromCache('products')) || [];
Products.cart = (await SyncService.loadFromCache('cart')) || {
items: [],
totalPrice: 0,
totalQty: 0,
userId: '',
};
Products.orders = (await SyncService.loadFromCache('orderscart')) || [];
},
async syncTablesInBackground(syncTables: any) {
const SyncService = (await import('@src/services/SyncService')).default;
const idapp = tools.getEnv('VITE_APP_ID');
console.log('🔄 Sync in background:', syncTables);
try {
// Sync tutte le tabelle indicate
const result = await SyncService.syncAll(idapp, syncTables);
if (result.success) {
console.log('✓ Sync completato');
// Ricarica dati aggiornati negli store
await this.loadSyncTablesFromCache();
}
} catch (error) {
console.error('Sync background failed:', error);
}
},
getProvinceByProv(provstr: string) { getProvinceByProv(provstr: string) {
const recprov = this.provinces.find((rec: any) => rec.prov === provstr); const recprov = this.provinces.find((rec: any) => rec.prov === provstr);

View File

@@ -1,6 +1,6 @@
<template> <template>
<q-page padding class="signup"> <q-page padding class="signup">
<CSignUp :showcell="false" :showaportador="true" :show_namesurname="true" :need_Telegram="true" :regexpire="regexpire"> <CSignUp :showcell="false" :showaportador="true" :show_namesurname="true" :need_Telegram="false" :regexpire="regexpire">
</CSignUp> </CSignUp>
</q-page> </q-page>

View File

@@ -532,7 +532,7 @@ export default defineComponent({
function getRegulation(reg: string) { function getRegulation(reg: string) {
const strreg = reg + ''; const strreg = reg + '';
if (!reg) { if (!reg) {
const mystringa = t('circuit.regolamento', { nomecircuito: circuit.value!.name }); const mystringa = circuitStore.transformRegulationHTML(circuit.value!.name);
return mystringa; return mystringa;
} else { } else {
return reg; return reg;

View File

@@ -307,7 +307,7 @@ export default defineComponent({
const strreg = reg + ''; const strreg = reg + '';
if (!reg) { if (!reg) {
let name = circuitSel.value; let name = circuitSel.value;
const mystringa = t('circuit.regolamento', { nomecircuito: name }); const mystringa = circuitStore.transformRegulationHTML(name);
return mystringa; return mystringa;
} else { } else {
return reg; return reg;