2023-06-14 11:39:00 -04:00
|
|
|
import { error } from "./error"
|
2023-07-22 15:46:52 -04:00
|
|
|
import { Tick, ITick } from './ticks'
|
2023-08-26 11:05:15 -04:00
|
|
|
import { dateQuery } from "./util"
|
2023-06-14 11:39:00 -04:00
|
|
|
|
|
|
|
export interface ITrack {
|
2023-06-25 14:06:25 -04:00
|
|
|
id?: number
|
2023-06-14 11:39:00 -04:00
|
|
|
name: String
|
|
|
|
description: String
|
|
|
|
icon: String
|
|
|
|
enabled: number
|
|
|
|
multiple_entries_per_day?: number
|
|
|
|
color?: number
|
|
|
|
order?: number
|
|
|
|
ticks?: Array<Tick>
|
|
|
|
}
|
|
|
|
|
|
|
|
export class Track implements ITrack {
|
2023-06-25 14:06:25 -04:00
|
|
|
id?: number
|
2023-06-14 11:39:00 -04:00
|
|
|
name: String
|
|
|
|
description: String
|
|
|
|
icon: String
|
|
|
|
enabled: number
|
|
|
|
multiple_entries_per_day?: number
|
|
|
|
color?: number
|
|
|
|
order?: number
|
|
|
|
ticks?: Array<Tick>
|
|
|
|
|
|
|
|
constructor(
|
2023-06-25 14:06:25 -04:00
|
|
|
id: number | undefined,
|
2023-06-14 11:39:00 -04:00
|
|
|
name: String,
|
|
|
|
description: String,
|
|
|
|
icon: String,
|
|
|
|
enabled: number,
|
|
|
|
multiple_entries_per_day?: number,
|
|
|
|
color?: number,
|
|
|
|
order?: number,
|
|
|
|
ticks?: Array<ITick>
|
|
|
|
) {
|
|
|
|
this.id = id
|
|
|
|
this.name = name
|
|
|
|
this.description = description
|
|
|
|
this.icon = icon
|
|
|
|
this.enabled = enabled
|
|
|
|
this.multiple_entries_per_day = multiple_entries_per_day
|
|
|
|
this.color = color
|
|
|
|
this.order = order
|
|
|
|
this.ticks = ticks?.map(tick => Tick.fromJSON(tick))
|
|
|
|
this.isSetOn = this.isSetOn.bind(this)
|
|
|
|
this.fetchTicks = this.fetchTicks.bind(this)
|
|
|
|
}
|
|
|
|
|
2023-08-26 11:05:15 -04:00
|
|
|
/**
|
|
|
|
* Add this track to the database. A `TrackAdded` event should have been
|
|
|
|
* received from the server on the event stream by the time this returns.
|
|
|
|
*
|
|
|
|
* @returns whether or not the query succeeded
|
|
|
|
*/
|
|
|
|
async create(): Promise<boolean> {
|
|
|
|
// note that this.id is expected to be `undefined` here.
|
|
|
|
const response = await fetch('/api/v1/tracks', {
|
|
|
|
method: "POST",
|
|
|
|
body: JSON.stringify(this),
|
|
|
|
headers: { "Content-Type": "application/json" }
|
|
|
|
})
|
|
|
|
if (!response.ok)
|
|
|
|
error(`error submitting track ${this.name}: ${response.statusText} (${response.status})`)
|
|
|
|
return response.ok
|
|
|
|
}
|
|
|
|
|
|
|
|
async delete() {
|
|
|
|
const id = this.id
|
|
|
|
if (id) await Track.deleteById(id)
|
|
|
|
}
|
|
|
|
|
|
|
|
static async deleteById(id: number) {
|
|
|
|
const response = await fetch(`/api/v1/tracks/${id}`, { method: "DELETE" })
|
|
|
|
if (!response.ok) error(`error deleting track with ID ${id}: ${response.statusText} (${response.status})`)
|
|
|
|
}
|
|
|
|
|
2023-06-14 11:39:00 -04:00
|
|
|
static fromJSON(track: ITrack): Track {
|
|
|
|
return new Track(track.id, track.name, track.description, track.icon, track.enabled, track.multiple_entries_per_day, track.color, track.order)
|
|
|
|
}
|
|
|
|
|
|
|
|
isSetOn(date: Date): boolean {
|
|
|
|
for (var tick of (this.ticks ?? [])) {
|
|
|
|
if (
|
|
|
|
date.getUTCFullYear() == tick.year &&
|
2023-06-20 08:40:35 -04:00
|
|
|
(date.getUTCMonth() + 1) == tick.month &&
|
|
|
|
// Javascript Date ^^^ uses 0-index for dates of months 🤦
|
2023-06-14 11:39:00 -04:00
|
|
|
date.getDate() == tick.day
|
|
|
|
) return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
async fetchTicks(): Promise<Track> {
|
|
|
|
const response = await fetch(`/api/v1/tracks/${this.id}/ticks`)
|
|
|
|
if (response.ok) {
|
|
|
|
this.ticks = await response.json()
|
|
|
|
} else {
|
|
|
|
throw new Error(`error fetching ticks: ${response.statusText} (${response.status})`)
|
|
|
|
}
|
|
|
|
return this
|
|
|
|
}
|
|
|
|
|
|
|
|
static async fetchAll(): Promise<Array<Track>> {
|
|
|
|
const result = await fetch('/api/v1/tracks')
|
|
|
|
if (result.ok) {
|
|
|
|
try {
|
|
|
|
const body = await result.text();
|
|
|
|
try {
|
|
|
|
const tracks = Array.prototype.map.call(JSON.parse(body), Track.fromJSON) as Array<Track>
|
|
|
|
return Promise.all(tracks.map((track: Track) => track.fetchTicks()))
|
|
|
|
} catch (e) {
|
|
|
|
console.error('error parsing body from JSON')
|
|
|
|
console.error(e)
|
|
|
|
console.debug(body)
|
|
|
|
}
|
|
|
|
} catch (err) {
|
|
|
|
console.error(err)
|
|
|
|
result.text()
|
|
|
|
.then(console.debug)
|
|
|
|
.catch(console.error)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
error(`error fetching tracks: ${result.statusText} (${result.status})`)
|
|
|
|
}
|
|
|
|
return []
|
|
|
|
}
|
2023-08-26 11:05:15 -04:00
|
|
|
/**
|
|
|
|
* Mark this track as being completed on the given date. A `TickAdded` event
|
|
|
|
* should have been received from the server on the event stream by the time
|
|
|
|
* this returns.
|
|
|
|
*
|
|
|
|
* @param date the date the task was completed
|
|
|
|
* @returns the decoded server API response
|
|
|
|
*/
|
|
|
|
async markComplete(date: Date) {
|
|
|
|
const query = dateQuery(date)
|
|
|
|
const response: Response = await fetch(`/api/v1/tracks/${this.id}/ticked?${query.toString()}`, { method: "PATCH" })
|
|
|
|
const body = await response.text()
|
|
|
|
if (!response.ok) {
|
|
|
|
error(body)
|
|
|
|
throw new Error(`error setting tick for track ${this.id} ("${this.name}"): ${response.status} ${response.statusText}`)
|
|
|
|
}
|
|
|
|
return JSON.parse(body)
|
|
|
|
}
|
|
|
|
/**
|
|
|
|
* Mark this track as being incomplete on the given date. A `TickAdded` event
|
|
|
|
* should have been received from the server on the event stream by the time
|
|
|
|
* this returns.
|
|
|
|
*
|
|
|
|
* @param date the date the task was completed
|
|
|
|
* @returns the decoded server API response
|
|
|
|
*/
|
|
|
|
async markIncomplete(date: Date) {
|
|
|
|
const query = dateQuery(date)
|
|
|
|
const { ok, status, statusText } = await fetch(`/api/v1/tracks/${this.id}/all-ticks?${query.toString()}`, { method: 'DELETE' })
|
|
|
|
if (!ok)
|
|
|
|
error(`error deleting ticks for ${this.id}: ${statusText} (${status})`)
|
|
|
|
}
|
2023-07-22 15:46:52 -04:00
|
|
|
}
|