diff --git a/frontend/.ember-cli b/frontend/.ember-cli index 8d84f5f07..d9ce3fd9d 100644 --- a/frontend/.ember-cli +++ b/frontend/.ember-cli @@ -11,5 +11,5 @@ Setting `isTypeScriptProject` to true will force the blueprint generators to generate TypeScript rather than JavaScript by default, when a TypeScript version of a given blueprint is available. */ - "isTypeScriptProject": false + "isTypeScriptProject": true } diff --git a/frontend/.eslintrc.js b/frontend/.eslintrc.js index 86236ebba..07df8ef80 100644 --- a/frontend/.eslintrc.js +++ b/frontend/.eslintrc.js @@ -1,17 +1,107 @@ "use-strict"; module.exports = { - extends: ["@adfinis/eslint-config/ember-app"], + root: true, + parser: "@typescript-eslint/parser", + parserOptions: { + ecmaVersion: "latest", + }, + plugins: ["ember", "@typescript-eslint", "import"], + extends: [ + "eslint:recommended", + "plugin:ember/recommended", + "plugin:prettier/recommended", + ], + env: { + browser: true, + }, rules: { - "ember/no-actions-hash": "warn", - "ember/no-component-lifecycle-hooks": "warn", - "ember/no-mixins": "warn", - "ember/no-new-mixins": "warn", - "ember/no-classic-classes": "warn", - "ember/no-classic-components": "warn", - "ember/no-get": "warn", - "ember/no-observers": "warn", - "qunit/no-assert-equal": "warn", - "ember/require-tagless-components": "warn", + // possible errors + "no-await-in-loop": "error", + + // best practices + "array-callback-return": "error", + "dot-notation": "error", + eqeqeq: "error", + "no-alert": "error", + "no-else-return": "error", + "no-eval": "error", + "no-extend-native": "error", + "no-extra-bind": "error", + "no-floating-decimal": "error", + "one-var": ["error", "never"], + curly: ["error", "multi-line"], + + // ES6 + "no-var": "error", + "object-shorthand": "error", + "prefer-const": "error", + "prefer-destructuring": [ + "error", + { AssignmentExpression: { array: false, object: false } }, + ], + "prefer-rest-params": "error", + "prefer-spread": "error", + "prefer-template": "error", + + // import + "import/no-duplicates": "error", + "import/no-unresolved": "off", + "import/order": [ + "error", + { + "newlines-between": "always", + alphabetize: { order: "asc", caseInsensitive: true }, + }, + ], + + // tooling + "no-console": ["error", { allow: ["warn", "error"] }], + "no-debugger": "error", }, + overrides: [ + // js files + { + files: ["**/*.js"], + extends: [ + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended", + ], + rules: {}, + }, + // ts files + { + files: ["**/*.ts"], + extends: [ + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended", + ], + rules: {}, + }, + // node files + { + files: [ + "./.eslintrc.js", + "./.prettierrc.js", + "./.stylelintrc.js", + "./.template-lintrc.js", + "./ember-cli-build.js", + "./testem.js", + "./blueprints/*/index.js", + "./config/**/*.js", + "./lib/*/index.js", + "./server/**/*.js", + ], + env: { + browser: false, + node: true, + }, + extends: ["plugin:n/recommended"], + }, + { + // test files + files: ["tests/**/*-test.{js,ts}"], + extends: ["plugin:qunit/recommended"], + }, + ], }; diff --git a/frontend/app/adapters/activity-block.js b/frontend/app/adapters/activity-block.ts similarity index 68% rename from frontend/app/adapters/activity-block.js rename to frontend/app/adapters/activity-block.ts index ae5488ce2..d1e7b36bc 100644 --- a/frontend/app/adapters/activity-block.js +++ b/frontend/app/adapters/activity-block.ts @@ -12,7 +12,7 @@ import ApplicationAdapter from "timed/adapters/application"; * @extends ApplicationAdapter * @public */ -export default ApplicationAdapter.extend({ +export default class ActivityBlockAdapter extends ApplicationAdapter { /** * Custom url for updating records * @@ -24,8 +24,8 @@ export default ApplicationAdapter.extend({ * @public */ urlForUpdateRecord(...args) { - return `${this._super(...args)}?include=activity`; - }, + return `${super.urlForUpdateRecord(...args)}?include=activity`; + } /** * Custom url for creating records @@ -38,6 +38,12 @@ export default ApplicationAdapter.extend({ * @public */ urlForCreateRecord(...args) { - return `${this._super(...args)}?include=activity`; - }, -}); + return `${super.urlForCreateRecord(...args)}?include=activity`; + } +} + +declare module "ember-data/types/registries/adapter" { + interface AdapterRegistry { + "activity-block": ActivityBlockAdapter; + } +} diff --git a/frontend/app/adapters/application.js b/frontend/app/adapters/application.ts similarity index 75% rename from frontend/app/adapters/application.js rename to frontend/app/adapters/application.ts index 326b5b5ac..c8696d328 100644 --- a/frontend/app/adapters/application.js +++ b/frontend/app/adapters/application.ts @@ -16,3 +16,9 @@ import OIDCJSONAPIAdapter from "ember-simple-auth-oidc/adapters/oidc-json-api-ad export default class ApplicationAdapter extends OIDCJSONAPIAdapter { namespace = "api/v1"; } + +declare module "ember-data/types/registries/adapter" { + interface AdapterRegistry { + application: ApplicationAdapter; + } +} diff --git a/frontend/app/config/environment.d.ts b/frontend/app/config/environment.d.ts new file mode 100644 index 000000000..e649732bd --- /dev/null +++ b/frontend/app/config/environment.d.ts @@ -0,0 +1,14 @@ +/** + * Type declarations for + * import config from 'my-app/config/environment' + */ +declare const config: { + environment: string; + modulePrefix: string; + podModulePrefix: string; + locationType: "history" | "hash" | "none" | "auto"; + rootURL: string; + APP: Record; +}; + +export default config; diff --git a/frontend/app/models/absence-balance.js b/frontend/app/models/absence-balance.js deleted file mode 100644 index ad425b658..000000000 --- a/frontend/app/models/absence-balance.js +++ /dev/null @@ -1,12 +0,0 @@ -import Model, { attr, belongsTo, hasMany } from "@ember-data/model"; - -export default class AbsenceBalance extends Model { - @attr("number") credit; - @attr("number") usedDays; - @attr("django-duration") usedDuration; - @attr("number") balance; - @belongsTo("user", { async: false, inverse: "absenceBalances" }) user; - @belongsTo("absence-type", { async: false, inverse: "absenceBalances" }) - absenceType; - @hasMany("absence-credit", { async: true, inverse: null }) absenceCredits; -} diff --git a/frontend/app/models/absence-balance.ts b/frontend/app/models/absence-balance.ts new file mode 100644 index 000000000..25dbbc2be --- /dev/null +++ b/frontend/app/models/absence-balance.ts @@ -0,0 +1,29 @@ +import type { AsyncHasMany } from "@ember-data/model"; +import Model, { attr, belongsTo, hasMany } from "@ember-data/model"; +import type { Duration } from "moment"; +import type AbsenceCredit from "timed/models/absence-credit"; +import type AbsenceType from "timed/models/absence-type"; +import type User from "timed/models/user"; + +export default class AbsenceBalance extends Model { + @attr("number") + declare credit?: number; + @attr("number") + declare usedDays?: number; + @attr("django-duration") + declare usedDuration?: Duration; + @attr("number") + declare balance?: number; + @belongsTo("user", { async: false, inverse: "absenceBalances" }) + declare user: User; + @belongsTo("absence-type", { async: false, inverse: "absenceBalances" }) + declare absenceType: AbsenceType; + @hasMany("absence-credit", { async: true, inverse: null }) + declare absenceCredits: AsyncHasMany; +} + +declare module "ember-data/types/registries/model" { + interface ModelRegistry { + "absence-balance": AbsenceBalance; + } +} diff --git a/frontend/app/models/absence-credit.js b/frontend/app/models/absence-credit.ts similarity index 54% rename from frontend/app/models/absence-credit.js rename to frontend/app/models/absence-credit.ts index 417c3960c..38249ebff 100644 --- a/frontend/app/models/absence-credit.js +++ b/frontend/app/models/absence-credit.ts @@ -1,9 +1,12 @@ +import Model, { attr, belongsTo } from "@ember-data/model"; +import type { Moment } from "moment"; +import type AbsenceType from "timed/models/absence-type"; +import type User from "timed/models/user"; /** * @module timed * @submodule timed-models * @public */ -import Model, { attr, belongsTo } from "@ember-data/model"; /** * The absence credit model @@ -19,7 +22,8 @@ export default class AbsenceCredit extends Model { * @property {Number} days * @public */ - @attr("number") days; + @attr("number") + declare days?: number; /** * The date @@ -27,7 +31,8 @@ export default class AbsenceCredit extends Model { * @property {moment} date * @public */ - @attr("django-date") date; + @attr("django-date") + declare date?: Moment; /** * The comment @@ -35,7 +40,8 @@ export default class AbsenceCredit extends Model { * @property {String} comment * @public */ - @attr("string", { defaultValue: "" }) comment; + @attr("string", { defaultValue: "" }) + declare comment: string; /** * The absence type for which this credit counts @@ -43,7 +49,8 @@ export default class AbsenceCredit extends Model { * @property {AbsenceType} absenceType * @public */ - @belongsTo("absence-type", { async: false, inverse: null }) absenceType; + @belongsTo("absence-type", { async: false, inverse: null }) + declare absenceType: AbsenceType; /** * The user to which this credit belongs to @@ -51,5 +58,12 @@ export default class AbsenceCredit extends Model { * @property {User} user * @public */ - @belongsTo("user", { async: false, inverse: null }) user; + @belongsTo("user", { async: false, inverse: null }) + declare user: User; +} + +declare module "ember-data/types/registries/model" { + interface ModelRegistry { + "absence-credit": AbsenceCredit; + } } diff --git a/frontend/app/models/absence-type.js b/frontend/app/models/absence-type.ts similarity index 64% rename from frontend/app/models/absence-type.js rename to frontend/app/models/absence-type.ts index 21b97249e..6cf819835 100644 --- a/frontend/app/models/absence-type.js +++ b/frontend/app/models/absence-type.ts @@ -1,9 +1,11 @@ +import type { AsyncHasMany } from "@ember-data/model"; +import Model, { attr, hasMany } from "@ember-data/model"; +import type AbsenceBalance from "timed/models/absence-balance"; /** * @module timed * @submodule timed-models * @public */ -import Model, { attr, hasMany } from "@ember-data/model"; /** * The absence type model @@ -21,7 +23,8 @@ export default class AbsenceType extends Model { * @property {String} name * @public */ - @attr("string") name; + @attr("string") + declare name?: string; /** * Whether the absence type only fills the worktime @@ -29,7 +32,8 @@ export default class AbsenceType extends Model { * @property {Boolean} fillWorktime * @public */ - @attr("boolean") fillWorktime; + @attr("boolean") + declare fillWorktime?: boolean; /** * The balances for this type @@ -38,5 +42,11 @@ export default class AbsenceType extends Model { * @public */ @hasMany("absence-balance", { async: true, inverse: "absenceType" }) - absenceBalances; + declare absenceBalances: AsyncHasMany; +} + +declare module "ember-data/types/registries/model" { + interface ModelRegistry { + "absence-type": AbsenceType; + } } diff --git a/frontend/app/models/absence.js b/frontend/app/models/absence.ts similarity index 53% rename from frontend/app/models/absence.js rename to frontend/app/models/absence.ts index 630f1b91c..e95c2d5ca 100644 --- a/frontend/app/models/absence.js +++ b/frontend/app/models/absence.ts @@ -1,10 +1,14 @@ +import type { AsyncBelongsTo } from "@ember-data/model"; +import Model, { attr, belongsTo } from "@ember-data/model"; +import type { Duration, Moment } from "moment"; +import moment from "moment"; +import type AbsenceType from "timed/models/absence-type"; +import type User from "timed/models/user"; /** * @module timed * @submodule timed-models * @public */ -import Model, { attr, belongsTo } from "@ember-data/model"; -import moment from "moment"; /** * The report model @@ -20,7 +24,8 @@ export default class Absence extends Model { * @property {String} comment * @public */ - @attr("string", { defaultValue: "" }) comment; + @attr("string", { defaultValue: "" }) + declare comment: string; /** * The duration @@ -28,7 +33,8 @@ export default class Absence extends Model { * @property {moment.duration} duration * @public */ - @attr("django-duration", { defaultValue: () => moment.duration() }) duration; + @attr("django-duration", { defaultValue: () => moment.duration() }) + declare duration: Duration; /** * The date @@ -36,7 +42,8 @@ export default class Absence extends Model { * @property {moment} date * @public */ - @attr("django-date") date; + @attr("django-date") + declare date?: Moment; /** * The type of the absence @@ -44,7 +51,8 @@ export default class Absence extends Model { * @property {AbsenceType} absenceType * @public */ - @belongsTo("absence-type", { async: false, inverse: null }) absenceType; + @belongsTo("absence-type", { async: false, inverse: null }) + declare absenceType: AbsenceType; /** * The user @@ -52,5 +60,12 @@ export default class Absence extends Model { * @property {User} user * @public */ - @belongsTo("user", { async: true, inverse: null }) user; + @belongsTo("user", { async: true, inverse: null }) + declare user: AsyncBelongsTo; +} + +declare module "ember-data/types/registries/model" { + interface ModelRegistry { + absence: Absence; + } } diff --git a/frontend/app/models/activity.js b/frontend/app/models/activity.ts similarity index 72% rename from frontend/app/models/activity.js rename to frontend/app/models/activity.ts index e92782c43..e16285b97 100644 --- a/frontend/app/models/activity.js +++ b/frontend/app/models/activity.ts @@ -1,21 +1,36 @@ import { service } from "@ember/service"; +import type { AsyncBelongsTo } from "@ember-data/model"; import Model, { attr, belongsTo } from "@ember-data/model"; +import type StoreService from "@ember-data/store"; +import type NotifyService from "ember-notify"; +import type { Moment } from "moment"; import moment from "moment"; import { all } from "rsvp"; +import type Task from "timed/models/task"; +import type User from "timed/models/user"; export default class Activity extends Model { - @attr("django-time") fromTime; - @attr("django-time") toTime; - @attr("string", { defaultValue: "" }) comment; - @attr("django-date") date; - @attr("boolean", { defaultValue: false }) transferred; - @attr("boolean", { defaultValue: false }) review; - @attr("boolean", { defaultValue: false }) notBillable; - @belongsTo("task", { async: true, inverse: null }) task; - @belongsTo("user", { async: true, inverse: null }) user; - - @service notify; - @service store; + @attr("django-time") + declare fromTime?: Moment; + @attr("django-time") + declare toTime?: Moment; + @attr("string", { defaultValue: "" }) + declare comment: string; + @attr("django-date") + declare date?: Moment; + @attr("boolean", { defaultValue: false }) + declare transferred: boolean; + @attr("boolean", { defaultValue: false }) + declare review: boolean; + @attr("boolean", { defaultValue: false }) + declare notBillable: boolean; + @belongsTo("task", { async: true, inverse: null }) + declare task: AsyncBelongsTo; + @belongsTo("user", { async: true, inverse: null }) + declare user: AsyncBelongsTo; + + @service declare notify: NotifyService; + @service declare store: StoreService; get active() { return !this.toTime && !!this.id; @@ -137,3 +152,9 @@ export default class Activity extends Model { } } } + +declare module "ember-data/types/registries/model" { + interface ModelRegistry { + activity: Activity; + } +} diff --git a/frontend/app/models/attendance.js b/frontend/app/models/attendance.ts similarity index 68% rename from frontend/app/models/attendance.js rename to frontend/app/models/attendance.ts index 34a6e8acc..c9f7c449c 100644 --- a/frontend/app/models/attendance.js +++ b/frontend/app/models/attendance.ts @@ -1,10 +1,13 @@ +import type { AsyncBelongsTo } from "@ember-data/model"; /** * @module timed * @submodule timed-models * @public */ import Model, { attr, belongsTo } from "@ember-data/model"; +import type { Moment } from "moment"; import moment from "moment"; +import type User from "timed/models/user"; /** * The attendance model @@ -20,7 +23,8 @@ export default class Attendance extends Model { * @property {moment} date * @public */ - @attr("django-date") date; + @attr("django-date") + declare date?: Moment; /** * The start time @@ -28,7 +32,8 @@ export default class Attendance extends Model { * @property {moment} from * @public */ - @attr("django-time") from; + @attr("django-time") + declare from?: Moment; /** * The end time @@ -36,7 +41,8 @@ export default class Attendance extends Model { * @property {moment} to * @public */ - @attr("django-time") to; + @attr("django-time") + declare to?: Moment; /** * The user @@ -45,7 +51,8 @@ export default class Attendance extends Model { * @type {User} * @public */ - @belongsTo("user", { async: true, inverse: null }) user; + @belongsTo("user", { async: true, inverse: null }) + declare user: AsyncBelongsTo; /** * The duration between start and end time @@ -65,3 +72,9 @@ export default class Attendance extends Model { return moment.duration(calcTo.diff(this.from)); } } + +declare module "ember-data/types/registries/model" { + interface ModelRegistry { + attendance: Attendance; + } +} diff --git a/frontend/app/models/billing-type.js b/frontend/app/models/billing-type.ts similarity index 66% rename from frontend/app/models/billing-type.js rename to frontend/app/models/billing-type.ts index de59d7548..2c7a6fe8c 100644 --- a/frontend/app/models/billing-type.js +++ b/frontend/app/models/billing-type.ts @@ -19,5 +19,12 @@ export default class BillingType extends Model { * @property {String} name * @public */ - @attr("string") name; + @attr("string") + declare name?: string; +} + +declare module "ember-data/types/registries/model" { + interface ModelRegistry { + "billing-type": BillingType; + } } diff --git a/frontend/app/models/cost-center.js b/frontend/app/models/cost-center.js deleted file mode 100644 index a1b80fb56..000000000 --- a/frontend/app/models/cost-center.js +++ /dev/null @@ -1,6 +0,0 @@ -import Model, { attr } from "@ember-data/model"; - -export default class CostCenter extends Model { - @attr("string") name; - @attr("string") reference; -} diff --git a/frontend/app/models/cost-center.ts b/frontend/app/models/cost-center.ts new file mode 100644 index 000000000..0c32645b7 --- /dev/null +++ b/frontend/app/models/cost-center.ts @@ -0,0 +1,14 @@ +import Model, { attr } from "@ember-data/model"; + +export default class CostCenter extends Model { + @attr("string") + declare name?: string; + @attr("string") + declare reference?: string; +} + +declare module "ember-data/types/registries/model" { + interface ModelRegistry { + "cost-center": CostCenter; + } +} diff --git a/frontend/app/models/customer-assignee.js b/frontend/app/models/customer-assignee.ts similarity index 53% rename from frontend/app/models/customer-assignee.js rename to frontend/app/models/customer-assignee.ts index 678ea0ef4..3c85a229e 100644 --- a/frontend/app/models/customer-assignee.js +++ b/frontend/app/models/customer-assignee.ts @@ -1,9 +1,12 @@ +import type { AsyncBelongsTo } from "@ember-data/model"; +import Model, { attr, belongsTo } from "@ember-data/model"; +import type Customer from "timed/models/customer"; +import type User from "timed/models/user"; /** * @module timed * @submodule timed-models * @public */ -import Model, { attr, belongsTo } from "@ember-data/model"; /** * The customer assignee model @@ -20,7 +23,8 @@ export default class CustomerAssignee extends Model { * @type {Customer} * @public */ - @belongsTo("customer", { async: true, inverse: null }) customer; + @belongsTo("customer", { async: true, inverse: null }) + declare customer: AsyncBelongsTo; /** * The user * @@ -28,7 +32,8 @@ export default class CustomerAssignee extends Model { * @type {User} * @public */ - @belongsTo("user", { async: true, inverse: null }) user; + @belongsTo("user", { async: true, inverse: null }) + declare user: AsyncBelongsTo; /** * Whether the assignee is a reviewer @@ -37,7 +42,8 @@ export default class CustomerAssignee extends Model { * @type {Boolean} * @public */ - @attr("boolean", { defaultValue: false }) isReviewer; + @attr("boolean", { defaultValue: false }) + declare isReviewer: boolean; /** * Whether the assignee is a manager @@ -46,7 +52,8 @@ export default class CustomerAssignee extends Model { * @type {Boolean} * @public */ - @attr("boolean", { defaultValue: false }) isManager; + @attr("boolean", { defaultValue: false }) + declare isManager: boolean; /** * Whether the assignee is a resource @@ -55,5 +62,12 @@ export default class CustomerAssignee extends Model { * @type {Boolean} * @public */ - @attr("boolean", { defaultValue: false }) isResource; + @attr("boolean", { defaultValue: false }) + declare isResource: boolean; +} + +declare module "ember-data/types/registries/model" { + interface ModelRegistry { + "customer-assignee": CustomerAssignee; + } } diff --git a/frontend/app/models/customer-statistic.js b/frontend/app/models/customer-statistic.js deleted file mode 100644 index 315e0a950..000000000 --- a/frontend/app/models/customer-statistic.js +++ /dev/null @@ -1,6 +0,0 @@ -import Model, { attr } from "@ember-data/model"; - -export default class CustomerStatistics extends Model { - @attr("django-duration") duration; - @attr name; -} diff --git a/frontend/app/models/customer-statistic.ts b/frontend/app/models/customer-statistic.ts new file mode 100644 index 000000000..ef88ed8d2 --- /dev/null +++ b/frontend/app/models/customer-statistic.ts @@ -0,0 +1,15 @@ +import Model, { attr } from "@ember-data/model"; +import type { Duration } from "moment"; + +export default class CustomerStatistics extends Model { + @attr("django-duration") + declare duration?: Duration; + @attr + declare name?: string; +} + +declare module "ember-data/types/registries/model" { + interface ModelRegistry { + "customer-statistic": CustomerStatistics; + } +} diff --git a/frontend/app/models/customer.js b/frontend/app/models/customer.ts similarity index 57% rename from frontend/app/models/customer.js rename to frontend/app/models/customer.ts index 49444cf6f..8fd9ba334 100644 --- a/frontend/app/models/customer.js +++ b/frontend/app/models/customer.ts @@ -1,9 +1,12 @@ +import type { AsyncHasMany } from "@ember-data/model"; +import Model, { attr, hasMany } from "@ember-data/model"; +import type CustomerAssignee from "timed/models/customer-assignee"; +import type Project from "timed/models/project"; /** * @module timed * @submodule timed-models * @public */ -import Model, { attr, hasMany } from "@ember-data/model"; /** * The customer model @@ -20,7 +23,8 @@ export default class Customer extends Model { * @type {String} * @public */ - @attr("string", { defaultValue: "" }) name; + @attr("string", { defaultValue: "" }) + declare name: string; /** * Whether the project is archived @@ -29,7 +33,8 @@ export default class Customer extends Model { * @type {Boolean} * @public */ - @attr("boolean", { defaultValue: false }) archived; + @attr("boolean", { defaultValue: false }) + declare archived: boolean; /** * The projects @@ -38,7 +43,8 @@ export default class Customer extends Model { * @type {Project[]} * @public */ - @hasMany("project", { async: true, inverse: "customer" }) projects; + @hasMany("project", { async: true, inverse: "customer" }) + declare projects: AsyncHasMany; /** * Long name - alias for name, used for filtering in the customer box @@ -57,5 +63,12 @@ export default class Customer extends Model { * @type {CustomerAssignee[]} * @public */ - @hasMany("customer-assignee", { async: true, inverse: null }) assignees; + @hasMany("customer-assignee", { async: true, inverse: null }) + declare assignees: AsyncHasMany; +} + +declare module "ember-data/types/registries/model" { + interface ModelRegistry { + customer: Customer; + } } diff --git a/frontend/app/models/employment.js b/frontend/app/models/employment.ts similarity index 53% rename from frontend/app/models/employment.js rename to frontend/app/models/employment.ts index 560ce4013..1a1a8be5e 100644 --- a/frontend/app/models/employment.js +++ b/frontend/app/models/employment.ts @@ -1,9 +1,13 @@ +import type { AsyncBelongsTo } from "@ember-data/model"; +import Model, { attr, belongsTo } from "@ember-data/model"; +import type { Duration, Moment } from "moment"; +import type Location from "timed/models/location"; +import type User from "timed/models/user"; /** * @module timed * @submodule timed-models * @public */ -import Model, { attr, belongsTo } from "@ember-data/model"; /** * The employment model @@ -19,7 +23,8 @@ export default class Employment extends Model { * @property {Number} percentage * @public */ - @attr("number") percentage; + @attr("number") + declare percentage?: number; /** * The time the user has to work every day @@ -27,7 +32,8 @@ export default class Employment extends Model { * @property {moment.duration} worktimePerDay * @public */ - @attr("django-duration") worktimePerDay; + @attr("django-duration") + declare worktimePerDay?: Duration; /** * The start date @@ -35,7 +41,8 @@ export default class Employment extends Model { * @property {moment} start * @public */ - @attr("django-date") start; + @attr("django-date") + declare start?: Moment; /** * Whether the employment is of an external employee @@ -43,7 +50,8 @@ export default class Employment extends Model { * @property {Boolean} isExternal * @public */ - @attr("boolean", { defaultValue: false }) isExternal; + @attr("boolean", { defaultValue: false }) + declare isExternal: boolean; /** * The end date @@ -51,7 +59,8 @@ export default class Employment extends Model { * @property {moment} end * @public */ - @attr("django-date") end; + @attr("django-date") + declare end?: Moment; /** * The employed user @@ -59,7 +68,8 @@ export default class Employment extends Model { * @property {User} user * @public */ - @belongsTo("user", { async: true, inverse: "employments" }) user; + @belongsTo("user", { async: true, inverse: "employments" }) + declare user: AsyncBelongsTo; /** * The work location @@ -67,5 +77,12 @@ export default class Employment extends Model { * @property {Location} location * @public */ - @belongsTo("location", { async: true, inverse: null }) location; + @belongsTo("location", { async: true, inverse: null }) + declare location: AsyncBelongsTo; +} + +declare module "ember-data/types/registries/model" { + interface ModelRegistry { + employment: Employment; + } } diff --git a/frontend/app/models/location.js b/frontend/app/models/location.ts similarity index 67% rename from frontend/app/models/location.js rename to frontend/app/models/location.ts index fe7c35580..08cfd7a04 100644 --- a/frontend/app/models/location.js +++ b/frontend/app/models/location.ts @@ -19,7 +19,8 @@ export default class Location extends Model { * @property {String} name * @public */ - @attr("string") name; + @attr("string") + declare name?: string; /** * The days on which users in this location need to work @@ -27,5 +28,12 @@ export default class Location extends Model { * @property {Number[]} workdays * @public */ - @attr("django-workdays") workdays; + @attr("django-workdays") + declare workdays?: number[]; +} + +declare module "ember-data/types/registries/model" { + interface ModelRegistry { + location: Location; + } } diff --git a/frontend/app/models/month-statistic.js b/frontend/app/models/month-statistic.js deleted file mode 100644 index bd36d4ea6..000000000 --- a/frontend/app/models/month-statistic.js +++ /dev/null @@ -1,7 +0,0 @@ -import Model, { attr } from "@ember-data/model"; - -export default class MonthStatistic extends Model { - @attr("number") year; - @attr("number") month; - @attr("django-duration") duration; -} diff --git a/frontend/app/models/month-statistic.ts b/frontend/app/models/month-statistic.ts new file mode 100644 index 000000000..65f436c34 --- /dev/null +++ b/frontend/app/models/month-statistic.ts @@ -0,0 +1,17 @@ +import Model, { attr } from "@ember-data/model"; +import type { Duration } from "moment"; + +export default class MonthStatistic extends Model { + @attr("number") + declare year?: number; + @attr("number") + declare month?: number; + @attr("django-duration") + declare duration?: Duration; +} + +declare module "ember-data/types/registries/model" { + interface ModelRegistry { + "month-statistic": MonthStatistic; + } +} diff --git a/frontend/app/models/overtime-credit.js b/frontend/app/models/overtime-credit.js deleted file mode 100644 index ba06c6308..000000000 --- a/frontend/app/models/overtime-credit.js +++ /dev/null @@ -1,8 +0,0 @@ -import Model, { attr, belongsTo } from "@ember-data/model"; - -export default class OvertimeCredit extends Model { - @attr("django-date") date; - @attr("django-duration") duration; - @attr("string", { defaultValue: "" }) comment; - @belongsTo("user", { async: false, inverse: null }) user; -} diff --git a/frontend/app/models/overtime-credit.ts b/frontend/app/models/overtime-credit.ts new file mode 100644 index 000000000..22480db1b --- /dev/null +++ b/frontend/app/models/overtime-credit.ts @@ -0,0 +1,20 @@ +import Model, { attr, belongsTo } from "@ember-data/model"; +import type { Moment, Duration } from "moment"; +import type User from "timed/models/user"; + +export default class OvertimeCredit extends Model { + @attr("django-date") + declare date?: Moment; + @attr("django-duration") + declare duration?: Duration; + @attr("string", { defaultValue: "" }) + declare comment: string; + @belongsTo("user", { async: false, inverse: null }) + declare user: User; +} + +declare module "ember-data/types/registries/model" { + interface ModelRegistry { + "overtime-credit": OvertimeCredit; + } +} diff --git a/frontend/app/models/project-assignee.js b/frontend/app/models/project-assignee.ts similarity index 53% rename from frontend/app/models/project-assignee.js rename to frontend/app/models/project-assignee.ts index 58a3b844e..9f2b3f0d6 100644 --- a/frontend/app/models/project-assignee.js +++ b/frontend/app/models/project-assignee.ts @@ -1,9 +1,12 @@ +import type { AsyncBelongsTo } from "@ember-data/model"; /** * @module timed * @submodule timed-models * @public */ import Model, { attr, belongsTo } from "@ember-data/model"; +import type Project from "timed/models/project"; +import type User from "timed/models/user"; /** * The project assignee model @@ -20,7 +23,8 @@ export default class ProjectAssignee extends Model { * @type {Project} * @public */ - @belongsTo("project", { async: true, inverse: null }) project; + @belongsTo("project", { async: true, inverse: null }) + declare project: AsyncBelongsTo; /** * The user @@ -29,7 +33,8 @@ export default class ProjectAssignee extends Model { * @type {User} * @public */ - @belongsTo("user", { async: true, inverse: null }) user; + @belongsTo("user", { async: true, inverse: null }) + declare user: AsyncBelongsTo; /** * Whether the assignee is a reviewer @@ -38,7 +43,8 @@ export default class ProjectAssignee extends Model { * @type {Boolean} * @public */ - @attr("boolean", { defaultValue: false }) isReviewer; + @attr("boolean", { defaultValue: false }) + declare isReviewer: boolean; /** * Whether the assignee is a manager @@ -47,7 +53,8 @@ export default class ProjectAssignee extends Model { * @type {Boolean} * @public */ - @attr("boolean", { defaultValue: false }) isManager; + @attr("boolean", { defaultValue: false }) + declare isManager: boolean; /** * Whether the assignee is a resource @@ -56,5 +63,12 @@ export default class ProjectAssignee extends Model { * @type {Boolean} * @public */ - @attr("boolean", { defaultValue: false }) isResource; + @attr("boolean", { defaultValue: false }) + declare isResource: boolean; +} + +declare module "ember-data/types/registries/model" { + interface ModelRegistry { + "project-assignee": ProjectAssignee; + } } diff --git a/frontend/app/models/project-statistic.js b/frontend/app/models/project-statistic.js deleted file mode 100644 index 12a607a84..000000000 --- a/frontend/app/models/project-statistic.js +++ /dev/null @@ -1,9 +0,0 @@ -import Model, { attr, belongsTo } from "@ember-data/model"; - -export default class ProjectStatistics extends Model { - @attr name; - @attr("django-duration") estimatedTime; - @attr("django-duration") duration; - @attr("django-duration") totalRemainingEffort; - @belongsTo("customer", { async: true, inverse: null }) customer; -} diff --git a/frontend/app/models/project-statistic.ts b/frontend/app/models/project-statistic.ts new file mode 100644 index 000000000..b92357c1c --- /dev/null +++ b/frontend/app/models/project-statistic.ts @@ -0,0 +1,23 @@ +import type { AsyncBelongsTo } from "@ember-data/model"; +import Model, { attr, belongsTo } from "@ember-data/model"; +import type { Duration } from "moment"; +import type Customer from "timed/models/customer"; + +export default class ProjectStatistics extends Model { + @attr + declare name?: string; + @attr("django-duration") + declare estimatedTime?: Duration; + @attr("django-duration") + declare duration?: Duration; + @attr("django-duration") + declare totalRemainingEffort?: Duration; + @belongsTo("customer", { async: true, inverse: null }) + declare customer: AsyncBelongsTo; +} + +declare module "ember-data/types/registries/model" { + interface ModelRegistry { + "project-statistic": ProjectStatistics; + } +} diff --git a/frontend/app/models/project.js b/frontend/app/models/project.ts similarity index 51% rename from frontend/app/models/project.js rename to frontend/app/models/project.ts index a5a0bb80f..e4c52357d 100644 --- a/frontend/app/models/project.js +++ b/frontend/app/models/project.ts @@ -1,9 +1,15 @@ +import type { AsyncHasMany, AsyncBelongsTo } from "@ember-data/model"; /** * @module timed * @submodule timed-models * @public */ import Model, { attr, belongsTo, hasMany } from "@ember-data/model"; +import type { Duration } from "moment"; +import type BillingType from "timed/models/billing-type"; +import type Customer from "timed/models/customer"; +import type ProjectAssignee from "timed/models/project-assignee"; +import type Task from "timed/models/task"; /** * The project model @@ -20,7 +26,8 @@ export default class Project extends Model { * @type {String} * @public */ - @attr("string", { defaultValue: "" }) name; + @attr("string", { defaultValue: "" }) + declare name: string; /** * Whether the project is archived @@ -29,7 +36,8 @@ export default class Project extends Model { * @type {Boolean} * @public */ - @attr("boolean", { defaultValue: false }) archived; + @attr("boolean", { defaultValue: false }) + declare archived: boolean; /** * The estimated time for this project @@ -37,7 +45,8 @@ export default class Project extends Model { * @property {moment.duration} estimatedTime * @public */ - @attr("django-duration") estimatedTime; + @attr("django-duration") + declare estimatedTime?: Duration; /** * Boolean indicating if the remainig effort should be trackable @@ -45,14 +54,16 @@ export default class Project extends Model { * @type {Boolean} * @public */ - @attr("boolean", { defaultValue: false }) remainingEffortTracking; + @attr("boolean", { defaultValue: false }) + declare remainingEffortTracking: boolean; /** * Total remainig effort for this project * @property {moment.duration} estimatedtime * @public */ - @attr("django-duration") totalRemainingEffort; + @attr("django-duration") + declare totalRemainingEffort?: Duration; /** * The customer @@ -61,7 +72,8 @@ export default class Project extends Model { * @type {Customer} * @public */ - @belongsTo("customer", { async: true, inverse: "projects" }) customer; + @belongsTo("customer", { async: true, inverse: "projects" }) + declare customer: AsyncBelongsTo; /** * Whether the project's comments are visible to the customer @@ -70,7 +82,8 @@ export default class Project extends Model { * @type {Boolean} * @public */ - @attr("boolean", { defaultValue: false }) customerVisible; + @attr("boolean", { defaultValue: false }) + declare customerVisible: boolean; /** * The billing @@ -78,7 +91,8 @@ export default class Project extends Model { * @property {BillingType} billingType * @public */ - @belongsTo("billing-type", { async: false, inverse: null }) billingType; + @belongsTo("billing-type", { async: false, inverse: null }) + declare billingType: BillingType; /** * The tasks @@ -87,7 +101,8 @@ export default class Project extends Model { * @type {Task[]} * @public */ - @hasMany("task", { async: true, inverse: "project" }) tasks; + @hasMany("task", { async: true, inverse: "project" }) + declare tasks: AsyncHasMany; /** * Assigned users to this project @@ -96,5 +111,12 @@ export default class Project extends Model { * @type {ProjectAssignee[]} * @public */ - @hasMany("project-assignee", { async: true, inverse: null }) assignees; + @hasMany("project-assignee", { async: true, inverse: null }) + declare assignees: AsyncHasMany; +} + +declare module "ember-data/types/registries/model" { + interface ModelRegistry { + project: Project; + } } diff --git a/frontend/app/models/public-holiday.js b/frontend/app/models/public-holiday.ts similarity index 51% rename from frontend/app/models/public-holiday.js rename to frontend/app/models/public-holiday.ts index 2bca8babc..9eacb3218 100644 --- a/frontend/app/models/public-holiday.js +++ b/frontend/app/models/public-holiday.ts @@ -1,9 +1,12 @@ +import type { AsyncBelongsTo } from "@ember-data/model"; +import Model, { attr, belongsTo } from "@ember-data/model"; +import type { Moment } from "moment"; +import type Location from "timed/models/location"; /** * @module timed * @submodule timed-models * @public */ -import Model, { attr, belongsTo } from "@ember-data/model"; /** * The public holiday model @@ -19,7 +22,8 @@ export default class PublicHoliday extends Model { * @property {String} name * @public */ - @attr("string") name; + @attr("string") + declare name?: string; /** * The date @@ -27,7 +31,8 @@ export default class PublicHoliday extends Model { * @property {moment} date * @public */ - @attr("django-date") date; + @attr("django-date") + declare date?: Moment; /** * The location @@ -35,5 +40,12 @@ export default class PublicHoliday extends Model { * @property {Location} location * @public */ - @belongsTo("location", { async: true, inverse: null }) location; + @belongsTo("location", { async: true, inverse: null }) + declare location: AsyncBelongsTo; +} + +declare module "ember-data/types/registries/model" { + interface ModelRegistry { + "public-holiday": PublicHoliday; + } } diff --git a/frontend/app/models/report-intersection.js b/frontend/app/models/report-intersection.js deleted file mode 100644 index 8b3f2eb5c..000000000 --- a/frontend/app/models/report-intersection.js +++ /dev/null @@ -1,15 +0,0 @@ -import Model, { attr, belongsTo } from "@ember-data/model"; - -export default class ReportIntersection extends Model { - @attr("string") comment; - @attr("boolean", { allowNull: true, defaultValue: null }) notBillable; - @attr("boolean", { allowNull: true, defaultValue: false }) rejected; - @attr("boolean", { allowNull: true, defaultValue: null }) review; - @attr("boolean", { allowNull: true, defaultValue: null }) billed; - @attr("boolean", { allowNull: true, defaultValue: null }) verified; - - @belongsTo("customer", { async: true, inverse: null }) customer; - @belongsTo("project", { async: true, inverse: null }) project; - @belongsTo("task", { async: true, inverse: null }) task; - @belongsTo("user", { async: true, inverse: null }) user; -} diff --git a/frontend/app/models/report-intersection.ts b/frontend/app/models/report-intersection.ts new file mode 100644 index 000000000..48483b1a0 --- /dev/null +++ b/frontend/app/models/report-intersection.ts @@ -0,0 +1,36 @@ +import type { AsyncBelongsTo } from "@ember-data/model"; +import Model, { attr, belongsTo } from "@ember-data/model"; +import type Customer from "timed/models/customer"; +import type Project from "timed/models/project"; +import type Task from "timed/models/task"; +import type User from "timed/models/user"; + +export default class ReportIntersection extends Model { + @attr("string") + declare comment?: string; + @attr("boolean", { allowNull: true, defaultValue: null }) + declare notBillable: boolean | null; + @attr("boolean", { allowNull: true, defaultValue: false }) + declare rejected: boolean | null; + @attr("boolean", { allowNull: true, defaultValue: null }) + declare review: boolean | null; + @attr("boolean", { allowNull: true, defaultValue: null }) + declare billed: boolean | null; + @attr("boolean", { allowNull: true, defaultValue: null }) + declare verified: boolean | null; + + @belongsTo("customer", { async: true, inverse: null }) + declare customer: AsyncBelongsTo; + @belongsTo("project", { async: true, inverse: null }) + declare project: AsyncBelongsTo; + @belongsTo("task", { async: true, inverse: null }) + declare task: AsyncBelongsTo; + @belongsTo("user", { async: true, inverse: null }) + declare user: AsyncBelongsTo; +} + +declare module "ember-data/types/registries/model" { + interface ModelRegistry { + "report-intersection": ReportIntersection; + } +} diff --git a/frontend/app/models/report.js b/frontend/app/models/report.ts similarity index 59% rename from frontend/app/models/report.js rename to frontend/app/models/report.ts index 1407ecbff..2b8aefef9 100644 --- a/frontend/app/models/report.js +++ b/frontend/app/models/report.ts @@ -1,10 +1,14 @@ +import type { AsyncBelongsTo } from "@ember-data/model"; +import Model, { attr, belongsTo } from "@ember-data/model"; +import type { Duration, Moment } from "moment"; +import moment from "moment"; +import type Task from "timed/models/task"; +import type User from "timed/models/user"; /** * @module timed * @submodule timed-models * @public */ -import Model, { attr, belongsTo } from "@ember-data/model"; -import moment from "moment"; /** * The report model @@ -20,7 +24,8 @@ export default class Report extends Model { * @property {String} comment * @public */ - @attr("string", { defaultValue: "" }) comment; + @attr("string", { defaultValue: "" }) + declare comment: string; /** * The date @@ -28,7 +33,8 @@ export default class Report extends Model { * @property {moment} date * @public */ - @attr("django-date") date; + @attr("django-date") + declare date?: Moment; /** * The duration @@ -36,7 +42,8 @@ export default class Report extends Model { * @property {moment.duration} duration * @public */ - @attr("django-duration", { defaultValue: () => moment.duration() }) duration; + @attr("django-duration", { defaultValue: () => moment.duration() }) + declare duration: Duration; /** * The remaining effort for the underlying task @@ -46,7 +53,7 @@ export default class Report extends Model { */ @attr("django-duration", { defaultValue: () => moment.duration() }) - remainingEffort; + declare remainingEffort: Duration; /** * Whether the report needs to be reviewed @@ -54,7 +61,8 @@ export default class Report extends Model { * @property {Boolean} review * @public */ - @attr("boolean", { defaultValue: false }) review; + @attr("boolean", { defaultValue: false }) + declare review: boolean; /** * Whether the report got rejected by the reviewer @@ -62,7 +70,8 @@ export default class Report extends Model { * @property {Boolean} reject * @public */ - @attr("boolean", { defaultValue: false }) rejected; + @attr("boolean", { defaultValue: false }) + declare rejected: boolean; /** * Whether the report is not billable @@ -70,7 +79,8 @@ export default class Report extends Model { * @property {Boolean} notBillable * @public */ - @attr("boolean", { defaultValue: false }) notBillable; + @attr("boolean", { defaultValue: false }) + declare notBillable: boolean; /** * Whether the report has been marked as billed @@ -78,7 +88,8 @@ export default class Report extends Model { * @property {Boolean} billed * @public */ - @attr("boolean", { defaultValue: false }) billed; + @attr("boolean", { defaultValue: false }) + declare billed: boolean; /** * The task @@ -86,7 +97,8 @@ export default class Report extends Model { * @property {Task} task * @public */ - @belongsTo("task", { async: false, inverse: null }) task; + @belongsTo("task", { async: false, inverse: null }) + declare task: Task; /** * The user @@ -94,7 +106,8 @@ export default class Report extends Model { * @property {User} user * @public */ - @belongsTo("user", { async: false, inverse: null }) user; + @belongsTo("user", { async: false, inverse: null }) + declare user: User; /** * The user which verified this report @@ -102,5 +115,12 @@ export default class Report extends Model { * @property {User} verifiedBy * @public */ - @belongsTo("user", { async: true, inverse: null }) verifiedBy; + @belongsTo("user", { async: true, inverse: null }) + declare verifiedBy: AsyncBelongsTo; +} + +declare module "ember-data/types/registries/model" { + interface ModelRegistry { + report: Report; + } } diff --git a/frontend/app/models/task-assignee.js b/frontend/app/models/task-assignee.ts similarity index 53% rename from frontend/app/models/task-assignee.js rename to frontend/app/models/task-assignee.ts index 4f8a58bbe..816f7762f 100644 --- a/frontend/app/models/task-assignee.js +++ b/frontend/app/models/task-assignee.ts @@ -1,9 +1,12 @@ +import type { AsyncBelongsTo } from "@ember-data/model"; /** * @module timed * @submodule timed-models * @public */ import Model, { attr, belongsTo } from "@ember-data/model"; +import type Task from "timed/models/task"; +import type User from "timed/models/user"; /** * The task assignee model @@ -20,7 +23,8 @@ export default class TaskAssignee extends Model { * @type {Task} * @public */ - @belongsTo("task", { async: true, inverse: "assignees" }) task; + @belongsTo("task", { async: true, inverse: "assignees" }) + declare task: AsyncBelongsTo; /** * The user * @@ -28,7 +32,8 @@ export default class TaskAssignee extends Model { * @type {User} * @public */ - @belongsTo("user", { async: true, inverse: null }) user; + @belongsTo("user", { async: true, inverse: null }) + declare user: AsyncBelongsTo; /** * Whether the assignee is a reviewer @@ -37,7 +42,8 @@ export default class TaskAssignee extends Model { * @type {Boolean} * @public */ - @attr("boolean", { defaultValue: false }) isReviewer; + @attr("boolean", { defaultValue: false }) + declare isReviewer: boolean; /** * Whether the assignee is a manager @@ -46,7 +52,8 @@ export default class TaskAssignee extends Model { * @type {Boolean} * @public */ - @attr("boolean", { defaultValue: false }) isManager; + @attr("boolean", { defaultValue: false }) + declare isManager: boolean; /** * Whether the assignee is a resource @@ -55,5 +62,12 @@ export default class TaskAssignee extends Model { * @type {Boolean} * @public */ - @attr("boolean", { defaultValue: false }) isResource; + @attr("boolean", { defaultValue: false }) + declare isResource: boolean; +} + +declare module "ember-data/types/registries/model" { + interface ModelRegistry { + "task-assignee": TaskAssignee; + } } diff --git a/frontend/app/models/task-statistic.js b/frontend/app/models/task-statistic.js deleted file mode 100644 index b9a234e25..000000000 --- a/frontend/app/models/task-statistic.js +++ /dev/null @@ -1,9 +0,0 @@ -import Model, { attr, belongsTo } from "@ember-data/model"; - -export default class TaskStatistics extends Model { - @attr name; - @attr("django-duration") duration; - @attr("django-duration") estimatedTime; - @attr("django-duration") mostRecentRemainingEffort; - @belongsTo("project", { async: true, inverse: null }) project; -} diff --git a/frontend/app/models/task-statistic.ts b/frontend/app/models/task-statistic.ts new file mode 100644 index 000000000..e63d73b50 --- /dev/null +++ b/frontend/app/models/task-statistic.ts @@ -0,0 +1,23 @@ +import type { AsyncBelongsTo } from "@ember-data/model"; +import Model, { attr, belongsTo } from "@ember-data/model"; +import type { Duration } from "moment"; +import type Project from "timed/models/project"; + +export default class TaskStatistics extends Model { + @attr + declare name?: string; + @attr("django-duration") + declare duration?: Duration; + @attr("django-duration") + declare estimatedTime?: Duration; + @attr("django-duration") + declare mostRecentRemainingEffort?: Duration; + @belongsTo("project", { async: true, inverse: null }) + declare project: AsyncBelongsTo; +} + +declare module "ember-data/types/registries/model" { + interface ModelRegistry { + "task-statistic": TaskStatistics; + } +} diff --git a/frontend/app/models/task.js b/frontend/app/models/task.js deleted file mode 100644 index 9882f902b..000000000 --- a/frontend/app/models/task.js +++ /dev/null @@ -1,40 +0,0 @@ -import Model, { attr, belongsTo, hasMany } from "@ember-data/model"; - -export default class Task extends Model { - @attr("string", { defaultValue: "" }) name; - @attr("django-duration") estimatedTime; - @attr("django-duration") mostRecentRemainingEffort; - @attr("boolean", { defaultValue: false }) archived; - @attr("string", { defaultValue: "" }) reference; - - @belongsTo("project", { - async: true, - inverse: "tasks", - }) - project; - @hasMany("task-assignee", { - async: true, - inverse: "task", - }) - assignees; - - /** - * Flag saying that this is a task. - * Used in /app/customer-suggestion/template.hbs - * We're using this as a workaround for the fact that one - * can't seem to use helpers like "(eq" in inline templates - * - * @property project - * @type {Project} - * @public - */ - isTask = true; - - get longName() { - const taskName = this.name; - const projectName = this.project.get("name"); - const customerName = this.project.get("customer.name"); - - return `${customerName} > ${projectName} > ${taskName}`; - } -} diff --git a/frontend/app/models/task.ts b/frontend/app/models/task.ts new file mode 100644 index 000000000..147a10897 --- /dev/null +++ b/frontend/app/models/task.ts @@ -0,0 +1,49 @@ +import type { AsyncHasMany, AsyncBelongsTo } from "@ember-data/model"; +import Model, { attr, belongsTo, hasMany } from "@ember-data/model"; +import type { Duration } from "moment"; +import type Project from "timed/models/project"; +import type TaskAssignee from "timed/models/task-assignee"; + +export default class Task extends Model { + @attr("string", { defaultValue: "" }) + declare name: string; + @attr("django-duration") + declare estimatedTime?: Duration; + @attr("django-duration") + declare mostRecentRemainingEffort?: Duration; + @attr("boolean", { defaultValue: false }) + declare archived: boolean; + @attr("string", { defaultValue: "" }) + declare reference: string; + + @belongsTo("project", { async: true, inverse: "tasks" }) + declare project: AsyncBelongsTo; + @hasMany("task-assignee", { async: true, inverse: "task" }) + declare assignees: AsyncHasMany; + + /** + * Flag saying that this is a task. + * Used in /app/customer-suggestion/template.hbs + * We're using this as a workaround for the fact that one + * can't seem to use helpers like "(eq" in inline templates + * + * @property project + * @type {Project} + * @public + */ + isTask = true; + + get longName() { + const taskName = this.name; + const projectName = this.project.get("name"); + const customerName = this.project.get("customer.name"); + + return `${customerName} > ${projectName} > ${taskName}`; + } +} + +declare module "ember-data/types/registries/model" { + interface ModelRegistry { + task: Task; + } +} diff --git a/frontend/app/models/user-statistic.js b/frontend/app/models/user-statistic.js deleted file mode 100644 index 1785551fe..000000000 --- a/frontend/app/models/user-statistic.js +++ /dev/null @@ -1,7 +0,0 @@ -import Model, { attr, belongsTo } from "@ember-data/model"; - -export default class UserStatistic extends Model { - @attr("django-duration") duration; - - @belongsTo("user", { async: true, inverse: null }) user; -} diff --git a/frontend/app/models/user-statistic.ts b/frontend/app/models/user-statistic.ts new file mode 100644 index 000000000..514edf6dd --- /dev/null +++ b/frontend/app/models/user-statistic.ts @@ -0,0 +1,18 @@ +import type { AsyncBelongsTo } from "@ember-data/model"; +import Model, { attr, belongsTo } from "@ember-data/model"; +import type { Duration } from "moment"; +import type User from "timed/models/user"; + +export default class UserStatistic extends Model { + @attr("django-duration") + declare duration?: Duration; + + @belongsTo("user", { async: true, inverse: null }) + declare user: AsyncBelongsTo; +} + +declare module "ember-data/types/registries/model" { + interface ModelRegistry { + "user-statistic": UserStatistic; + } +} diff --git a/frontend/app/models/user.js b/frontend/app/models/user.ts similarity index 69% rename from frontend/app/models/user.js rename to frontend/app/models/user.ts index 3db1a1a6c..0313796b6 100644 --- a/frontend/app/models/user.js +++ b/frontend/app/models/user.ts @@ -1,11 +1,16 @@ +import { service } from "@ember/service"; +import type { SyncHasMany, AsyncHasMany } from "@ember-data/model"; +import Model, { attr, hasMany } from "@ember-data/model"; +import type StoreService from "@ember-data/store"; +import moment from "moment"; +import type AbsenceBalance from "timed/models/absence-balance"; +import type Employment from "timed/models/employment"; /** * @module timed * @submodule timed-models * @public */ -import { service } from "@ember/service"; -import Model, { attr, hasMany } from "@ember-data/model"; -import moment from "moment"; +import type WorktimeBalance from "timed/models/worktime-balance"; /** * The user model * @@ -14,14 +19,15 @@ import moment from "moment"; * @public */ export default class User extends Model { - @service store; + @service declare store: StoreService; /** * The username * * @property {String} username * @public */ - @attr("string") username; + @attr("string") + declare username?: string; /** * The first name @@ -29,7 +35,8 @@ export default class User extends Model { * @property {String} firstName * @public */ - @attr("string") firstName; + @attr("string") + declare firstName?: string; /** * The last name @@ -37,7 +44,8 @@ export default class User extends Model { * @property {String} lastName * @public */ - @attr("string") lastName; + @attr("string") + declare lastName?: string; /** * The email address @@ -45,7 +53,8 @@ export default class User extends Model { * @property {String} email * @public */ - @attr("string") email; + @attr("string") + declare email?: string; /** * Defines if the user is a superuser @@ -53,7 +62,8 @@ export default class User extends Model { * @property {Boolean} isSuperuser * @public */ - @attr("boolean") isSuperuser; + @attr("boolean") + declare isSuperuser?: boolean; /** * Whether a user is active @@ -61,7 +71,8 @@ export default class User extends Model { * @property {Boolean} isActive * @public */ - @attr("boolean") isActive; + @attr("boolean") + declare isActive?: boolean; /** * Whether the user is a reviewer in a project @@ -69,12 +80,14 @@ export default class User extends Model { * @property {Boolean} isReviewer * @public */ - @attr("boolean") isReviewer; + @attr("boolean") + declare isReviewer?: boolean; /** * Whether the user is an accountant */ - @attr("boolean", { defaultValue: false }) isAccountant; + @attr("boolean", { defaultValue: false }) + declare isAccountant: boolean; /** * Whether the user completed the app tour @@ -82,7 +95,8 @@ export default class User extends Model { * @property {Boolean} tourDone * @public */ - @attr("boolean") tourDone; + @attr("boolean") + declare tourDone?: boolean; /** * The users supervisors @@ -90,7 +104,8 @@ export default class User extends Model { * @property {User[]} supervisors * @public */ - @hasMany("user", { inverse: "supervisees", async: false }) supervisors; + @hasMany("user", { inverse: "supervisees", async: false }) + declare supervisors: SyncHasMany; /** * The users supervisees @@ -98,7 +113,8 @@ export default class User extends Model { * @property {User[]} supervisees * @public */ - @hasMany("user", { inverse: "supervisors", async: false }) supervisees; + @hasMany("user", { inverse: "supervisors", async: false }) + declare supervisees: SyncHasMany; /** * The users employments @@ -106,7 +122,8 @@ export default class User extends Model { * @property {Employment[]} employments * @public */ - @hasMany("employment", { async: true, inverse: "user" }) employments; + @hasMany("employment", { async: true, inverse: "user" }) + declare employments: AsyncHasMany; /** * The users worktime balances @@ -115,7 +132,7 @@ export default class User extends Model { * @public */ @hasMany("worktime-balances", { async: true, inverse: "user" }) - worktimeBalances; + declare worktimeBalances: AsyncHasMany; /** * The users absence balances @@ -123,7 +140,8 @@ export default class User extends Model { * @property {AbsenceBalance[]} absenceBalances * @public */ - @hasMany("absence-balance", { async: true, inverse: "user" }) absenceBalances; + @hasMany("absence-balance", { async: true, inverse: "user" }) + declare absenceBalances: AsyncHasMany; /** * The full name @@ -200,3 +218,9 @@ export default class User extends Model { }; } } + +declare module "ember-data/types/registries/model" { + interface ModelRegistry { + user: User; + } +} diff --git a/frontend/app/models/worktime-balance.js b/frontend/app/models/worktime-balance.js deleted file mode 100644 index f7cb73ead..000000000 --- a/frontend/app/models/worktime-balance.js +++ /dev/null @@ -1,7 +0,0 @@ -import Model, { attr, belongsTo } from "@ember-data/model"; - -export default class WorktimeBalance extends Model { - @attr("django-date") date; - @attr("django-duration") balance; - @belongsTo("user", { async: true, inverse: "worktimeBalances" }) user; -} diff --git a/frontend/app/models/worktime-balance.ts b/frontend/app/models/worktime-balance.ts new file mode 100644 index 000000000..881ebdd1a --- /dev/null +++ b/frontend/app/models/worktime-balance.ts @@ -0,0 +1,19 @@ +import type { AsyncBelongsTo } from "@ember-data/model"; +import Model, { attr, belongsTo } from "@ember-data/model"; +import type { Moment, Duration } from "moment"; +import type User from "timed/models/user"; + +export default class WorktimeBalance extends Model { + @attr("django-date") + declare date?: Moment; + @attr("django-duration") + declare balance?: Duration; + @belongsTo("user", { async: true, inverse: "worktimeBalances" }) + declare user: AsyncBelongsTo; +} + +declare module "ember-data/types/registries/model" { + interface ModelRegistry { + "worktime-balance": WorktimeBalance; + } +} diff --git a/frontend/app/models/year-statistic.js b/frontend/app/models/year-statistic.js deleted file mode 100644 index 54108d33d..000000000 --- a/frontend/app/models/year-statistic.js +++ /dev/null @@ -1,6 +0,0 @@ -import Model, { attr } from "@ember-data/model"; - -export default class YearStatistic extends Model { - @attr("number") year; - @attr("django-duration") duration; -} diff --git a/frontend/app/models/year-statistic.ts b/frontend/app/models/year-statistic.ts new file mode 100644 index 000000000..a2d59de19 --- /dev/null +++ b/frontend/app/models/year-statistic.ts @@ -0,0 +1,15 @@ +import Model, { attr } from "@ember-data/model"; +import type { Duration } from "moment"; + +export default class YearStatistic extends Model { + @attr("number") + declare year?: number; + @attr("django-duration") + declare duration?: Duration; +} + +declare module "ember-data/types/registries/model" { + interface ModelRegistry { + "year-statistic": YearStatistic; + } +} diff --git a/frontend/app/serializers/attendance.js b/frontend/app/serializers/attendance.js index d2ddaf624..2bbb80998 100644 --- a/frontend/app/serializers/attendance.js +++ b/frontend/app/serializers/attendance.js @@ -12,7 +12,7 @@ import ApplicationSerializer from "timed/serializers/application"; * @extends ApplicationSerializer * @public */ -export default ApplicationSerializer.extend({ +export default class AttendanceSerializer extends ApplicationSerializer { /** * The attribute mapping * @@ -24,8 +24,8 @@ export default ApplicationSerializer.extend({ * @property {String} to * @public */ - attrs: { + attrs = { from: "from-time", to: "to-time", - }, -}); + } +} diff --git a/frontend/app/serializers/employment.js b/frontend/app/serializers/employment.js index de54c6c2b..3ac3a3f1b 100644 --- a/frontend/app/serializers/employment.js +++ b/frontend/app/serializers/employment.js @@ -12,7 +12,7 @@ import ApplicationSerializer from "timed/serializers/application"; * @extends ApplicationSerializer * @public */ -export default ApplicationSerializer.extend({ +export default class EmploymentSerializer extends ApplicationSerializer{ /** * The attribute mapping * @@ -24,8 +24,8 @@ export default ApplicationSerializer.extend({ * @property {String} end * @public */ - attrs: { + attrs = { start: "start-date", end: "end-date", - }, -}); + } +} diff --git a/frontend/app/services/autostart-tour.js b/frontend/app/services/autostart-tour.ts similarity index 88% rename from frontend/app/services/autostart-tour.js rename to frontend/app/services/autostart-tour.ts index 65e326a6b..cbb4d433e 100644 --- a/frontend/app/services/autostart-tour.js +++ b/frontend/app/services/autostart-tour.ts @@ -37,3 +37,9 @@ export default class AutostartTourService extends Service { return this.undoneTours.length === 0; } } + +declare module "@ember/service" { + interface Registry { + "autostart-tour": AutostartTourService; + } +} diff --git a/frontend/app/services/current-user.js b/frontend/app/services/current-user.ts similarity index 62% rename from frontend/app/services/current-user.js rename to frontend/app/services/current-user.ts index d2e6524e2..2e5dc37f6 100644 --- a/frontend/app/services/current-user.js +++ b/frontend/app/services/current-user.ts @@ -1,11 +1,15 @@ +import type RouterService from "@ember/routing/router-service"; import Service, { service } from "@ember/service"; +import type StoreService from "@ember-data/store"; +import type SessionService from "ember-simple-auth-oidc/services/session"; import moment from "moment"; +import type FetchService from "timed/services/fetch"; export default class CurrentUserService extends Service { - @service session; - @service fetch; - @service store; - @service router; + @service declare session: SessionService; + @service declare fetch: FetchService; + @service declare store: StoreService; + @service declare router: RouterService; async load() { if (!this.session.isAuthenticated) { @@ -38,3 +42,9 @@ export default class CurrentUserService extends Service { this.user = usermodel; } } + +declare module "@ember/service" { + interface Registry { + "current-user": CurrentUserService; + } +} diff --git a/frontend/app/services/fetch.js b/frontend/app/services/fetch.ts similarity index 92% rename from frontend/app/services/fetch.js rename to frontend/app/services/fetch.ts index e950f900f..46e2f3ccb 100644 --- a/frontend/app/services/fetch.js +++ b/frontend/app/services/fetch.ts @@ -2,6 +2,7 @@ import Service, { service } from "@ember/service"; import { isEmpty } from "@ember/utils"; import { isUnauthorizedResponse } from "ember-fetch/errors"; import { handleUnauthorized } from "ember-simple-auth-oidc"; +import type SessionService from "ember-simple-auth-oidc/services/session"; import fetch from "fetch"; const CONTENT_TYPE = "application/vnd.api+json"; @@ -40,7 +41,7 @@ const stringifyBodyData = (obj) => { }; export default class FetchService extends Service { - @service session; + @service declare session: SessionService; async fetch(resource, init = {}) { await this.session.refreshAuthentication.perform(); @@ -97,3 +98,9 @@ export default class FetchService extends Service { }; } } + +declare module "@ember/service" { + interface Registry { + fetch: FetchService; + } +} diff --git a/frontend/app/services/metadata-fetcher.js b/frontend/app/services/metadata-fetcher.ts similarity index 92% rename from frontend/app/services/metadata-fetcher.js rename to frontend/app/services/metadata-fetcher.ts index eeb9f7429..7312781fa 100644 --- a/frontend/app/services/metadata-fetcher.js +++ b/frontend/app/services/metadata-fetcher.ts @@ -1,6 +1,7 @@ import Service, { service } from "@ember/service"; import { camelize, capitalize, dasherize } from "@ember/string"; import { restartableTask } from "ember-concurrency"; +import type FetchService from "timed/services/fetch"; import DjangoDurationTransform from "timed/transforms/django-duration"; const DJANGO_DURATION_TRANSFORM = DjangoDurationTransform.create(); @@ -45,7 +46,7 @@ export default class MetadataFetcherService extends Service { * @property {Emberfetch} fetch * @public */ - @service fetch; + @service declare fetch: FetchService; /** * Task to fetch a single records metadata @@ -95,3 +96,9 @@ export default class MetadataFetcherService extends Service { return { ...attributesValues, ...metaValues }; } } + +declare module "@ember/service" { + interface Registry { + "metadata-fetcher": MetadataFetcherService; + } +} diff --git a/frontend/app/services/rejected-reports.js b/frontend/app/services/rejected-reports.ts similarity index 73% rename from frontend/app/services/rejected-reports.js rename to frontend/app/services/rejected-reports.ts index 88197b3ae..06bbb9dc4 100644 --- a/frontend/app/services/rejected-reports.js +++ b/frontend/app/services/rejected-reports.ts @@ -1,15 +1,18 @@ import Service, { service } from "@ember/service"; +import type StoreService from "@ember-data/store"; import { macroCondition, isTesting } from "@embroider/macros"; import { tracked } from "@glimmer/tracking"; +import type NotifyService from "ember-notify"; +import type CurrentUserService from "timed/services/current-user"; const INTERVAL_DELAY = 10 * 60000; // 10 Minutes export default class RejectedReportsService extends Service { - @service store; + @service declare store: StoreService; - @service currentUser; + @service declare currentUser: CurrentUserService; - @service notify; + @service declare notify: NotifyService; @tracked amountReports = 0; @tracked intervalId; @@ -53,3 +56,9 @@ export default class RejectedReportsService extends Service { clearInterval(this.intervalId); } } + +declare module "@ember/service" { + interface Registry { + "rejected-reports": RejectedReportsService; + } +} diff --git a/frontend/app/services/tour.js b/frontend/app/services/tour.ts similarity index 87% rename from frontend/app/services/tour.js rename to frontend/app/services/tour.ts index d57f0ac1f..2d4f32200 100644 --- a/frontend/app/services/tour.js +++ b/frontend/app/services/tour.ts @@ -1,17 +1,21 @@ +import type RouterService from "@ember/routing/router-service"; import { schedule, later } from "@ember/runloop"; import { service } from "@ember/service"; import { waitFor } from "@ember/test-waiters"; import { isTesting, macroCondition } from "@embroider/macros"; import { tracked } from "@glimmer/tracking"; +import type NotifyService from "ember-notify"; import Tour from "ember-shepherd/services/tour"; +import type AutostartTourService from "timed/services/autostart-tour"; +import type MediaService from "timed/services/media"; import TOURS from "timed/tours"; import { cached } from "tracked-toolbox"; export default class TourService extends Tour { - @service notify; - @service media; - @service router; - @service autostartTour; + @service declare notify: NotifyService; + @service declare media: MediaService; + @service declare router: RouterService; + @service declare autostartTour: AutostartTourService; @tracked model; constructor(...args) { @@ -51,8 +55,7 @@ export default class TourService extends Tour { super.willDestroy(...args); } - @cached - get routeName() { + @cached get routeName() { return this.router.currentRouteName.replace(/\.index$/, ""); } @@ -132,8 +135,7 @@ export default class TourService extends Tour { } } - @waitFor - async startTour() { + @waitFor async startTour() { if (this._wantsTour && this.hasTourForRoute) { await this.prepareTourForCurrentRoute(); schedule("afterRender", this, () => { @@ -172,3 +174,9 @@ export default class TourService extends Tour { } } } + +declare module "@ember/service" { + interface Registry { + tour: TourService; + } +} diff --git a/frontend/app/services/tracking.js b/frontend/app/services/tracking.ts similarity index 96% rename from frontend/app/services/tracking.js rename to frontend/app/services/tracking.ts index 0df09deef..9f9569e5f 100644 --- a/frontend/app/services/tracking.js +++ b/frontend/app/services/tracking.ts @@ -2,9 +2,11 @@ import { getOwner } from "@ember/application"; import { scheduleOnce } from "@ember/runloop"; import Service, { service } from "@ember/service"; import { camelize, capitalize } from "@ember/string"; +import type StoreService from "@ember-data/store"; import { isTesting, macroCondition } from "@embroider/macros"; import { tracked } from "@glimmer/tracking"; import { dropTask, task, timeout } from "ember-concurrency"; +import type NotifyService from "ember-notify"; import { trackedTask } from "ember-resources/util/ember-concurrency"; import moment from "moment"; import formatDuration from "timed/utils/format-duration"; @@ -25,7 +27,7 @@ export default class TrackingService extends Service { * @property {Ember.Store} store * @public */ - @service store; + @service declare store: StoreService; /** * The notify service @@ -33,7 +35,7 @@ export default class TrackingService extends Service { * @property {EmberNotify.NotifyService} notify * @public */ - @service notify; + @service declare notify: NotifyService; /** * Flag indicating if the tracking reports is currently generated. @@ -363,3 +365,9 @@ export default class TrackingService extends Service { return; } } + +declare module "@ember/service" { + interface Registry { + tracking: TrackingService; + } +} diff --git a/frontend/app/services/unverified-reports.js b/frontend/app/services/unverified-reports.ts similarity index 77% rename from frontend/app/services/unverified-reports.js rename to frontend/app/services/unverified-reports.ts index b320b70e8..b6579c8d2 100644 --- a/frontend/app/services/unverified-reports.js +++ b/frontend/app/services/unverified-reports.ts @@ -1,7 +1,10 @@ import Service, { service } from "@ember/service"; +import type StoreService from "@ember-data/store"; import { isTesting, macroCondition } from "@embroider/macros"; import { tracked } from "@glimmer/tracking"; +import type NotifyService from "ember-notify"; import moment from "moment"; +import type CurrentUserService from "timed/services/current-user"; const INTERVAL_DELAY = 10 * 60000; // 10 Minutes @@ -16,11 +19,11 @@ const INTERVAL_DELAY = 10 * 60000; // 10 Minutes * @public */ export default class UnverifiedReportsService extends Service { - @service store; + @service declare store: StoreService; - @service currentUser; + @service declare currentUser: CurrentUserService; - @service notify; + @service declare notify: NotifyService; @tracked amountReports = 0; @@ -65,3 +68,9 @@ export default class UnverifiedReportsService extends Service { clearInterval(this.intervalId); } } + +declare module "@ember/service" { + interface Registry { + "unverified-reports": UnverifiedReportsService; + } +} diff --git a/frontend/app/transforms/django-date.js b/frontend/app/transforms/django-date.ts similarity index 74% rename from frontend/app/transforms/django-date.js rename to frontend/app/transforms/django-date.ts index 7d1764609..144d88dea 100644 --- a/frontend/app/transforms/django-date.js +++ b/frontend/app/transforms/django-date.ts @@ -18,3 +18,9 @@ export default class DjangoDateTransform extends MomentTransform { */ format = "YYYY-MM-DD"; } + +declare module "ember-data/types/registries/transform" { + interface TransformRegistry { + "django-date": DjangoDateTransform; + } +} diff --git a/frontend/app/transforms/django-datetime.js b/frontend/app/transforms/django-datetime.ts similarity index 74% rename from frontend/app/transforms/django-datetime.js rename to frontend/app/transforms/django-datetime.ts index fdbfd2ed5..1e81896ac 100644 --- a/frontend/app/transforms/django-datetime.js +++ b/frontend/app/transforms/django-datetime.ts @@ -18,3 +18,9 @@ export default class DjangoDatetimeTransform extends MomentTransform { */ format = "YYYY-MM-DDTHH:mm:ss.SSSSZ"; } + +declare module "ember-data/types/registries/transform" { + interface TransformRegistry { + "django-datetime": DjangoDatetimeTransform; + } +} diff --git a/frontend/app/transforms/django-duration.js b/frontend/app/transforms/django-duration.ts similarity index 90% rename from frontend/app/transforms/django-duration.js rename to frontend/app/transforms/django-duration.ts index c607ba709..925aaa3bd 100644 --- a/frontend/app/transforms/django-duration.js +++ b/frontend/app/transforms/django-duration.ts @@ -1,5 +1,5 @@ import Transform from "@ember-data/serializer/transform"; -import moment from "moment"; +import moment, { type Duration } from "moment"; import { pad2joincolon } from "timed/utils/pad"; import parseDjangoDuration from "timed/utils/parse-django-duration"; @@ -29,7 +29,7 @@ export default class DjangoDurationTransform extends Transform { * @return {moment.duration} The deserialized moment duration * @public */ - deserialize(serialized) { + deserialize(serialized: string) { return parseDjangoDuration(serialized); } @@ -69,7 +69,7 @@ export default class DjangoDurationTransform extends Transform { * @return {String} The serialized django duration * @public */ - serialize(deserialized) { + serialize(deserialized: Duration): string { if (!moment.isDuration(deserialized)) { return null; } @@ -90,3 +90,9 @@ export default class DjangoDurationTransform extends Transform { return string; } } + +declare module "ember-data/types/registries/transform" { + interface TransformRegistry { + "django-duration": DjangoDurationTransform; + } +} diff --git a/frontend/app/transforms/django-time.js b/frontend/app/transforms/django-time.ts similarity index 74% rename from frontend/app/transforms/django-time.js rename to frontend/app/transforms/django-time.ts index 465159bec..8852397e1 100644 --- a/frontend/app/transforms/django-time.js +++ b/frontend/app/transforms/django-time.ts @@ -18,3 +18,9 @@ export default class DjangoTimeTransform extends MomentTransform { */ format = "HH:mm:ss"; } + +declare module "ember-data/types/registries/transform" { + interface TransformRegistry { + "django-time": DjangoTimeTransform; + } +} diff --git a/frontend/app/transforms/django-workdays.js b/frontend/app/transforms/django-workdays.ts similarity index 79% rename from frontend/app/transforms/django-workdays.js rename to frontend/app/transforms/django-workdays.ts index 243146a30..b5ad782d3 100644 --- a/frontend/app/transforms/django-workdays.js +++ b/frontend/app/transforms/django-workdays.ts @@ -18,7 +18,7 @@ export default class DjangoWorkdaysTransform extends Transform { * @return {Number[]} The deserialized array * @public */ - deserialize(serialized) { + deserialize(serialized): number[] { return serialized.split(",").map(Number); } @@ -30,7 +30,13 @@ export default class DjangoWorkdaysTransform extends Transform { * @return {String} The serialized string * @public */ - serialize(deserialized) { + serialize(deserialized): string { return deserialized.join(); } } + +declare module "ember-data/types/registries/transform" { + interface TransformRegistry { + "django-workdays": DjangoWorkdaysTransform; + } +} diff --git a/frontend/app/transforms/moment.js b/frontend/app/transforms/moment.ts similarity index 80% rename from frontend/app/transforms/moment.js rename to frontend/app/transforms/moment.ts index 2720cd6fc..e6f74499c 100644 --- a/frontend/app/transforms/moment.js +++ b/frontend/app/transforms/moment.ts @@ -1,5 +1,5 @@ import Transform from "@ember-data/serializer/transform"; -import moment from "moment"; +import moment, { type Moment } from "moment"; /** * The moment transform @@ -27,7 +27,7 @@ export default class MomentTransform extends Transform { * @return {moment.duration} The deserialized moment object * @public */ - deserialize(serialized) { + deserialize(serialized): Moment { return serialized ? moment(serialized, this.format) : null; } @@ -39,9 +39,15 @@ export default class MomentTransform extends Transform { * @return {moment.duration} The serialized date string * @public */ - serialize(deserialized) { + serialize(deserialized): string { return deserialized && deserialized.isValid() ? deserialized.format(this.format) : null; } } + +declare module "ember-data/types/registries/transform" { + interface TransformRegistry { + moment: MomentTransform; + } +} diff --git a/frontend/app/utils/format-duration.js b/frontend/app/utils/format-duration.ts similarity index 66% rename from frontend/app/utils/format-duration.js rename to frontend/app/utils/format-duration.ts index 87f89227e..d631c75ba 100644 --- a/frontend/app/utils/format-duration.js +++ b/frontend/app/utils/format-duration.ts @@ -3,21 +3,18 @@ * @submodule timed-utils * @public */ -import moment from "moment"; +import moment, { type Duration } from "moment"; import { pad2joincolon } from "timed/utils/pad"; const { floor, abs } = Math; /** * Converts a moment duration into a string with zeropadded digits - * - * @function formatDuration - * @param {moment.duration} duration The duration to format - * @param {Boolean} seconds Whether to show seconds - * @return {String} The formatted duration - * @public */ -export default function formatDuration(duration, seconds = true) { +export default function formatDuration( + duration: Duration | number, + seconds: boolean = true +): string { if (typeof duration === "number") { duration = moment.duration(duration); } @@ -26,7 +23,7 @@ export default function formatDuration(duration, seconds = true) { return seconds ? "--:--:--" : "--:--"; } - const prefix = duration < 0 ? "-" : ""; + const prefix = +duration < 0 ? "-" : ""; const hours = floor(abs(duration.asHours())); const minutes = abs(duration.minutes()); diff --git a/frontend/app/utils/humanize-duration.js b/frontend/app/utils/humanize-duration.ts similarity index 65% rename from frontend/app/utils/humanize-duration.js rename to frontend/app/utils/humanize-duration.ts index b459f5b99..18e6a9e0d 100644 --- a/frontend/app/utils/humanize-duration.js +++ b/frontend/app/utils/humanize-duration.ts @@ -4,24 +4,23 @@ * @public */ +import type { Duration } from "moment"; + const { abs, floor } = Math; /** * Converts a moment duration into a string with hours minutes and optionally * seconds - * - * @function humanizeDuration - * @param {moment.duration} duration The duration to format - * @param {Boolean} seconds Whether to show seconds - * @return {String} The formatted duration - * @public */ -export default function humanizeDuration(duration, seconds = false) { +export default function humanizeDuration( + duration: Duration, + seconds: boolean = false +): string { if (!duration || duration.milliseconds() < 0) { return seconds ? "0h 0m 0s" : "0h 0m"; } - const prefix = duration < 0 ? "-" : ""; + const prefix = +duration < 0 ? "-" : ""; // TODO: The locale should be defined by the browser const h = floor(abs(duration.asHours())).toLocaleString("de-CH"); diff --git a/frontend/app/utils/pad.js b/frontend/app/utils/pad.ts similarity index 56% rename from frontend/app/utils/pad.js rename to frontend/app/utils/pad.ts index 4221daf1c..00026ec51 100644 --- a/frontend/app/utils/pad.js +++ b/frontend/app/utils/pad.ts @@ -6,13 +6,8 @@ /** * Pad items with 0 and join them with a colon - * - * @function pad2joincolon - * @param {any[]} items - The items to pad and join - * @return {String} The joined string - * @public */ -function pad2joincolon(...items) { +function pad2joincolon(...items: unknown[]): string { return items.map((v) => String(v).padStart(2, "0")).join(":"); } diff --git a/frontend/app/utils/parse-django-duration.js b/frontend/app/utils/parse-django-duration.js deleted file mode 100644 index 797b6a95e..000000000 --- a/frontend/app/utils/parse-django-duration.js +++ /dev/null @@ -1,34 +0,0 @@ -/** - * @module timed - * @submodule timed-utils - * @public - */ -import moment from "moment"; - -/** - * Converts a django duration string to a moment duration - * - * @function parseDjangoDuration - * @param {String} str The django duration string representation - * @return {moment.duration} The parsed duration - * @public - */ -export default function parseDjangoDuration(str) { - if (!str) { - return null; - } - - const re = new RegExp(/^(-?\d+)?\s?(\d{2}):(\d{2}):(\d{2})(\.\d{6})?$/); - - const [, days, hours, minutes, seconds, microseconds] = str - .match(re) - .map((m) => Number(m) || 0); - - return moment.duration({ - days, - hours, - minutes, - seconds, - milliseconds: microseconds * 1000, - }); -} diff --git a/frontend/app/utils/parse-django-duration.ts b/frontend/app/utils/parse-django-duration.ts new file mode 100644 index 000000000..63a999be5 --- /dev/null +++ b/frontend/app/utils/parse-django-duration.ts @@ -0,0 +1,54 @@ +/** + * @module timed + * @submodule timed-utils + * @public + */ +import moment, { type Duration } from "moment"; + +interface Groups { + days?: number; + hours: number; + minutes: number; + seconds: number; + microseconds?: number; +} + +/** + * Converts a django duration string to a moment duration + */ +export default function parseDjangoDuration(str: string): Duration | null { + if (!str) { + return null; + } + + const re = + /^(?-?\d+)?\s?(?\d{2}):(?\d{2}):(?\d{2})(?\.\d{6})?$/; + + const matches = str.match(re) as { + groups: Groups; + } | null; + + if (!matches) { + return null; + } + + const { + days: _days, + hours, + minutes, + seconds, + microseconds: _microseconds, + } = matches.groups; + + const [days, microseconds] = [_days, _microseconds].map( + (v) => Number(v) || 0 + ) as [number, number]; + + return moment.duration({ + days, + hours, + minutes, + seconds, + milliseconds: microseconds * 1000, + }); +} diff --git a/frontend/app/utils/parse-filename.js b/frontend/app/utils/parse-filename.ts similarity index 93% rename from frontend/app/utils/parse-filename.js rename to frontend/app/utils/parse-filename.ts index c74220a37..def18da19 100644 --- a/frontend/app/utils/parse-filename.js +++ b/frontend/app/utils/parse-filename.ts @@ -11,7 +11,7 @@ const REGEX = /filename[^;=\n]*=(?(?['"]).*?\k|[^;\n]*)/; -const parseFileName = (contentDisposition) => { +const parseFileName = (contentDisposition: string) => { const { quote, filename } = REGEX.exec(contentDisposition)?.groups ?? {}; if (!filename) return "Unknown file"; const _filename = filename.startsWith("utf-8''") diff --git a/frontend/app/utils/query-params.js b/frontend/app/utils/query-params.ts similarity index 64% rename from frontend/app/utils/query-params.js rename to frontend/app/utils/query-params.ts index 18515e508..aa9bf9d19 100644 --- a/frontend/app/utils/query-params.js +++ b/frontend/app/utils/query-params.ts @@ -1,3 +1,4 @@ +import type Controller from "@ember/controller"; import { get } from "@ember/object"; import { underscore } from "@ember/string"; import { @@ -8,25 +9,33 @@ import { /** * Filter params by key */ -export const filterQueryParams = (params, ...keys) => { - return Object.keys(params).reduce((obj, key) => { - return keys.includes(key) ? obj : { ...obj, [key]: get(params, key) }; - }, {}); +export const filterQueryParams = < + T extends Record, + K extends string[] +>( + params: T, + ...keys: K +) => { + return Object.fromEntries( + Object.entries(params).filter(([k]) => !keys.includes(k)) + ) as Omit; }; /** * Underscore all object keys */ -export const underscoreQueryParams = (params) => { - return Object.keys(params).reduce((obj, key) => { - return { ...obj, [underscore(key)]: get(params, key) }; - }, {}); -}; +export const underscoreQueryParams = (params: Record) => + Object.fromEntries( + Object.entries(params).map(([k, v]) => [underscore(k), v]) + ); -export const serializeQueryParams = (params, queryParamsObject) => { +export const serializeQueryParams = >( + params: T, + queryParamsObject: { [K in keyof T]?: (deserialized: T[K]) => string } +) => { return Object.keys(params).reduce((parsed, key) => { - const serializeFn = get(queryParamsObject, key)?.serialize; - const value = get(params, key); + const serializeFn = queryParamsObject[key as keyof T]; + const value = params[key as keyof T]; return key === "type" ? parsed @@ -34,22 +43,19 @@ export const serializeQueryParams = (params, queryParamsObject) => { ...parsed, [key]: serializeFn ? serializeFn(value) : value, }; - }, {}); + }, {} as Record); }; /** - * - * @param {string} param - * @returns {string} | {undefined} * ? in all controllers, the only parameter that have the default value is `ordering`, and the value is "-date" */ -export function getDefaultQueryParamValue(param) { +export function getDefaultQueryParamValue(param: string) { if (param === "ordering") return "-date"; else if (param === "type") return "year"; return undefined; } -export function allQueryParams(controller) { +export function allQueryParams(controller: C) { const params = {}; for (const qpKey of controller.queryParams) { params[qpKey] = controller[qpKey]; @@ -57,7 +63,7 @@ export function allQueryParams(controller) { return params; } -export function queryParamsState(controller) { +export function queryParamsState(controller: C) { const states = {}; for (const param of controller.queryParams) { const defaultValue = getDefaultQueryParamValue(param); @@ -94,7 +100,10 @@ export function queryParamsState(controller) { return states; } -export function resetQueryParams(controller, ...args) { +export function resetQueryParams( + controller: C, + ...args: string[] +) { if (!args[0]) { return; } diff --git a/frontend/app/utils/serialize-moment.js b/frontend/app/utils/serialize-moment.ts similarity index 58% rename from frontend/app/utils/serialize-moment.js rename to frontend/app/utils/serialize-moment.ts index 256ad3f14..83246b786 100644 --- a/frontend/app/utils/serialize-moment.js +++ b/frontend/app/utils/serialize-moment.ts @@ -1,13 +1,13 @@ -import moment from "moment"; +import moment, { type Moment, type MomentInput } from "moment"; export const DATE_FORMAT = "YYYY-MM-DD"; -export function serializeMoment(momentObject) { +export function serializeMoment(momentObject: Moment) { if (momentObject) { momentObject = moment(momentObject); } return (momentObject && momentObject.format(DATE_FORMAT)) || null; } -export function deserializeMoment(momentString) { +export function deserializeMoment(momentString: MomentInput) { return (momentString && moment(momentString, DATE_FORMAT)) || null; } diff --git a/frontend/app/utils/url.js b/frontend/app/utils/url.js deleted file mode 100644 index 8af6f806a..000000000 --- a/frontend/app/utils/url.js +++ /dev/null @@ -1,11 +0,0 @@ -const notNullOrUndefined = (value) => value !== null && value !== undefined; - -export const cleanParams = (params) => - Object.keys(params) - .filter((key) => notNullOrUndefined(params[key])) - .reduce((cleaned, key) => ({ ...cleaned, [key]: params[key] }), {}); - -export const toQueryString = (params) => - Object.keys(params) - .map((key) => `${key}=${params[key]}`) - .join("&"); diff --git a/frontend/app/utils/url.ts b/frontend/app/utils/url.ts new file mode 100644 index 000000000..fec1269d4 --- /dev/null +++ b/frontend/app/utils/url.ts @@ -0,0 +1,25 @@ +type ExcludeNullOrUndefined = T extends null | undefined ? never : T; + +type CleanedObject = { + [K in keyof T as ExcludeNullOrUndefined extends never + ? never + : K]: ExcludeNullOrUndefined; +}; + +const notNullOrUndefined = (value: T): value is ExcludeNullOrUndefined => + value !== null && value !== undefined; + +export const cleanParams = (params: T): CleanedObject => { + const cleanedEntries = Object.entries(params).filter(([, value]) => + notNullOrUndefined(value) + ); + return Object.fromEntries(cleanedEntries) as CleanedObject; +}; + +export const toQueryString = (params: Record): string => + Object.entries(params) + .map( + ([key, value]) => + `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}` + ) + .join("&"); diff --git a/frontend/ember-cli-build.js b/frontend/ember-cli-build.js index a83d50b35..1ead31ebd 100644 --- a/frontend/ember-cli-build.js +++ b/frontend/ember-cli-build.js @@ -6,6 +6,7 @@ const EmberApp = require("ember-cli/lib/broccoli/ember-app"); module.exports = function (defaults) { const app = new EmberApp(defaults, { + "ember-cli-babel": { enableTypeScriptTransform: true }, babel: { plugins: [ require.resolve("ember-concurrency/async-arrow-task-transform"), diff --git a/frontend/package.json b/frontend/package.json index f2f7c05f5..d184155c4 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -17,8 +17,9 @@ "lint:fix": "concurrently \"npm:lint:*:fix\" --names \"fix:\"", "lint:hbs": "ember-template-lint . --config-path .template-lintrc-ci.js", "lint:hbs:fix": "ember-template-lint . --fix", - "lint:js": "eslint --config .eslintrc.js .", - "lint:js:fix": "eslint --config .eslintrc.js . --fix", + "lint:js": "eslint . --cache", + "lint:js:fix": "eslint . --fix", + "lint:types": "tsc --noEmit", "start": "ember server --proxy https://timed.localhost --secure-proxy=false", "test": "concurrently \"npm:lint\" \"npm:test:*\" --names \"lint,test:\"", "test:ember": "ember test" @@ -44,6 +45,37 @@ "@glimmer/tracking": "1.1.2", "@html-next/vertical-collection": "4.0.2", "@sentry/ember": "^7.98.0", + "@tsconfig/ember": "^3.0.8", + "@tsconfig/strictest": "^2.0.5", + "@types/ember": "^4.0.11", + "@types/ember-data": "^4.4.16", + "@types/ember-data__adapter": "^4.0.6", + "@types/ember-data__model": "^4.0.5", + "@types/ember-data__serializer": "^4.0.6", + "@types/ember-data__store": "^4.0.7", + "@types/ember-qunit": "^6.1.1", + "@types/ember-resolver": "^9.0.0", + "@types/ember__application": "^4.0.11", + "@types/ember__array": "^4.0.10", + "@types/ember__component": "^4.0.22", + "@types/ember__controller": "^4.0.12", + "@types/ember__debug": "^4.0.8", + "@types/ember__destroyable": "^4.0.5", + "@types/ember__engine": "^4.0.11", + "@types/ember__error": "^4.0.6", + "@types/ember__object": "^4.0.12", + "@types/ember__polyfills": "^4.0.6", + "@types/ember__routing": "^4.0.22", + "@types/ember__runloop": "^4.0.10", + "@types/ember__service": "^4.0.9", + "@types/ember__string": "^3.0.15", + "@types/ember__template": "^4.0.7", + "@types/ember__test": "^4.0.6", + "@types/ember__utils": "^4.0.7", + "@types/qunit": "^2.19.10", + "@types/rsvp": "^4.0.9", + "@typescript-eslint/eslint-plugin": "^8.4.0", + "@typescript-eslint/parser": "^8.4.0", "broccoli-asset-rev": "^3.0.0", "broccoli-funnel": "^3.0.8", "concurrently": "^8.0.1", @@ -125,6 +157,7 @@ "stylelint-scss": "^6.3.0", "tracked-built-ins": "3.1.1", "tracked-toolbox": "2.0.0", + "typescript": "^5.5.4", "webpack": "5.92.1" }, "engines": { diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index ebb84ee46..21d56dfe9 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -10,7 +10,7 @@ importers: devDependencies: '@adfinis/eslint-config': specifier: ^2.1.1 - version: 2.1.1(@babel/core@7.22.9)(@babel/eslint-parser@7.21.3(@babel/core@7.22.9)(eslint@8.46.0))(@babel/plugin-proposal-decorators@7.24.7(@babel/core@7.22.9))(eslint-config-prettier@8.8.0(eslint@8.46.0))(eslint-plugin-ember@11.12.0(eslint@8.46.0))(eslint-plugin-import@2.28.1(eslint@8.46.0))(eslint-plugin-n@15.7.0(eslint@8.46.0))(eslint-plugin-prettier@4.2.1(eslint-config-prettier@8.8.0(eslint@8.46.0))(eslint@8.46.0)(prettier@2.8.8))(eslint-plugin-qunit@7.3.4(eslint@8.46.0))(eslint@8.46.0)(prettier@2.8.8) + version: 2.1.1(@babel/core@7.22.9)(@babel/eslint-parser@7.21.3(@babel/core@7.22.9)(eslint@8.46.0))(@babel/plugin-proposal-decorators@7.24.7(@babel/core@7.22.9))(eslint-config-prettier@8.8.0(eslint@8.46.0))(eslint-plugin-ember@11.12.0(eslint@8.46.0))(eslint-plugin-import@2.28.1(@typescript-eslint/parser@8.4.0(eslint@8.46.0)(typescript@5.5.4))(eslint@8.46.0))(eslint-plugin-n@15.7.0(eslint@8.46.0))(eslint-plugin-prettier@4.2.1(eslint-config-prettier@8.8.0(eslint@8.46.0))(eslint@8.46.0)(prettier@2.8.8))(eslint-plugin-qunit@7.3.4(eslint@8.46.0))(eslint@8.46.0)(prettier@2.8.8) '@apollo/client': specifier: 3.8.6 version: 3.8.6(graphql@16.8.1) @@ -68,6 +68,99 @@ importers: '@sentry/ember': specifier: ^7.98.0 version: 7.118.0(@glint/template@1.4.0)(webpack@5.92.1) + '@tsconfig/ember': + specifier: ^3.0.8 + version: 3.0.8 + '@tsconfig/strictest': + specifier: ^2.0.5 + version: 2.0.5 + '@types/ember': + specifier: ^4.0.11 + version: 4.0.11(@babel/core@7.22.9) + '@types/ember-data': + specifier: ^4.4.16 + version: 4.4.16(@babel/core@7.22.9) + '@types/ember-data__adapter': + specifier: ^4.0.6 + version: 4.0.6(@babel/core@7.22.9) + '@types/ember-data__model': + specifier: ^4.0.5 + version: 4.0.5(@babel/core@7.22.9) + '@types/ember-data__serializer': + specifier: ^4.0.6 + version: 4.0.6(@babel/core@7.22.9) + '@types/ember-data__store': + specifier: ^4.0.7 + version: 4.0.7(@babel/core@7.22.9) + '@types/ember-qunit': + specifier: ^6.1.1 + version: 6.1.1(@ember/test-helpers@2.9.3(@babel/core@7.22.9)(@glint/template@1.4.0)(ember-source@5.4.1(@babel/core@7.22.9)(@glimmer/component@1.1.2(@babel/core@7.22.9))(@glint/template@1.4.0)(rsvp@4.8.5)(webpack@5.92.1)))(@glint/template@1.4.0)(ember-source@5.4.1(@babel/core@7.22.9)(@glimmer/component@1.1.2(@babel/core@7.22.9))(@glint/template@1.4.0)(rsvp@4.8.5)(webpack@5.92.1))(qunit@2.19.4)(webpack@5.92.1) + '@types/ember-resolver': + specifier: ^9.0.0 + version: 9.0.0(@ember/string@3.1.1)(ember-source@5.4.1(@babel/core@7.22.9)(@glimmer/component@1.1.2(@babel/core@7.22.9))(@glint/template@1.4.0)(rsvp@4.8.5)(webpack@5.92.1)) + '@types/ember__application': + specifier: ^4.0.11 + version: 4.0.11(@babel/core@7.22.9) + '@types/ember__array': + specifier: ^4.0.10 + version: 4.0.10(@babel/core@7.22.9) + '@types/ember__component': + specifier: ^4.0.22 + version: 4.0.22(@babel/core@7.22.9) + '@types/ember__controller': + specifier: ^4.0.12 + version: 4.0.12(@babel/core@7.22.9) + '@types/ember__debug': + specifier: ^4.0.8 + version: 4.0.8(@babel/core@7.22.9) + '@types/ember__destroyable': + specifier: ^4.0.5 + version: 4.0.5 + '@types/ember__engine': + specifier: ^4.0.11 + version: 4.0.11(@babel/core@7.22.9) + '@types/ember__error': + specifier: ^4.0.6 + version: 4.0.6 + '@types/ember__object': + specifier: ^4.0.12 + version: 4.0.12(@babel/core@7.22.9) + '@types/ember__polyfills': + specifier: ^4.0.6 + version: 4.0.6 + '@types/ember__routing': + specifier: ^4.0.22 + version: 4.0.22(@babel/core@7.22.9) + '@types/ember__runloop': + specifier: ^4.0.10 + version: 4.0.10(@babel/core@7.22.9) + '@types/ember__service': + specifier: ^4.0.9 + version: 4.0.9(@babel/core@7.22.9) + '@types/ember__string': + specifier: ^3.0.15 + version: 3.0.15 + '@types/ember__template': + specifier: ^4.0.7 + version: 4.0.7 + '@types/ember__test': + specifier: ^4.0.6 + version: 4.0.6(@babel/core@7.22.9) + '@types/ember__utils': + specifier: ^4.0.7 + version: 4.0.7(@babel/core@7.22.9) + '@types/qunit': + specifier: ^2.19.10 + version: 2.19.10 + '@types/rsvp': + specifier: ^4.0.9 + version: 4.0.9 + '@typescript-eslint/eslint-plugin': + specifier: ^8.4.0 + version: 8.4.0(@typescript-eslint/parser@8.4.0(eslint@8.46.0)(typescript@5.5.4))(eslint@8.46.0)(typescript@5.5.4) + '@typescript-eslint/parser': + specifier: ^8.4.0 + version: 8.4.0(eslint@8.46.0)(typescript@5.5.4) broccoli-asset-rev: specifier: ^3.0.0 version: 3.0.0 @@ -241,7 +334,7 @@ importers: version: 11.12.0(eslint@8.46.0) eslint-plugin-import: specifier: 2.28.1 - version: 2.28.1(eslint@8.46.0) + version: 2.28.1(@typescript-eslint/parser@8.4.0(eslint@8.46.0)(typescript@5.5.4))(eslint@8.46.0) eslint-plugin-n: specifier: 15.7.0 version: 15.7.0(eslint@8.46.0) @@ -292,25 +385,28 @@ importers: version: 6.2.7 stylelint: specifier: ^16.4.0 - version: 16.6.1(typescript@5.5.2) + version: 16.6.1(typescript@5.5.4) stylelint-config-standard: specifier: ^32.0.0 - version: 32.0.0(stylelint@16.6.1(typescript@5.5.2)) + version: 32.0.0(stylelint@16.6.1(typescript@5.5.4)) stylelint-config-standard-scss: specifier: ^13.1.0 - version: 13.1.0(postcss@8.4.39)(stylelint@16.6.1(typescript@5.5.2)) + version: 13.1.0(postcss@8.4.39)(stylelint@16.6.1(typescript@5.5.4)) stylelint-prettier: specifier: ^3.0.0 - version: 3.0.0(prettier@2.8.8)(stylelint@16.6.1(typescript@5.5.2)) + version: 3.0.0(prettier@2.8.8)(stylelint@16.6.1(typescript@5.5.4)) stylelint-scss: specifier: ^6.3.0 - version: 6.3.2(stylelint@16.6.1(typescript@5.5.2)) + version: 6.3.2(stylelint@16.6.1(typescript@5.5.4)) tracked-built-ins: specifier: 3.1.1 version: 3.1.1 tracked-toolbox: specifier: 2.0.0 version: 2.0.0(@babel/core@7.22.9)(ember-source@5.4.1(@babel/core@7.22.9)(@glimmer/component@1.1.2(@babel/core@7.22.9))(@glint/template@1.4.0)(rsvp@4.8.5)(webpack@5.92.1)) + typescript: + specifier: ^5.5.4 + version: 5.5.4 webpack: specifier: 5.92.1 version: 5.92.1 @@ -1629,6 +1725,12 @@ packages: '@socket.io/component-emitter@3.1.2': resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} + '@tsconfig/ember@3.0.8': + resolution: {integrity: sha512-OVnIsZIt/8q0VEtcdz3rRryNrm6gdJTxXlxefkGIrkZnME0wqslmwHlUEZ7mvh377df9FqBhNKrYNarhCW8zJA==} + + '@tsconfig/strictest@2.0.5': + resolution: {integrity: sha512-ec4tjL2Rr0pkZ5hww65c+EEPYwxOi4Ryv+0MtjeaSQRJyq322Q27eOQiFbuNgw2hpL4hB1/W/HBGk3VKS43osg==} + '@types/acorn@4.0.6': resolution: {integrity: sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==} @@ -1654,6 +1756,86 @@ packages: '@types/cors@2.8.17': resolution: {integrity: sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==} + '@types/ember-data@4.4.16': + resolution: {integrity: sha512-plFqPkgw7n4YlkzvApkQAIhvvYTERlx8PeI2J5gSFtMtsKuoaIo8fXm4w22ZdBQtTzeh/kwvpO2WY8R/d5Ttfg==} + + '@types/ember-data__adapter@4.0.6': + resolution: {integrity: sha512-vSrx1cqz8jO0cBYwu98SrwFk5s722C+BGpw++NregwQio3g9VbNoymx+HsYeXvsWvGUsoRewxu9K7TwAOJSsvw==} + + '@types/ember-data__model@4.0.5': + resolution: {integrity: sha512-zyfHh3tQiMdpbPZ1/RFxi4m9TdwsZxDmVY0e7V1UYp7pWvm/DeqEXqoy3WS9xa/O01xoJFpQtJHyasdXvVTfbg==} + + '@types/ember-data__serializer@4.0.6': + resolution: {integrity: sha512-ebxxySSvTiay1/hgN4fElahSuL0MX+1sMMwx/RQyy7z98ifV8wafW62BwlYtbH44aeTIdcaSBE2pm3R1aP2yUA==} + + '@types/ember-data__store@4.0.7': + resolution: {integrity: sha512-0S3Etr32i4aGvhXhZM6CXaosvX8E6c+RCx8pCxUXVdn+SmICm8jqFXsDC6Gnqq4bJeTkf9cglqz46prOVR1Tcg==} + + '@types/ember-qunit@6.1.1': + resolution: {integrity: sha512-1g5A3vPKhvB/CuN/EP9rBLXYaJOjzKyLYWeBtNDEQNkTuG1dAo/Hg0CCixgbBgLlzaDP8mR/n1xpg8HqQ8SUKg==} + deprecated: This is a stub types definition. ember-qunit provides its own type definitions, so you do not need this installed. + + '@types/ember-resolver@9.0.0': + resolution: {integrity: sha512-lEuC2QD8K6rRAbELMejrALFBgelRPt6OQtapny4Oke07ZtK/Lbf9zn5KIDl7PNkirxMD0AStsQTdUqFu6eVbVw==} + deprecated: This is a stub types definition. ember-resolver provides its own type definitions, so you do not need this installed. + + '@types/ember@4.0.11': + resolution: {integrity: sha512-v7VIex0YILK8fP87LkIfzeeYKNnu74+xwf6U56v6MUDDGfSs9q/6NCxiUfwkxD+z5nQiUcwvfKVokX8qzZFRLw==} + + '@types/ember__application@4.0.11': + resolution: {integrity: sha512-U1S7XW0V70nTWbFckWoraJbYGBJK69muP/CsPFLeAuUYHfkkDiwh1SfqgAUN9aHtrEJM5SuSYVYp2YsTI2yLuA==} + + '@types/ember__array@4.0.10': + resolution: {integrity: sha512-UrhDbopLI3jB0MqV14y8yji2IuPNmeDrtT1PRYJL4CThLHrRkfeYyFvxqvrxWxn0wXKjbbjfH1gOe7BU57QrLQ==} + + '@types/ember__component@4.0.22': + resolution: {integrity: sha512-m72EtmBN/RxOChXqRsyOg4RR5+AiB4LQ8s1CEKNYAfvANt18m4hjqxtA7QZYLTq2ZjEVJGpdMsrdDuONWjwRSQ==} + + '@types/ember__controller@4.0.12': + resolution: {integrity: sha512-80rdnSC0UJBqoUX5/vkQcM2xkRdTPTvY0dPXEfY5cC5OZITbcSeRo5qa7ZGhgNBfH6XYyh55Lo/b811LwU3N9w==} + + '@types/ember__debug@4.0.8': + resolution: {integrity: sha512-9wF7STmDHDsUxSjyCq2lpMq/03QOPkBQMGJnV8yOBnVZxB6f+FJH/kxaCprdMkUe7iwAPNEC2zrFFx1tzH75Kg==} + + '@types/ember__destroyable@4.0.5': + resolution: {integrity: sha512-spJyZxpvecssbXkaOQYcbnlWgb+TasFaKrgAYVbykZY6saMwUdMOGDDoW6uP/y/+A8Jj/fUIatPWJLepeSfgww==} + + '@types/ember__engine@4.0.11': + resolution: {integrity: sha512-ryR4Q1Xm3kQ3Ap58w10CxV3+vb3hs1cJqi7UZ5IlSdLRql7AbpS6hIjxSQ3EQ4zadeeJ6/D8JJcSwqR7eX3PFA==} + + '@types/ember__error@4.0.6': + resolution: {integrity: sha512-vYrLaGGjHkN14K89Vm8yqB2mkpJQefE5w7QJkkgYyV+smzns1vKlPbvuFevRtoeYNn4u4yY0JyF7HceNkm3H0Q==} + + '@types/ember__object@4.0.12': + resolution: {integrity: sha512-ZEpikPjZ02m1QCBiTPTayMJwVwF4UBlHlGDoScRB3IP/SUS1O5mmn1/CnSQDxzzF3ctfmhNuTZzVBBc1Y8OC1A==} + + '@types/ember__owner@4.0.9': + resolution: {integrity: sha512-iyBda4aUIjBmeiKTKmPow/EJO7xWn8m85CnQTOCqQzTWJyJpgfObbXSHahOHXOfMm279Oa5NlbmS/EontB+XiQ==} + + '@types/ember__polyfills@4.0.6': + resolution: {integrity: sha512-hbds3Qv+oVm/QKIaY1E6atvrCoJTH/MPSl4swOhX6P0RiMB2fOfFCrFSD1mP1KrU1LqpHJ2Rzs7XLe53SWVzgw==} + + '@types/ember__routing@4.0.22': + resolution: {integrity: sha512-qLk9Vd2GMxdlGmX9xbzg4Farths+AQGzYDH901Wo2Nsre+Cwv1Tk1rbCiay2V3ICYZYufytdWT6V++DISF3nvw==} + + '@types/ember__runloop@4.0.10': + resolution: {integrity: sha512-9MZfOJBXuUP7RqLjovmzy1yY2xKTxVpqHMapqy6QJ8mjAekRmq9IJ+ni2zJ5CWftyb3Lqu3Eks05CL7fnbhcJA==} + + '@types/ember__service@4.0.9': + resolution: {integrity: sha512-DrepocL/4hH5YxbDWbxEKMDcAchBPSGGa4g+LEINW1Po81RmSdKw5GZV4UO0mvRWgkdu3EbWUxbTzB4gmbDSeQ==} + + '@types/ember__string@3.0.15': + resolution: {integrity: sha512-SxoaweAJUJKSIt82clIwpi/Fm0IfeisozLnXthnBp/hE2JyVcnOb1wMIbw0CCfzercmyWG1njV45VBqy8SrLDQ==} + + '@types/ember__template@4.0.7': + resolution: {integrity: sha512-jv4hhG+8d1zdma+jhbCdJ3Ak7C22YNatGyWWvB3N9zbXq358AAPXaJoyNY8QTDbD/RIR9P6yoRk4u9vLbF6zfA==} + + '@types/ember__test@4.0.6': + resolution: {integrity: sha512-Nswm/epfTepXknT8scZvWyyop1aqJcZcyzY4THGHFcXvYQQfA9rgmgrx6vo9vCJmYHh3jm0TTAIAIfoCvGaX5g==} + + '@types/ember__utils@4.0.7': + resolution: {integrity: sha512-qQPBeWRyIPigKnZ68POlkqI5e6XA78Q4G3xHo687wQTcEtfoL/iZyPC4hn70mdijcZq8Hjch2Y3E5yhsEMzK+g==} + '@types/eslint-scope@3.7.7': resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} @@ -1714,6 +1896,9 @@ packages: '@types/qs@6.9.15': resolution: {integrity: sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==} + '@types/qunit@2.19.10': + resolution: {integrity: sha512-gVB+rxvxmbyPFWa6yjjKgcumWal3hyqoTXI0Oil161uWfo1OCzWZ/rnEumsx+6uVgrwPrCrhpQbLkzfildkSbg==} + '@types/range-parser@1.2.7': resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} @@ -1723,6 +1908,9 @@ packages: '@types/rimraf@2.0.5': resolution: {integrity: sha512-YyP+VfeaqAyFmXoTh3HChxOQMyjByRMsHU7kc5KOJkSlXudhMhQIALbYV7rHh/l8d2lX3VUQzprrcAgWdRuU8g==} + '@types/rsvp@4.0.9': + resolution: {integrity: sha512-F6vaN5mbxw2MBCu/AD9fSKwrhnto2pE77dyUsi415qz9IP9ni9ZOWXHxnXfsM4NW9UjW+it189jvvqnhv37Z7Q==} + '@types/send@0.17.4': resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==} @@ -1735,6 +1923,63 @@ packages: '@types/ungap__structured-clone@0.3.3': resolution: {integrity: sha512-RNmhIPwoip6K/zZOv3ypksTAqaqLEXvlNSXKyrC93xMSOAHZCR7PifW6xKZCwkbbnbM9dwB9X56PPoNTlNwEqw==} + '@typescript-eslint/eslint-plugin@8.4.0': + resolution: {integrity: sha512-rg8LGdv7ri3oAlenMACk9e+AR4wUV0yrrG+XKsGKOK0EVgeEDqurkXMPILG2836fW4ibokTB5v4b6Z9+GYQDEw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/parser@8.4.0': + resolution: {integrity: sha512-NHgWmKSgJk5K9N16GIhQ4jSobBoJwrmURaLErad0qlLjrpP5bECYg+wxVTGlGZmJbU03jj/dfnb6V9bw+5icsA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/scope-manager@8.4.0': + resolution: {integrity: sha512-n2jFxLeY0JmKfUqy3P70rs6vdoPjHK8P/w+zJcV3fk0b0BwRXC/zxRTEnAsgYT7MwdQDt/ZEbtdzdVC+hcpF0A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/type-utils@8.4.0': + resolution: {integrity: sha512-pu2PAmNrl9KX6TtirVOrbLPLwDmASpZhK/XU7WvoKoCUkdtq9zF7qQ7gna0GBZFN0hci0vHaSusiL2WpsQk37A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/types@8.4.0': + resolution: {integrity: sha512-T1RB3KQdskh9t3v/qv7niK6P8yvn7ja1mS7QK7XfRVL6wtZ8/mFs/FHf4fKvTA0rKnqnYxl/uHFNbnEt0phgbw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.4.0': + resolution: {integrity: sha512-kJ2OIP4dQw5gdI4uXsaxUZHRwWAGpREJ9Zq6D5L0BweyOrWsL6Sz0YcAZGWhvKnH7fm1J5YFE1JrQL0c9dd53A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/utils@8.4.0': + resolution: {integrity: sha512-swULW8n1IKLjRAgciCkTCafyTHHfwVQFt8DovmaF69sKbOxTSFMmIZaSHjqO9i/RV0wIblaawhzvtva8Nmm7lQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + + '@typescript-eslint/visitor-keys@8.4.0': + resolution: {integrity: sha512-zTQD6WLNTre1hj5wp09nBIDiOc2U5r/qmzo7wxPn4ZgAjHql09EofqhF9WF+fZHzL5aCyaIpPcT2hyxl73kr9A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@ungap/structured-clone@0.3.4': resolution: {integrity: sha512-TSVh8CpnwNAsPC5wXcIyh92Bv1gq6E9cNDeeLu7Z4h8V4/qWtXJp7y42qljRkqcpmsve1iozwv1wr+3BNdILCg==} @@ -5557,6 +5802,10 @@ packages: resolution: {integrity: sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==} engines: {node: '>=16 || 14 >=14.17'} + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} @@ -7250,6 +7499,12 @@ packages: resolution: {integrity: sha512-WZGXGstmCWgeevgTL54hrCuw1dyMQIzWy7ZfqRJfSmJZBwklI15egmQytFP6bPidmw3M8d5yEowl1niq4vmqZw==} engines: {node: '>=0.10.0'} + ts-api-utils@1.3.0: + resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} + engines: {node: '>=16'} + peerDependencies: + typescript: '>=4.2.0' + ts-invariant@0.10.3: resolution: {integrity: sha512-uivwYcQaxAucv1CzRp2n/QdYPo4ILf9VXgH19zEIjFx2EJufV16P0JtJVpYHy89DItG6Kwj2oIUjrcK5au+4tQ==} engines: {node: '>=8'} @@ -7315,8 +7570,8 @@ packages: typescript-memoize@1.1.1: resolution: {integrity: sha512-GQ90TcKpIH4XxYTI2F98yEQYZgjNMOGPpOgdjIBhaLaWji5HPWlRnZ4AeA1hfBxtY7bCGDJsqDDHk/KaHOl5bA==} - typescript@5.5.2: - resolution: {integrity: sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==} + typescript@5.5.4: + resolution: {integrity: sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==} engines: {node: '>=14.17'} hasBin: true @@ -7665,14 +7920,14 @@ packages: snapshots: - '@adfinis/eslint-config@2.1.1(@babel/core@7.22.9)(@babel/eslint-parser@7.21.3(@babel/core@7.22.9)(eslint@8.46.0))(@babel/plugin-proposal-decorators@7.24.7(@babel/core@7.22.9))(eslint-config-prettier@8.8.0(eslint@8.46.0))(eslint-plugin-ember@11.12.0(eslint@8.46.0))(eslint-plugin-import@2.28.1(eslint@8.46.0))(eslint-plugin-n@15.7.0(eslint@8.46.0))(eslint-plugin-prettier@4.2.1(eslint-config-prettier@8.8.0(eslint@8.46.0))(eslint@8.46.0)(prettier@2.8.8))(eslint-plugin-qunit@7.3.4(eslint@8.46.0))(eslint@8.46.0)(prettier@2.8.8)': + '@adfinis/eslint-config@2.1.1(@babel/core@7.22.9)(@babel/eslint-parser@7.21.3(@babel/core@7.22.9)(eslint@8.46.0))(@babel/plugin-proposal-decorators@7.24.7(@babel/core@7.22.9))(eslint-config-prettier@8.8.0(eslint@8.46.0))(eslint-plugin-ember@11.12.0(eslint@8.46.0))(eslint-plugin-import@2.28.1(@typescript-eslint/parser@8.4.0(eslint@8.46.0)(typescript@5.5.4))(eslint@8.46.0))(eslint-plugin-n@15.7.0(eslint@8.46.0))(eslint-plugin-prettier@4.2.1(eslint-config-prettier@8.8.0(eslint@8.46.0))(eslint@8.46.0)(prettier@2.8.8))(eslint-plugin-qunit@7.3.4(eslint@8.46.0))(eslint@8.46.0)(prettier@2.8.8)': dependencies: '@babel/core': 7.22.9 '@babel/eslint-parser': 7.21.3(@babel/core@7.22.9)(eslint@8.46.0) '@babel/plugin-proposal-decorators': 7.24.7(@babel/core@7.22.9) eslint: 8.46.0 eslint-config-prettier: 8.8.0(eslint@8.46.0) - eslint-plugin-import: 2.28.1(eslint@8.46.0) + eslint-plugin-import: 2.28.1(@typescript-eslint/parser@8.4.0(eslint@8.46.0)(typescript@5.5.4))(eslint@8.46.0) eslint-plugin-prettier: 4.2.1(eslint-config-prettier@8.8.0(eslint@8.46.0))(eslint@8.46.0)(prettier@2.8.8) prettier: 2.8.8 optionalDependencies: @@ -10189,6 +10444,10 @@ snapshots: '@socket.io/component-emitter@3.1.2': {} + '@tsconfig/ember@3.0.8': {} + + '@tsconfig/strictest@2.0.5': {} + '@types/acorn@4.0.6': dependencies: '@types/estree': 1.0.5 @@ -10220,6 +10479,195 @@ snapshots: dependencies: '@types/node': 20.14.9 + '@types/ember-data@4.4.16(@babel/core@7.22.9)': + dependencies: + '@types/ember': 4.0.11(@babel/core@7.22.9) + '@types/ember__error': 4.0.6 + '@types/ember__object': 4.0.12(@babel/core@7.22.9) + '@types/rsvp': 4.0.9 + transitivePeerDependencies: + - '@babel/core' + - supports-color + + '@types/ember-data__adapter@4.0.6(@babel/core@7.22.9)': + dependencies: + '@types/ember-data': 4.4.16(@babel/core@7.22.9) + transitivePeerDependencies: + - '@babel/core' + - supports-color + + '@types/ember-data__model@4.0.5(@babel/core@7.22.9)': + dependencies: + '@types/ember-data': 4.4.16(@babel/core@7.22.9) + transitivePeerDependencies: + - '@babel/core' + - supports-color + + '@types/ember-data__serializer@4.0.6(@babel/core@7.22.9)': + dependencies: + '@types/ember-data': 4.4.16(@babel/core@7.22.9) + transitivePeerDependencies: + - '@babel/core' + - supports-color + + '@types/ember-data__store@4.0.7(@babel/core@7.22.9)': + dependencies: + '@types/ember-data': 4.4.16(@babel/core@7.22.9) + transitivePeerDependencies: + - '@babel/core' + - supports-color + + '@types/ember-qunit@6.1.1(@ember/test-helpers@2.9.3(@babel/core@7.22.9)(@glint/template@1.4.0)(ember-source@5.4.1(@babel/core@7.22.9)(@glimmer/component@1.1.2(@babel/core@7.22.9))(@glint/template@1.4.0)(rsvp@4.8.5)(webpack@5.92.1)))(@glint/template@1.4.0)(ember-source@5.4.1(@babel/core@7.22.9)(@glimmer/component@1.1.2(@babel/core@7.22.9))(@glint/template@1.4.0)(rsvp@4.8.5)(webpack@5.92.1))(qunit@2.19.4)(webpack@5.92.1)': + dependencies: + ember-qunit: 6.2.0(@ember/test-helpers@2.9.3(@babel/core@7.22.9)(@glint/template@1.4.0)(ember-source@5.4.1(@babel/core@7.22.9)(@glimmer/component@1.1.2(@babel/core@7.22.9))(@glint/template@1.4.0)(rsvp@4.8.5)(webpack@5.92.1)))(@glint/template@1.4.0)(ember-source@5.4.1(@babel/core@7.22.9)(@glimmer/component@1.1.2(@babel/core@7.22.9))(@glint/template@1.4.0)(rsvp@4.8.5)(webpack@5.92.1))(qunit@2.19.4)(webpack@5.92.1) + transitivePeerDependencies: + - '@ember/test-helpers' + - '@glint/template' + - ember-source + - qunit + - supports-color + - webpack + + '@types/ember-resolver@9.0.0(@ember/string@3.1.1)(ember-source@5.4.1(@babel/core@7.22.9)(@glimmer/component@1.1.2(@babel/core@7.22.9))(@glint/template@1.4.0)(rsvp@4.8.5)(webpack@5.92.1))': + dependencies: + ember-resolver: 10.0.0(@ember/string@3.1.1)(ember-source@5.4.1(@babel/core@7.22.9)(@glimmer/component@1.1.2(@babel/core@7.22.9))(@glint/template@1.4.0)(rsvp@4.8.5)(webpack@5.92.1)) + transitivePeerDependencies: + - '@ember/string' + - ember-source + - supports-color + + '@types/ember@4.0.11(@babel/core@7.22.9)': + dependencies: + '@types/ember__application': 4.0.11(@babel/core@7.22.9) + '@types/ember__array': 4.0.10(@babel/core@7.22.9) + '@types/ember__component': 4.0.22(@babel/core@7.22.9) + '@types/ember__controller': 4.0.12(@babel/core@7.22.9) + '@types/ember__debug': 4.0.8(@babel/core@7.22.9) + '@types/ember__engine': 4.0.11(@babel/core@7.22.9) + '@types/ember__error': 4.0.6 + '@types/ember__object': 4.0.12(@babel/core@7.22.9) + '@types/ember__polyfills': 4.0.6 + '@types/ember__routing': 4.0.22(@babel/core@7.22.9) + '@types/ember__runloop': 4.0.10(@babel/core@7.22.9) + '@types/ember__service': 4.0.9(@babel/core@7.22.9) + '@types/ember__string': 3.0.15 + '@types/ember__template': 4.0.7 + '@types/ember__test': 4.0.6(@babel/core@7.22.9) + '@types/ember__utils': 4.0.7(@babel/core@7.22.9) + '@types/rsvp': 4.0.9 + transitivePeerDependencies: + - '@babel/core' + - supports-color + + '@types/ember__application@4.0.11(@babel/core@7.22.9)': + dependencies: + '@glimmer/component': 1.1.2(@babel/core@7.22.9) + '@types/ember': 4.0.11(@babel/core@7.22.9) + '@types/ember__engine': 4.0.11(@babel/core@7.22.9) + '@types/ember__object': 4.0.12(@babel/core@7.22.9) + '@types/ember__owner': 4.0.9 + '@types/ember__routing': 4.0.22(@babel/core@7.22.9) + transitivePeerDependencies: + - '@babel/core' + - supports-color + + '@types/ember__array@4.0.10(@babel/core@7.22.9)': + dependencies: + '@types/ember': 4.0.11(@babel/core@7.22.9) + '@types/ember__object': 4.0.12(@babel/core@7.22.9) + transitivePeerDependencies: + - '@babel/core' + - supports-color + + '@types/ember__component@4.0.22(@babel/core@7.22.9)': + dependencies: + '@types/ember': 4.0.11(@babel/core@7.22.9) + '@types/ember__object': 4.0.12(@babel/core@7.22.9) + transitivePeerDependencies: + - '@babel/core' + - supports-color + + '@types/ember__controller@4.0.12(@babel/core@7.22.9)': + dependencies: + '@types/ember__object': 4.0.12(@babel/core@7.22.9) + transitivePeerDependencies: + - '@babel/core' + - supports-color + + '@types/ember__debug@4.0.8(@babel/core@7.22.9)': + dependencies: + '@types/ember__object': 4.0.12(@babel/core@7.22.9) + '@types/ember__owner': 4.0.9 + transitivePeerDependencies: + - '@babel/core' + - supports-color + + '@types/ember__destroyable@4.0.5': {} + + '@types/ember__engine@4.0.11(@babel/core@7.22.9)': + dependencies: + '@types/ember__object': 4.0.12(@babel/core@7.22.9) + '@types/ember__owner': 4.0.9 + transitivePeerDependencies: + - '@babel/core' + - supports-color + + '@types/ember__error@4.0.6': {} + + '@types/ember__object@4.0.12(@babel/core@7.22.9)': + dependencies: + '@types/ember': 4.0.11(@babel/core@7.22.9) + '@types/rsvp': 4.0.9 + transitivePeerDependencies: + - '@babel/core' + - supports-color + + '@types/ember__owner@4.0.9': {} + + '@types/ember__polyfills@4.0.6': {} + + '@types/ember__routing@4.0.22(@babel/core@7.22.9)': + dependencies: + '@types/ember': 4.0.11(@babel/core@7.22.9) + '@types/ember__controller': 4.0.12(@babel/core@7.22.9) + '@types/ember__object': 4.0.12(@babel/core@7.22.9) + '@types/ember__service': 4.0.9(@babel/core@7.22.9) + transitivePeerDependencies: + - '@babel/core' + - supports-color + + '@types/ember__runloop@4.0.10(@babel/core@7.22.9)': + dependencies: + '@types/ember': 4.0.11(@babel/core@7.22.9) + transitivePeerDependencies: + - '@babel/core' + - supports-color + + '@types/ember__service@4.0.9(@babel/core@7.22.9)': + dependencies: + '@types/ember__object': 4.0.12(@babel/core@7.22.9) + transitivePeerDependencies: + - '@babel/core' + - supports-color + + '@types/ember__string@3.0.15': {} + + '@types/ember__template@4.0.7': {} + + '@types/ember__test@4.0.6(@babel/core@7.22.9)': + dependencies: + '@types/ember__application': 4.0.11(@babel/core@7.22.9) + transitivePeerDependencies: + - '@babel/core' + - supports-color + + '@types/ember__utils@4.0.7(@babel/core@7.22.9)': + dependencies: + '@types/ember': 4.0.11(@babel/core@7.22.9) + transitivePeerDependencies: + - '@babel/core' + - supports-color + '@types/eslint-scope@3.7.7': dependencies: '@types/eslint': 8.56.10 @@ -10290,6 +10738,8 @@ snapshots: '@types/qs@6.9.15': {} + '@types/qunit@2.19.10': {} + '@types/range-parser@1.2.7': {} '@types/resolve@1.20.2': {} @@ -10299,6 +10749,8 @@ snapshots: '@types/glob': 8.1.0 '@types/node': 20.14.9 + '@types/rsvp@4.0.9': {} + '@types/send@0.17.4': dependencies: '@types/mime': 1.3.5 @@ -10314,6 +10766,87 @@ snapshots: '@types/ungap__structured-clone@0.3.3': {} + '@typescript-eslint/eslint-plugin@8.4.0(@typescript-eslint/parser@8.4.0(eslint@8.46.0)(typescript@5.5.4))(eslint@8.46.0)(typescript@5.5.4)': + dependencies: + '@eslint-community/regexpp': 4.11.0 + '@typescript-eslint/parser': 8.4.0(eslint@8.46.0)(typescript@5.5.4) + '@typescript-eslint/scope-manager': 8.4.0 + '@typescript-eslint/type-utils': 8.4.0(eslint@8.46.0)(typescript@5.5.4) + '@typescript-eslint/utils': 8.4.0(eslint@8.46.0)(typescript@5.5.4) + '@typescript-eslint/visitor-keys': 8.4.0 + eslint: 8.46.0 + graphemer: 1.4.0 + ignore: 5.3.1 + natural-compare: 1.4.0 + ts-api-utils: 1.3.0(typescript@5.5.4) + optionalDependencies: + typescript: 5.5.4 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.4.0(eslint@8.46.0)(typescript@5.5.4)': + dependencies: + '@typescript-eslint/scope-manager': 8.4.0 + '@typescript-eslint/types': 8.4.0 + '@typescript-eslint/typescript-estree': 8.4.0(typescript@5.5.4) + '@typescript-eslint/visitor-keys': 8.4.0 + debug: 4.3.5 + eslint: 8.46.0 + optionalDependencies: + typescript: 5.5.4 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.4.0': + dependencies: + '@typescript-eslint/types': 8.4.0 + '@typescript-eslint/visitor-keys': 8.4.0 + + '@typescript-eslint/type-utils@8.4.0(eslint@8.46.0)(typescript@5.5.4)': + dependencies: + '@typescript-eslint/typescript-estree': 8.4.0(typescript@5.5.4) + '@typescript-eslint/utils': 8.4.0(eslint@8.46.0)(typescript@5.5.4) + debug: 4.3.5 + ts-api-utils: 1.3.0(typescript@5.5.4) + optionalDependencies: + typescript: 5.5.4 + transitivePeerDependencies: + - eslint + - supports-color + + '@typescript-eslint/types@8.4.0': {} + + '@typescript-eslint/typescript-estree@8.4.0(typescript@5.5.4)': + dependencies: + '@typescript-eslint/types': 8.4.0 + '@typescript-eslint/visitor-keys': 8.4.0 + debug: 4.3.5 + fast-glob: 3.3.2 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.6.2 + ts-api-utils: 1.3.0(typescript@5.5.4) + optionalDependencies: + typescript: 5.5.4 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.4.0(eslint@8.46.0)(typescript@5.5.4)': + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.46.0) + '@typescript-eslint/scope-manager': 8.4.0 + '@typescript-eslint/types': 8.4.0 + '@typescript-eslint/typescript-estree': 8.4.0(typescript@5.5.4) + eslint: 8.46.0 + transitivePeerDependencies: + - supports-color + - typescript + + '@typescript-eslint/visitor-keys@8.4.0': + dependencies: + '@typescript-eslint/types': 8.4.0 + eslint-visitor-keys: 3.4.3 + '@ungap/structured-clone@0.3.4': {} '@webassemblyjs/ast@1.12.1': @@ -12122,14 +12655,14 @@ snapshots: object-assign: 4.1.1 vary: 1.1.2 - cosmiconfig@9.0.0(typescript@5.5.2): + cosmiconfig@9.0.0(typescript@5.5.4): dependencies: env-paths: 2.2.1 import-fresh: 3.3.0 js-yaml: 4.1.0 parse-json: 5.2.0 optionalDependencies: - typescript: 5.5.2 + typescript: 5.5.4 create-ecdh@4.0.4: dependencies: @@ -13925,10 +14458,11 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.8.1(eslint-import-resolver-node@0.3.9)(eslint@8.46.0): + eslint-module-utils@2.8.1(@typescript-eslint/parser@8.4.0(eslint@8.46.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint@8.46.0): dependencies: debug: 3.2.7 optionalDependencies: + '@typescript-eslint/parser': 8.4.0(eslint@8.46.0)(typescript@5.5.4) eslint: 8.46.0 eslint-import-resolver-node: 0.3.9 transitivePeerDependencies: @@ -13959,7 +14493,7 @@ snapshots: eslint-utils: 2.1.0 regexpp: 3.2.0 - eslint-plugin-import@2.28.1(eslint@8.46.0): + eslint-plugin-import@2.28.1(@typescript-eslint/parser@8.4.0(eslint@8.46.0)(typescript@5.5.4))(eslint@8.46.0): dependencies: array-includes: 3.1.8 array.prototype.findlastindex: 1.2.5 @@ -13969,7 +14503,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.46.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.1(eslint-import-resolver-node@0.3.9)(eslint@8.46.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@8.4.0(eslint@8.46.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint@8.46.0) has: 1.0.4 is-core-module: 2.14.0 is-glob: 4.0.3 @@ -13979,6 +14513,8 @@ snapshots: object.values: 1.2.0 semver: 6.3.1 tsconfig-paths: 3.15.0 + optionalDependencies: + '@typescript-eslint/parser': 8.4.0(eslint@8.46.0)(typescript@5.5.4) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack @@ -15785,6 +16321,10 @@ snapshots: dependencies: brace-expansion: 2.0.1 + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.1 + minimist@1.2.8: {} minipass@2.9.0: @@ -17242,57 +17782,57 @@ snapshots: styled_string@0.0.1: {} - stylelint-config-recommended-scss@14.0.0(postcss@8.4.39)(stylelint@16.6.1(typescript@5.5.2)): + stylelint-config-recommended-scss@14.0.0(postcss@8.4.39)(stylelint@16.6.1(typescript@5.5.4)): dependencies: postcss-scss: 4.0.9(postcss@8.4.39) - stylelint: 16.6.1(typescript@5.5.2) - stylelint-config-recommended: 14.0.1(stylelint@16.6.1(typescript@5.5.2)) - stylelint-scss: 6.3.2(stylelint@16.6.1(typescript@5.5.2)) + stylelint: 16.6.1(typescript@5.5.4) + stylelint-config-recommended: 14.0.1(stylelint@16.6.1(typescript@5.5.4)) + stylelint-scss: 6.3.2(stylelint@16.6.1(typescript@5.5.4)) optionalDependencies: postcss: 8.4.39 - stylelint-config-recommended@11.0.0(stylelint@16.6.1(typescript@5.5.2)): + stylelint-config-recommended@11.0.0(stylelint@16.6.1(typescript@5.5.4)): dependencies: - stylelint: 16.6.1(typescript@5.5.2) + stylelint: 16.6.1(typescript@5.5.4) - stylelint-config-recommended@14.0.1(stylelint@16.6.1(typescript@5.5.2)): + stylelint-config-recommended@14.0.1(stylelint@16.6.1(typescript@5.5.4)): dependencies: - stylelint: 16.6.1(typescript@5.5.2) + stylelint: 16.6.1(typescript@5.5.4) - stylelint-config-standard-scss@13.1.0(postcss@8.4.39)(stylelint@16.6.1(typescript@5.5.2)): + stylelint-config-standard-scss@13.1.0(postcss@8.4.39)(stylelint@16.6.1(typescript@5.5.4)): dependencies: - stylelint: 16.6.1(typescript@5.5.2) - stylelint-config-recommended-scss: 14.0.0(postcss@8.4.39)(stylelint@16.6.1(typescript@5.5.2)) - stylelint-config-standard: 36.0.1(stylelint@16.6.1(typescript@5.5.2)) + stylelint: 16.6.1(typescript@5.5.4) + stylelint-config-recommended-scss: 14.0.0(postcss@8.4.39)(stylelint@16.6.1(typescript@5.5.4)) + stylelint-config-standard: 36.0.1(stylelint@16.6.1(typescript@5.5.4)) optionalDependencies: postcss: 8.4.39 - stylelint-config-standard@32.0.0(stylelint@16.6.1(typescript@5.5.2)): + stylelint-config-standard@32.0.0(stylelint@16.6.1(typescript@5.5.4)): dependencies: - stylelint: 16.6.1(typescript@5.5.2) - stylelint-config-recommended: 11.0.0(stylelint@16.6.1(typescript@5.5.2)) + stylelint: 16.6.1(typescript@5.5.4) + stylelint-config-recommended: 11.0.0(stylelint@16.6.1(typescript@5.5.4)) - stylelint-config-standard@36.0.1(stylelint@16.6.1(typescript@5.5.2)): + stylelint-config-standard@36.0.1(stylelint@16.6.1(typescript@5.5.4)): dependencies: - stylelint: 16.6.1(typescript@5.5.2) - stylelint-config-recommended: 14.0.1(stylelint@16.6.1(typescript@5.5.2)) + stylelint: 16.6.1(typescript@5.5.4) + stylelint-config-recommended: 14.0.1(stylelint@16.6.1(typescript@5.5.4)) - stylelint-prettier@3.0.0(prettier@2.8.8)(stylelint@16.6.1(typescript@5.5.2)): + stylelint-prettier@3.0.0(prettier@2.8.8)(stylelint@16.6.1(typescript@5.5.4)): dependencies: prettier: 2.8.8 prettier-linter-helpers: 1.0.0 - stylelint: 16.6.1(typescript@5.5.2) + stylelint: 16.6.1(typescript@5.5.4) - stylelint-scss@6.3.2(stylelint@16.6.1(typescript@5.5.2)): + stylelint-scss@6.3.2(stylelint@16.6.1(typescript@5.5.4)): dependencies: known-css-properties: 0.31.0 postcss-media-query-parser: 0.2.3 postcss-resolve-nested-selector: 0.1.1 postcss-selector-parser: 6.1.0 postcss-value-parser: 4.2.0 - stylelint: 16.6.1(typescript@5.5.2) + stylelint: 16.6.1(typescript@5.5.4) - stylelint@16.6.1(typescript@5.5.2): + stylelint@16.6.1(typescript@5.5.4): dependencies: '@csstools/css-parser-algorithms': 2.7.0(@csstools/css-tokenizer@2.3.2) '@csstools/css-tokenizer': 2.3.2 @@ -17301,7 +17841,7 @@ snapshots: '@dual-bundle/import-meta-resolve': 4.1.0 balanced-match: 2.0.0 colord: 2.9.3 - cosmiconfig: 9.0.0(typescript@5.5.2) + cosmiconfig: 9.0.0(typescript@5.5.4) css-functions-list: 3.2.2 css-tree: 2.3.1 debug: 4.3.5 @@ -17673,6 +18213,10 @@ snapshots: trim-right@1.0.1: {} + ts-api-utils@1.3.0(typescript@5.5.4): + dependencies: + typescript: 5.5.4 + ts-invariant@0.10.3: dependencies: tslib: 2.6.3 @@ -17747,8 +18291,7 @@ snapshots: typescript-memoize@1.1.1: {} - typescript@5.5.2: - optional: true + typescript@5.5.4: {} uc.micro@1.0.6: {} diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 000000000..79f0e7826 --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "@tsconfig/strictest/tsconfig.json", + "@tsconfig/ember/tsconfig.json" + ], + "compilerOptions": { + // The combination of `baseUrl` with `paths` allows Ember's classic package + // layout, which is not resolvable with the Node resolution algorithm, to + // work with TypeScript. + "allowJs": true, + "baseUrl": ".", + "paths": { + "timed/tests/*": [ + "tests/*" + ], + "timed/mirage/*": [ + "mirage/*" + ], + "timed/*": [ + "app/*" + ], + "*": [ + "types/*" + ] + } + }, + "include": [ + "app/**/*", + "tests/**/*", + "types/**/*", + "mirage/**/*" + ] +} \ No newline at end of file diff --git a/frontend/types/ember-data/types/registries/model.d.ts b/frontend/types/ember-data/types/registries/model.d.ts new file mode 100644 index 000000000..e7a68fcd0 --- /dev/null +++ b/frontend/types/ember-data/types/registries/model.d.ts @@ -0,0 +1,6 @@ +/** + * Catch-all for ember-data. + */ +export default interface ModelRegistry { + [key: string]: any; +} diff --git a/frontend/types/global.d.ts b/frontend/types/global.d.ts new file mode 100644 index 000000000..9c026f740 --- /dev/null +++ b/frontend/types/global.d.ts @@ -0,0 +1,7 @@ +// Types for compiled templates +declare module "timed/templates/*" { + import { TemplateFactory } from "ember-cli-htmlbars"; + + const tmpl: TemplateFactory; + export default tmpl; +} diff --git a/frontend/types/timed/index.d.ts b/frontend/types/timed/index.d.ts new file mode 100644 index 000000000..8a50f34a4 --- /dev/null +++ b/frontend/types/timed/index.d.ts @@ -0,0 +1,11 @@ +import Ember from "ember"; + +declare global { + // Prevents ESLint from "fixing" this via its auto-fix to turn it into a type + // alias (e.g. after running any Ember CLI generator) + // eslint-disable-next-line @typescript-eslint/no-empty-interface + interface Array extends Ember.ArrayPrototypeExtensions {} + // interface Function extends Ember.FunctionPrototypeExtensions {} +} + +export {};