Ctrl K
ComunidadGuías…

scriptable/mrr_widget_v2.js

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
class LineChart {
  // LineChart by https://kevinkub.de/

  constructor(width, height, values) {
    this.ctx = new DrawContext()
    this.ctx.size = new Size(width, height)
    this.values = values
  }

  _calculatePath() {
    const maxValue = Math.max(...this.values)
    const minValue = Math.min(...this.values)
    const difference = maxValue - minValue
    const count = this.values.length
    const step = this.ctx.size.width / (count - 1)
    const points = this.values.map((current, index, all) => {
      const x = step * index
      const y = this.ctx.size.height - (current - minValue) / difference * this.ctx.size.height
      return new Point(x, y)
    })
    return this._getSmoothPath(points)
  }

  _getSmoothPath(points) {
    const path = new Path()
    path.move(new Point(0, this.ctx.size.height))
    path.addLine(points[0])
    for (let i = 0; i < points.length - 1; i++) {
      const xAvg = (points[i].x + points[i + 1].x) / 2
      const yAvg = (points[i].y + points[i + 1].y) / 2
      const avg = new Point(xAvg, yAvg)
      const cp1 = new Point((xAvg + points[i].x) / 2, points[i].y)
      const next = new Point(points[i + 1].x, points[i + 1].y)
      const cp2 = new Point((xAvg + points[i + 1].x) / 2, points[i + 1].y)
      path.addQuadCurve(avg, cp1)
      path.addQuadCurve(next, cp2)
    }
    path.addLine(new Point(this.ctx.size.width, this.ctx.size.height))
    path.closeSubpath()
    return path
  }

  configure(fn) {
    const path = this._calculatePath()
    if (fn) {
      fn(this.ctx, path)
    }
    else {
      this.ctx.addPath(path)
      this.ctx.fillPath(path)
    }
    return this.ctx
  }
}

// Load MRR
// https://gist.github.com/daolf/ae104b1ab7cabf564b47770c88d4214b

const API_TOKEN = 'PROFITWELL_API_TOKEN'

// Recreating a basic auth with Scriptable lib
const currentYear = new Date().getFullYear()
const tempMonth = new Date().getMonth() + 1
const currentDate = new Date().getDate()
const currentMonth = (tempMonth < 10 ? `0${tempMonth}` : tempMonth)
const date = `${currentYear}-${currentMonth}`
const endpoint = `https://api.profitwell.com/v2/metrics/daily/?metrics=recurring_revenue,active_customers&month=${date}`

async function loadItems() {
  const at = endpoint
  const req = new Request(at)
  req.headers = { Authorization: API_TOKEN }
  const response = await req.loadJSON()
  return response
}

function kFormatter(num) {
  return Math.abs(num) > 999 ? `${Math.sign(num) * ((Math.abs(num) / 1000).toFixed(1))}k` : Math.sign(num) * Math.abs(num)
}

const json = await loadItems()
let MRR = json.data.recurring_revenue[json.data.recurring_revenue.length - 1].value
const ARR = kFormatter(MRR * 12)

const absoluteChange = Math.floor(MRR - json.data.recurring_revenue[0].value).toString()
const percentChange = (absoluteChange * 100 / MRR).toFixed(1).toString()

const dailyAbsoluteChange = currentDate >= 2 ? Math.floor(MRR - json.data.recurring_revenue[currentDate - 2].value).toString() : null
MRR = Math.floor(MRR)

const nbActiveCustomers = json.data.active_customers[currentDate - 1].value

const today = new Date()
today.setHours(23, 59, 0, 0)
const data = []
for (const val of json.data.recurring_revenue) {
  const date = new Date(val.date)

  if (date <= today)
    data.push(val.value)
}

const widget = new ListWidget()
const chart = new LineChart(400, 400, data).configure((ctx, path) => {
  ctx.opaque = false
  ctx.setFillColor(new Color('8cc5ff', 0.5))
  ctx.addPath(path)
  ctx.fillPath(path)
}).getImage()
widget.addText(`MRR $${MRR.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')}`)
widget.addText(`ARR $${ARR.toString()}`)

// display (% change)
widget.addSpacer(10)
if (absoluteChange >= 0) {
  const changeText = widget.addText(`+$${absoluteChange.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')} (${percentChange}%) this month`)
  changeText.textColor = Color.green()
  changeText.font = Font.semiboldSystemFont(10)
}
else {
  const changeText = widget.addText(`$${Math.abs(absoluteChange).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')} (-${percentChange}%) this month`)
  changeText.textColor = Color.red()
  changeText.font = Font.semiboldSystemFont(10)
}

// Daily change
widget.addSpacer(4)
if (dailyAbsoluteChange !== null) {
  if (dailyAbsoluteChange >= 0) {
	  const dailyChangeText = widget.addText(`+$${dailyAbsoluteChange.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')} today`)
	  if (dailyAbsoluteChange > 0)
		  dailyChangeText.textColor = Color.green()

	  dailyChangeText.font = Font.semiboldSystemFont(10)
  }
  else {
	  const dailyChangeText = widget.addText(`-$${Math.abs(dailyAbsoluteChange).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')} today`)
	  dailyChangeText.textColor = Color.red()
	  dailyChangeText.font = Font.semiboldSystemFont(10)
  }
}

widget.addSpacer(4)
const activeCustomerText = widget.addText(`${nbActiveCustomers} customers`)
activeCustomerText.font = Font.semiboldSystemFont(10)
activeCustomerText.textColor = Color.white()

widget.addSpacer()
widget.backgroundImage = chart

Script.setWidget(widget)
if (!config.runsInWidget)
  await widget.presentSmall()

Script.complete()