- 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(
' [ VER-' +
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-base: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
$gradient-primary: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
.stat-card {
position: relative;
width: 100%;
@@ -15,7 +17,7 @@ $transition-base: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
padding: 0;
border-radius: $card-radius-desktop;
background: linear-gradient(145deg, #ffffff 0%, #f8f9fa 100%);
box-shadow:
box-shadow:
0 10px 30px -5px rgba(0, 0, 0, 0.1),
0 0 0 1px rgba(0, 0, 0, 0.02);
transition: $transition-base;
@@ -24,7 +26,7 @@ $transition-base: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
&:hover {
transform: translateY(-6px) scale(1.02);
box-shadow:
box-shadow:
0 20px 40px -8px rgba(0, 0, 0, 0.15),
0 0 0 1px rgba(0, 0, 0, 0.03);
@@ -108,7 +110,7 @@ $transition-base: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
height: 64px;
border-radius: 50%;
background: linear-gradient(145deg, #ffffff, #f5f5f5);
box-shadow:
box-shadow:
0 4px 12px rgba(0, 0, 0, 0.08),
inset 0 1px 2px rgba(255, 255, 255, 0.9);
@@ -158,26 +160,26 @@ $transition-base: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.stat-title {
font-size: 0.75rem;
font-size: 1rem;
font-weight: 600;
letter-spacing: 0.03em;
line-height: 1.3;
opacity: 0.85;
text-transform: uppercase;
margin: 4px 0;
color: #424242;
color: #718096;
@media (max-width: 960px) {
font-size: 0.7rem;
font-size: 0.9rem;
}
@media (max-width: 718px) {
font-size: 0.65rem;
font-size: 0.8rem;
margin: 2px 0;
}
@media (max-width: 480px) {
font-size: 0.6rem;
font-size: 0.8rem;
letter-spacing: 0.02em;
}
}
@@ -195,11 +197,14 @@ $transition-base: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.stat-value {
font-size: 1.5rem;
font-weight: 700;
line-height: 1.1;
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) {
font-size: 1.375rem;
@@ -223,7 +228,7 @@ $transition-base: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
font-size: 0.625rem;
font-weight: 600;
color: white;
box-shadow:
box-shadow:
0 3px 8px rgba(0, 0, 0, 0.2),
inset 0 1px 2px rgba(255, 255, 255, 0.2);
backdrop-filter: blur(8px);
@@ -276,12 +281,10 @@ $transition-base: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
left: -50%;
width: 200%;
height: 200%;
background: linear-gradient(
45deg,
transparent 30%,
rgba(255, 255, 255, 0.4) 50%,
transparent 70%
);
background: linear-gradient(45deg,
transparent 30%,
rgba(255, 255, 255, 0.4) 50%,
transparent 70%);
transform: translateX(-100%) translateY(-100%);
transition: transform 0.8s ease;
pointer-events: none;
@@ -294,8 +297,8 @@ $transition-base: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
border-radius: inherit;
padding: 2px;
background: linear-gradient(145deg, rgba(33, 150, 243, 0.3), rgba(156, 39, 176, 0.3));
-webkit-mask:
linear-gradient(#fff 0 0) content-box,
-webkit-mask:
linear-gradient(#fff 0 0) content-box,
linear-gradient(#fff 0 0);
-webkit-mask-composite: xor;
mask-composite: exclude;
@@ -307,10 +310,13 @@ $transition-base: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
// Animations
@keyframes pulse {
0%, 100% {
0%,
100% {
opacity: 0.3;
transform: scale(1);
}
50% {
opacity: 0.5;
transform: scale(1.05);
@@ -318,9 +324,12 @@ $transition-base: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
@keyframes bounce-subtle {
0%, 100% {
0%,
100% {
transform: translateY(0);
}
50% {
transform: translateY(-2px);
}
@@ -339,9 +348,11 @@ $transition-base: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
opacity: 0;
transform: scale(0.5) translateY(10px);
}
60% {
transform: scale(1.1) translateY(-2px);
}
100% {
opacity: 1;
transform: scale(1) translateY(0);
@@ -398,12 +409,12 @@ $transition-base: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
.body--dark {
.stat-card {
background: linear-gradient(145deg, #1e1e1e 0%, #2a2a2a 100%);
box-shadow:
box-shadow:
0 10px 30px -5px rgba(0, 0, 0, 0.4),
0 0 0 1px rgba(255, 255, 255, 0.05);
&:hover {
box-shadow:
box-shadow:
0 20px 40px -8px rgba(0, 0, 0, 0.5),
0 0 0 1px rgba(255, 255, 255, 0.08);
}
@@ -411,7 +422,7 @@ $transition-base: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
.stat-icon-wrapper {
background: linear-gradient(145deg, #2a2a2a, #1e1e1e);
box-shadow:
box-shadow:
0 4px 12px rgba(0, 0, 0, 0.3),
inset 0 1px 2px rgba(255, 255, 255, 0.05);
}

View File

@@ -6,18 +6,16 @@
<div :class="['stat-content', classColor]">
<!-- Icon con effetto hover e glow -->
<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
:name="icon"
:class="['stat-icon', classColor]"
/>
</div>
<!-- Title -->
<div class="stat-title">
{{ title }}
</div>
<!-- Value con tipografia gerarchica -->
<div class="stat-value-container">
<div class="stat-value">
@@ -32,11 +30,19 @@
:style="{ backgroundColor: colBack }"
>
<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>
</div>
</transition>
</div>
<!-- Title -->
<div class="stat-title">
{{ title }}
</div>
</div>
<!-- Decorative elements -->
@@ -45,9 +51,8 @@
</q-card>
</template>
<script lang="ts" src="./CElemStat.ts">
</script>
<script lang="ts" src="./CElemStat.ts"></script>
<style lang="scss" scoped>
@import "./CElemStat.scss";
@import './CElemStat.scss';
</style>

View File

@@ -6,6 +6,9 @@ $border-radius: 10px;
$transition-speed: 0.3s;
$mobile-breakpoint: 768px;
@use 'sass:color';
$shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.06);
$shadow-md: 0 2px 6px rgba(0, 0, 0, 0.08);
$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 {
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) {
width: 100%;
padding: 4px; // Ridotto su mobile
}
}

View File

@@ -1451,84 +1451,95 @@ export default defineComponent({
}
function onRequest(myprops: any) {
const { page, rowsPerPage, rowsNumber, sortBy, descending } = myprops.pagination;
const myfilternow = myfilter.value;
const myfilterandnow = myfilterand.value;
try {
const { page, rowsPerPage, rowsNumber, sortBy, descending } = myprops.pagination;
const myfilternow = myfilter.value;
const myfilterandnow = myfilterand.value;
savefilter();
savefilter();
if (!mytable.value) {
startsearch.value = false;
return;
}
// console.log('myfilterandnow', myfilterandnow, 'myfilterandnow', myfilterandnow)
// console.log('onRequest', 'myfilter = ', myfilter.value)
loading.value = true;
spinner_visible.value = true;
// update rowsCount with appropriate value
// function all rows if "All" (0) is rowsel
const fetchCount = rowsPerPage === 0 ? rowsNumber : rowsPerPage;
// calculate starting row of data
const startRow = (page - 1) * rowsPerPage;
const endRow = startRow + fetchCount;
console.log('onRequest: startRow', startRow, 'endRow', endRow);
serverData.value = [];
// fetch data from "server"
return fetchFromServer(
startRow,
endRow,
myfilternow,
myfilterandnow,
sortBy,
descending
).then((ris: any) => {
pagination.value.rowsNumber = getRowsNumberCount();
// clear out existing data and add new
if (!returnedData.value || returnedData.value.length === 0) {
serverData.value = [];
} else {
// if (serverData.length > 0)
// serverData.splice(0, serverData.length, ...returnedData)
// else
try {
serverData.value = [...returnedData.value];
} catch (e) {
serverData.value = [];
}
if (!mytable.value) {
startsearch.value = false;
return;
}
// console.log('serverData', serverData)
// console.log('myfilterandnow', myfilterandnow, 'myfilterandnow', myfilterandnow)
// don't forfunction to update local pagination object
pagination.value.page = page;
pagination.value.rowsPerPage = rowsPerPage;
//pagination.value.sortBy = getObjSort(sortBy, descending)
pagination.value.sortBy = sortBy;
// ordinam.value = sortBy
ordinam_desc.value = descending;
pagination.value.descending = descending;
// console.log('onRequest', 'myfilter = ', myfilter.value)
// console.log('pagination', pagination)
loading.value = true;
// ...and turn of loading indicator
loading.value = false;
spinner_visible.value = false;
changetable.value = false;
startsearch.value = false;
spinner_visible.value = true;
checkScrollPosition();
});
// update rowsCount with appropriate value
// function all rows if "All" (0) is rowsel
const fetchCount = rowsPerPage === 0 ? rowsNumber : rowsPerPage;
// calculate starting row of data
const startRow = (page - 1) * rowsPerPage;
const endRow = startRow + fetchCount;
console.log('onRequest: startRow', startRow, 'endRow', endRow);
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"
return fetchFromServer(
startRow,
endRow,
myfilternow,
myfilterandnow,
sortBy,
descending
).then((ris: any) => {
pagination.value.rowsNumber = getRowsNumberCount();
// clear out existing data and add new
if (!returnedData.value || returnedData.value.length === 0) {
serverData.value = [];
} else {
// if (serverData.length > 0)
// serverData.splice(0, serverData.length, ...returnedData)
// else
try {
serverData.value = [...returnedData.value];
} catch (e) {
serverData.value = [];
}
}
// console.log('serverData', serverData)
// don't forfunction to update local pagination object
pagination.value.page = page;
pagination.value.rowsPerPage = rowsPerPage;
//pagination.value.sortBy = getObjSort(sortBy, descending)
pagination.value.sortBy = sortBy;
// ordinam.value = sortBy
ordinam_desc.value = descending;
pagination.value.descending = descending;
// console.log('pagination', pagination)
// ...and turn of loading indicator
loading.value = false;
spinner_visible.value = false;
changetable.value = false;
startsearch.value = false;
checkScrollPosition();
});
} catch (e) {
console.error('Error onrequest', e);
}
}
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) => {
refresh()
})*/

View File

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

View File

@@ -10,6 +10,8 @@ $negative-color: #c10015;
$info-color: #31ccec;
$warning-color: #f2c037;
@use 'sass:color';
$grey-text: #555;
$grey-light: #999;
$grey-dark: #333;
@@ -658,7 +660,7 @@ $gradient-hover: linear-gradient(135deg, rgba(102, 126, 234, 0.05) 0%, rgba(118,
.accom_maxosp {
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-text-fill-color: transparent;
background-clip: text;

View File

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

View File

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

View File

@@ -35,7 +35,7 @@ $mobile-breakpoint: 768px;
// ========================================
.text-subtitle2 {
&.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-text-fill-color: transparent;
background-clip: text;
@@ -116,7 +116,7 @@ $mobile-breakpoint: 768px;
// Colori con gradienti
&[class*='bg-primary'],
&[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);
&:hover {
@@ -127,7 +127,7 @@ $mobile-breakpoint: 768px;
&[class*='bg-secondary'],
&[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);
&:hover {
@@ -138,7 +138,7 @@ $mobile-breakpoint: 768px;
&[class*='bg-positive'],
&[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);
&:hover {
@@ -161,7 +161,7 @@ $mobile-breakpoint: 768px;
&[class*='bg-warning'],
&[class*='bg-orange'],
&[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);
&:hover {
@@ -182,7 +182,7 @@ $mobile-breakpoint: 768px;
}
&[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);
&:hover {
@@ -192,7 +192,7 @@ $mobile-breakpoint: 768px;
}
&[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);
&:hover {
@@ -203,7 +203,7 @@ $mobile-breakpoint: 768px;
&[class*='bg-grey'],
&[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);
&:hover {

View File

@@ -3,113 +3,703 @@
flex: 1;
}
.regulation-container {
:deep(.regulation-content) {
max-width: 900px;
margin: 0 auto;
// CSS MODERNO PER REGOLAMENTO
.reg-header {
text-align: center;
margin-bottom: 2rem;
padding-bottom: 1.5rem;
border-bottom: 2px solid #6b8e23;
// Variabili
$s-xs: 4px;
$s-sm: 8px;
$s-md: 12px;
$s-lg: 16px;
$s-xl: 24px;
h1 {
font-size: 2rem;
font-weight: 700;
color: #6b8e23;
margin: 0;
}
$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 {
background: $gradient-primary;
color: white;
padding: $s-xl;
border-radius: $r-lg;
text-align: center;
margin-bottom: $s-xl;
box-shadow: 0 8px 24px rgba(102, 126, 234, 0.3);
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 {
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;
}
.reg-section {
margin-bottom: 2.5rem;
.section-title {
font-size: 1.5rem;
font-weight: 600;
color: #556b2f;
margin-bottom: 1rem;
padding-bottom: 0.5rem;
border-bottom: 1px solid rgba(107, 142, 35, 0.3);
}
.section-content {
font-size: 1rem;
line-height: 1.7;
color: #333;
text-align: justify;
margin-bottom: 1rem;
strong {
color: #6b8e23;
font-weight: 600;
}
}
.section-list {
list-style: none;
padding-left: 0;
scssli {
padding: 0.75rem 0 0.75rem 3rem; // aumenta da 2rem a 3rem
position: relative;
line-height: 1.6;
&:before {
content: "";
position: absolute;
left: 1rem; // aumenta da 0.5rem a 1rem
color: #6b8e23;
font-size: 1.5rem;
line-height: 1.6;
}
}
}
.highlight-box {
background: rgba(107, 142, 35, 0.08);
border-left: 4px solid #6b8e23;
padding: 1rem 1.5rem;
margin: 1rem 0;
border-radius: 4px;
strong {
display: block;
margin-bottom: 0.5rem;
}
}
}
.reg-footer {
margin-top: 3rem;
padding-top: 1.5rem;
border-top: 1px solid rgba(107, 142, 35, 0.3);
font-size: 0.95rem;
color: #666;
p {
margin: 0;
color: #64748b;
line-height: 1.6;
}
}
}
// Responsive
@media (max-width: 768px) {
.regulation-container :deep(.regulation-content) {
.reg-header h1 {
font-size: 1.5rem;
// Sezioni
.reg-section {
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;
}
.reg-section {
.section-title {
font-size: 1.25rem;
}
.section-title {
margin: 0;
font-size: 1.5rem;
font-weight: 700;
color: #1e293b;
}
}
.section-content {
text-align: left;
}
.section-body {
.section-content {
line-height: 1.8;
color: #475569;
margin-bottom: $s-lg;
.section-list li {
padding-left: 1.5rem;
strong {
color: #667eea;
font-weight: 600;
}
}
}
}
// Info boxes
.info-box {
display: flex;
gap: $s-md;
padding: $s-lg;
border-radius: $r-md;
margin: $s-lg 0;
align-items: flex-start;
&.primary {
background: linear-gradient(135deg, rgba(102, 126, 234, 0.1) 0%, rgba(118, 75, 162, 0.1) 100%);
border-left: 4px solid #667eea;
}
&.success {
background: linear-gradient(135deg, rgba(16, 185, 129, 0.1) 0%, rgba(5, 150, 105, 0.1) 100%);
border-left: 4px solid #10b981;
}
&.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;
flex-shrink: 0;
}
.info-text {
flex: 1;
line-height: 1.6;
color: #475569;
strong {
display: block;
margin-bottom: $s-xs;
color: #1e293b;
}
}
}
// Timeline
.timeline {
margin: $s-xl 0;
position: relative;
&::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
@media (max-width: 600px) {
.regulation-content {
padding: $s-sm;
}
.reg-header h1 {
font-size: 1.5rem;
}
.reg-section {
padding: $s-lg;
.section-number {
font-size: 2rem;
top: $s-sm;
right: $s-sm;
}
.section-header .section-title {
font-size: 1.2rem;
}
}
.timeline::before {
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 account = computed(() => {
if (groupnameSel.value) {
return userStore.getAccountGroupByCircuitId(circuit.value._id, groupnameSel.value);
return userStore.getAccountGroupByCircuitId(
circuit.value._id,
groupnameSel.value
);
} else {
return circuit.value ? userStore.getAccountByCircuitId(circuit.value._id) : null;
}
@@ -164,133 +167,9 @@ export default defineComponent({
}
// 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);
return {

View File

@@ -459,4 +459,10 @@ h1 {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.scroll-input {
:deep(textarea) {
max-height: 400px;
overflow-y: auto;
}
}

View File

@@ -931,7 +931,6 @@
@update:model-value="modifElem"
>
</q-toggle>
<CMyEditor
v-model:value="myel.containerHtml"
title=""
@@ -941,9 +940,35 @@
@update:value="modifElem"
@showandsave="saveElem"
:start-in-code-mode="myel.parambool2"
:custom-styles="myel.container2"
>
</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 v-else-if="myel.type === shared_consts.ELEMTYPE.IMAGE">

View File

@@ -2,3 +2,36 @@
display: flex;
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 { 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 { useI18n } from 'vue-i18n';
@@ -24,6 +24,11 @@ export default defineComponent({
required: false,
default: '',
},
customStyles: {
type: String,
required: false,
default: '',
},
showButtons: {
type: Boolean,
required: false,
@@ -62,6 +67,21 @@ export default defineComponent({
const myvalue = 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({
arial: 'Arial',
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) {
// Crea un elemento temporaneo per convertire HTML in testo
const div = document.createElement('div');
@@ -197,6 +256,12 @@ export default defineComponent({
characterCount.value = getTextLength(myvalue.value);
if (props.customStyles) {
setTimeout(() => {
applyCustomStyles(props.customStyles);
}, 300);
}
if (props.startInCodeMode) {
// Attiva modalità codice di default
setTimeout(() => {
@@ -231,6 +296,13 @@ export default defineComponent({
onMounted(mounted);
// Cleanup quando componente viene distrutto
onUnmounted(() => {
if (styleElement.value && styleElement.value.parentNode) {
styleElement.value.parentNode.removeChild(styleElement.value);
}
});
return {
myfonts,
toolbarcomp,
@@ -248,6 +320,8 @@ export default defineComponent({
showtools,
characterCount,
t,
applyCustomStyles,
editorId,
};
},
});

View File

@@ -15,9 +15,19 @@
v-close-popup
></q-btn>
</q-toolbar>
<q-card-section class="inset-shadow" style="padding: 4px !important">
<CTitleBanner v-if="title" :title="title"></CTitleBanner>
<form autocapitalize="off" autocomplete="off" spellcheck="false">
<q-card-section
class="inset-shadow"
style="padding: 4px !important"
>
<CTitleBanner
v-if="title"
:title="title"
></CTitleBanner>
<form
autocapitalize="off"
autocomplete="off"
spellcheck="false"
>
<q-toggle
v-if="!hideTools"
v-model="showtools"
@@ -25,34 +35,53 @@
@click="tools.setCookie('showtools', showtools ? '1' : '0')"
></q-toggle>
<br />
<q-btn v-if="showtools && !hideTools" rounded size="sm" color="primary">
<q-icon name="colorize" class="cursor-pointer">
<q-btn
v-if="showtools && !hideTools"
rounded
size="sm"
color="primary"
>
<q-icon
name="colorize"
class="cursor-pointer"
>
<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-icon>
</q-btn>
<q-editor
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"
<div
class="cmyeditor-wrapper"
:data-editor-id="editorId"
>
</q-editor>
<div v-if="maxlength" class="text-gray text-italic">Caratteri: {{ characterCount }} / {{ maxlength }}</div>
<q-editor
ref="editorRef"
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"
>
Caratteri: {{ characterCount }} / {{ maxlength }}
</div>
</form>
</q-card-section>
<q-card-actions v-if="showButtons" align="center">
<q-card-actions
v-if="showButtons"
align="center"
>
<q-btn
v-if="canModify"
:label="$t('dialog.ok')"
@@ -78,8 +107,7 @@
</div>
</template>
<script lang="ts" src="./CMyEditor.ts">
</script>
<script lang="ts" src="./CMyEditor.ts"></script>
<style lang="scss" scoped>
@import './CMyEditor.scss';

View File

@@ -8,6 +8,7 @@ import {
watch,
nextTick,
onUnmounted,
onUpdated,
} from 'vue';
import type { IOptCatalogo, ICoordGPS, IMyElem, ISocial } from '@src/model';
@@ -206,6 +207,8 @@ export default defineComponent({
const enableAdd = ref(true);
const visushare = ref(false);
const htmlContainer = ref(null);
const tabcatalogo = ref('griglia');
const enablePwa = computed(() => globalStore.site.confpages?.enablePwa);
@@ -338,6 +341,10 @@ export default defineComponent({
canShowVersion.value = true;
}, 60000);
setTimeout(() => {
executeScript();
}, 500);
if (props.myelem) newtype.value = props.myelem.type;
nextTick(() => {
@@ -442,6 +449,23 @@ export default defineComponent({
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 {
onInvitoInviato,
onTelegramClick,
@@ -494,6 +518,7 @@ export default defineComponent({
enablePwa,
mostraInviti,
nascondiBottone,
htmlContainer,
};
},
});

View File

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

View File

@@ -11,6 +11,8 @@ $negative-color: #c10015;
$info-color: #31ccec;
$warning-color: #f2c037;
@use 'sass:color';
$grey-text: #555;
$grey-light: #999;
@@ -24,8 +26,8 @@ $shadow-lg: 0 4px 16px rgba(0, 0, 0, 0.15);
$mobile-breakpoint: 768px;
// Gradiente speciale per gruppi
$gradient-group-primary: linear-gradient(135deg, $secondary-color 0%, lighten($secondary-color, 15%) 100%);
$gradient-group-hover: linear-gradient(135deg, darken($secondary-color, 5%) 0%, $secondary-color 100%);
$gradient-group-primary: linear-gradient(135deg, $secondary-color 0%, color.adjust($secondary-color, $lightness: 15%) 100%);
$gradient-group-hover: linear-gradient(135deg, color.adjust($secondary-color, $lightness: -5%) 0%, $secondary-color 100%);
// ========================================
// 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)
&[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);
&: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);
transform: translateY(-2px);
}
@@ -513,7 +515,7 @@ $gradient-group-hover: linear-gradient(135deg, darken($secondary-color, 5%) 0%,
border-radius: 12px;
background: linear-gradient(135deg, rgba($info-color, 0.1), rgba($info-color, 0.05));
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-weight: 600;
margin-left: 8px;

View File

@@ -1,3 +1,5 @@
@use "sass:color";
// ========================================
// VARIABILI
// ========================================
@@ -28,17 +30,17 @@ $mobile-breakpoint: 768px;
@media (max-width: $mobile-breakpoint) {
margin: 2px auto;
}
&.is-even {
.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);
}
}
&.is-odd {
.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);
}
}
@@ -167,7 +169,7 @@ $mobile-breakpoint: 768px;
flex: 1;
position: relative;
overflow: hidden;
// L'altezza viene gestita dinamicamente dal Vue
}
@@ -280,26 +282,29 @@ $mobile-breakpoint: 768px;
}
.tag-chip {
height: 19px;
font-size: 0.6875rem;
padding: 0 5px;
height: 20px;
font-size: 0.9rem;
padding: 0 px;
border-radius: 4px;
box-shadow: none;
font-weight: 500;
&.subsector {
background: linear-gradient(135deg, $positive-color, #26a69a);
// Categoria principale - più scura e intensa
&.sector {
background: linear-gradient(135deg, $primary-color, #1976d2);
color: white;
}
&.sector {
background: linear-gradient(135deg, $primary-color, #42a5f5);
// Sottocategoria - stessa base ma più chiara
&.subsector {
background: linear-gradient(135deg, color.adjust($primary-color, $lightness: 10%), #42a5f5);
color: white;
opacity: 0.9; // Opzionale: leggera trasparenza
}
@media (max-width: $mobile-breakpoint) {
height: 17px;
font-size: 0.625rem;
height: 19px;
font-size: 0.8rem;
padding: 0 4px;
}
@@ -505,4 +510,21 @@ $mobile-breakpoint: 768px;
@media (max-width: $mobile-breakpoint) {
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(() => {
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(
() => props.prop_myrec,
@@ -168,6 +174,8 @@ export default defineComponent({
globalStore,
computedWidth,
computedEventImageHeight,
arrSubSector,
arrSector,
};
},
});

View File

@@ -153,22 +153,47 @@
>
<!-- Tags e Chips -->
<q-item-label class="tags-row">
<q-chip
v-for="(rec, ind) of tools.getArrSubSector(table, myrec)"
: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"
dense
class="tag-chip sector"
>
{{ rec.descr }}
</q-chip>
<div class="category-hierarchy">
<q-chip
v-for="(rec, ind) of arrSector"
:key="'sec-' + ind"
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 }}
</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">
<q-badge
v-if="

View File

@@ -11,6 +11,8 @@ $negative-color: #c10015;
$info-color: #31ccec;
$warning-color: #f2c037;
@use 'sass:color';
$grey-text: #555;
$grey-light: #999;
@@ -165,7 +167,8 @@ $mobile-breakpoint: 768px;
border-radius: 6px;
background: linear-gradient(135deg, rgba($info-color, 0.1), rgba($info-color, 0.05));
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;
&:hover {

View File

@@ -15,6 +15,12 @@ $r-md: 10px;
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
.balance-header {
display: flex;
@@ -30,7 +36,7 @@ $r-md: 10px;
}
.balance-current {
font-size: 1.1rem;
font-size: 1.2rem;
font-weight: 800;
&.negative {
@@ -166,13 +172,21 @@ $r-md: 10px;
padding-top: $s-md;
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 {
display: flex;
align-items: center;
gap: $s-xs;
.availability-text {
font-size: 1rem;
font-size: 0.8rem;
opacity: 0.95;
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
@@ -192,7 +213,7 @@ $r-md: 10px;
}
.balance-current {
font-size: 1rem;
font-size: 1.1rem;
}
}

View File

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

View File

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

View File

@@ -9,6 +9,8 @@ $positive-color: #21ba45;
$negative-color: #c10015;
$warning-color: #f2c037;
@use 'sass:color';
$border-radius: 16px;
$border-radius-sm: 12px;
$border-radius-lg: 24px;
@@ -1040,7 +1042,7 @@ $mobile-footer-height: 80px;
background: linear-gradient(135deg, $primary-color, $primary-dark);
&: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);
}
}

View File

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

View File

@@ -1,336 +1,230 @@
<template>
<div>
<div class="status-reg-modern">
<div v-if="visustat">
<CTitleBanner class="q-pa-xs" :title="$t('pages.status')" bgcolor="bg-primary" 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>
<CTitleBanner class="title-banner" :title="t('pages.status')" bgcolor="bg-primary"
clcolor="text-white" mystyle="" myclass="sfondo_gradiente_blu myshad" :canopen="true">
<!--<CCardState :mytext="$t('statusreg.autorizzare')" :myval="datastat.num_autorizzare"
mycolor="yellow" :myperc="(datastat.num_autorizzare / datastat.num_teleg_attivo) * 100"></CCardState>
-->
<div class="stats-wrapper">
<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">
<CCardStat :mytext="$t('stat.accepted')" :myval="datastat.num_part_accepted"></CCardStat>
</div>
<CElemStat myclass="stat-card" :title="t('statusreg.online_today')" icon="fas fa-wifi"
:mytextval="tools.numtostr(datastat.online_today)"
classColor="text-orange" colBack="yellow" />
<q-list bordered v-if="tools.isLogged()">
<q-expansion-item group="somegroup" icon="fas fa-user-plus" :label="$t('statusreg.newreg')" default-opened
header-class="text-primary">
<q-card>
<q-card-section>
<div class="q-pa-md" style="max-width: 350px; margin: auto">
<TransitionGroup name="fade" appear enter-active-class="animazione fadeIn"
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-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>
<span v-html="tools.getUserNameOnlyIfToShow(user, null, {
showprov: false,
html: true,
})"></span>
</q-item-label>
<q-item-label class="iscritto_da">
{{ t("statusreg.invite_by") }}:
<span class="iscritto_da_name">{{
tools.getNameToShow(user.user_aportador)
}}</span>
</q-item-label>
</q-item-section>
<q-item-section side>
<q-item-label style="color: white">{{
tools.getstrshortDate(user.date_reg)
}}</q-item-label>
</q-item-section>
</q-item>
</TransitionGroup>
<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>
<q-list bordered class="expansion-list">
<q-expansion-item group="somegroup" icon="fas fa-user-plus" :label="t('statusreg.newreg')"
default-opened header-class="text-primary expansion-header">
<q-card class="expansion-card">
<q-card-section class="card-content">
<q-item v-for="(user, index) in lastsreg" :key="index"
class="user-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 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 class="iscritto_da">
{{ t("statusreg.invite_by") }}:
<span class="iscritto_da_name">{{ tools.getNameToShow(user.user_aportador) }}</span>
</q-item-label>
</q-item-section>
<q-item-section side>
<q-item-label class="date-label">{{ tools.getstrshortDate(user.date_reg) }}</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-medal"
:label="t('statusreg.lastsharedlink')" header-class="text-purple expansion-header">
<q-card class="expansion-card">
<q-card-section class="card-content">
<div class="info-section">
<div class="info-title">Unisciti a {{tools.sitename()}}</div>
<div class="info-text">
Se ancora non sei registrato a {{tools.sitename()}}, scegli un invitante che conosci.
</div>
</q-card-section>
</q-card>
</q-expansion-item>
<q-separator />
<q-expansion-item expand-separator group="somegroup" icon="fas fa-medal"
:label="$t('statusreg.lastsharedlink')" header-class="text-purple">
<div>
<div class="text-center text-bold text-h6">Unisciti a {{tools.sitename()}}</div>
<div class="text-center">
Se ancora non sei registrato a {{tools.sitename()}}, scegli un invitante che
conosci. Questa persona dovrà ammetterti per permetterti di
accedere alle funzionalità.
</div>
</div>
<q-item v-for="(user, index) in lastssharedlink" :key="index" class="animated chip_shadow q-ma-sm"
clickable v-ripple @click="gotoPage(`/registrati/${user.user_aportador.username}`)">
<q-item-section avatar>
<q-avatar round size="48px">
<img :src="userStore.getImgByProfile(user.user_aportador)" />
</q-avatar>
</q-item-section>
<q-item-section class="">
<q-item-label>
<span v-html="tools.getNameToShow(user.user_aportador, null, {
showprov: true,
html: true,
})
"></span>
</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">
{{ t("statusreg.has_invited") }}:
<span class="iscritto_da_name">{{
tools.getNameToShow(user)
}}</span>
</q-item-label>
</q-item-section>
<q-item-section side><span class="text-h6 q-mr-sm"></span></q-item-section>
</q-item>
</q-expansion-item>
<q-item v-for="(user, index) in lastssharedlink" :key="index"
class="user-item" clickable v-ripple
@click="gotoPage(`/registrati/${user.user_aportador.username}`)">
<q-item-section avatar>
<q-avatar round size="44px">
<img :src="userStore.getImgByProfile(user.user_aportador)" />
</q-avatar>
</q-item-section>
<q-item-section>
<q-item-label class="user-name">
<span v-html="tools.getNameToShow(user.user_aportador, null, { showprov: true, html: true })"></span>
</q-item-label>
<q-item-label caption class="username-caption">
<span v-html="tools.getUserNameOnlyIfToShow(user.user_aportador, null, { showprov: false, html: true })"></span>
</q-item-label>
<q-item-label class="iscritto_da">
{{ t("statusreg.has_invited") }}:
<span class="iscritto_da_name">{{ tools.getNameToShow(user) }}</span>
</q-item-label>
</q-item-section>
</q-item>
</q-card-section>
</q-card>
</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>
<q-separator />
<q-expansion-item group="somegroup" icon="fas fa-wifi"
:label="t('statusreg.onlineusers', { today: datastat.online_today })"
header-class="text-teal expansion-header">
<q-card class="expansion-card">
<q-card-section class="card-content">
<q-item v-for="(user, index) in lastsonline" :key="index"
class="user-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 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>
</q-card-section>
</q-card>
</q-expansion-item>
</div>
<q-separator />
<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>
<div class="index_diffusore">{{ index + 1 }}°</div>
</q-item-label>
<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">
{{ tools.getUserNameOnlyIfToShow(user, null, { showprov: false, html: true }) }}
</q-item-label>
</q-item-section>
<q-item-section side>
<div class="ranking-count">{{ user.count }}</div>
</q-item-section>
</q-item>
</q-card-section>
</q-card>
</q-expansion-item>
<q-expansion-item v-if="false" expand-separator group="somegroup" icon="fas fa-medal"
:label="$t('statusreg.diffusori')" header-class="text-purple">
<q-card>
<q-card-section>
<div class="q-pa-md" style="max-width: 350px; margin: auto">
<div class="text-center text-bold text-h6">
Aiuta {{tools.sitename()}} a crescere
</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-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 overline>
<div class="index_diffusore">
{{ index + 1 }}°
</div>
</q-item-label>
<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-mx-sm q-px-sm text-bold ` +
($q.dark.isActive ? `text-white` : `text-black`)
">
{{ user.count }}
</div>
</q-item-section>
</q-item>
</TransitionGroup>
</q-list>
<q-separator />
<q-expansion-item group="somegroup" icon="fas fa-handshake"
:label="t('handshake.last_strettedimano')"
header-class="bg-teal text-white expansion-header" expand-icon-class="text-white">
<q-card class="expansion-card bg-teal-1">
<q-card-section class="card-content">
<div class="info-section">
<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>
</q-card-section>
</q-card>
</q-expansion-item>
</div>
<q-separator />
<q-item v-for="(user, index) in strettelist" :key="index"
class="user-item handshake-item" clickable v-ripple>
<q-item-section avatar @click="gotoPage(`/my/${user.username}`)">
<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 class="clDateStrette">
{{ tools.getstrDateTimeLong(user.profile.handshake.date) }}
</q-item-label>
<q-item-label class="user-name">
<span v-html="tools.getNameToShow(user, null, { showprov: true, html: true })"></span>
</q-item-label>
<q-item-label class="handshake-friend">
<span v-html="tools.getNameToShow(user.userfriend, null, { showprov: true, html: true })"></span>
</q-item-label>
</q-item-section>
<q-item-section side @click="gotoPage(`/my/${user.userfriend.username}`)">
<q-avatar round size="44px">
<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-item-section>
</q-item>
</q-card-section>
</q-card>
</q-expansion-item>
</q-list>
<q-expansion-item group="somegroup" icon="fas fa-handshake" :label="$t('handshake.last_strettedimano')"
header-class="bg-teal text-white" expand-icon-class="text-white">
<q-card class="bg-teal-2">
<q-card-section>
<div class="q-pa-md" style="max-width: 350px; margin: auto">
<div class="text-center text-bold text-h6">
Strette di Mano
</div>
<div class="text-center">
Più persone conoscerai di persona e maggiore aumenterà la
Rete di fiducia.
</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 strettelist" :key="index" class="animated chip_shadow q-ma-sm"
clickable v-ripple>
<q-item-section avatar>
<q-avatar round size="48px" @click="gotoPage(`/my/${user.username}`)">
<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 class="clDateStrette">
{{
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 style="text-align: right">
<span v-html="tools.getNameToShow(user.userfriend, null, {
showprov: true,
html: true,
})
"></span>
</q-item-label>
</q-item-section>
<q-item-section side>
<q-avatar round size="48px" @click="
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>
</q-avatar>
</q-item-section>
</q-item>
</TransitionGroup>
</q-list>
</div>
</q-card-section>
</q-card>
</q-expansion-item>
</q-list>
<!--<CGeoChart :mydata="datastat.arr_nations">
</CGeoChart>-->
<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 class="chart-wrapper">
<CLineChart :mydata="datastat.reg_daily" :title="t('stat.reg_daily')"
color="blue" bordercolor="blue" :sum="true" :showMedia="true" />
</div>
</CTitleBanner>
</div>
@@ -341,5 +235,5 @@
</script>
<style lang="scss" scoped>
@import "./CStatusReg.scss";
@import "./CStatusReg_Modern.scss";
</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{
width: 52px;
height: 52px;
width: 32px;
height: 32px;
margin: 4px !important;
}

View File

@@ -1,45 +1,87 @@
<template>
<q-footer v-if="tools.isUserOk() && site && site.confpages"
:class="($q.dark.isActive ? `bg-black` : `bg-white`) + ` small-screen-only`" bordered>
<q-footer
v-if="tools.isUserOk() && site && site.confpages"
:class="($q.dark.isActive ? `bg-black` : `bg-white`) + ` small-screen-only`"
bordered
>
<q-toolbar>
<q-toolbar-title>
<q-tabs dense :class="($q.dark.isActive
? `text-white bg-black`
: `text-grey-10 bg-white`) + ` mylabfooter mysmalltabs`
" style="padding: 0px !important" content-class="mysmalltabs" active-color="white" active-bg-color="blue"
no-caps indicator-color="transparent">
<q-route-tab v-if="site.confpages?.showButtHome" to="/" class="mylabfooter" :label="$t('tabdown.home')"
icon="fas fa-home" />
<!--<q-route-tab
v-if="site.confpages?.showViewGroups"
<q-tabs
dense
:class="
($q.dark.isActive ? `text-white bg-black` : `text-grey-10 bg-white`) +
` mylabfooter mysmalltabs`
"
style="padding: 0px !important"
content-class="mysmalltabs"
active-color="white"
active-bg-color="blue"
no-caps
indicator-color="transparent"
>
<q-route-tab
v-if="site.confpages?.showButtHome"
to="/"
class="mylabfooter"
:label="$t('tabdown.groups')"
to="/groups"
icon="fas fa-users"
/>-->
<q-route-tab v-if="site.confpages?.showViewEventi" class="mylabfooter" :label="$t('tabdown.eventi')"
to="/events" icon="fas fa-calendar-week" />
<q-img v-if="site.confpages?.showViewCircuits" src="/images/1ris_rosso_100.png" round flat
:style="currentPath === '/circuits' ? `background-color: lightblue` : ''" class="img_in_tab"
@click="naviga(tools.updateLink('/circuits'))"></q-img>
<q-route-tab 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`" />
:label="$t('tabdown.home')"
icon="fas fa-home"
/>
<q-route-tab
v-if="site.confpages?.showViewEventi"
class="mylabfooter"
:label="$t('tabdown.eventi')"
to="/events"
icon="fas fa-calendar-week"
/>
<q-route-tab
v-if="site.confpages?.showViewCircuits"
class="mylabfooter"
:label="$t('tabdown.circuits')"
to="/circuits"
icon="img:/images/1ris_rosso_100.png"
/>
<q-route-tab
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-toolbar-title>
</q-toolbar>
</q-footer>
</template>
<script lang="ts" src="./MyFooter.ts">
</script>
<script lang="ts" src="./MyFooter.ts"></script>
<style lang="scss" scoped>
@import './MyFooter.scss';

View File

@@ -227,10 +227,18 @@ $gradient-teal: linear-gradient(135deg, #14b8a6 0%, #06b6d4 100%);
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 {
background: $gradient-primary;
border-radius: $r-lg;
padding: $s-lg;
border-radius: $r-md;
padding: $s-md;
color: white;
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 {
padding: $s-lg;
padding: $s-md;
}
.annunci-options-mobile {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: $s-md;
gap: $s-sm;
}
.annuncio-option {
@@ -846,7 +854,7 @@ $gradient-teal: linear-gradient(135deg, #14b8a6 0%, #06b6d4 100%);
flex-direction: column;
align-items: center;
gap: $s-sm;
padding: $s-lg;
padding: $s-md;
border-radius: $r-lg;
cursor: pointer;
transition: all 0.3s ease;
@@ -886,14 +894,14 @@ $gradient-teal: linear-gradient(135deg, #14b8a6 0%, #06b6d4 100%);
}
.option-title {
font-size: 1.1rem;
font-size: 1.5rem;
font-weight: 700;
text-align: center;
line-height: 1.2;
}
.option-subtitle {
font-size: 0.9rem;
font-size: 1rem;
opacity: 0.9;
text-align: center;
line-height: 1.3;
@@ -926,11 +934,11 @@ $gradient-teal: linear-gradient(135deg, #14b8a6 0%, #06b6d4 100%);
}
.option-title {
font-size: 1rem;
font-size: 1.3rem;
}
.option-subtitle {
font-size: 0.7rem;
font-size: 0.9rem;
}
}
}
@@ -1284,6 +1292,24 @@ section {
.circuit-selector {
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 {
width: 100%;
@@ -1299,6 +1325,19 @@ section {
}
}
// Animazione puntamento
@keyframes pointRight {
0%,
100% {
transform: translateX(0);
}
50% {
transform: translateX(4px);
}
}
// Overview saldi circuiti
.circuits-balance-overview {
display: grid;
@@ -1492,4 +1531,42 @@ section {
position: absolute;
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 { CRISBalanceBar } from '@src/components/CRISBalanceBar';
import { tools } from '@tools';
import { useUserStore } from 'app/src/store';
const isTest = true; // Cambia a false in produzione
@@ -12,10 +14,16 @@ export default defineComponent({
// State
const showAnnunciDialog = ref(false);
const showBannScambio = ref(true);
const selectedCircuit = ref<'provinciale' | 'italia'>('provinciale');
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)
const availableBalance = ref(0); // Saldo utilizzabile (include fido)
const realBalance = ref(0); // Saldo reale effettivo
@@ -51,6 +59,8 @@ export default defineComponent({
const uniqueMembers = ref(0);
const lastTradeTime = ref('Mai');
const userStore = useUserStore()
// Organizzazioni
const organizations = ref<any[]>([
// Esempio struttura:
@@ -73,26 +83,22 @@ export default defineComponent({
// Dialog Annunci
const openAnnunciDialog = () => {
showAnnunciDialog.value = true;
// TODO: implementare logica apertura dialog
};
// Navigazione Annunci
const goToGoods = () => {
showAnnunciDialog.value = false;
// TODO: navigare a /goods
// $router.push('/goods')
$router.push('/goods')
};
const goToServices = () => {
showAnnunciDialog.value = false;
// TODO: navigare a /services
// $router.push('/services')
$router.push('/services')
};
const goToHospitality = () => {
showAnnunciDialog.value = false;
// TODO: navigare a /hosps
// $router.push('/hosps')
$router.push('/hosps')
};
const goToTransport = () => {
@@ -109,17 +115,17 @@ export default defineComponent({
const goToCircuits = () => {
// TODO: navigare ai circuiti RIS
// $router.push('/circuits')
$router.push('/circuits')
};
const goToEvents = () => {
// TODO: navigare alla lista eventi
// $router.push('/events')
$router.push('/events')
};
const goToProfile = () => {
// TODO: navigare al profilo utente
// $router.push('/profile')
$router.push('/my/' + userStore.my.username)
};
// Azioni Rapide
@@ -153,7 +159,7 @@ export default defineComponent({
// Eventi
const goToAllEvents = () => {
// TODO: navigare a tutti gli eventi
// $router.push('/events')
$router.push('/events')
};
const openEvent = (event: any) => {
@@ -192,18 +198,18 @@ export default defineComponent({
const goToAllOrganizations = () => {
// TODO: navigare a lista completa organizzazioni
// $router.push('/groups')
$router.push('/groups')
};
// Footer
const openFAQ = () => {
// TODO: navigare a FAQ
// $router.push('/faq')
$router.push('/faq_ris')
};
const openGuide = () => {
// TODO: navigare a guida
// $router.push('/guide')
$router.push('/guida')
};
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
invitesSent.value = 7;
@@ -504,15 +537,16 @@ export default defineComponent({
loadTestData();
// ========== LIFECYCLE (da implementare) ==========
// onMounted(() => {
// loadWalletData()
// loadRecentEvents()
// loadRecentActivity()
// loadStats()
// loadOrganizations()
// loadTelegramLinks()
// })
onMounted(() => {
showBannScambio.value = tools.getCookieBool('bann_scambio', true);
// loadWalletData()
// loadRecentEvents()
// loadRecentActivity()
// loadStats()
// loadOrganizations()
// loadTelegramLinks()
});
return {
// State
@@ -583,6 +617,11 @@ export default defineComponent({
openTransaction,
openHandshake,
goToAllCircuits,
showBannScambio,
tools,
transactionsView,
allTransactions,
userStore,
};
},
});

View File

@@ -50,8 +50,8 @@
name="account_circle"
size="2.5rem"
/>
<span class="card-title">Profilo</span>
<span class="card-subtitle">Gestisci account</span>
<span class="card-title">Il mio Profilo</span>
<span class="card-subtitle text-italic">({{ userStore.my.username }})</span>
</div>
</section>
@@ -83,7 +83,7 @@
<div class="wallet-header">
<h2 class="section-title-white">
<q-icon name="fas fa-coins" />
I Tuoi RIS - Circuiti di Scambio
I Tuoi Scambi in RIS
</h2>
<q-btn
flat
@@ -103,6 +103,7 @@
:min-limit="circuits.provinciale.trustLimit"
:max-limit="circuits.provinciale.maxAccumulation"
:label="circuits.provinciale.title"
small
/>
</div>
<div class="circuit-balance-mini">
@@ -111,12 +112,17 @@
:min-limit="circuits.italia.trustLimit"
:max-limit="circuits.italia.maxAccumulation"
:label="circuits.italia.title"
small
/>
</div>
</div>
<!-- Selettore Circuito -->
<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
v-model="selectedCircuit"
dense
@@ -143,6 +149,14 @@
<span class="stat-number">{{ currentCircuitData.totalTransactions }}</span>
<span class="stat-label">Transazioni</span>
</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">
<q-icon
name="arrow_upward"
@@ -159,25 +173,10 @@
<span class="stat-number">{{ currentCircuitData.receivedCount }}</span>
<span class="stat-label">Ricevute</span>
</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>
<!-- Saldi compatti circuito selezionato -->
<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
:current-balance="currentCircuitData.realBalance"
:min-limit="currentCircuitData.trustLimit"
@@ -187,9 +186,31 @@
</div>
<!-- Ultime 3 transazioni del circuito selezionato -->
<!-- SOSTITUISCI la sezione recent-transactions -->
<div class="recent-transactions">
<h4 class="transactions-title">Ultime Tue Transazioni</h4>
<div class="transaction-list">
<div class="transactions-header">
<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
v-for="(tx, idx) in currentCircuitData.recentTransactions.slice(0, 3)"
:key="idx"
@@ -214,27 +235,51 @@
</span>
</div>
</div>
<!-- Transazioni Community -->
<div
v-if="transactionsView === 'all'"
class="transaction-list"
>
<div
v-for="(tx, idx) in allTransactions.slice(0, 3)"
:key="idx"
class="transaction-item"
@click="openTransaction(tx)"
>
<div class="transaction-users">
<q-avatar
size="28px"
color="primary"
text-color="white"
>
{{ tx.fromInitial }}
</q-avatar>
<q-icon
name="arrow_forward"
size="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>
<!-- 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
unelevated
rounded
@@ -244,6 +289,34 @@
@click="goToTransactions"
/>
</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>
<!-- Prossimi Eventi -->
<section
@@ -300,6 +373,87 @@
</div>
</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 -->
<section class="community-actions-section">
<h2 class="section-title">
@@ -348,126 +502,9 @@
</div>
</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>
<!-- 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 -->
<!-- Attività Community
<section class="community-section">
<!-- Strette di Mano -->
<div class="community-card">
<div class="community-header">
<h3 class="community-title">
@@ -557,9 +594,10 @@
</div>
</div>
</div>
-->
<!-- Ultimi Scambi -->
<div class="community-card">
<!--<div class="community-card">
<div class="community-header">
<h3 class="community-title">
<q-icon name="swap_horiz" />
@@ -599,10 +637,10 @@
</div>
</div>
</div>
</section>
</section>-->
<!-- Canali Telegram -->
<section
<!--<section
v-if="hasTelegramLinks"
class="content-section"
>
@@ -655,6 +693,7 @@
</a>
</div>
</section>
-->
<!-- Footer Links -->
<section class="footer-section-modern">
@@ -745,7 +784,6 @@
<div
class="annuncio-option gradient-teal"
@click="goToTransport"
>
<q-icon
name="directions_car"
@@ -753,6 +791,7 @@
/>
<span class="option-title">Trasporti</span>
<span class="option-subtitle">Condivisione viaggi</span>
<span class="option-subtitle"> (IN ARRIVO...)</span>
</div>
</div>
</q-card-section>

View File

@@ -1,4 +1,4 @@
import { costanti } from '@costanti'
import { costanti } from '@costanti';
export let idbKeyval = (() => {
let db;
@@ -17,18 +17,52 @@ export let idbKeyval = (() => {
openreq.onupgradeneeded = () => {
// First time setup: create an empty object store
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) {
const tab = mymeth + mytab
openreq.result.createObjectStore(tab, { keyPath: 'BOMID', autoIncrement: true });
const tab = mymeth + mytab;
openreq.result.createObjectStore(tab, {
keyPath: 'BOMID',
autoIncrement: true,
});
}
}
for (const mytab of costanti.OtherTables) {
// 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 = () => {
resolve(openreq.result);
};
@@ -50,7 +84,7 @@ export let idbKeyval = (() => {
return {
async get(key) {
let req;
await withStore('readonly', 'keyval', store => {
await withStore('readonly', 'keyval', (store) => {
req = store.get(key);
});
return req.result;
@@ -62,15 +96,14 @@ export let idbKeyval = (() => {
async getdata(table, key) {
let req;
await withStore('readonly', table, store => {
await withStore('readonly', table, (store) => {
// console.log('getdata', table, key)
req = store.get(key);
// console.log(' req', req)
});
if (req) {
console.log('getdata', table, key, req.result)
console.log('getdata', table, key, req.result);
return req.result;
} else {
return null;
@@ -79,7 +112,7 @@ export let idbKeyval = (() => {
async getalldata(table) {
try {
let result;
await withStore('readonly', table, store => {
await withStore('readonly', table, (store) => {
const req = store.getAll();
req.onsuccess = () => (result = req.result);
});
@@ -92,7 +125,7 @@ export let idbKeyval = (() => {
async count(table) {
let req;
await withStore('readonly', table, store => {
await withStore('readonly', table, (store) => {
req = store.count();
});
@@ -106,7 +139,7 @@ export let idbKeyval = (() => {
},
async set(key, value) {
let req;
await withStore('readwrite', 'keyval', store => {
await withStore('readwrite', 'keyval', (store) => {
req = store.put(value, key);
});
return req.result;
@@ -115,24 +148,24 @@ export let idbKeyval = (() => {
let req;
// console.log('setdata', table, value)
await withStore('readwrite', table, store => {
await withStore('readwrite', table, (store) => {
req = store.put(value);
});
return req.result;
},
async delete(key) {
return withStore('readwrite', 'keyval', store => {
return withStore('readwrite', 'keyval', (store) => {
store.delete(key);
});
},
async deletedata(table, key) {
return withStore('readwrite', table, store => {
return withStore('readwrite', table, (store) => {
store.delete(key);
});
},
async clearalldata(table) {
// console.log('clearalldata', table)
return withStore('readwrite', table, store => {
return withStore('readwrite', table, (store) => {
store.clear();
});
},
@@ -142,10 +175,10 @@ export let idbKeyval = (() => {
// iOS add-to-homescreen is missing IDB, or at least it used to.
// I haven't tested this in a while.
if (!self.indexedDB) {
console.log('NO indexedDB')
console.log('NO indexedDB');
idbKeyval = {
get: key => Promise.resolve(localStorage.getItem(key)),
get: (key) => Promise.resolve(localStorage.getItem(key)),
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;
$bg-light: #f9fafb;
@use 'sass:color';
@mixin center-flex {
display: flex;
justify-content: center;
@@ -64,7 +66,7 @@ section.relative.overflow-hidden {
color: white;
&: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,
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,
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,
showWhen: costanti.showWhen.NewRec + costanti.showWhen.InEdit,
visible: false,
sortable: false,
}),
AddCol({
name: 'visibility',
@@ -2508,6 +2509,7 @@ export const colmyGoods = [
required: false,
showWhen: costanti.showWhen.NewRec + costanti.showWhen.InEdit,
visible: false,
sortable: false,
}),
AddCol({
name: 'idShipping',

View File

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

View File

@@ -56,6 +56,8 @@ import { useCatalogStore } from './CatalogStore';
const stateConnDefault = 'online';
const USASYNC = false;
async function getConfig(id: any) {
return globalroutines('read', 'config', null, id);
}
@@ -1271,8 +1273,10 @@ export const useGlobalStore = defineStore('GlobalStore', {
// console.log('loadAfterLogin')
this.clearDataAfterLoginOnlyIfActiveConnection();
await userStore.loadListaEditori();
await userStore.loadListaReferenti();
if (tools.isGruppoMacro()) {
await userStore.loadListaEditori();
await userStore.loadListaReferenti();
}
await globalroutines('readall', 'config', null);
@@ -2245,6 +2249,21 @@ export const useGlobalStore = defineStore('GlobalStore', {
// 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();
// const isLogged = localStorage.getItem(toolsext.localStorage.username)
@@ -2306,6 +2325,49 @@ export const useGlobalStore = defineStore('GlobalStore', {
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) {
const recprov = this.provinces.find((rec: any) => rec.prov === provstr);

View File

@@ -1,6 +1,6 @@
<template>
<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>
</q-page>

View File

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

View File

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