Skip to content

Commit

Permalink
Merge pull request #77 from ya-erm/dev
Browse files Browse the repository at this point in the history
MVP of repeating operations
  • Loading branch information
ya-erm authored Dec 19, 2024
2 parents 260f3c2 + 21ded10 commit 06b0277
Show file tree
Hide file tree
Showing 31 changed files with 1,310 additions and 389 deletions.
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"browser-preview.startUrl": "http://127.0.0.1:5173/",
"browser-preview.chromeExecutable": "/Applications/Yandex.app/Contents/MacOS/Yandex",
"cSpell.words": ["datetime", "dndzone", "iconify", "Initialisable", "networkidle"],
"cSpell.words": ["datetime", "dndzone", "iconify", "Initialisable", "networkidle", "repeatings"],
"cSpell.language": "en,ru,en-US,en-GB",
"eslint.validate": ["javascript", "typescript", "svelte"],
"editor.defaultFormatter": "esbenp.prettier-vscode",
Expand Down
694 changes: 359 additions & 335 deletions package-lock.json

Large diffs are not rendered by default.

58 changes: 32 additions & 26 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,47 +20,53 @@
"generate": "npm run generate:icons && npm run generate:prisma"
},
"devDependencies": {
"@eslint/eslintrc": "^3.1.0",
"@iconify/json": "^2.2.264",
"@iconify/svelte": "^4.0.2",
"@iconify/utils": "^2.1.33",
"@playwright/test": "^1.48.2",
"@eslint/eslintrc": "^3.2.0",
"@iconify/json": "^2.2.286",
"@iconify/svelte": "^4.1.0",
"@iconify/utils": "^2.2.1",
"@playwright/test": "^1.49.1",
"@sveltejs/adapter-auto": "^3.3.1",
"@sveltejs/adapter-node": "^5.2.9",
"@sveltejs/kit": "^2.8.3",
"@sveltejs/kit": "~2.8.5",
"@types/bcrypt": "^5.0.2",
"@types/chart.js": "^2.9.41",
"@types/uuid": "^10.0.0",
"@typescript-eslint/eslint-plugin": "8.11.0",
"@typescript-eslint/parser": "^8.11.0",
"@typescript-eslint/eslint-plugin": "^8.18.1",
"@typescript-eslint/parser": "^8.18.1",
"@vite-pwa/sveltekit": "^0.6.6",
"autoprefixer": "^10.4.20",
"chart.js": "^4.4.5",
"dotenv": "^16.4.5",
"eslint": "^9.13.0",
"chart.js": "^4.4.7",
"dotenv": "^16.4.7",
"eslint": "^9.17.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-svelte": "^2.46.0",
"eslint-plugin-svelte": "^2.46.1",
"globals": "^15.11.0",
"prettier": "^3.3.3",
"prettier-plugin-svelte": "^3.2.7",
"prisma": "^5.21.1",
"svelte": "^5.1.3",
"svelte-check": "^4.0.5",
"prettier": "^3.4.2",
"prettier-plugin-svelte": "^3.3.2",
"prisma": "^5.22.0",
"svelte": "^5.14.4",
"svelte-check": "^4.1.1",
"svelte-i18n": "^4.0.1",
"svelte-preprocess": "^6.0.3",
"tslib": "^2.8.0",
"typescript": "^5.6.3",
"vite": "^5.4.10"
"tslib": "^2.8.1",
"typescript": "^5.7.2",
"vite": "^5.4.11"
},
"dependencies": {
"@prisma/client": "^5.21.1",
"@vercel/analytics": "^1.3.2",
"@prisma/client": "^5.22.0",
"@vercel/analytics": "^1.4.1",
"bcrypt": "^5.1.1",
"dayjs": "^1.11.13",
"idb": "^8.0.0",
"svelte-dnd-action": "^0.9.52",
"idb": "^8.0.1",
"svelte-dnd-action": "^0.9.53",
"timezones-list": "^3.0.3",
"uuid": "^11.0.0"
"uuid": "^11.0.3"
},
"type": "module"
"type": "module",
"overrides": {
"@sveltejs/kit": {
"cookie": ">=0.7.0"
}
},
"packageManager": "pnpm@9.7.0+sha256.b35018fbfa8f583668b2649e407922a721355cd81f61beeb4ac1d4258e585559"
}
5 changes: 3 additions & 2 deletions src/lib/data/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
export { accountTagsService, accountTagsStore } from './accountTags';
export { accountsService, accountsStore } from './accounts';
export { accountTagsService, accountTagsStore } from './accountTags';
export { categoriesService, categoriesStore } from './categories';
export { currencyRatesService, currencyRatesStore } from './currencyRates';
export { groupingsService, groupingsStore } from './groupings';
export { journalService } from './journal';
export { mainService } from './main';
export { memberSettingsStore, membersService, membersStore, selectedMemberStore } from './members';
export { operationTagsService, operationTagsStore } from './operationTags';
export { operationsService, operationsStore } from './operations';
export { operationTagsService, operationTagsStore } from './operationTags';
export { repeatingsService, repeatingsStore } from './repeatings';
export { settingsService, settingsStore } from './settings';
17 changes: 17 additions & 0 deletions src/lib/data/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export type JournalOperation = {
categoriesInOrder?: string[];
categoriesOutOrder?: string[];
grouping?: Grouping;
repeating?: Repeating;
};

export type JournalSubscriber = {
Expand Down Expand Up @@ -113,6 +114,7 @@ export type Transaction = {
anotherCurrencyAmount?: number | null;
excludeFromAnalysis?: boolean;
tagIds?: string[];
repeatingId?: string;
deleted?: boolean;
};

Expand All @@ -123,6 +125,7 @@ type TransactionWithAccountAndCategory = Transaction & {

export type TransactionViewModel = TransactionWithAccountAndCategory & {
linkedTransaction?: TransactionWithAccountAndCategory;
repeating?: Repeating;
tags: Tag[];
};

Expand All @@ -140,6 +143,15 @@ export type Group = {
accountIds?: string[];
};

export type Repeating = {
id: string;
count: number;
interval: 'day' | 'week' | 'month' | 'year';
dayOfMonth?: number;
endDate?: string;
deleted?: boolean;
};

export interface LocalDB extends DBSchema {
globalSettings: {
key: string;
Expand Down Expand Up @@ -198,6 +210,11 @@ export interface LocalDB extends DBSchema {
value: WithOwner<Grouping>;
indexes: { 'by-owner': string };
};
repeatings: {
key: string;
value: WithOwner<Repeating>;
indexes: { 'by-owner': string };
};
}

export type DateIntervalType = 'month' | 'custom';
2 changes: 2 additions & 0 deletions src/lib/data/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { journalService } from './journal';
import { membersService } from './members';
import { operationTagsService } from './operationTags';
import { operationsService } from './operations';
import { repeatingsService } from './repeatings';
import { settingsService } from './settings';

const logger = new Logger('MainService', { disabled: false, color: '#00cc55' });
Expand Down Expand Up @@ -51,6 +52,7 @@ class MainService implements Initialisable {
operationsService,
currencyRatesService,
groupingsService,
repeatingsService,
];

logger.log(`Initialise ${services.length} services to load data from local DB`);
Expand Down
21 changes: 19 additions & 2 deletions src/lib/data/operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
import { $initialized } from './initialized';
import type { Transaction, TransactionViewModel } from './interfaces';
import { operationTagsService, operationTagsStore } from './operationTags';
import { repeatingsService, repeatingsStore } from './repeatings';
import { BaseService } from './service';

const logger = new Logger('OperationsService', { disabled: false });
Expand All @@ -39,8 +40,8 @@ export class OperationsService extends BaseService<Transaction> {
accountsService.deleteAccountOperations = this.deleteTransactionsByAccount;

this._operations = derived(
[this.$items, $initialized, accountsStore, categoriesStore, operationTagsStore],
([transactions, initialized, accounts, _categories, tags]) => {
[this.$items, $initialized, accountsStore, categoriesStore, operationTagsStore, repeatingsStore],
([transactions, initialized, accounts, _categories, tags, repeatings]) => {
if (!initialized) return [];

const categories = _categories.concat(SYSTEM_CATEGORY_TRANSFER_IN, SYSTEM_CATEGORY_TRANSFER_OUT);
Expand Down Expand Up @@ -86,6 +87,20 @@ export class OperationsService extends BaseService<Transaction> {
return tag;
}

function findRepeating(id?: string) {
if (!id) return undefined;
const repeating = repeatings.find((item) => item.id === id);
if (!repeating) {
const deletedRepeating = repeatingsService.deletedItems.find((item) => item.id === id);
if (!deletedRepeating) {
throw new Error(`Repeating ${id} not found`);
}
warnings.push(`Repeating "${deletedRepeating.id}" ${id} is deleted`);
return deletedRepeating;
}
return repeating;
}

transactions.sort((a, b) => dayjs(b.date).diff(dayjs(a.date)));

const problems: Array<{ transaction: Transaction; e: unknown }> = [];
Expand All @@ -109,6 +124,7 @@ export class OperationsService extends BaseService<Transaction> {
}
: undefined,
tags: transaction.tagIds?.map(findTag) || [],
repeating: findRepeating(transaction.repeatingId),
};
return viewModel;
} catch (e) {
Expand Down Expand Up @@ -179,6 +195,7 @@ export function cloneOperation(item: Transaction): Transaction {
anotherCurrencyAmount: item.anotherCurrencyAmount,
excludeFromAnalysis: item.excludeFromAnalysis,
tagIds: item.tagIds ? [...item.tagIds] : undefined,
repeatingId: item.repeatingId,
};
}

Expand Down
20 changes: 20 additions & 0 deletions src/lib/data/repeatings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { Repeating } from './interfaces';
import { BaseService } from './service';

export class RepeatingsService extends BaseService<Repeating> {
constructor() {
super('RepeatingService', 'repeatings', 'repeating');
}

get repeatings() {
return this.items;
}

get $repeatings() {
return this.$items;
}
}

export const repeatingsService = new RepeatingsService();

export const repeatingsStore = repeatingsService.$repeatings;
15 changes: 12 additions & 3 deletions src/lib/data/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,25 @@ import type {
JournalItem,
JournalOperation,
JournalSubscriber,
Repeating,
Tag,
Transaction,
} from './interfaces';
import { journalService } from './journal';
import { membersService } from './members';
import { useDB } from './useDB';

type StorageName = 'categories' | 'accounts' | 'transactions' | 'tags' | 'accountTags' | 'currencyRates' | 'groupings';

type EntityType = Category | Account | Transaction | Tag | CurrencyRate;
type StorageName =
| 'categories'
| 'accounts'
| 'transactions'
| 'tags'
| 'accountTags'
| 'currencyRates'
| 'groupings'
| 'repeatings';

type EntityType = Category | Account | Transaction | Tag | CurrencyRate | Repeating;

export class BaseService<T extends EntityType> implements Initialisable, JournalSubscriber {
private _name: string;
Expand Down
7 changes: 6 additions & 1 deletion src/lib/data/useDB.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { openDB } from 'idb';
import type { LocalDB } from './interfaces';

const CURRENT_VERSION = 10;
const CURRENT_VERSION = 11;

export async function useDB() {
return await openDB<LocalDB>('mk-2', CURRENT_VERSION, {
Expand Down Expand Up @@ -67,6 +67,11 @@ export async function useDB() {
const objectStore = db.createObjectStore('groupings', { keyPath: 'id' });
objectStore.createIndex('by-owner', 'owner');
});

migration(11, 'Creating repeatings store', () => {
const objectStore = db.createObjectStore('repeatings', { keyPath: 'id' });
objectStore.createIndex('by-owner', 'owner');
});
},
});
}
5 changes: 5 additions & 0 deletions src/lib/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type RouteKey =
| 'analytics.categories'
| 'categories'
| 'categories.create'
| 'repeatings'
| 'transactions'
| 'transactions.create'
| 'transactions.import'
Expand Down Expand Up @@ -110,6 +111,10 @@ export const routes: { [key in RouteKey]: Route } = {
path: '/categories/create',
title: 'categories.create_category',
},
repeatings: {
path: '/repeatings',
title: 'repeatings.title',
},
transactions: {
path: '/transactions',
title: 'transactions.title',
Expand Down
32 changes: 32 additions & 0 deletions src/lib/translate/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,14 @@ export const enDict: Dictionary = {
'groups.delete_group_confirm_description':
"All accounts, categories and operations will be deleted. This action can' be undone.",
'groups.failed_to_delete_group': 'Failed to delete group',
// Repeatings
'repeatings.title': 'Repeating operations',
'repeatings.no_data': 'No repeating yet. Add the new one.',
'repeatings.active': 'Active',
'repeatings.inactive': 'Inactive',
'repeatings.operations': 'Operations',
'repeatings.empty_repeating': 'Empty',
'repeatings.empty_repeating.info': 'Add an operation or delete item',
// Transactions
'transactions.title': 'Operations',
'transactions.create_transaction': 'Create operation',
Expand Down Expand Up @@ -262,6 +270,29 @@ export const enDict: Dictionary = {
'transactions.same_currency': 'Same currency',
'transactions.additional_parameters': 'Additional parameters',
'transactions.exclude_from_analytics': 'Exclude from analytics',
'transactions.repeatings': 'Repeat operation',
'transactions.repeatings.configure': 'Configure',
'transactions.repeatings.title': 'Repeating options',
'transactions.repeatings.create_new': 'Create new repeating',
'transactions.repeatings.select_existing': 'Select existing',
'transactions.repeatings.select_repeating': 'Select repeating',
'transactions.repeatings.interval': 'Interval of repeating',
'transactions.repeatings.interval.day': '{count, plural, one {day} other {days}}',
'transactions.repeatings.interval.week': '{count, plural, one {week} other {weeks}}',
'transactions.repeatings.interval.month': '{count, plural, one {month} other {months}}',
'transactions.repeatings.interval.year': '{count, plural, one {year} other {years}}',
'transactions.repeatings.every_x_days': '{count, plural, =1 {Every day} other {Every # days}}',
'transactions.repeatings.every_x_weeks': '{count, plural, =1 {Every week} other {Every # weeks}} at {day}',
'transactions.repeatings.every_x_years': '{count, plural, =1 {Every year} other {Every# years}} {date}',
'transactions.repeatings.every_x_day_of_month': 'Every {value}-th day of month',
'transactions.repeatings.every_x_day_of_month.every': 'Every',
'transactions.repeatings.every_x_day_of_month.nth_day': '{value}-th day',
'transactions.repeatings.every_x_day_of_month.of_x_months': '{count, plural, =1 {of month} other {of # months}}',
'transactions.repeatings.every_x_day_of_month.change': 'Change',
'transactions.repeatings.day_of_month.title': 'Day of month',
'transactions.repeatings.day_of_month.info': 'Type value from 1 to 31',
'transactions.repeatings.end_date': 'End date',
'transactions.repeatings.operations_list': 'Show operations list',
// Transactions import
'transactions.import': 'Import',
'transactions.import.title': 'Import operations',
Expand Down Expand Up @@ -340,6 +371,7 @@ export const enDict: Dictionary = {
'settings.theme.dark': 'Dark',
'settings.theme.system': 'System',
'settings.import_export': 'Import / Export',
'settings.repeatings': 'Repeating operations',
'settings.profile': 'Profile',
'settings.profile.change_name': 'Change name',
'settings.profile.change_name_failure': 'Failed to change name',
Expand Down
Loading

0 comments on commit 06b0277

Please sign in to comment.