- Nuova Home Page RISO Moderna! Passo 1 - la struttura

This commit is contained in:
Surya Paolo
2025-11-29 21:14:26 +01:00
parent 2abdda3b44
commit 8b6a636a96
17 changed files with 3865 additions and 82 deletions

View File

@@ -190,6 +190,7 @@ export const shared_consts = {
PAGE_SECTION: 1500,
PROFILE_COMPLETITION: 1510,
RISOHOME: 1600,
RISOHOME_MODERN: 1610
},
QUERYTYPE_MYGROUP: 1,
@@ -1943,6 +1944,11 @@ export const shared_consts = {
label: 'HomePage RISO',
icon: 'fas fa-home',
},
{
value: 1610, // RISOHOME_MODERN
label: 'RISO Home Modern',
icon: 'fas fa-home',
},
{
value: 258,
label: 'Registration',

View File

@@ -1,42 +1,188 @@
.text-cls{
font-weight: bold;
// Spacing ridotto
$s-xs: 4px;
$s-sm: 6px;
$s-md: 8px;
// Wrapper moderno
.modern-btn-wrapper {
padding: $s-xs;
&.modern-btn-col-3 {
flex: 0 0 33.333%;
max-width: 33.333%;
@media (max-width: 599px) {
flex: 0 0 33.333%;
max-width: 33.333%;
}
@media (min-width: 600px) and (max-width: 1023px) {
flex: 0 0 33.333%;
max-width: 33.333%;
}
@media (min-width: 1024px) and (max-width: 1439px) {
flex: 0 0 25%;
max-width: 25%;
}
@media (min-width: 1440px) {
flex: 0 0 16.666%;
max-width: 16.666%;
}
}
&.modern-btn-col-2 {
flex: 0 0 50%;
max-width: 50%;
@media (min-width: 600px) and (max-width: 1023px) {
flex: 0 0 50%;
max-width: 50%;
}
@media (min-width: 1024px) and (max-width: 1439px) {
flex: 0 0 33.333%;
max-width: 33.333%;
}
@media (min-width: 1440px) {
flex: 0 0 25%;
max-width: 25%;
}
}
}
.my-text {
font-size: 1rem;
font-weight: bold;
line-height: 1.5rem;
letter-spacing: 0.0125em;
}
.my-text_3 {
font-size: 1rem;
font-weight: bold;
line-height: 1.5rem;
letter-spacing: 0.0125em;
}
.my-text-small {
font-size: 1rem;
line-height: 1rem;
letter-spacing: 0.0125em;
}
.mybox_3 {
min-width: 100px;
min-height: 100px;
// Bottoni moderni
.modern-bigbtn {
width: 100%;
min-height: 90px;
min-width: 90px;
padding: $s-sm;
flex-direction: column;
gap: $s-xs;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
&::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
transition: left 0.5s ease;
}
&:hover::before {
left: 100%;
}
&:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.2);
}
.btn-icon {
margin: $s-xs;
font-size: 2rem;
}
.btn-icon-large {
margin: $s-xs;
}
.btn-label,
.btn-label-large {
margin: $s-xs;
font-size: 0.95rem;
font-weight: 700;
line-height: 1.3;
letter-spacing: 0.01em;
text-align: center;
white-space: normal;
word-break: break-word;
}
.btn-label-large {
font-size: 1.1rem;
}
}
.mybox {
min-width: 112px;
min-height: 112px;
.modern-smallbtn {
width: 100%;
min-height: 70px;
min-width: 80px;
padding: $s-sm;
flex-direction: column;
gap: $s-xs;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
&::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
transition: left 0.5s ease;
}
&:hover::before {
left: 100%;
}
&:hover {
transform: translateY(-2px);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
}
.btn-icon-small {
margin: $s-xs;
}
.btn-label-small {
margin: $s-xs;
font-size: 0.85rem;
font-weight: 600;
line-height: 1.2;
letter-spacing: 0.01em;
text-align: center;
white-space: normal;
word-break: break-word;
}
}
.mybox_small {
min-width: 110px;
width: 100%;
}
// Responsive adjustments
@media (max-width: 599px) {
.modern-bigbtn {
min-height: 85px;
min-width: 85px;
.btn-icon {
font-size: 1.8rem;
}
.btn-label {
font-size: 0.85rem;
}
.btn-label-large {
font-size: 1rem;
}
}
.modern-smallbtn {
min-height: 65px;
min-width: 70px;
.btn-label-small {
font-size: 0.75rem;
}
}
}

View File

@@ -1,27 +1,63 @@
<template>
<div v-if="numcol === 3" class="col-xs-4 col-sm-4 col-md-3 col-lg-2">
<div class="q-ma-sm">
<q-btn v-if="!small" :flat="flat" class="mybox_3" :color="color" rounded push :to="tools.updateLink(to)" v-bind="$attrs" :style="tools.getbackgroundGradient(color, 180)">
<q-icon class="q-ma-sm" :name="icon"/>
<div class="q-ma-sm my-text_3 text-cls no-wrap"><span v-html="label"></span></div>
</q-btn>
<q-btn v-if="small" :flat="flat" class="mybox_small" :color="color" rounded push :to="tools.updateLink(to)" v-bind="$attrs" :style="tools.getbackgroundGradient(color, 180)">
<q-icon class="q-ma-sm" :name="icon" size="sm"/>
<div class="q-ma-xs my-text-small text-cls no-wrap"><span v-html="label"></span></div>
</q-btn>
</div>
<div v-if="numcol === 3" class="modern-btn-wrapper modern-btn-col-3">
<q-btn
v-if="!small"
:flat="flat"
class="modern-bigbtn"
:color="color"
rounded
push
:to="tools.updateLink(to)"
v-bind="$attrs"
:style="tools.getbackgroundGradient(color, 180)"
>
<q-icon class="btn-icon" :name="icon"/>
<div class="btn-label"><span v-html="label"></span></div>
</q-btn>
<q-btn
v-if="small"
:flat="flat"
class="modern-smallbtn"
:color="color"
rounded
push
:to="tools.updateLink(to)"
v-bind="$attrs"
:style="tools.getbackgroundGradient(color, 180)"
>
<q-icon class="btn-icon-small" :name="icon" size="sm"/>
<div class="btn-label-small"><span v-html="label"></span></div>
</q-btn>
</div>
<div v-else class="col-xs-6 col-sm-6 col-md-4 col-lg-3">
<div class="q-ma-sm">
<q-btn v-if="!small" :flat="flat" class="mybox" :color="color" rounded push :to="tools.updateLink(to)" v-bind="$attrs" :style="tools.getbackgroundGradient(color, 0)">
<q-icon class="q-ma-sm" size="3rem" :name="icon"/>
<div class="q-ma-sm text-h5-diff text-cls no-wrap"><span v-html="label"></span></div>
</q-btn>
<q-btn v-if="small" :flat="flat" class="mybox_small" :color="color" rounded push :to="tools.updateLink(to)" v-bind="$attrs" :style="tools.getbackgroundGradient(color, 0)">
<q-icon class="q-ma-sm" :name="icon" size="sm"/>
<div class="q-ma-xs my-text-small text-cls no-wrap"><span v-html="label"></span></div>
</q-btn>
</div>
<div v-else class="modern-btn-wrapper modern-btn-col-2">
<q-btn
v-if="!small"
:flat="flat"
class="modern-bigbtn"
:color="color"
rounded
push
:to="tools.updateLink(to)"
v-bind="$attrs"
:style="tools.getbackgroundGradient(color, 0)"
>
<q-icon class="btn-icon-large" size="3rem" :name="icon"/>
<div class="btn-label-large"><span v-html="label"></span></div>
</q-btn>
<q-btn
v-if="small"
:flat="flat"
class="modern-smallbtn"
:color="color"
rounded
push
:to="tools.updateLink(to)"
v-bind="$attrs"
:style="tools.getbackgroundGradient(color, 0)"
>
<q-icon class="btn-icon-small" :name="icon" size="sm"/>
<div class="btn-label-small"><span v-html="label"></span></div>
</q-btn>
</div>
</template>

View File

@@ -7,9 +7,9 @@
<q-toggle
v-if="tools.isCollaboratore() && !showMap && editOn"
v-model="editOn"
label="Abilita Modifiche"
color="green"
icon="fas fa-pencil-alt"
class="fixed-toggle"
>
</q-toggle>
<div

View File

@@ -0,0 +1,333 @@
// Spacing ridotto 40-50%
$sp-xs: 4px;
$sp-sm: 8px;
$sp-md: 12px;
$sp-lg: 16px;
$radius-sm: 8px;
$radius-md: 12px;
$radius-lg: 16px;
// Layout
.riso-homepage {
padding: $sp-sm;
max-width: 1200px;
margin: 0 auto;
background: linear-gradient(135deg, #f5f7fa 0%, #e3e9f2 100%);
min-height: 100vh;
}
// Hero Section
.hero-section {
display: grid;
gap: $sp-sm;
margin-bottom: $sp-md;
background: rgba(255, 255, 255, 0.7);
backdrop-filter: blur(10px);
border-radius: $radius-md;
padding: $sp-sm;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
@media (max-width: 600px) {
grid-template-columns: repeat(2, 1fr);
}
@media (min-width: 601px) and (max-width: 1024px) {
grid-template-columns: repeat(2, 1fr);
}
@media (min-width: 1025px) {
grid-template-columns: repeat(4, 1fr);
}
}
// Secondary Section
.secondary-section {
display: grid;
gap: $sp-sm;
margin-bottom: $sp-md;
background: rgba(255, 255, 255, 0.6);
backdrop-filter: blur(8px);
border-radius: $radius-md;
padding: $sp-sm;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
@media (max-width: 600px) {
grid-template-columns: repeat(3, 1fr);
}
@media (min-width: 601px) {
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
}
}
// Content Sections
.content-section {
background: white;
border-radius: $radius-lg;
padding: $sp-md;
margin-bottom: $sp-md;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
&:hover {
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
transform: translateY(-2px);
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: $sp-md;
padding-bottom: $sp-sm;
border-bottom: 2px solid rgba(0, 0, 0, 0.05);
.section-title {
font-size: 1.25rem;
font-weight: 600;
margin: 0;
display: flex;
align-items: center;
gap: $sp-sm;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
.q-icon {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
}
}
}
// Modern Tabs
.modern-tabs {
margin-bottom: $sp-md;
:deep(.q-tab) {
padding: $sp-sm $sp-md;
border-radius: $radius-sm;
transition: all 0.3s ease;
&:hover {
background: rgba(102, 126, 234, 0.1);
}
&.q-tab--active {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
}
}
// Placeholder Content
.placeholder-content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: $sp-sm;
padding: $sp-lg;
background: rgba(0, 0, 0, 0.02);
border-radius: $radius-md;
border: 2px dashed rgba(0, 0, 0, 0.1);
color: rgba(0, 0, 0, 0.4);
font-size: 0.9rem;
min-height: 100px;
transition: all 0.3s ease;
&:hover {
background: rgba(0, 0, 0, 0.04);
border-color: rgba(0, 0, 0, 0.2);
}
&.horizontal {
flex-direction: row;
min-width: 280px;
}
.q-icon {
opacity: 0.3;
}
}
// Eventi Scroll
.eventi-scroll {
display: flex;
gap: $sp-md;
overflow-x: auto;
padding-bottom: $sp-sm;
scroll-behavior: smooth;
&::-webkit-scrollbar {
height: 4px;
}
&::-webkit-scrollbar-thumb {
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
border-radius: 2px;
}
&::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.05);
}
}
// Wallet Card
.wallet-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: $radius-md;
padding: $sp-lg;
color: white;
display: flex;
flex-direction: column;
gap: $sp-md;
box-shadow: 0 4px 16px rgba(102, 126, 234, 0.3);
.wallet-balance {
display: flex;
flex-direction: column;
gap: $sp-xs;
.balance-label {
font-size: 0.9rem;
opacity: 0.9;
}
.balance-value {
font-size: 2rem;
font-weight: 700;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
}
.wallet-actions {
display: flex;
gap: $sp-sm;
.q-btn {
flex: 1;
border-color: white;
color: white;
&:hover {
background: rgba(255, 255, 255, 0.2);
}
}
}
}
// Telegram Grid
.telegram-grid {
display: grid;
gap: $sp-md;
@media (max-width: 600px) {
grid-template-columns: 1fr;
}
@media (min-width: 601px) {
grid-template-columns: repeat(3, 1fr);
}
}
.telegram-card {
display: flex;
flex-direction: column;
background: linear-gradient(135deg, rgba(0, 136, 204, 0.08) 0%, rgba(43, 171, 217, 0.08) 100%);
border-radius: $radius-md;
overflow: hidden;
transition: all 0.3s ease;
text-decoration: none;
border: 1px solid rgba(0, 136, 204, 0.2);
&:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0, 136, 204, 0.2);
border-color: rgba(0, 136, 204, 0.4);
}
.telegram-img {
height: 120px;
}
.telegram-content {
padding: $sp-md;
display: flex;
justify-content: space-between;
align-items: center;
.telegram-title {
font-weight: 600;
color: #0088cc;
}
}
}
// Footer
.footer-section {
margin-top: $sp-lg;
padding: $sp-lg;
background: rgba(255, 255, 255, 0.5);
backdrop-filter: blur(10px);
border-radius: $radius-lg;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
.footer-links {
display: flex;
justify-content: space-around;
flex-wrap: wrap;
gap: $sp-sm;
.footer-btn {
flex: 1;
min-width: 90px;
color: #667eea;
font-weight: 500;
&:hover {
background: rgba(102, 126, 234, 0.1);
}
}
}
}
// Animazioni
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.content-section {
animation: fadeIn 0.5s ease-out;
}
// Responsive
@media (max-width: 600px) {
.riso-homepage {
padding: $sp-xs;
}
.content-section {
padding: $sp-sm;
margin-bottom: $sp-sm;
}
.section-header .section-title {
font-size: 1.1rem;
}
.wallet-card {
padding: $sp-md;
}
}

View File

@@ -1,9 +1,12 @@
<template>
<div v-if="tools.isUserOk()">
<div
<div
v-if="tools.isUserOk()"
class="riso-home"
>
<!-- Hero Section: Annunci principali -->
<section
v-if="cardsbig.length > 0"
class="row q-ma-sm shadow justify-center"
style="border-radius: 4px; border: 1px solid rgba(0, 0, 0, 0.12)"
class="hero-section"
>
<CBigBtn
v-for="(card, ind) of cardsbig"
@@ -17,13 +20,13 @@
:numcol="2"
:hint="card.hint"
:disable="card.disable"
>
</CBigBtn>
</div>
<div
/>
</section>
<!-- Secondary: Circuiti e Organizzazioni -->
<section
v-if="cardssmall.length > 0"
class="row shadow justify-center"
style="border-radius: 4px; border: 1px solid rgba(0, 0, 0, 0.12)"
class="secondary-section"
>
<CBigBtn
v-for="(card, ind) of cardssmall"
@@ -38,24 +41,12 @@
:numcol="3"
:hint="card.hint"
:disable="card.disable"
>
</CBigBtn>
</div>
/>
</section>
</div>
<!--<div class="row justify-center">
<CBigBtn
label="Info" to="" @click="showInfo = true" icon="fas fa-info" color="primary"
:numcol="3"
>
</CBigBtn>
</div>
<div v-if="showInfo">
</div>-->
</template>
<script lang="ts" src="./CMainView.ts">
</script>
<script lang="ts" src="./CMainView.ts"></script>
<style lang="scss" scoped>
@import './CMainView.scss';

View File

@@ -32,6 +32,7 @@ import { LandingFooter } from '@src/components/LandingFooter';
import { CMyActivities } from '@src/components/CMyActivities';
import { CECommerce } from '@src/components/CECommerce';
import { HomeRiso } from '@src/components/HomeRiso';
import { Riso_Home_Modern } from '@src/components/Riso_Home_Modern';
import { InvitaAmico } from '@src/components/InvitaAmico';
import { CMyVideoYoutube } from '@src/components/CMyVideoYoutube';
import { editprofile } from '@src/components/editprofile';
@@ -102,6 +103,7 @@ export default defineComponent({
CMyPageIntro,
InvitaAmico,
HomeRiso,
Riso_Home_Modern,
CMyEditor,
editprofile,
CMyFieldRec,

View File

@@ -91,6 +91,12 @@
>
<HomeRiso />
</div>
<div
v-else-if="myel.type === shared_consts.ELEMTYPE.RISOHOME_MODERN"
class="myElemBase"
>
<Riso_Home_Modern />
</div>
<div
v-else-if="myel.type === shared_consts.ELEMTYPE.IMGTITLE"
class="myElemBase"

View File

@@ -0,0 +1,235 @@
// Spacing ridotto
$s-xs: 4px;
$s-sm: 8px;
$s-md: 12px;
$s-lg: 16px;
$r-sm: 6px;
$r-md: 10px;
.ris-balance-bar {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(5px);
border-radius: $r-md;
padding: $s-md;
border: 1px solid rgba(255, 255, 255, 0.2);
}
// Header con label e valore corrente
.balance-header {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
margin-bottom: $s-md;
.balance-label {
font-size: 0.85rem;
font-weight: 600;
opacity: 0.9;
}
.balance-current {
font-size: 1.1rem;
font-weight: 800;
&.negative {
color: #fecaca;
}
&.neutral {
color: #e5e7eb;
}
&.positive {
color: #d1fae5;
}
}
}
// Container progressione
.progress-container {
position: relative;
margin-bottom: $s-lg;
}
// Track di sfondo
.progress-track {
display: flex;
height: 24px;
border-radius: $r-sm;
overflow: hidden;
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.2);
position: relative;
.progress-zone {
&.negative-zone {
background: linear-gradient(90deg, #ef4444 0%, #f87171 100%);
}
&.zero-marker {
width: 2px;
background: white;
box-shadow: 0 0 8px rgba(255, 255, 255, 0.8);
}
&.positive-zone {
background: linear-gradient(90deg, #10b981 0%, #34d399 100%);
}
}
}
// Indicatore posizione corrente
.current-indicator {
position: absolute;
top: -14px;
transform: translateX(-50%);
z-index: 10;
transition: left 0.5s ease;
.indicator-dot {
width: 16px;
height: 16px;
border-radius: 50%;
border: 3px solid white;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
&.negative {
background: #dc2626;
}
&.neutral {
background: #6b7280;
}
&.positive {
background: #059669;
}
}
.indicator-line {
width: 2px;
height: 32px;
background: white;
margin: 0 auto;
box-shadow: 0 0 4px rgba(255, 255, 255, 0.6);
}
}
// Markers limiti
.markers {
display: flex;
justify-content: space-between;
margin-top: $s-sm;
position: relative;
.marker {
display: flex;
flex-direction: column;
align-items: center;
gap: 2px;
&.min-marker {
align-items: flex-start;
}
&.max-marker {
align-items: flex-end;
}
&.zero-marker-label {
position: absolute;
left: var(--zero-position); // <-- Usa variabile CSS dinamica
transform: translateX(-50%);
}
.marker-value {
font-size: 0.85rem;
font-weight: 700;
color: white;
}
.marker-label {
font-size: 0.7rem;
opacity: 0.8;
text-transform: uppercase;
font-weight: 600;
}
}
}
// Info disponibilità
.availability-info {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: $s-sm;
padding-top: $s-md;
border-top: 1px solid rgba(255, 255, 255, 0.2);
.availability-item {
display: flex;
align-items: center;
gap: $s-xs;
.availability-text {
font-size: 1rem;
opacity: 0.95;
line-height: 1.3;
strong {
font-weight: 700;
display: block;
}
}
}
}
// Responsive
@media (max-width: 599px) {
.balance-header {
.balance-label {
font-size: 1rem;
}
.balance-current {
font-size: 1rem;
}
}
.progress-track {
height: 20px;
}
.current-indicator {
.indicator-dot {
width: 14px;
height: 14px;
}
.indicator-line {
height: 15px;
}
}
.markers {
.marker {
.marker-value {
font-size: 0.75rem;
}
.marker-label {
font-size: 0.65rem;
}
}
}
.availability-info {
grid-template-columns: 1fr;
.availability-item {
.availability-text {
font-size: 0.9rem;
}
}
}
}

View File

@@ -0,0 +1,87 @@
import { defineComponent, computed } from 'vue';
export default defineComponent({
name: 'CRISBalanceBar',
props: {
// Saldo corrente
currentBalance: {
type: Number,
required: true,
default: 0,
},
// Limite minimo (negativo)
minLimit: {
type: Number,
required: true,
default: -100,
},
// Limite massimo (positivo)
maxLimit: {
type: Number,
required: true,
default: 200,
},
// Label opzionale
label: {
type: String,
default: 'Range disponibile',
},
},
setup(props) {
// Range totale
const totalRange = computed(() => {
return Math.abs(props.minLimit) + props.maxLimit;
});
// Larghezza zona negativa in percentuale
const negativeZoneWidth = computed(() => {
return (Math.abs(props.minLimit) / totalRange.value) * 100;
});
// Larghezza zona positiva in percentuale
const positiveZoneWidth = computed(() => {
return (props.maxLimit / totalRange.value) * 100;
});
const zeroPosition = computed(() => {
return negativeZoneWidth.value;
});
// Posizione indicatore in percentuale (0-100)
const indicatorPosition = computed(() => {
// Il minLimit è negativo, quindi lo convertiamo in positivo per il calcolo
const offsetFromMin = props.currentBalance - props.minLimit;
const position = (offsetFromMin / totalRange.value) * 100;
// Clamp tra 0 e 100
return Math.max(0, Math.min(100, position));
});
// Classe CSS per il colore in base al saldo
const balanceClass = computed(() => {
if (props.currentBalance < 0) return 'negative';
if (props.currentBalance === 0) return 'neutral';
return 'positive';
});
// Quanto può ancora dare (quanto può andare in negativo)
const canGive = computed(() => {
return Math.abs(props.minLimit) + props.currentBalance;
});
// Quanto può ancora ricevere (quanto può andare in positivo)
const canReceive = computed(() => {
return props.maxLimit - props.currentBalance;
});
return {
negativeZoneWidth,
positiveZoneWidth,
indicatorPosition,
balanceClass,
canGive,
canReceive,
zeroPosition,
};
},
});

View File

@@ -0,0 +1,87 @@
<template>
<div class="ris-balance-bar">
<!-- Label e valore corrente -->
<div class="balance-header">
<span class="balance-label">{{ label }}</span>
<span :class="['balance-current', balanceClass]">
{{ currentBalance > 0 ? '+' : '' }}{{ currentBalance }} RIS
</span>
</div>
<!-- Barra di progressione -->
<div class="progress-container">
<!-- Linea di sfondo -->
<div class="progress-track">
<!-- Zona negativa (rossa) -->
<div
class="progress-zone negative-zone"
:style="{ width: negativeZoneWidth + '%' }"
></div>
<!-- Zona zero (grigia) -->
<div class="progress-zone zero-marker"></div>
<!-- Zona positiva (verde) -->
<div
class="progress-zone positive-zone"
:style="{ width: positiveZoneWidth + '%' }"
></div>
</div>
<!-- Indicatore posizione corrente -->
<div
class="current-indicator"
:style="{ left: indicatorPosition + '%' }"
>
<div :class="['indicator-dot', balanceClass]"></div>
<div class="indicator-line"></div>
</div>
<!-- Markers limiti -->
<div
class="markers"
:style="{ '--zero-position': zeroPosition + '%' }"
>
<div class="marker min-marker">
<span class="marker-value">{{ minLimit }}</span>
<span class="marker-label">Fido</span>
</div>
<div class="marker zero-marker-label">
<span class="marker-value">0</span>
</div>
<div class="marker max-marker">
<span class="marker-value">+{{ maxLimit }}</span>
<span class="marker-label">Max</span>
</div>
</div>
</div>
<!-- Info disponibilità -->
<div class="availability-info">
<div class="availability-item">
<q-icon
name="arrow_downward"
size="xs"
color="negative"
/>
<span class="availability-text">
Puoi dare ancora: <strong>{{ canGive }} RIS</strong>
</span>
</div>
<div class="availability-item">
<q-icon
name="arrow_upward"
size="xs"
color="positive"
/>
<span class="availability-text">
Puoi ricevere: <strong>{{ canReceive }} RIS</strong>
</span>
</div>
</div>
</div>
</template>
<script lang="ts" src="./CRISBalanceBar.ts"></script>
<style lang="scss" scoped>
@import './CRISBalanceBar.scss';
</style>

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,588 @@
import { defineComponent, ref, computed } from 'vue';
import { useRouter } from 'vue-router';
import { CRISBalanceBar } from '@src/components/CRISBalanceBar';
const isTest = true; // Cambia a false in produzione
export default defineComponent({
name: 'RisoHomeModern',
components: { CRISBalanceBar },
setup() {
const $router = useRouter();
// State
const showAnnunciDialog = ref(false);
const selectedCircuit = ref<'provinciale' | 'italia'>('provinciale');
const handshakesView = ref<'mine' | 'all'>('mine');
// Dati wallet (da collegare allo store)
const availableBalance = ref(0); // Saldo utilizzabile (include fido)
const realBalance = ref(0); // Saldo reale effettivo
const trustLimit = ref(-100); // Fido concesso (negativo)
const maxAccumulation = ref(200); // Massimo accumulo
const receivedCount = ref(0);
const sentCount = ref(0);
// Dati eventi (da collegare allo store)
const upcomingEventsCount = ref(0);
const recentEvents = ref<any[]>([
// Esempio struttura:
// { day: '15', month: 'DIC', title: 'Mercatino', location: 'Roma' }
]);
// Dati attività (da collegare allo store)
const recentTrades = ref<any[]>([
// Esempio struttura:
// { userInitial: 'M', description: 'Scambio completato', time: '2h fa', amount: 50 }
]);
const recentConnections = ref<any[]>([
// Esempio struttura:
// { userInitial: 'A', name: 'Mario Rossi', time: '1 giorno fa' }
]);
// Stats
const totalMembers = ref(0);
const totalTrades = ref(0);
const totalEvents = ref(0);
const totalTransactions = ref(0);
const uniqueMembers = ref(0);
const lastTradeTime = ref('Mai');
// Organizzazioni
const organizations = ref<any[]>([
// Esempio struttura:
// { initial: 'AS', name: 'Associazione Locale', membersCount: 45 }
]);
// Telegram
const telegramLinks = ref<any[]>([
// Esempio struttura:
// { title: 'Canale Principale', url: 'https://t.me/...', image: '/path/to/img' }
]);
// Computed
const hasEvents = computed(() => recentEvents.value.length > 0);
const hasOrganizations = computed(() => organizations.value.length > 0);
const hasTelegramLinks = computed(() => telegramLinks.value.length > 0);
// ========== METODI DA IMPLEMENTARE ==========
// 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')
};
const goToServices = () => {
showAnnunciDialog.value = false;
// TODO: navigare a /services
// $router.push('/services')
};
const goToHospitality = () => {
showAnnunciDialog.value = false;
// TODO: navigare a /hosps
// $router.push('/hosps')
};
const goToTransport = () => {
showAnnunciDialog.value = false;
// TODO: navigare a /transport (da creare?)
// $router.push('/transport')
};
// Hero Cards
const goToWallet = () => {
// TODO: navigare al portafoglio dettagliato
// $router.push('/wallet')
};
const goToCircuits = () => {
// TODO: navigare ai circuiti RIS
// $router.push('/circuits')
};
const goToEvents = () => {
// TODO: navigare alla lista eventi
// $router.push('/events')
};
const goToProfile = () => {
// TODO: navigare al profilo utente
// $router.push('/profile')
};
// Azioni Rapide
const sendRIS = () => {
// TODO: aprire dialog/pagina invio RIS
};
const receiveRIS = () => {
// TODO: aprire dialog/pagina ricezione RIS
};
const inviteFriend = () => {
// TODO: aprire dialog/pagina invito amico
};
const showMembers = () => {
// TODO: navigare alla lista iscritti
// $router.push('/members')
};
// Wallet
const refreshWallet = () => {
// TODO: ricaricare dati wallet dallo store/API
};
const goToTransactions = () => {
// TODO: navigare alla lista transazioni
// $router.push('/transactions')
};
// Eventi
const goToAllEvents = () => {
// TODO: navigare a tutti gli eventi
// $router.push('/events')
};
const openEvent = (event: any) => {
// TODO: aprire dettaglio evento
// $router.push(`/events/${event.id}`)
};
// Community
const openTrade = (trade: any) => {
// TODO: aprire dettaglio scambio
};
const openConnection = (connection: any) => {
// TODO: aprire profilo utente connesso
};
const goToAllTrades = () => {
// TODO: navigare a lista completa scambi
// $router.push('/trades')
};
const goToAllConnections = () => {
// TODO: navigare a lista completa connessioni
// $router.push('/connections')
};
// Organizzazioni
const createOrganization = () => {
// TODO: aprire form creazione organizzazione
};
const openOrganization = (org: any) => {
// TODO: aprire dettaglio organizzazione
// $router.push(`/groups/${org.id}`)
};
const goToAllOrganizations = () => {
// TODO: navigare a lista completa organizzazioni
// $router.push('/groups')
};
// Footer
const openFAQ = () => {
// TODO: navigare a FAQ
// $router.push('/faq')
};
const openGuide = () => {
// TODO: navigare a guida
// $router.push('/guide')
};
const openInfo = () => {
// TODO: navigare a info
// $router.push('/info')
};
const loadTestData = () => {
if (!isTest) return;
// Circuito Provinciale
circuits.value.provinciale = {
title: 'RIS Provinciale',
availableBalance: 50,
realBalance: -50,
trustLimit: -100,
maxAccumulation: 200,
totalTransactions: 15,
sentCount: 8,
receivedCount: 7,
uniqueMembers: 12,
lastTradeTime: '2 giorni fa',
recentTransactions: [
{ userInitial: 'M', description: 'Ortaggi freschi', time: '2h fa', amount: 15 },
{
userInitial: 'L',
description: 'Lezione chitarra',
time: '5h fa',
amount: -25,
},
{
userInitial: 'G',
description: 'Riparazione bici',
time: '1 giorno fa',
amount: 30,
},
],
};
// Circuito Italia
circuits.value.italia = {
title: 'RIS Italia',
availableBalance: -50,
realBalance: 150,
trustLimit: -200,
maxAccumulation: 400,
totalTransactions: 8,
sentCount: 3,
receivedCount: 5,
uniqueMembers: 7,
lastTradeTime: '5 giorni fa',
recentTransactions: [
{
userInitial: 'A',
description: 'Consulenza legale',
time: '3 giorni fa',
amount: -80,
},
{
userInitial: 'F',
description: 'Prodotti artigianali',
time: '4 giorni fa',
amount: 50,
},
{
userInitial: 'S',
description: 'Corso di cucina',
time: '5 giorni fa',
amount: -40,
},
],
};
// Community
invitesSent.value = 7;
myHandshakes.value = [
{ userInitial: 'M', name: 'Mario Rossi', time: '2 giorni fa' },
{ userInitial: 'L', name: 'Laura Bianchi', time: '3 giorni fa' },
{ userInitial: 'G', name: 'Giovanni Verdi', time: '1 settimana fa' },
{ userInitial: 'C', name: 'Chiara Neri', time: '2 settimane fa' },
{ userInitial: 'P', name: 'Paolo Conti', time: '3 settimane fa' },
];
allHandshakes.value = [
{
user1Initial: 'A',
user2Initial: 'B',
user1Name: 'Anna',
user2Name: 'Bruno',
time: '1h fa',
},
{
user1Initial: 'C',
user2Initial: 'D',
user1Name: 'Carlo',
user2Name: 'Diana',
time: '3h fa',
},
{
user1Initial: 'E',
user2Initial: 'F',
user1Name: 'Elena',
user2Name: 'Franco',
time: '5h fa',
},
{
user1Initial: 'G',
user2Initial: 'H',
user1Name: 'Giulia',
user2Name: 'Hugo',
time: '1 giorno fa',
},
{
user1Initial: 'I',
user2Initial: 'L',
user1Name: 'Irene',
user2Name: 'Luca',
time: '2 giorni fa',
},
];
// Eventi test data con immagini
upcomingEventsCount.value = 7;
recentEvents.value = [
{
day: '15',
month: 'DIC',
title: 'Mercatino di Natale',
location: 'Roma Centro',
id: 1,
image: '',
},
{
day: '20',
month: 'DIC',
title: 'Corso di Panificazione',
location: 'Milano',
id: 2,
image: '',
},
{
day: '22',
month: 'DIC',
title: 'Scambio Semi e Piante',
location: 'Bologna',
id: 3,
image: '',
},
{
day: '28',
month: 'DIC',
title: 'Incontro Produttori Locali',
location: 'Firenze',
id: 4,
image: '',
},
{
day: '05',
month: 'GEN',
title: 'Festa della Comunità',
location: 'Torino',
id: 5,
image: '',
},
];
// Scambi test data
recentTrades.value = [
{
userInitial: 'M',
description: 'Scambio ortaggi freschi',
time: '2h fa',
amount: 15,
},
{
userInitial: 'L',
description: 'Lezione di chitarra',
time: '5h fa',
amount: -25,
},
{
userInitial: 'G',
description: 'Riparazione bicicletta',
time: '1 giorno fa',
amount: 30,
},
{
userInitial: 'A',
description: 'Pane fatto in casa',
time: '2 giorni fa',
amount: -10,
},
{
userInitial: 'S',
description: 'Consulenza informatica',
time: '3 giorni fa',
amount: 40,
},
];
// Statistiche test data
totalMembers.value = 342;
totalTrades.value = 1567;
totalEvents.value = 89;
// Organizzazioni test data
organizations.value = [
{ initial: 'AS', name: 'Associazione Scambio Locale', membersCount: 45, id: 1 },
{ initial: 'GP', name: 'Gruppo Produttori Bio', membersCount: 32, id: 2 },
{ initial: 'CE', name: 'Comunità Energetica', membersCount: 28, id: 3 },
{ initial: 'RT', name: 'Rete Tempo', membersCount: 56, id: 4 },
{ initial: 'OC', name: 'Orto Condiviso', membersCount: 23, id: 5 },
{ initial: 'LB', name: 'La Banca del Tempo', membersCount: 41, id: 6 },
{ initial: 'MP', name: 'Mercato Popolare', membersCount: 38, id: 7 },
{ initial: 'CC', name: 'Casa Comune', membersCount: 19, id: 8 },
{ initial: 'SF', name: 'Sharing Food', membersCount: 27, id: 9 },
{ initial: 'EA', name: 'Economia Alternativa', membersCount: 34, id: 10 },
];
// Telegram test data con immagini
telegramLinks.value = [
{
title: 'Canale Principale RISO',
url: 'https://t.me/riso_italia',
image: '/images/telegram-main.jpg',
},
{
title: 'Gruppo Scambi Locali',
url: 'https://t.me/riso_scambi',
image: '/images/telegram-scambi.jpg',
},
{
title: 'Eventi e Incontri',
url: 'https://t.me/riso_eventi',
image: '/images/telegram-eventi.jpg',
},
];
};
// Dati circuiti
const circuits = ref({
provinciale: {
availableBalance: 0,
realBalance: 0,
trustLimit: -100,
maxAccumulation: 200,
totalTransactions: 0,
sentCount: 0,
receivedCount: 0,
uniqueMembers: 0,
lastTradeTime: 'Mai',
recentTransactions: [] as any[],
title: 'RIS Provinciale',
},
italia: {
availableBalance: 0,
realBalance: 0,
trustLimit: -200,
maxAccumulation: 400,
totalTransactions: 0,
sentCount: 0,
receivedCount: 0,
uniqueMembers: 0,
lastTradeTime: 'Mai',
recentTransactions: [] as any[],
title: 'RIS Italia',
},
});
// Dati community
const invitesSent = ref(0);
const myHandshakes = ref<any[]>([]);
const allHandshakes = ref<any[]>([]);
// Circuiti overview
const activeCircuitsCount = ref(2);
const totalCircuitsAvailable = ref(12);
// Computed per circuito corrente
const currentCircuitData = computed(() => {
return circuits.value[selectedCircuit.value];
});
// Metodi
const openTransaction = (tx: any) => {
// TODO: aprire dettaglio transazione
};
const openHandshake = (handshake: any) => {
// TODO: aprire dettaglio stretta di mano
};
const goToAllCircuits = () => {
// TODO: navigare a lista circuiti
// $router.push('/circuits')
};
loadTestData();
// ========== LIFECYCLE (da implementare) ==========
// onMounted(() => {
// loadWalletData()
// loadRecentEvents()
// loadRecentActivity()
// loadStats()
// loadOrganizations()
// loadTelegramLinks()
// })
return {
// State
showAnnunciDialog,
// Data
availableBalance,
realBalance,
trustLimit,
maxAccumulation,
receivedCount,
sentCount,
upcomingEventsCount,
recentEvents,
recentTrades,
recentConnections,
totalMembers,
totalTrades,
totalEvents,
organizations,
telegramLinks,
// Computed
hasEvents,
hasOrganizations,
hasTelegramLinks,
// Methods
openAnnunciDialog,
goToGoods,
goToServices,
goToHospitality,
goToTransport,
goToWallet,
goToEvents,
goToProfile,
sendRIS,
receiveRIS,
inviteFriend,
showMembers,
refreshWallet,
goToTransactions,
goToAllEvents,
openEvent,
openTrade,
openConnection,
goToAllTrades,
goToAllConnections,
createOrganization,
openOrganization,
goToAllOrganizations,
openFAQ,
openGuide,
openInfo,
totalTransactions,
uniqueMembers,
lastTradeTime,
goToCircuits,
selectedCircuit,
handshakesView,
circuits,
currentCircuitData,
invitesSent,
myHandshakes,
allHandshakes,
activeCircuitsCount,
totalCircuitsAvailable,
openTransaction,
openHandshake,
goToAllCircuits,
};
},
});

View File

@@ -0,0 +1,768 @@
<template>
<div class="riso-modern-home">
<!-- Hero Section: Card Principali -->
<section class="hero-cards">
<!-- Card Annunci -->
<div
class="hero-card gradient-primary"
@click="openAnnunciDialog"
>
<q-icon
name="campaign"
size="2.5rem"
/>
<span class="card-title">Annunci</span>
<span class="card-subtitle">Beni, Servizi, Ospitalità</span>
</div>
<!-- Card Circuiti RIS -->
<div
class="hero-card gradient-success"
@click="goToCircuits"
>
<q-icon
name="fas fa-coins"
size="2.5rem"
/>
<span class="card-title">Circuiti RIS</span>
<span class="card-subtitle">Gestisci i tuoi RIS</span>
</div>
<!-- Card Eventi -->
<div
class="hero-card gradient-accent"
@click="goToEvents"
>
<q-icon
name="event"
size="2.5rem"
/>
<span class="card-title">Eventi</span>
<span class="card-subtitle">{{ upcomingEventsCount }} prossimi</span>
</div>
<!-- Card Profilo -->
<div
class="hero-card gradient-orange"
@click="goToProfile"
>
<q-icon
name="account_circle"
size="2.5rem"
/>
<span class="card-title">Profilo</span>
<span class="card-subtitle">Gestisci account</span>
</div>
</section>
<!-- Azioni RIS -->
<section class="ris-actions-section">
<div class="ris-actions-grid">
<q-btn
unelevated
rounded
class="ris-action-btn send-btn"
icon="arrow_upward"
label="Invia RIS"
@click="sendRIS"
/>
<q-btn
unelevated
rounded
class="ris-action-btn receive-btn"
icon="arrow_downward"
label="Ricevi RIS"
@click="receiveRIS"
/>
</div>
</section>
<!-- Wallet Card Dettagliata con 2 Circuiti -->
<section class="wallet-section">
<div class="wallet-card">
<div class="wallet-header">
<h2 class="section-title-white">
<q-icon name="fas fa-coins" />
I Tuoi RIS - Circuiti di Scambio
</h2>
<q-btn
flat
dense
round
icon="refresh"
text-color="white"
@click="refreshWallet"
/>
</div>
<!-- Saldi Entrambi Circuiti -->
<div class="circuits-balance-overview">
<div class="circuit-balance-mini">
<CRISBalanceBar
:current-balance="circuits.provinciale.realBalance"
:min-limit="circuits.provinciale.trustLimit"
:max-limit="circuits.provinciale.maxAccumulation"
:label="circuits.provinciale.title"
/>
</div>
<div class="circuit-balance-mini">
<CRISBalanceBar
:current-balance="circuits.italia.realBalance"
:min-limit="circuits.italia.trustLimit"
:max-limit="circuits.italia.maxAccumulation"
:label="circuits.italia.title"
/>
</div>
</div>
<!-- Selettore Circuito -->
<div class="circuit-selector">
<q-btn-toggle
v-model="selectedCircuit"
dense
no-caps
unelevated
rounded
toggle-color="white"
toggle-text-color="primary"
:options="[
{ label: 'RIS Provinciale', value: 'provinciale' },
{ label: 'RIS Italia', value: 'italia' },
]"
class="circuit-toggle"
/>
</div>
<!-- Statistiche transazioni del circuito selezionato -->
<div class="wallet-transactions">
<div class="transaction-stat total">
<q-icon
name="repeat"
size="sm"
/>
<span class="stat-number">{{ currentCircuitData.totalTransactions }}</span>
<span class="stat-label">Transazioni</span>
</div>
<div class="transaction-stat sent">
<q-icon
name="arrow_upward"
size="sm"
/>
<span class="stat-number">{{ currentCircuitData.sentCount }}</span>
<span class="stat-label">Inviate</span>
</div>
<div class="transaction-stat received">
<q-icon
name="arrow_downward"
size="sm"
/>
<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"
:max-limit="currentCircuitData.maxAccumulation"
:label="currentCircuitData.title"
/>
</div>
<!-- Ultime 3 transazioni del circuito selezionato -->
<div class="recent-transactions">
<h4 class="transactions-title">Ultime Tue Transazioni</h4>
<div class="transaction-list">
<div
v-for="(tx, idx) in currentCircuitData.recentTransactions.slice(0, 3)"
:key="idx"
class="transaction-item"
@click="openTransaction(tx)"
>
<q-avatar
size="32px"
:color="tx.amount > 0 ? 'positive' : 'negative'"
text-color="white"
>
{{ tx.userInitial }}
</q-avatar>
<div class="transaction-content">
<span class="transaction-desc">{{ tx.description }}</span>
<span class="transaction-time">{{ tx.time }}</span>
</div>
<span
:class="['transaction-amount', tx.amount > 0 ? 'positive' : 'negative']"
>
{{ tx.amount > 0 ? '+' : '' }}{{ tx.amount }}
</span>
</div>
</div>
</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
class="wallet-detail-btn"
label="Dettaglio Transazioni"
icon-right="arrow_forward"
@click="goToTransactions"
/>
</div>
</section>
<!-- Prossimi Eventi -->
<section
v-if="hasEvents"
class="content-section"
>
<div class="section-header">
<h2 class="section-title">
<q-icon name="event" />
Prossimi Eventi
</h2>
<q-btn
flat
dense
round
icon="arrow_forward"
@click="goToAllEvents"
/>
</div>
<div
v-for="(event, idx) in recentEvents"
:key="idx"
class="event-card"
@click="openEvent(event)"
>
<q-img
:src="event.image || '/images/event-placeholder.png'"
class="event-image"
ratio="1"
>
<template
v-if="!event.image"
v-slot:error
>
<div class="event-image-placeholder">
<q-icon
name="event"
size="2rem"
color="grey-5"
/>
</div>
</template>
</q-img>
<div class="event-date">
<span class="event-day">{{ event.day }}</span>
<span class="event-month">{{ event.month }}</span>
</div>
<div class="event-info">
<span class="event-title">{{ event.title }}</span>
<span class="event-location">{{ event.location }}</span>
</div>
<q-icon name="chevron_right" />
</div>
</section>
<!-- Community Actions -->
<section class="community-actions-section">
<h2 class="section-title">
<q-icon name="people" />
Community
</h2>
<div class="invite-banner">
<div class="invite-message">
<q-icon
name="campaign"
size="md"
color="positive"
/>
<div class="invite-text">
<span class="invite-main"
>Aiuta la community a crescere, parla di RISO ed invita le persone alla
App</span
>
<span class="invite-count"
>Hai già inviato <strong>{{ invitesSent }}</strong> inviti</span
>
</div>
</div>
</div>
<div class="community-actions-grid">
<q-btn
unelevated
rounded
class="community-action-btn"
icon="person_add"
label="Invita Amici"
color="positive"
@click="inviteFriend"
/>
<q-btn
unelevated
rounded
class="community-action-btn"
icon="list"
label="Lista Iscritti"
color="info"
@click="showMembers"
/>
</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 -->
<section class="community-section">
<!-- Strette di Mano -->
<div class="community-card">
<div class="community-header">
<h3 class="community-title">
<q-icon name="handshake" />
Strette di Mano
</h3>
<q-btn-toggle
v-model="handshakesView"
dense
no-caps
unelevated
rounded
size="sm"
toggle-color="primary"
:options="[
{ label: 'Tue', value: 'mine' },
{ label: 'Generali', value: 'all' },
]"
/>
</div>
<div
v-if="handshakesView === 'mine'"
class="activity-list"
>
<div
v-for="(handshake, idx) in myHandshakes.slice(0, 5)"
:key="idx"
class="activity-item"
@click="openHandshake(handshake)"
>
<q-avatar
size="40px"
color="purple-6"
text-color="white"
>
{{ handshake.userInitial }}
</q-avatar>
<div class="activity-content">
<span class="activity-title">{{ handshake.name }}</span>
<span class="activity-time">Stretta di mano {{ handshake.time }}</span>
</div>
<q-icon
name="handshake"
color="purple-6"
/>
</div>
</div>
<div
v-if="handshakesView === 'all'"
class="activity-list"
>
<div
v-for="(handshake, idx) in allHandshakes.slice(0, 5)"
:key="idx"
class="activity-item"
@click="openHandshake(handshake)"
>
<div class="handshake-avatars">
<q-avatar
size="32px"
color="purple-6"
text-color="white"
>
{{ handshake.user1Initial }}
</q-avatar>
<q-avatar
size="32px"
color="purple-4"
text-color="white"
class="avatar-overlap"
>
{{ handshake.user2Initial }}
</q-avatar>
</div>
<div class="activity-content">
<span class="activity-title"
>{{ handshake.user1Name }} e {{ handshake.user2Name }}</span
>
<span class="activity-time">{{ handshake.time }}</span>
</div>
<q-icon
name="handshake"
color="purple-6"
/>
</div>
</div>
</div>
<!-- Ultimi Scambi -->
<div class="community-card">
<div class="community-header">
<h3 class="community-title">
<q-icon name="swap_horiz" />
Ultimi Scambi
</h3>
<q-btn
flat
dense
round
icon="arrow_forward"
@click="goToAllTrades"
/>
</div>
<div class="activity-list">
<div
v-for="(trade, idx) in recentTrades.slice(0, 3)"
:key="idx"
class="activity-item"
@click="openTrade(trade)"
>
<q-avatar
size="40px"
color="primary"
text-color="white"
>
{{ trade.userInitial }}
</q-avatar>
<div class="activity-content">
<span class="activity-title">{{ trade.description }}</span>
<span class="activity-time">{{ trade.time }}</span>
</div>
<div class="activity-amount">
<span :class="trade.amount > 0 ? 'positive' : 'negative'">
{{ trade.amount > 0 ? '+' : '' }}{{ trade.amount }} RIS
</span>
</div>
</div>
</div>
</div>
</section>
<!-- Canali Telegram -->
<section
v-if="hasTelegramLinks"
class="content-section"
>
<div class="section-header">
<h2 class="section-title">
<q-icon name="telegram" />
Unisciti ai Canali
</h2>
</div>
<div class="telegram-grid-modern">
<a
v-for="(link, idx) in telegramLinks.slice(0, 3)"
:key="idx"
:href="link.url"
target="_blank"
class="telegram-card-modern"
>
<q-img
v-if="link.image"
:src="link.image"
class="telegram-card-image"
ratio="16/9"
>
<div class="telegram-overlay">
<q-icon
name="telegram"
size="3rem"
color="white"
/>
</div>
</q-img>
<div
v-else
class="telegram-icon-wrapper"
>
<q-icon
name="telegram"
size="3rem"
color="white"
/>
</div>
<div class="telegram-content-modern">
<span class="telegram-title">{{ link.title }}</span>
<q-icon
name="open_in_new"
size="sm"
/>
</div>
</a>
</div>
</section>
<!-- Footer Links -->
<section class="footer-section-modern">
<q-btn
unelevated
rounded
class="footer-btn-modern"
icon="help_outline"
label="FAQ"
color="primary"
@click="openFAQ"
/>
<q-btn
unelevated
rounded
class="footer-btn-modern"
icon="school"
label="Guida"
color="secondary"
@click="openGuide"
/>
<q-btn
unelevated
rounded
class="footer-btn-modern"
icon="support_agent"
label="Assistenza"
color="positive"
@click="openInfo"
/>
</section>
<!-- Dialog Annunci -->
<q-dialog
v-model="showAnnunciDialog"
transition-show="slide-up"
transition-hide="slide-down"
>
<q-card class="annunci-dialog">
<q-card-section class="dialog-header">
<h3 class="dialog-title">Scegli Categoria</h3>
<q-btn
flat
round
dense
icon="close"
v-close-popup
/>
</q-card-section>
<q-card-section class="dialog-content">
<div class="annunci-options-mobile">
<div
class="annuncio-option gradient-indigo"
@click="goToGoods"
>
<q-icon
name="fas fa-tshirt"
size="2.5rem"
/>
<span class="option-title">Beni</span>
<span class="option-subtitle">Autoproduzioni, cibo</span>
</div>
<div
class="annuncio-option gradient-red"
@click="goToServices"
>
<q-icon
name="fas fa-house-user"
size="2.5rem"
/>
<span class="option-title">Servizi</span>
<span class="option-subtitle">Competenze, aiuti</span>
</div>
<div
class="annuncio-option gradient-lime"
@click="goToHospitality"
>
<q-icon
name="fas fa-bed"
size="2.5rem"
/>
<span class="option-title">Ospitalità</span>
<span class="option-subtitle">Ospitare viaggiatori</span>
</div>
<div
class="annuncio-option gradient-teal"
@click="goToTransport"
>
<q-icon
name="directions_car"
size="2.5rem"
/>
<span class="option-title">Trasporti</span>
<span class="option-subtitle">Condivisione viaggi</span>
</div>
</div>
</q-card-section>
</q-card>
</q-dialog>
</div>
</template>
<script lang="ts" src="./Riso_Home_Modern.ts"></script>
<style lang="scss" scoped>
@import './Riso_Home_Modern.scss';
</style>

View File

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

View File

@@ -74,3 +74,4 @@ export * from './CCurrencyValue'
export * from './CNotifAtTop'
export * from './CShowContentPage'
export * from './CCheckIfIsLogged'
export * from './CRISBalanceBar'