diff --git a/migrate-repos.sh b/migrate-repos.sh new file mode 100755 index 00000000..43e1f5de --- /dev/null +++ b/migrate-repos.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# Salva come migrate-repos.sh + +GITEA_URL="http://95.216.147.38:3000" +USERNAME="surya" +TOKEN="8ed0622aac269414f4d333d0c89e22b1c42dd4d1" # Crea su Gitea: Settings β†’ Applications β†’ Generate Token +SEARCH_PATH="$HOME/myproject" + +# Trova tutti i repository +find "$SEARCH_PATH" -name ".git" -type d 2>/dev/null | while read gitdir; do + REPO_PATH=$(dirname "$gitdir") + REPO_NAME=$(basename "$REPO_PATH") + + echo "Processing: $REPO_NAME" + + # Crea repository su Gitea via API + curl -X POST "$GITEA_URL/api/v1/user/repos" \ + -H "Authorization: token $TOKEN" \ + -H "Content-Type: application/json" \ + -d "{\"name\":\"$REPO_NAME\",\"private\":false}" + + # Push + cd "$REPO_PATH" + git remote remove origin 2>/dev/null + git remote add origin "$GITEA_URL/$USERNAME/$REPO_NAME.git" + git push -u origin --all + git push -u origin --tags +done diff --git a/public/images/favicon-16x16.png b/public/images/favicon-16x16.png index 2ee58086..c641bd87 100644 Binary files a/public/images/favicon-16x16.png and b/public/images/favicon-16x16.png differ diff --git a/public/images/favicon-32x32.png b/public/images/favicon-32x32.png index 4966c174..49cc8c73 100644 Binary files a/public/images/favicon-32x32.png and b/public/images/favicon-32x32.png differ diff --git a/public/images/favicon-96x96.png b/public/images/favicon-96x96.png index 7917d432..6fa45ce9 100644 Binary files a/public/images/favicon-96x96.png and b/public/images/favicon-96x96.png differ diff --git a/public/images/logo.png b/public/images/logo.png index 51a29654..6865b55e 100644 Binary files a/public/images/logo.png and b/public/images/logo.png differ diff --git a/public/images/riso-android-icon-144x144.png b/public/images/riso-android-icon-144x144.png index f3ef595e..95ffebdc 100644 Binary files a/public/images/riso-android-icon-144x144.png and b/public/images/riso-android-icon-144x144.png differ diff --git a/public/images/riso-android-icon-192x192.png b/public/images/riso-android-icon-192x192.png index bbd24308..83e6b02c 100644 Binary files a/public/images/riso-android-icon-192x192.png and b/public/images/riso-android-icon-192x192.png differ diff --git a/public/images/riso-android-icon-36x36.png b/public/images/riso-android-icon-36x36.png index a760fd45..6f21caa7 100644 Binary files a/public/images/riso-android-icon-36x36.png and b/public/images/riso-android-icon-36x36.png differ diff --git a/public/images/riso-android-icon-384x384.png b/public/images/riso-android-icon-384x384.png index 9d3710bc..ce59503f 100644 Binary files a/public/images/riso-android-icon-384x384.png and b/public/images/riso-android-icon-384x384.png differ diff --git a/public/images/riso-android-icon-48x48.png b/public/images/riso-android-icon-48x48.png index ef38311d..2a0d9b4d 100644 Binary files a/public/images/riso-android-icon-48x48.png and b/public/images/riso-android-icon-48x48.png differ diff --git a/public/images/riso-android-icon-512x512.png b/public/images/riso-android-icon-512x512.png index 7902dcfc..18a0d5ef 100644 Binary files a/public/images/riso-android-icon-512x512.png and b/public/images/riso-android-icon-512x512.png differ diff --git a/public/images/riso-android-icon-72x72.png b/public/images/riso-android-icon-72x72.png index ae698892..97c38fe8 100644 Binary files a/public/images/riso-android-icon-72x72.png and b/public/images/riso-android-icon-72x72.png differ diff --git a/public/images/riso-android-icon-96x96.png b/public/images/riso-android-icon-96x96.png index 7917d432..6fa45ce9 100644 Binary files a/public/images/riso-android-icon-96x96.png and b/public/images/riso-android-icon-96x96.png differ diff --git a/public/images/riso-apple-icon-114x114.png b/public/images/riso-apple-icon-114x114.png index 3cde0659..9fa0ce49 100644 Binary files a/public/images/riso-apple-icon-114x114.png and b/public/images/riso-apple-icon-114x114.png differ diff --git a/public/images/riso-apple-icon-120x120.png b/public/images/riso-apple-icon-120x120.png index e9012198..655c9a58 100644 Binary files a/public/images/riso-apple-icon-120x120.png and b/public/images/riso-apple-icon-120x120.png differ diff --git a/public/images/riso-apple-icon-144x144.png b/public/images/riso-apple-icon-144x144.png index f3ef595e..95ffebdc 100644 Binary files a/public/images/riso-apple-icon-144x144.png and b/public/images/riso-apple-icon-144x144.png differ diff --git a/public/images/riso-apple-icon-152x152.png b/public/images/riso-apple-icon-152x152.png index a3878a56..c2043d09 100644 Binary files a/public/images/riso-apple-icon-152x152.png and b/public/images/riso-apple-icon-152x152.png differ diff --git a/public/images/riso-apple-icon-180x180.png b/public/images/riso-apple-icon-180x180.png index 2e541930..6f23b57a 100644 Binary files a/public/images/riso-apple-icon-180x180.png and b/public/images/riso-apple-icon-180x180.png differ diff --git a/public/images/riso-apple-icon-57x57.png b/public/images/riso-apple-icon-57x57.png index 29741800..d43640aa 100644 Binary files a/public/images/riso-apple-icon-57x57.png and b/public/images/riso-apple-icon-57x57.png differ diff --git a/public/images/riso-apple-icon-60x60.png b/public/images/riso-apple-icon-60x60.png index 0180c7a9..309ee6ee 100644 Binary files a/public/images/riso-apple-icon-60x60.png and b/public/images/riso-apple-icon-60x60.png differ diff --git a/public/images/riso-apple-icon-72x72.png b/public/images/riso-apple-icon-72x72.png index ae698892..97c38fe8 100644 Binary files a/public/images/riso-apple-icon-72x72.png and b/public/images/riso-apple-icon-72x72.png differ diff --git a/public/images/riso-apple-icon-76x76.png b/public/images/riso-apple-icon-76x76.png index 87b581a2..5d5923c6 100644 Binary files a/public/images/riso-apple-icon-76x76.png and b/public/images/riso-apple-icon-76x76.png differ diff --git a/public/images/riso-apple-icon.png b/public/images/riso-apple-icon.png deleted file mode 100644 index 3483f0f5..00000000 Binary files a/public/images/riso-apple-icon.png and /dev/null differ diff --git a/public/images/riso-logo-full.png b/public/images/riso-logo-full.png index 887e14b8..6865b55e 100644 Binary files a/public/images/riso-logo-full.png and b/public/images/riso-logo-full.png differ diff --git a/public/images/riso_quadrato.jpg b/public/images/riso_quadrato.jpg new file mode 100644 index 00000000..a86fbeab Binary files /dev/null and b/public/images/riso_quadrato.jpg differ diff --git a/src/common/shared_vuejs.ts b/src/common/shared_vuejs.ts index 6c78fc75..667882af 100755 --- a/src/common/shared_vuejs.ts +++ b/src/common/shared_vuejs.ts @@ -147,7 +147,6 @@ export const shared_consts = { DASHBOARD: 140, DASHGROUP: 145, MOVEMENTS: 148, - CSENDRISTO: 150, STATUSREG: 160, CHECKIFISLOGGED: 170, INFO_VERSION: 180, @@ -193,6 +192,7 @@ export const shared_consts = { RISOHOME_MODERN: 1610, PAGERIS: 1620, CMYCIRCUITS: 1630, + CREA_VOLANTINO: 1700, }, QUERYTYPE_MYGROUP: 1, @@ -2009,11 +2009,6 @@ export const shared_consts = { label: 'Lista Movimenti', icon: 'fas fa-list', }, - { - value: 150, // CSENDRISTO - label: 'Bott (Invia/Ricevi RIS)', - icon: 'fas fa-wallet', - }, { value: 280, label: 'Tutorial', @@ -2057,6 +2052,11 @@ export const shared_consts = { label: 'Check Email', icon: 'fas fa-envelope', }, + { + value: 1700, // CREA_VOLANTINO + label: 'Genera Volantini', + icon: 'fas fa-user-tie', + }, { value: 120, label: 'OpenStreetMap', @@ -2481,6 +2481,7 @@ export const shared_consts = { link_group: 1, totCircolante: 1, totTransato: 1, + numTransazioni: 1, systemUserId: 1, createdBy: 1, date_created: 1, diff --git a/src/components/AIImageGenerator/AIImageGenerator.vue b/src/components/AIImageGenerator/AIImageGenerator.vue new file mode 100644 index 00000000..d93f21bd --- /dev/null +++ b/src/components/AIImageGenerator/AIImageGenerator.vue @@ -0,0 +1,749 @@ + + + + + diff --git a/src/components/CContactUser/CContactUser.ts b/src/components/CContactUser/CContactUser.ts index 2b25d483..94bb6da7 100755 --- a/src/components/CContactUser/CContactUser.ts +++ b/src/components/CContactUser/CContactUser.ts @@ -31,6 +31,11 @@ export default defineComponent({ required: false, default: '', }, + circuitSel: { + type: String, + required: false, + default: '', + }, causalDest: { type: String, required: false, diff --git a/src/components/CContactUser/CContactUser.vue b/src/components/CContactUser/CContactUser.vue index 5a4cd087..eeedfc4a 100755 --- a/src/components/CContactUser/CContactUser.vue +++ b/src/components/CContactUser/CContactUser.vue @@ -96,6 +96,7 @@ :to_user="myuser" :sendRIS="sendRIS" :causalDest="causalDest" + :circuitname="circuitSel" @close=" showsendCoinTo = false; loading = false; diff --git a/src/components/CCopyBtnSmall/CCopyBtnSmall.scss b/src/components/CCopyBtnSmall/CCopyBtnSmall.scss index c7e418d8..1bef946d 100755 --- a/src/components/CCopyBtnSmall/CCopyBtnSmall.scss +++ b/src/components/CCopyBtnSmall/CCopyBtnSmall.scss @@ -1,9 +1,215 @@ -.my-custom-container { - max-width: 100%; /* Imposta la larghezza massima per il contenitore */ +// Variables +$border-radius-sm: 8px; +$border-radius-md: 12px; +$transition-fast: 0.2s ease; + +.copy-share-container { + width: 100%; + + &.small-variant { + font-size: 13px; + } + + &.btn-only { + display: inline-flex; + } } -.wrapword { - overflow: hidden; /* Nasconde il contenuto in eccesso */ - white-space: nowrap; /* Impedisce il wrapping del testo */ - text-overflow: ellipsis; /* Mostra "..." se il testo Γ¨ troppo lungo */ +// ═══════════════════════════════════════════ +// Link Display Wrapper +// ═══════════════════════════════════════════ +.link-display-wrapper { + display: flex; + flex-direction: column; + + gap: 12px; + width: 100%; } + +// Link Preview Box +.link-preview-box { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 8px; + padding: 10px 12px; + background: #f8fafc; + border: 1.5px solid #e2e8f0; + border-radius: $border-radius-sm; + transition: all $transition-fast; + + &:hover { + border-color: #cbd5e1; + background: #f1f5f9; + } + + &.compact { + padding: 8px 10px; + } + + .link-icon { + flex-shrink: 0; + opacity: 0.7; + } + + .link-text { + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + color: #475569; + font-size: 13px; + font-family: 'SF Mono', 'Monaco', 'Consolas', monospace; + } + + .copy-icon-btn { + flex-shrink: 0; + transition: all $transition-fast; + + &:hover { + transform: scale(1.1); + } + } +} + +// ═══════════════════════════════════════════ +// Action Buttons +// ═══════════════════════════════════════════ +.action-buttons { + display: flex; + gap: 10px; + justify-content: center; + flex-wrap: wrap; + + &.compact { + gap: 8px; + + .q-btn { + min-width: auto; + } + } + + .share-btn, + .whatsapp-btn, + .telegram-btn { + flex: 1; + max-width: 160px; + text-transform: none; + font-weight: 600; + font-size: 13px; + + &:hover { + transform: translateY(-1px); + } + } + + .whatsapp-btn { + &:not(.q-btn--flat) { + background: linear-gradient(135deg, #25d366 0%, #128c7e 100%) !important; + border: none !important; + color: white !important; + } + } + + .telegram-btn { + &:not(.q-btn--flat) { + background: linear-gradient(135deg, #0088cc 0%, #0077b5 100%) !important; + border: none !important; + color: white !important; + } + } +} + +// ═══════════════════════════════════════════ +// Default Layout +// ═══════════════════════════════════════════ +.default-layout { + display: flex; + flex-direction: column; + align-items: center; + gap: 12px; + width: 100%; + + .text-preview { + text-align: center; + color: #64748b; + font-size: 14px; + word-break: break-all; + padding: 0 8px; + + &.small { + font-size: 12px; + } + } + + .share-main-btn { + text-transform: none; + font-weight: 600; + padding: 10px 24px; + box-shadow: 0 2px 8px rgba(102, 126, 234, 0.25); + transition: all $transition-fast; + + &:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(102, 126, 234, 0.35); + } + } +} + +// ═══════════════════════════════════════════ +// Copied State Animation +// ═══════════════════════════════════════════ +.copied-state { + animation: pulse-success 0.3s ease; +} + +@keyframes pulse-success { + 0% { + transform: scale(1); + } + 50% { + transform: scale(1.05); + } + 100% { + transform: scale(1); + } +} + +// ═══════════════════════════════════════════ +// Dark Mode +// ═══════════════════════════════════════════ +.body--dark { + .link-preview-box { + background: #1e293b; + border-color: #334155; + + &:hover { + border-color: #475569; + background: #273449; + } + + .link-text { + color: #cbd5e1; + } + } + + .text-preview { + color: #94a3b8; + } +} + +// ═══════════════════════════════════════════ +// Responsive +// ═══════════════════════════════════════════ +@media (max-width: 400px) { + .action-buttons:not(.compact) { + flex-direction: column; + + .share-btn, + .whatsapp-btn, + .telegram-btn { + max-width: 100%; + } + } +} \ No newline at end of file diff --git a/src/components/CCopyBtnSmall/CCopyBtnSmall.ts b/src/components/CCopyBtnSmall/CCopyBtnSmall.ts index 718beb94..6204b6d4 100755 --- a/src/components/CCopyBtnSmall/CCopyBtnSmall.ts +++ b/src/components/CCopyBtnSmall/CCopyBtnSmall.ts @@ -1,55 +1,208 @@ import { tools } from '../../store/Modules/tools' import { useQuasar } from 'quasar' import { useI18n } from 'vue-i18n' -import { useUserStore } from '@store/UserStore' -import { useGlobalStore } from '@store/globalStore' -import { defineComponent } from 'vue' +import { defineComponent, ref, computed } from 'vue' import { shared_consts } from '@/common/shared_vuejs' export default defineComponent({ name: 'CCopyBtnSmall', props: { + // Testo/link da copiare texttocopy: { type: String, required: true, }, + // Titolo per la condivisione title: { type: String, - required: false, + default: 'Condividi questo link', + }, + // Messaggio personalizzato per la condivisione + shareMessage: { + type: String, default: '', }, + // Lunghezza massima del testo visualizzato + maxLength: { + type: Number, + default: 400, + }, + // Dimensione compatta small: { type: Boolean, - required: false, default: false, - } + }, + // Mostra solo il pulsante (senza link preview) + btn: { + type: Boolean, + default: false, + }, + // Mostra solo icona (senza label) + iconOnly: { + type: Boolean, + default: false, + }, + // Stile flat + flat: { + type: Boolean, + default: false, + }, + // Stile outline + outline: { + type: Boolean, + default: false, + }, + // Colore del pulsante + btnColor: { + type: String, + default: 'primary', + }, + // Label personalizzata + label: { + type: String, + default: 'Copia link', + }, + // Mostra il link preview + showLink: { + type: Boolean, + default: false, + }, + // Mostra i pulsanti di azione + showActions: { + type: Boolean, + default: true, + }, + // Mostra pulsante Share nativo + showShareBtn: { + type: Boolean, + default: true, + }, + // Mostra pulsante WhatsApp + showWhatsApp: { + type: Boolean, + default: true, + }, + // Mostra pulsante Telegram + showTelegram: { + type: Boolean, + default: false, + }, }, - components: {}, - setup(props) { + + emits: ['copied', 'shared'], + + setup(props, { emit }) { const $q = useQuasar() const { t } = useI18n() - async function copytoclipandsend() { - tools.copyStringToClipboard($q, props.texttocopy, true) + // State + const copied = ref(false) + let copyTimeout: ReturnType | null = null - let msg = 'Questo Γ¨ il link che puoi condividere per farti inviare i RIS:

πŸ‘‰πŸ» ' + props.texttocopy + // Computed + const truncatedText = computed(() => { + if (!props.texttocopy) return '' + if (props.texttocopy.length <= props.maxLength) return props.texttocopy + return props.texttocopy.substring(0, props.maxLength) + '...' + }) - tools.sendMsgTelegramCmd($q, t, shared_consts.MsgTeleg.SHARE_TEXT, false, msg) + const containerClasses = computed(() => ({ + 'small-variant': props.small, + 'btn-only': props.btn && !props.showLink, + })) - } + const shareText = computed(() => { + if (props.shareMessage) return props.shareMessage + return `${props.title}\n\nπŸ‘‰ ${props.texttocopy}` + }) - function getclass() { - if (props.small) { - return 'text-h7' - } else { - return 'text-h5' + // Methods + async function handleCopy() { + try { + await navigator.clipboard.writeText(props.texttocopy) + + copied.value = true + emit('copied', props.texttocopy) + + $q.notify({ + type: 'positive', + message: 'Link copiato negli appunti!', + icon: 'check', + timeout: 2000, + position: 'bottom', + }) + + // Reset stato dopo 2 secondi + if (copyTimeout) clearTimeout(copyTimeout) + copyTimeout = setTimeout(() => { + copied.value = false + }, 2000) + + } catch (err) { + // Fallback per browser piΓΉ vecchi + tools.copyStringToClipboard($q, props.texttocopy, true) + copied.value = true + + if (copyTimeout) clearTimeout(copyTimeout) + copyTimeout = setTimeout(() => { + copied.value = false + }, 2000) } } + async function handleShare() { + // Prima copia negli appunti + await handleCopy() + + // Prova Web Share API + if (navigator.share) { + try { + await navigator.share({ + title: props.title, + text: props.shareMessage || 'Ecco il link:', + url: props.texttocopy, + }) + emit('shared', 'native') + } catch (err) { + // L'utente ha annullato o errore + console.log('Share cancelled or failed') + } + } else { + // Fallback: usa Telegram come default + handleTelegram() + } + } + + function handleWhatsApp() { + const text = encodeURIComponent(shareText.value) + window.open(`https://wa.me/?text=${text}`, '_blank') + emit('shared', 'whatsapp') + } + + function handleTelegram() { + const msg = props.shareMessage || + `Questo Γ¨ il link che puoi condividere:\n\nπŸ‘‰ ${props.texttocopy}` + + tools.sendMsgTelegramCmd($q, t, shared_consts.MsgTeleg.SHARE_TEXT, false, msg) + emit('shared', 'telegram') + } + return { - copytoclipandsend, + // State + copied, + + // Computed + truncatedText, + containerClasses, + + // Methods + handleCopy, + handleShare, + handleWhatsApp, + handleTelegram, + + // Utils tools, - getclass, t, } }, diff --git a/src/components/CCopyBtnSmall/CCopyBtnSmall.vue b/src/components/CCopyBtnSmall/CCopyBtnSmall.vue index 3051b943..c7db8076 100755 --- a/src/components/CCopyBtnSmall/CCopyBtnSmall.vue +++ b/src/components/CCopyBtnSmall/CCopyBtnSmall.vue @@ -1,11 +1,127 @@ diff --git a/src/components/CMyCircuit/CMyCircuit.vue b/src/components/CMyCircuit/CMyCircuit.vue index da51269d..896ca6ac 100755 --- a/src/components/CMyCircuit/CMyCircuit.vue +++ b/src/components/CMyCircuit/CMyCircuit.vue @@ -219,7 +219,7 @@ " > {{ - circuit.askManagerToEnter ? t('circuit.ask') : t('circuit.enter') + circuit.askManagerToEnter ? t('circuit.ask') : t('circuit.Iscriviti') }} @@ -436,7 +436,7 @@ " icon="fas fa-user-plus" color="primary" - :label="circuit.askManagerToEnter ? t('circuit.ask') : t('circuit.enter')" + :label="circuit.askManagerToEnter ? t('circuit.ask') : t('circuit.Iscriviti')" rounded size="lg" @click=" diff --git a/src/components/CMyCircuits/CMyCircuits.ts b/src/components/CMyCircuits/CMyCircuits.ts index 6d9523da..aaf3d2e6 100755 --- a/src/components/CMyCircuits/CMyCircuits.ts +++ b/src/components/CMyCircuits/CMyCircuits.ts @@ -14,12 +14,11 @@ import { tools } from '@tools' import { CUserNonVerif } from '@/components/CUserNonVerif' import { CTitleBanner } from '@/components/CTitleBanner' import { CMovements } from '@/components/CMovements' -import { CSendRISTo } from '@/components/CSendRISTo' export default defineComponent({ name: 'CMyCircuits', - components: { CMyCircuit, CUserNonVerif, CTitleBanner, CMovements, CSendRISTo }, + components: { CMyCircuit, CUserNonVerif, CTitleBanner, CMovements }, emits: ['update:modelValue'], props: { modelValue: { diff --git a/src/components/CMyCircuits/CMyCircuits.vue b/src/components/CMyCircuits/CMyCircuits.vue index 59c48449..e932565c 100755 --- a/src/components/CMyCircuits/CMyCircuits.vue +++ b/src/components/CMyCircuits/CMyCircuits.vue @@ -5,7 +5,6 @@
-
+
˚ +
+ CREA POSTER VOLANTINI: +
+ + +
-
-
- Bottoni (Invia/Ricevi RIS) CSendRISTo -
- -
(null); + + // State const state = reactive({ - data: null - }) + data: null as string | null, + isDownloading: false, + uploadedFile: null as File | null, + }); - function onDecode(data: any) { - if (data) - state.data = data + // Computed + const qrSize = computed(() => { + // Responsive size + if ($q.screen.lt.sm) { + return Math.min(props.size, window.innerWidth - 80); + } + return props.size; + }); + + const logoImage = computed(() => { + return props.imglogo || tools.getimglogo(); + }); + + const dotsOptions = computed(() => ({ + type: 'rounded' as const, + color: props.primaryColor, + gradient: { + type: 'linear' as const, + rotation: 0, + colorStops: [ + { offset: 0, color: props.primaryColor }, + { offset: 1, color: lightenColor(props.primaryColor, 20) }, + ], + }, + })); + + const canShare = computed(() => { + return !!navigator.share; + }); + + const downloadFilename = computed(() => { + const username = userStore.my?.username || 'user'; + const timestamp = Date.now(); + return `qrcode-${username}-${timestamp}`; + }); + + // Methods + function onDecode(data: string) { + if (data) { + state.data = data; + emit('decoded', data); + + // Vibrazione feedback su mobile + if (navigator.vibrate) { + navigator.vibrate(100); + } + + $q.notify({ + type: 'positive', + message: 'QR Code rilevato!', + position: 'top', + timeout: 2000, + }); + } } - const text = ref(''); + function handleFileUpload(file: File | null) { + if (!file) return; + + // Qui potresti usare una libreria per decodificare QR da immagine + // Per esempio: jsQR + $q.notify({ + type: 'info', + message: 'Analisi immagine in corso...', + position: 'top', + }); + } + + async function findCanvas(maxAttempts = 10): Promise { + for (let i = 0; i < maxAttempts; i++) { + // Cerca in vari modi + let canvas: HTMLCanvasElement | null = null; + + // Metodo 1: cerca nel ref + if (qrDisplayRef.value) { + canvas = qrDisplayRef.value.querySelector('canvas'); + } + + // Metodo 2: cerca con classe specifica + if (!canvas) { + const qrCode = document.querySelector('.qr-code'); + canvas = qrCode?.querySelector('canvas') || null; + } + + // Metodo 3: cerca nel container .qr-display + if (!canvas) { + const container = document.querySelector('.qr-display'); + canvas = container?.querySelector('canvas') || null; + } + + if (canvas && canvas.width > 0 && canvas.height > 0) { + return canvas; + } + + // Aspetta prima del prossimo tentativo + await new Promise((resolve) => setTimeout(resolve, 100)); + } + + return null; + } + + // Nuova funzione downloadQR + async function downloadQR() { + state.isDownloading = true; + + try { + await nextTick(); + await new Promise((resolve) => setTimeout(resolve, 200)); + + let dataUrl: string | null = null; + + // Metodo 1: Cerca l'immagine (QRCodeVue3 genera un con base64) + const qrDisplay = qrDisplayRef.value || document.querySelector('.qr-display'); + + if (qrDisplay) { + const img = qrDisplay.querySelector('img') as HTMLImageElement; + + if (img && img.src && img.src.startsWith('data:image')) { + console.log('βœ… Immagine base64 trovata!'); + dataUrl = img.src; + } + } + + // Metodo 2: Se non trova img, cerca canvas (fallback) + if (!dataUrl) { + const canvas = document.querySelector( + '.qr-display canvas' + ) as HTMLCanvasElement; + if (canvas && canvas.width > 0) { + console.log('βœ… Canvas trovato come fallback'); + dataUrl = canvas.toDataURL('image/png'); + } + } + + if (!dataUrl) { + debugCanvas(); + throw new Error('QR Code non trovato. Riprova.'); + } + + // Download + const link = document.createElement('a'); + link.href = dataUrl; + link.download = `${downloadFilename.value}.png`; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + + $q.notify({ + type: 'positive', + message: 'QR Code scaricato!', + icon: 'download_done', + position: 'top', + }); + } catch (error: any) { + console.error('Errore download QR:', error); + $q.notify({ + type: 'negative', + message: error.message || 'Errore durante il download', + position: 'top', + }); + emit('error', error); + } finally { + state.isDownloading = false; + } + } + async function shareQR() { + try { + // Cerca l'immagine base64 + const qrDisplay = qrDisplayRef.value || document.querySelector('.qr-display'); + const img = qrDisplay?.querySelector('img') as HTMLImageElement; + + if (img && img.src && img.src.startsWith('data:image')) { + // Converti base64 in blob + const response = await fetch(img.src); + const blob = await response.blob(); + + const file = new File([blob], `${downloadFilename.value}.png`, { + type: 'image/png', + }); + + if (navigator.canShare && navigator.canShare({ files: [file] })) { + await navigator.share({ + title: props.textlink || 'QR Code', + files: [file], + }); + + $q.notify({ + type: 'positive', + message: 'Condivisione avviata!', + position: 'top', + }); + return; + } + } + + // Fallback: condividi solo il link + await navigator.share({ + title: props.textlink || 'QR Code', + url: props.link, + }); + } catch (error: any) { + if (error.name !== 'AbortError') { + console.error('Errore condivisione:', error); + } + } + } + function copyToClipboard(text: string) { + navigator.clipboard + .writeText(text) + .then(() => { + $q.notify({ + type: 'positive', + message: 'Copiato negli appunti!', + icon: 'content_copy', + position: 'top', + timeout: 1500, + }); + }) + .catch((err) => { + console.error('Errore copia:', err); + }); + } + + function isValidUrl(text: string): boolean { + try { + new URL(text); + return true; + } catch { + return false; + } + } + + function truncateUrl(url: string, maxLength: number = 400): string { + if (!url || url.length <= maxLength) return url; + return url.substring(0, maxLength) + '...'; + } + + function lightenColor(color: string, percent: number): string { + const num = parseInt(color.replace('#', ''), 16); + const amt = Math.round(2.55 * percent); + const R = (num >> 16) + amt; + const G = ((num >> 8) & 0x00ff) + amt; + const B = (num & 0x0000ff) + amt; + return ( + '#' + + ( + 0x1000000 + + (R < 255 ? (R < 1 ? 0 : R) : 255) * 0x10000 + + (G < 255 ? (G < 1 ? 0 : G) : 255) * 0x100 + + (B < 255 ? (B < 1 ? 0 : B) : 255) + ) + .toString(16) + .slice(1) + ); + } function naviga(path: string) { - $router.push(path) + $router.push(path); } - onMounted(mounted) + function debugCanvas() { + console.log('=== DEBUG QR ==='); + console.log('1. qrDisplayRef.value:', qrDisplayRef.value); - function mounted() { - // ... + const qrDisplay = qrDisplayRef.value || document.querySelector('.qr-display'); + + if (qrDisplay) { + // Cerca immagini + const imgs = qrDisplay.querySelectorAll('img'); + console.log('2. Immagini trovate:', imgs.length); + imgs.forEach((img, i) => { + const imgEl = img as HTMLImageElement; + console.log( + ` Img ${i}: src starts with data:image = ${imgEl.src?.startsWith('data:image')}` + ); + }); + + // Cerca canvas + const canvases = qrDisplay.querySelectorAll('canvas'); + console.log('3. Canvas trovati:', canvases.length); + + console.log( + '4. innerHTML (primi 200 char):', + qrDisplay.innerHTML.substring(0, 200) + ); + } } + // Lifecycle + onMounted(() => { + // Inizializzazione se necessaria + }); + return { + // Stores + globalStore, + userStore, + + // Utils t, tools, costanti, toolsext, - text, - userStore, + + // State ...toRefs(state), + + // Computed + qrSize, + logoImage, + dotsOptions, + canShare, + downloadFilename, + primaryColor: props.primaryColor, + + // Methods onDecode, + handleFileUpload, + qrDisplayRef, + downloadQR, + shareQR, + copyToClipboard, + isValidUrl, + truncateUrl, naviga, - globalStore, - } + }; }, -}) +}); diff --git a/src/components/CQRCode/CQRCode.vue b/src/components/CQRCode/CQRCode.vue index 5cf18716..08497d90 100755 --- a/src/components/CQRCode/CQRCode.vue +++ b/src/components/CQRCode/CQRCode.vue @@ -1,71 +1,215 @@ - + + diff --git a/src/components/CRISBalanceBar/CRISBalanceBar.scss b/src/components/CRISBalanceBar/CRISBalanceBar.scss index c5cdff09..15fbc89d 100755 --- a/src/components/CRISBalanceBar/CRISBalanceBar.scss +++ b/src/components/CRISBalanceBar/CRISBalanceBar.scss @@ -11,7 +11,7 @@ $r-md: 10px; background: rgba(255, 255, 255, 0.1); backdrop-filter: blur(5px); border-radius: $r-md; - padding: $s-md; + padding: $s-sm; border: 1px solid rgba(255, 255, 255, 0.2); } @@ -27,7 +27,7 @@ $r-md: 10px; flex-direction: column; justify-content: space-between; align-items: center; - margin-bottom: $s-md; + margin-bottom: $s-lg; .balance-label { font-size: 0.85rem; @@ -56,7 +56,7 @@ $r-md: 10px; // Container progressione .progress-container { position: relative; - margin-bottom: $s-lg; + margin-bottom: $s-xs; } // Track di sfondo @@ -160,6 +160,7 @@ $r-md: 10px; opacity: 0.8; text-transform: uppercase; font-weight: 600; + color: white; } } } @@ -169,7 +170,7 @@ $r-md: 10px; display: grid; grid-template-columns: repeat(2, 1fr); gap: $s-sm; - padding-top: $s-md; + padding-top: $s-sm; border-top: 1px solid rgba(255, 255, 255, 0.2); // Layout inline quando !small diff --git a/src/components/CRISBalanceBar/CRISBalanceBar.ts b/src/components/CRISBalanceBar/CRISBalanceBar.ts index 9e343292..2ce10569 100755 --- a/src/components/CRISBalanceBar/CRISBalanceBar.ts +++ b/src/components/CRISBalanceBar/CRISBalanceBar.ts @@ -1,4 +1,5 @@ import { defineComponent, computed } from 'vue'; +import { useI18n } from 'vue-i18n'; export default defineComponent({ name: 'CRISBalanceBar', @@ -24,14 +25,24 @@ export default defineComponent({ // Label opzionale label: { type: String, - default: 'Range disponibile', + default: '', }, small: { type: Boolean, default: false, + }, + color: { + type: String, + default: '', + }, + info: { + type: Boolean, + default: true, } }, setup(props) { + + const { t } = useI18n(); // Range totale const totalRange = computed(() => { return Math.abs(props.minLimit) + props.maxLimit; @@ -86,6 +97,7 @@ export default defineComponent({ canGive, canReceive, zeroPosition, + t, }; }, }); diff --git a/src/components/CRISBalanceBar/CRISBalanceBar.vue b/src/components/CRISBalanceBar/CRISBalanceBar.vue index 3ae32c4e..ea60110e 100755 --- a/src/components/CRISBalanceBar/CRISBalanceBar.vue +++ b/src/components/CRISBalanceBar/CRISBalanceBar.vue @@ -1,8 +1,15 @@ +
@@ -100,196 +140,383 @@ {{ $t('circuit.insertprovince_text') }} - -
- - - - - - - - - - - - - -
- - -
- -
-
- - - - - - - - -
-
-
- - -
- - -
- + + +