src/loot/providers/states.ts
import type { Entity, EntityModel } from "~/loot/types";
import AccountIndexView from "~/accounts/views/index.html";
import type AccountModel from "~/accounts/models/account";
import type { Accounts } from "~/accounts/types";
import AuthenticationEditView from "~/authentication/views/edit.html";
import type AuthenticationModel from "~/authentication/models/authentication";
import type { Category } from "~/categories/types";
import CategoryIndexView from "~/categories/views/index.html";
import type CategoryModel from "~/categories/models/category";
import LootLayoutView from "~/loot/views/layout.html";
import type { Payee } from "~/payees/types";
import PayeeIndexView from "~/payees/views/index.html";
import type PayeeModel from "~/payees/models/payee";
import type QueryService from "~/transactions/services/query";
import type { Schedule } from "~/schedules/types";
import ScheduleIndexView from "~/schedules/views/index.html";
import type ScheduleModel from "~/schedules/models/schedule";
import type { Security } from "~/securities/types";
import SecurityIndexView from "~/securities/views/index.html";
import type SecurityModel from "~/securities/models/security";
import type { TransactionBatch } from "~/transactions/types";
import TransactionIndexView from "~/transactions/views/index.html";
import type TransactionModel from "~/transactions/models/transaction";
export default class LootStatesProvider {
public $get: () => LootStatesProvider;
public constructor($stateProvider: angular.ui.IStateProvider) {
const transactionViews: Record<string, angular.ui.IState> = {
"@root": {
templateUrl: TransactionIndexView,
controller: "TransactionIndexController",
controllerAs: "vm",
},
};
function basicState(): angular.ui.IState {
return {
url: "/:id",
};
}
function transactionsState(parentContext: string): angular.ui.IState {
return {
url: "/transactions",
data: {
title: `${
parentContext.charAt(0).toUpperCase() + parentContext.substring(1)
} Transactions`,
},
resolve: {
contextModel: [
"authenticated",
`${parentContext}Model`,
(
authenticated: boolean,
contextModel: EntityModel,
): EntityModel | false => authenticated && contextModel,
],
context: [
"authenticated",
"$stateParams",
"contextModel",
(
authenticated: boolean,
$stateParams: angular.ui.IStateParamsService,
contextModel: EntityModel,
): angular.IPromise<Entity> | false =>
authenticated && contextModel.find(Number($stateParams.id)),
],
transactionBatch: [
"authenticated",
"transactionModel",
"contextModel",
"context",
(
authenticated: boolean,
transactionModel: TransactionModel,
contextModel: EntityModel,
context: Entity,
): angular.IPromise<TransactionBatch> | false => {
const unreconciledOnly: boolean =
"function" ===
typeof (contextModel as AccountModel).isUnreconciledOnly &&
(contextModel as AccountModel).isUnreconciledOnly(
Number(context.id),
);
return (
authenticated &&
transactionModel.all(
contextModel.path(context.id),
null,
"prev",
unreconciledOnly,
)
);
},
],
},
views: transactionViews,
};
}
function transactionState(): angular.ui.IState {
return {
url: "/:transactionId",
};
}
$stateProvider
.state("root", {
abstract: true,
templateUrl: LootLayoutView,
controller: "LayoutController",
controllerAs: "vm",
data: {
title: "Welcome",
},
resolve: {
authenticated: [
"$uibModal",
"authenticationModel",
(
$uibModal: angular.ui.bootstrap.IModalService,
authenticationModel: AuthenticationModel,
): angular.IPromise<boolean> | boolean => {
// Check if the user is authenticated
if (!authenticationModel.isAuthenticated) {
// Not authenticated, show the login modal
return $uibModal
.open({
templateUrl: AuthenticationEditView,
controller: "AuthenticationEditController",
controllerAs: "vm",
backdrop: "static",
size: "sm",
})
.result.then(
(): boolean => authenticationModel.isAuthenticated,
)
.catch((): false => false);
}
// User is authenticated
return true;
},
],
},
})
.state("root.accounts", {
url: "/accounts",
templateUrl: AccountIndexView,
controller: "AccountIndexController",
controllerAs: "vm",
data: {
title: "Accounts",
},
resolve: {
accounts: [
"authenticated",
"accountModel",
(
authenticated: boolean,
accountModel: AccountModel,
): angular.IPromise<Accounts> | false =>
authenticated && accountModel.allWithBalances(),
],
},
})
.state("root.accounts.account", basicState())
.state("root.accounts.account.transactions", transactionsState("account"))
.state(
"root.accounts.account.transactions.transaction",
transactionState(),
)
.state("root.schedules", {
url: "/schedules",
templateUrl: ScheduleIndexView,
controller: "ScheduleIndexController",
controllerAs: "vm",
data: {
title: "Schedules",
},
resolve: {
schedules: [
"authenticated",
"scheduleModel",
(
authenticated: boolean,
scheduleModel: ScheduleModel,
): angular.IPromise<Schedule[]> | false =>
authenticated && scheduleModel.all(),
],
},
})
.state("root.schedules.schedule", basicState())
.state("root.payees", {
url: "/payees",
templateUrl: PayeeIndexView,
controller: "PayeeIndexController",
controllerAs: "vm",
data: {
title: "Payees",
},
resolve: {
payees: [
"authenticated",
"payeeModel",
(
authenticated: boolean,
payeeModel: PayeeModel,
): angular.IPromise<Payee[]> | false =>
authenticated && payeeModel.allList(),
],
},
})
.state("root.payees.payee", basicState())
.state("root.payees.payee.transactions", transactionsState("payee"))
.state("root.payees.payee.transactions.transaction", transactionState())
.state("root.categories", {
url: "/categories",
templateUrl: CategoryIndexView,
controller: "CategoryIndexController",
controllerAs: "vm",
data: {
title: "Categories",
},
resolve: {
categories: [
"authenticated",
"categoryModel",
(
authenticated: boolean,
categoryModel: CategoryModel,
): angular.IPromise<Category[]> | false =>
authenticated && categoryModel.allWithChildren(),
],
},
})
.state("root.categories.category", basicState())
.state(
"root.categories.category.transactions",
transactionsState("category"),
)
.state(
"root.categories.category.transactions.transaction",
transactionState(),
)
.state("root.securities", {
url: "/securities",
templateUrl: SecurityIndexView,
controller: "SecurityIndexController",
controllerAs: "vm",
data: {
title: "Securities",
},
resolve: {
securities: [
"authenticated",
"securityModel",
(
authenticated: boolean,
securityModel: SecurityModel,
): angular.IPromise<Security[]> | false =>
authenticated && securityModel.allWithBalances(),
],
},
})
.state("root.securities.security", basicState())
.state(
"root.securities.security.transactions",
transactionsState("security"),
)
.state(
"root.securities.security.transactions.transaction",
transactionState(),
)
.state("root.transactions", {
url: "/transactions?query",
data: {
title: "Search Transactions",
},
resolve: {
previousState: [
"$state",
($state: angular.ui.IStateService): angular.ui.IState | null => {
if (!$state.includes("root.transactions")) {
return {
name: $state.current.name,
params: { ...$state.params },
};
}
return null;
},
],
contextModel: (): null => null,
context: [
"$stateParams",
($stateParams: angular.ui.IStateParamsService): string =>
String($stateParams.query),
],
transactionBatch: [
"authenticated",
"transactionModel",
"context",
(
authenticated: boolean,
transactionModel: TransactionModel,
context: string,
): angular.IPromise<TransactionBatch> | false =>
authenticated && transactionModel.query(context, null, "prev"),
],
},
views: transactionViews,
onEnter: [
"$stateParams",
"queryService",
"previousState",
(
$stateParams: angular.ui.IStateParamsService,
queryService: QueryService,
previousState: angular.ui.IState | null,
): void => {
queryService.previousState =
previousState ?? queryService.previousState;
queryService.query = String($stateParams.query);
},
],
onExit: [
"queryService",
(queryService: QueryService): void => {
// Can't use concise function body because implicit return of 'false' causes route transition to cancel in ui-router@1.x
queryService.query = null;
},
],
})
.state("root.transactions.transaction", transactionState());
this.$get = (): LootStatesProvider => this;
}
}
LootStatesProvider.$inject = ["$stateProvider"];