Datos Blog

Learn about business automation and operations

How to automatically show clients how many hours you’ve tracked

Like many freelance consultants, I charge clients by the hour and bill every two weeks. The problem is sometimes I accumulate quite a bit of time between invoices and some clients are surprised when they see the invoice for the last two weeks.

I thought to myself: there’s gotta be a better way to let people know how much time you’re accumulating without having to constantly remind them, send them emails, or wait for them to reach out.

I ended up building custom AirTable views for my clients to see their own hours in real time as I tracked it using Toggl. To pull the systems together, I used n8n (an open-source alternative to Zapier).

Here’s the video I recorded on how to do this yourself:

Here is the JSON code to add this automation into your N8N instance (explained near the end of the video):

{
  "nodes": [
    {
      "parameters": {
        "triggerTimes": {
          "item": [
            {
              "mode": "everyX",
              "value": 12
            }
          ]
        }
      },
      "name": "Cron",
      "type": "n8n-nodes-base.cron",
      "typeVersion": 1,
      "position": [
        450,
        450
      ]
    },
    {
      "parameters": {
        "functionCode": "var date = new Date();\nvar firstDay = new Date(date. getFullYear(), date. getMonth(), 1).toJSON().split(\"T\")[0];\nvar midmonth = new Date(date. getFullYear(), date. getMonth(), 16).toJSON().split(\"T\")[0];\nvar currentday = new Date().getDate();\n\nif (currentday > 16 || currentday == 1) {\nitems[0].json.togglday = midmonth;\n}\nelse {\nitems[0].json.togglday = firstDay;\n}\n\nitems[0].json.firstday = firstDay;\nitems[0].json.midmonth = midmonth;\nitems[0].json.currentday = currentday;\n\nreturn items;"
      },
      "name": "Get Dates",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        650,
        450
      ]
    },
    {
      "parameters": {
        "authentication": "basicAuth",
        "url": "=https://api.track.toggl.com/reports/api/v2/summary?workspace_id=2384903234823&user_agent=n8napi&grouping=clients&since={{$node[\"Get Dates\"].json[\"togglday\"]}}",
        "options": {},
        "headerParametersUi": {
          "parameter": [
            {
              "name": "Content-type",
              "value": "application/json"
            }
          ]
        }
      },
      "name": "Toggl Report",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 1,
      "position": [
        840,
        450
      ],
      "credentials": {
        "httpBasicAuth": "Toggl"
      }
    },
    {
      "parameters": {
        "operation": "update",
        "application": "iofdjsf3208",
        "table": "Table 1",
        "id": "={{ $node[\"Get Time Spent and AirTable ID\"].json[\"airtableid\"] }}",
        "updateAllFields": false,
        "fields": [
          "Hours"
        ],
        "options": {}
      },
      "name": "Airtable",
      "type": "n8n-nodes-base.airtable",
      "typeVersion": 1,
      "position": [
        2140,
        260
      ],
      "credentials": {
        "airtableApi": "Datos AirTable"
      }
    },
    {
      "parameters": {
        "values": {
          "number": [
            {
              "name": "Hours",
              "value": "={{$node[\"Get Time Spent and AirTable ID\"].json[\"timespent\"]}}"
            }
          ]
        },
        "options": {}
      },
      "name": "Set",
      "type": "n8n-nodes-base.set",
      "typeVersion": 1,
      "position": [
        2000,
        460
      ]
    },
    {
      "parameters": {
        "operation": "list",
        "application": "23ryu29hf9723f",
        "table": "Table 1",
        "additionalOptions": {
          "filterByFormula": "={Name}=\"{{$node[\"SplitInBatches\"].json[\"title\"][\"client\"]}}\""
        }
      },
      "name": "Get AirTable field by name",
      "type": "n8n-nodes-base.airtable",
      "typeVersion": 1,
      "position": [
        1390,
        460
      ],
      "credentials": {
        "airtableApi": "Datos AirTable"
      }
    },
    {
      "parameters": {
        "functionCode": "/*const results = []\n\nconst events = items[0].json[\"data\"]\n\nfor (event of events) {\n  results.push({ json: event })\n}\n\nreturn results;*/\n\nreturn items[0].json.data.map(item => {\n  return {\n    json: item\n  }\n});\n"
      },
      "name": "Function",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        1030,
        450
      ]
    },
    {
      "parameters": {
        "functionCode": "var time = $item(\"0\").$node[\"SplitInBatches\"].json[\"time\"] / 3600000;\nvar airtableid = $item(\"0\").$node[\"Get AirTable field by name\"].json[\"id\"];\nvar clientemail = $item(\"0\").$node[\"Get AirTable field by name\"].json[\"fields\"][\"Email\"];\nvar duedate = new Date();\nduedate.setDate(duedate.getDate() + 15);\n\nreturn [{json: \n{\"timespent\": time, \"airtableid\": airtableid, \"clientemail\": clientemail, \"duedate\": duedate}\n}];\n"
      },
      "name": "Get Time Spent and AirTable ID",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        1770,
        460
      ]
    },
    {
      "parameters": {
        "batchSize": 1,
        "options": {
          "reset": false
        }
      },
      "name": "SplitInBatches",
      "type": "n8n-nodes-base.splitInBatches",
      "typeVersion": 1,
      "position": [
        1210,
        450
      ]
    }
  ],
  "connections": {
    "Cron": {
      "main": [
        [
          {
            "node": "Get Dates",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Dates": {
      "main": [
        [
          {
            "node": "Toggl Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Toggl Report": {
      "main": [
        [
          {
            "node": "Function",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Airtable": {
      "main": [
        [
          {
            "node": "SplitInBatches",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set": {
      "main": [
        [
          {
            "node": "Airtable",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get AirTable field by name": {
      "main": [
        [
          {
            "node": "Get Time Spent and AirTable ID",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Function": {
      "main": [
        [
          {
            "node": "SplitInBatches",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Time Spent and AirTable ID": {
      "main": [
        [
          {
            "node": "Set",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "SplitInBatches": {
      "main": [
        [
          {
            "node": "Get AirTable field by name",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}

Skip forward

Never miss a post from Datos