Rendi le email più fruibili con i componenti aggiuntivi di Google Workspace

1. Panoramica

In questo codelab, utilizzerai Google Apps Script per scrivere un componente aggiuntivo di Google Workspace per Gmail che consenta agli utenti di aggiungere i dati della ricevuta da un'email a un foglio di lavoro direttamente in Gmail. Quando un utente riceve una ricevuta via email, apre il componente aggiuntivo che riceve automaticamente le informazioni pertinenti sulle spese dall'email. L'utente può modificare le informazioni sulle spese e inviarle per registrarle in un foglio di lavoro di Fogli Google.

Obiettivi didattici

  • Crea un componente aggiuntivo di Google Workspace per Gmail utilizzando Google Apps Script
  • Analizzare un'email con Google Apps Script
  • Interagire con Fogli Google tramite Google Apps Script
  • Archiviare i valori utente utilizzando il servizio Proprietà di Google Apps Script

Che cosa ti serve

  • Accesso a internet e a un browser web
  • Un Account Google
  • Alcuni messaggi, preferibilmente ricevute via email, in Gmail

2. recupera il codice campione

Mentre lavori in questo codelab, potrebbe essere utile fare riferimento a una versione funzionante del codice che scriverai. Il repository GitHub contiene codice campione che puoi utilizzare come riferimento.

Per ottenere il codice campione, dalla riga di comando esegui:

git clone https://github.com/googleworkspace/gmail-add-on-codelab.git

3. Crea un componente aggiuntivo di base

Inizia scrivendo il codice per una versione semplice del componente aggiuntivo che mostri un modulo di spese insieme a un'email.

Per prima cosa, crea un nuovo progetto Apps Script e apri il relativo file manifest.

  1. Vai a script.google.com. Qui puoi creare, gestire e monitorare i tuoi progetti Apps Script.
  2. Per creare un nuovo progetto, fai clic su Nuovo progetto in alto a sinistra. Il nuovo progetto si apre con un file predefinito denominato Code.gs. Per il momento non usare Code.gs, ci userai più tardi.
  3. Fai clic su Progetto senza titolo, assegna al progetto il nome Expense It! e fai clic su Rinomina.
  4. A sinistra, fai clic su Impostazioni progetto Impostazioni progetto.
  5. Seleziona Mostra "appscript.json" "file manifest nell'editor".
  6. Fai clic su Editor Editor.
  7. Per aprire il file manifest, fai clic su appscript.json a sinistra.

In appscript.json, specifica i metadati associati al componente aggiuntivo, come il nome e le autorizzazioni di cui ha bisogno. Sostituisci i contenuti di appsscript.json con queste impostazioni di configurazione:

{
  "timeZone": "GMT",
  "oauthScopes": [
    "https://www.googleapis.com/auth/gmail.addons.execute"
  ],
  "gmail": {
    "name": "Expense It!",
    "logoUrl": "https://www.gstatic.com/images/icons/material/system/1x/receipt_black_24dp.png",
    "contextualTriggers": [{
      "unconditional": {
      },
      "onTriggerFunction": "getContextualAddOn"
    }],
    "primaryColor": "#41f470",
    "secondaryColor": "#94f441"
  }
}

Presta particolare attenzione alla parte del file manifest denominata contextualTriggers. Questa parte del file manifest identifica la funzione definita dall'utente da chiamare alla prima attivazione del componente aggiuntivo. In questo caso, viene chiamato getContextualAddOn, che riceve i dettagli dell'email aperta e restituisce una serie di schede da mostrare all'utente.

Per creare la funzione getContextualAddOn:

  1. A sinistra, tieni premuto il puntatore su Code.gs e fai clic su Menu Menu Altro > Rinomina.
  2. Digita GetContextualAddOn e premi il tasto Enter. Apps Script aggiunge automaticamente .gs al nome del file, così non è necessario digitare un'estensione del file. Se digiti GetContextualAddOn.gs, Apps Script assegna al file il nome GetContextualAddOn.gs.gs.
  3. In GetContextualAddOn.gs, sostituisci il codice predefinito con la funzione getContextualAddOn:
/**
 * Returns the contextual add-on data that should be rendered for
 * the current e-mail thread. This function satisfies the requirements of
 * an 'onTriggerFunction' and is specified in the add-on's manifest.
 *
 * @param {Object} event Event containing the message ID and other context.
 * @returns {Card[]}
 */
function getContextualAddOn(event) {
  var card = CardService.newCardBuilder();
  card.setHeader(CardService.newCardHeader().setTitle('Log Your Expense'));

  var section = CardService.newCardSection();
  section.addWidget(CardService.newTextInput()
    .setFieldName('Date')
    .setTitle('Date'));
  section.addWidget(CardService.newTextInput()
    .setFieldName('Amount')
    .setTitle('Amount'));
  section.addWidget(CardService.newTextInput()
    .setFieldName('Description')
    .setTitle('Description'));
  section.addWidget(CardService.newTextInput()
    .setFieldName('Spreadsheet URL')
    .setTitle('Spreadsheet URL'));

  card.addSection(section);

  return [card.build()];
}

L'interfaccia utente di ogni componente aggiuntivo di Google Workspace è composta da schede suddivise in una o più sezioni, ciascuna contenente widget che possono visualizzare e ottenere informazioni dall'utente. La funzione getContextualAddOn crea una singola scheda che ottiene i dettagli di una spesa rilevata in un'email. La scheda ha una sezione contenente i campi di immissione di testo per i dati pertinenti. La funzione restituisce un array di schede del componente aggiuntivo. In questo caso, l'array restituito include una sola scheda.

Prima di distribuire la funzionalità Expense It! è necessario un progetto Google Cloud Platform (Google Cloud), utilizzato dai progetti Apps Script per gestire le autorizzazioni, i servizi avanzati e altri dettagli. Per saperne di più, consulta i progetti della piattaforma Google Cloud.

Per eseguire il deployment del componente aggiuntivo ed eseguirlo:

  1. Apri il progetto Google Cloud e copia il numero del progetto.
  2. Dal progetto Apps Script, a sinistra, fai clic su Impostazioni progetto Impostazioni progetto.
  3. Nella sezione "Progetto Google Cloud Platform (Google Cloud)", fai clic su Cambia progetto.
  4. Inserisci il numero di progetto del progetto Google Cloud e fai clic su Imposta progetto.
  5. Fai clic su Esegui il deployment > Testa i deployment.
  6. Assicurati che il tipo di deployment sia Componente aggiuntivo di Google Workspace. Se necessario, nella parte superiore della finestra di dialogo, fai clic su Abilita tipi di deployment Abilita tipi di deployment e seleziona Componente aggiuntivo Google Workspace come tipo di deployment.
  7. Accanto ad Applicazioni: Gmail, fai clic su Installa.
  8. Fai clic su Fine.

Ora puoi vedere il componente aggiuntivo nella Posta in arrivo di Gmail.

  1. Apri Gmail sul computer.
  2. Nel riquadro laterale a destra, la voce Spese Viene visualizzato il componente aggiuntivo Spese! icona ricevuta. Potresti dover fare clic su Altri componenti aggiuntivi Altri componenti aggiuntivi per trovarla.
  3. Apri un'email, preferibilmente una ricevuta con le spese.
  4. Per aprire il componente aggiuntivo, nel riquadro laterale a destra, fai clic su Spese Spese! icona ricevuta.
  5. Dai una spesa! l'accesso al tuo Account Google facendo clic su Autorizza accesso e segui le istruzioni.

Il componente aggiuntivo mostra un modulo semplice insieme a un messaggio Gmail aperto. Non fa ancora nulla, ma ne svilupperai le funzionalità nella prossima sezione.

Per visualizzare gli aggiornamenti del componente aggiuntivo nel corso di questo lab, ti basterà salvare il codice e aggiornare Gmail. Non sono necessari deployment aggiuntivi.

4. Accesso ai messaggi email

Aggiungi il codice che recuperi i contenuti dell'email e modularizza il codice per una maggiore organizzazione.

Accanto a File, fai clic su Aggiungi Aggiungi un file > Script e crea un file denominato Cards. Crea un secondo file di script denominato Helpers. Cards.gs crea la scheda e utilizza le funzioni da Helpers.gs per compilare i campi del modulo in base ai contenuti dell'email.

Sostituisci il codice predefinito in Cards.gs con questo codice:

var FIELDNAMES = ['Date', 'Amount', 'Description', 'Spreadsheet URL'];

/**
 * Creates the main card users see with form inputs to log expenses.
 * Form can be prefilled with values.
 *
 * @param {String[]} opt_prefills Default values for each input field.
 * @param {String} opt_status Optional status displayed at top of card.
 * @returns {Card}
 */
function createExpensesCard(opt_prefills, opt_status) {
  var card = CardService.newCardBuilder();
  card.setHeader(CardService.newCardHeader().setTitle('Log Your Expense'));
  
  if (opt_status) {
    if (opt_status.indexOf('Error: ') == 0) {
      opt_status = '<font color=\'#FF0000\'>' + opt_status + '</font>';
    } else {
      opt_status = '<font color=\'#228B22\'>' + opt_status + '</font>';
    }
    var statusSection = CardService.newCardSection();
    statusSection.addWidget(CardService.newTextParagraph()
      .setText('<b>' + opt_status + '</b>'));
    card.addSection(statusSection);
  }
  
  var formSection = createFormSection(CardService.newCardSection(),
                                      FIELDNAMES, opt_prefills);
  card.addSection(formSection);
  
  return card;
}

/**
 * Creates form section to be displayed on card.
 *
 * @param {CardSection} section The card section to which form items are added.
 * @param {String[]} inputNames Names of titles for each input field.
 * @param {String[]} opt_prefills Default values for each input field.
 * @returns {CardSection}
 */
function createFormSection(section, inputNames, opt_prefills) {
  for (var i = 0; i < inputNames.length; i++) {
    var widget = CardService.newTextInput()
      .setFieldName(inputNames[i])
      .setTitle(inputNames[i]);
    if (opt_prefills && opt_prefills[i]) {
      widget.setValue(opt_prefills[i]);
    }
    section.addWidget(widget);
  }
  return section;
}

La funzione createExpensesCard accetta un array di valori per precompilare il modulo come argomento facoltativo. La funzione può visualizzare un messaggio di stato facoltativo, che è di colore rosso se lo stato inizia con "Error:" ed è altrimenti verde. Anziché aggiungere manualmente ogni campo al modulo, una funzione helper chiamata createFormSection ripete il processo di creazione di widget di input di testo, imposta ogni valore predefinito con setValue e poi aggiunge i widget alle rispettive sezioni della scheda.

Ora sostituisci il codice predefinito in Helpers.gs con questo codice:

/**
 * Finds largest dollar amount from email body.
 * Returns null if no dollar amount is found.
 *
 * @param {Message} message An email message.
 * @returns {String}
 */
function getLargestAmount(message) {
  return 'TODO';
}

/**
 * Determines date the email was received.
 *
 * @param {Message} message An email message.
 * @returns {String}
 */
function getReceivedDate(message) {
  return 'TODO';
}

/**
 * Determines expense description by joining sender name and message subject.
 *
 * @param {Message} message An email message.
 * @returns {String}
 */
function getExpenseDescription(message) {
  return 'TODO';
}

/**
 * Determines most recent spreadsheet URL.
 * Returns null if no URL was previously submitted.
 *
 * @returns {String}
 */
function getSheetUrl() {
  return 'TODO';
}

Le funzioni in Helpers.gs vengono chiamate da getContextualAddon per determinare i valori precompilati nel modulo. Per il momento, queste funzioni restituiscono solo la stringa "TODO" perché implementerai la logica di precompilazione in un passaggio successivo.

Poi aggiorna il codice in GetContextualAddon.gs in modo che utilizzi il codice in Cards.gs e Helpers.gs. Sostituisci il codice in GetContextualAddon.gs con questo codice:

/**
 * Copyright 2017 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Returns the contextual add-on data that should be rendered for
 * the current e-mail thread. This function satisfies the requirements of
 * an 'onTriggerFunction' and is specified in the add-on's manifest.
 *
 * @param {Object} event Event containing the message ID and other context.
 * @returns {Card[]}
 */
function getContextualAddOn(event) {
  var message = getCurrentMessage(event);
  var prefills = [getReceivedDate(message),
                  getLargestAmount(message),
                  getExpenseDescription(message),
                  getSheetUrl()];
  var card = createExpensesCard(prefills);

  return [card.build()];
}

/**
 * Retrieves the current message given an action event object.
 * @param {Event} event Action event object
 * @return {Message}
 */
function getCurrentMessage(event) {
  var accessToken = event.messageMetadata.accessToken;
  var messageId = event.messageMetadata.messageId;
  GmailApp.setCurrentMessageAccessToken(accessToken);
  return GmailApp.getMessageById(messageId);
}

Osserva la nuova funzione getCurrentMessage, che utilizza l'evento fornito da Gmail per leggere il messaggio attualmente aperto dell'utente. Affinché questa funzione funzioni, aggiungi un ulteriore ambito al manifest dello script che consenta l'accesso di sola lettura ai messaggi Gmail.

In appscript.json, aggiorna oauthScopes in modo che richieda anche l'ambito https://www.googleapis.com/auth/gmail.addons.current.message.readonly.

"oauthScopes": [
  "https://www.googleapis.com/auth/gmail.addons.execute",
   "https://www.googleapis.com/auth/gmail.addons.current.message.readonly"
],

In Gmail, esegui il componente aggiuntivo e autorizza l'accesso per Expense It! per visualizzare i messaggi email. I campi del modulo sono ora precompilati con "TODO".

5. Interagire con Fogli Google

Spese il componente aggiuntivo ha un modulo che consente all'utente di inserire i dettagli di una spesa, ma questi dettagli non sono più disponibili. Aggiungiamo un pulsante che invii i dati del modulo a un foglio Google.

Per aggiungere un pulsante, utilizzeremo la classe ButtonSet. Per interfacciarti con Fogli Google, utilizzeremo il servizio Fogli Google.

Modifica createFormSection per restituire un pulsante con l'etichetta "Invia" nell'apposita sezione della scheda. Procedi in questo modo:

  1. Crea un pulsante di testo utilizzando CardService.newTextButton(), assegnando al pulsante l'etichetta "Invia" utilizzando CardService.TextButton.setText().
  2. Progetta il pulsante in modo che, quando viene fatto clic, la seguente azione submitForm venga richiamata tramite CardService.TextButton.setOnClickAction():
/**
 * Logs form inputs into a spreadsheet given by URL from form.
 * Then displays edit card.
 *
 * @param {Event} e An event object containing form inputs and parameters.
 * @returns {Card}
 */
function submitForm(e) {
  var res = e['formInput'];
  try {
    FIELDNAMES.forEach(function(fieldName) {
      if (! res[fieldName]) {
        throw 'incomplete form';
      }
    });
    var sheet = SpreadsheetApp
      .openByUrl((res['Spreadsheet URL']))
      .getActiveSheet();
    sheet.appendRow(objToArray(res, FIELDNAMES.slice(0, FIELDNAMES.length - 1)));
    return createExpensesCard(null, 'Logged expense successfully!').build();
  }
  catch (err) {
    if (err == 'Exception: Invalid argument: url') {
      err = 'Invalid URL';
      res['Spreadsheet URL'] = null;
    }
    return createExpensesCard(objToArray(res, FIELDNAMES), 'Error: ' + err).build();
  }
}

/**
 * Returns an array corresponding to the given object and desired ordering of keys.
 *
 * @param {Object} obj Object whose values will be returned as an array.
 * @param {String[]} keys An array of key names in the desired order.
 * @returns {Object[]}
 */
function objToArray(obj, keys) {
  return keys.map(function(key) {
    return obj[key];
  });
}
  1. Crea un widget del set di pulsanti utilizzando CardService.newButtonSet() e aggiungi il pulsante di testo al set di pulsanti con CardService.ButtonSet.addButton().
  2. Aggiungi il widget della serie di pulsanti alla sezione del modulo della scheda utilizzando CardService.CardSection.addWidget().

Con poche righe di codice siamo in grado di aprire un foglio di lavoro in base all'URL e quindi di aggiungere una riga di dati al foglio. Tieni presente che gli input del modulo vengono passati alla funzione come parte dell'evento e e verifichiamo che l'utente abbia compilato tutti i campi. Se non si verificano errori, creiamo una scheda delle spese vuota con uno stato favorevole. Se individuiamo un errore, ti restituiremo la scheda riempita originale insieme al messaggio di errore. La funzione helper objToArray semplifica la conversione delle risposte del modulo in un array, che può quindi essere aggiunto al foglio di lavoro.

Infine, aggiorna la sezione oauthScopes in appsscript.json richiedi di nuovo l'ambito https://www.googleapis.com/auth/spreadsheets. Una volta autorizzato, questo ambito consente al componente aggiuntivo di leggere e modificare Fogli Google di un utente.

"oauthScopes": [
  "https://www.googleapis.com/auth/gmail.addons.execute",
  "https://www.googleapis.com/auth/gmail.addons.current.message.readonly",
  "https://www.googleapis.com/auth/spreadsheets"
],

Se non hai ancora creato un nuovo foglio di lavoro, creane uno all'indirizzo https://docs.google.com/spreadsheets/.

Ora esegui di nuovo il componente aggiuntivo e prova a inviare il modulo. Assicurati di inserire l'URL completo dell'URL di destinazione nel campo del modulo URL foglio di lavoro.

6. Archiviare i valori con il servizio Proprietà

Spesso gli utenti registrano molte spese nello stesso foglio di lavoro, quindi sarebbe conveniente offrire l'URL del foglio di lavoro più recente come valore predefinito nella scheda. Per conoscere l'URL del foglio di lavoro più recente, dovremo memorizzare queste informazioni ogni volta che viene utilizzato il componente aggiuntivo.

Il servizio Proprietà ci consente di memorizzare coppie chiave-valore. Nel nostro caso, una chiave ragionevole sarebbe "SPREAD pubblicitari_URL" mentre il valore è l'URL stesso. Per memorizzare questo valore, devi modificare submitForm in Cards.gs in modo che l'URL del foglio di lavoro venga archiviato come proprietà dopo l'aggiunta di una nuova riga al foglio.

Tieni presente che le proprietà possono avere uno dei tre ambiti seguenti: script, utente o documento. L'ambito documento non si applica ai componenti aggiuntivi di Gmail, anche se è pertinente a un tipo separato di componente aggiuntivo quando si archiviano informazioni specifiche di un determinato documento o foglio Google. Per il nostro componente aggiuntivo, il comportamento desiderato è che un individuo visualizzi il proprio foglio di lavoro più recente (anziché quello di qualcun altro) come opzione predefinita del modulo. Di conseguenza, selezioniamo l'ambito user anziché l'ambito script.

Utilizza PropertiesService.getUserProperties().setProperty() per memorizzare l'URL del foglio di lavoro. Aggiungi il seguente codice a submitForm in Cards.gs:

PropertiesService.getUserProperties().setProperty('SPREADSHEET_URL', 
    res['Spreadsheet URL']);

Poi modifica la funzione getSheetUrl in Helpers.gs in modo che restituisca la proprietà memorizzata in modo che l'utente possa visualizzare l'URL più recente ogni volta che utilizza il componente aggiuntivo. Usa PropertiesService.getUserProperties().getProperty() per ottenere il valore della proprietà.

/**
 * Determines most recent spreadsheet URL.
 * Returns null if no URL was previously submitted.
 *
 * @returns {String}
 */
function getSheetUrl() {
  return PropertiesService.getUserProperties().getProperty('SPREADSHEET_URL');
}

Infine, per accedere al servizio Proprietà, dovrà essere autorizzato anche lo script. Aggiungi l'ambito https://www.googleapis.com/auth/script.storage al file manifest come prima per consentire al componente aggiuntivo di leggere e scrivere informazioni sulla proprietà.

7. Analizza il messaggio di Gmail

Per salvare davvero le attività degli utenti precompilare il modulo con le informazioni pertinenti sulla spesa inviata via email. Abbiamo già creato in Helpers.gs funzioni che svolgono questo ruolo, ma finora abbiamo restituito solo "TODO" la data, l'importo e la descrizione della spesa.

Ad esempio, possiamo recuperare la data di ricezione dell'email e utilizzarla come valore predefinito per la data della spesa.

/**
 * Determines date the email was received.
 *
 * @param {Message} message - The message currently open.
 * @returns {String}
 */
function getReceivedDate(message) {
  return message.getDate().toLocaleDateString();
}

Implementa le due funzioni rimanenti:

  1. getExpenseDescription potrebbe comportare l'unione sia del nome del mittente sia dell'oggetto del messaggio, sebbene esistano modi più sofisticati per analizzare il corpo del messaggio e recapitare una descrizione ancora più accurata.
  2. Per getLargestAmount, puoi cercare simboli specifici associati al denaro. Nelle ricevute vengono spesso indicati più valori, ad esempio tasse e altre commissioni. Pensa a come potresti identificare l'importo corretto. Possono essere utili anche le espressioni regolari.

Se hai bisogno di ulteriore ispirazione, esplora la documentazione di riferimento per GmailMessage o controlla il codice della soluzione che hai scaricato all'inizio del codelab. Dopo aver definito le tue implementazioni per tutte le funzioni di Helpers.gs, prova il tuo componente aggiuntivo. Apri le ricevute e inizia a registrarle in un foglio di lavoro.

8. Cancella il modulo con le azioni della scheda

Cosa succede se si spende! erronea una spesa in un'email aperta e precompila il modulo con informazioni errate? L'utente cancella il modulo. La classe CardAction ci consente di specificare una funzione che viene chiamata quando un utente fa clic sull'azione. Usiamola per offrire all'utente un modo rapido per cancellare il modulo.

Modifica createExpensesCard in modo che la carta restituita presenti un'azione della carta contrassegnata come "Cancella modulo" e quando viene selezionato richiama la seguente funzione clearForm, che puoi incollare in Cards.gs. Devi passare in opt_status come parametro denominato "Stato" all'azione per assicurarti che il messaggio di stato rimanga quando il modulo viene cancellato. Tieni presente che i parametri facoltativi per le azioni devono essere di tipo Object.<string, string>, quindi se opt_status non è disponibile, devi trasmettere {'Status' : ''}.

/**
 * Recreates the main card without prefilled data.
 *
 * @param {Event} e An event object containing form inputs and parameters.
 * @returns {Card}
 */
function clearForm(e) {
  return createExpensesCard(null, e['parameters']['Status']).build();
}

9. Crea un foglio di lavoro

Oltre a utilizzare Google Apps Script per modificare un foglio di lavoro esistente, puoi crearne uno completamente nuovo a livello di programmazione. Per il nostro componente aggiuntivo, consentiamo all'utente di creare un foglio di lavoro per le spese. Per iniziare, aggiungi la seguente sezione della carta alla carta restituita da createExpensesCard.

var newSheetSection = CardService.newCardSection();
var sheetName = CardService.newTextInput()
  .setFieldName('Sheet Name')
  .setTitle('Sheet Name');
var createExpensesSheet = CardService.newAction()
  .setFunctionName('createExpensesSheet');
var newSheetButton = CardService.newTextButton()
  .setText('New Sheet')
  .setOnClickAction(createExpensesSheet);
newSheetSection.addWidget(sheetName);
newSheetSection.addWidget(CardService.newButtonSet().addButton(newSheetButton));
card.addSection(newSheetSection);

Ora, quando l'utente fa clic su "Nuovo foglio" il componente aggiuntivo genera un nuovo foglio di lavoro formattato con una riga di intestazione bloccata in modo da essere sempre visibile. L'utente specifica un titolo per il nuovo foglio di lavoro nel modulo, ma potrebbe essere una buona scelta includere un valore predefinito nel caso in cui il modulo sia vuoto. Nella tua implementazione di createExpensesSheet, restituisci una scheda quasi identica a quella esistente, con l'aggiunta di un messaggio di stato appropriato e precompilando il campo dell'URL con l'URL del nuovo foglio di lavoro.

10. Complimenti!

Hai progettato e implementato con successo un componente aggiuntivo di Gmail che rileva una spesa in un'email e aiuta gli utenti a registrarla in un foglio di lavoro in pochi secondi. Hai utilizzato Google Apps Script per interagire con più API di Google e hai mantenuto i dati tra più esecuzioni del componente aggiuntivo.

Possibili miglioramenti

Lasciati guidare dalla tua immaginazione mentre migliori Expense It!, ma ecco alcune idee per realizzare un prodotto ancora più utile:

  • Link al foglio di lavoro una volta che l'utente ha registrato una spesa
  • Aggiungere la possibilità di modificare/annullare la registrazione di una spesa
  • Integrare API esterne per consentire agli utenti di effettuare pagamenti e richiedere denaro

Scopri di più