diff --git a/src/components/inputs/FrequencyInput.svelte b/src/components/inputs/FrequencyInput.svelte
index ba96034..5fd457d 100644
--- a/src/components/inputs/FrequencyInput.svelte
+++ b/src/components/inputs/FrequencyInput.svelte
@@ -2,6 +2,8 @@
import DateInputs from './DateInputs.svelte';
export let selectedFrequency = '1-month';
+ export let interval = null;
+ export let unitOfMeasurement = null;
export let daysOfMonth = [];
export let date;
@@ -86,6 +88,17 @@
/>
+
+
+
+
@@ -111,6 +124,58 @@
{/each}
+{:else if selectedFrequency === 'custom'}
+
+
+
+
+
+
{:else}
{/if}
diff --git a/src/lib/copy-recurring.js b/src/lib/copy-recurring.js
index 237e0a1..7ab654a 100644
--- a/src/lib/copy-recurring.js
+++ b/src/lib/copy-recurring.js
@@ -138,6 +138,53 @@ const copyRecurring =
break;
}
default: {
+ const gapInDays = Math.floor((currentMonth - originalDate) / 1000 / 3600 / 24);
+ const [interval, unitOfMeasurement] = entry.frequency.split('-');
+ let numberOfDays = 0;
+ let numberOfMonths = 0;
+ switch (unitOfMeasurement) {
+ case 'day':
+ numberOfDays = Number.parseInt(interval, 10);
+ break;
+ case 'week':
+ numberOfDays = Number.parseInt(interval, 10) * 7;
+ break;
+ case 'month':
+ numberOfMonths = Number.parseInt(interval, 10);
+ break;
+ default:
+ break;
+ }
+ if (numberOfDays === 0 && numberOfMonths === 0) {
+ break;
+ }
+
+ let newEntryDate = new Date(numericYear, numericMonth, originalDateDay);
+ if (numberOfDays > 0) {
+ const offset = numberOfDays - (gapInDays % numberOfDays);
+ newEntryDate = new Date(numericYear, numericMonth, 1 + offset);
+ } else if (numberOfMonths > 0) {
+ newEntryDate = new Date(
+ numericYear,
+ originalDateMonth + numberOfMonths,
+ originalDateDay
+ );
+ }
+
+ while (newEntryDate < nextMonth) {
+ const newEntry = {
+ ...entry,
+ date: new Date(newEntryDate),
+ };
+ delete newEntry.frequency;
+ delete newEntry.days_of_month;
+ newEntries.push(newEntry);
+ if (numberOfDays > 0) {
+ newEntryDate.setDate(newEntryDate.getDate() + numberOfDays);
+ } else {
+ newEntryDate.setDate(newEntryDate.getDate() + numberOfMonths * 31);
+ }
+ }
break;
}
}
diff --git a/src/lib/format-recurring.js b/src/lib/format-recurring.js
index 11b6bd8..bb8ed86 100644
--- a/src/lib/format-recurring.js
+++ b/src/lib/format-recurring.js
@@ -1,3 +1,19 @@
+import { formatDate, formatAmount } from '$lib/format-inputs';
+
+const customAnnualMultiplier = (frequency) => {
+ const [interval, unitOfMeasurement] = frequency.split('-');
+ switch (unitOfMeasurement) {
+ case 'day':
+ return 365 / Number.parseInt(interval, 10);
+ case 'week':
+ return 52 / Number.parseInt(interval, 10);
+ case 'month':
+ return 12 / Number.parseInt(interval, 10);
+ default:
+ return 1;
+ }
+};
+
export const annualAmount = ({ frequency, amount }) => {
switch (frequency) {
case '1-month':
@@ -15,10 +31,15 @@ export const annualAmount = ({ frequency, amount }) => {
case 'twice-per-month':
return amount * 24;
default:
- return 0;
+ return amount * customAnnualMultiplier(frequency);
}
};
+const customFrequencyDescription = (frequency) => {
+ const [interval, unitOfMeasurement] = frequency.split('-');
+ return `/${interval} ${unitOfMeasurement}${unitOfMeasurement !== '1' ? 's' : ''}`;
+};
+
export const formatFrequency = (frequency) => {
switch (frequency) {
case '1-month':
@@ -36,6 +57,67 @@ export const formatFrequency = (frequency) => {
case 'twice-per-month':
return ' twice per month';
default:
- return '';
+ return customFrequencyDescription(frequency);
}
};
+
+export const getRecurringEntryFormData = (formData) => {
+ const amount = formatAmount(formData.get('amount'));
+
+ const selectedCategory = formData.get('category');
+ const newCategory = formData.get('new-category');
+ const category = newCategory ?? selectedCategory;
+
+ const description = formData.get('description') || category;
+
+ const now = new Date();
+ const year = formData.get('year') ?? now.getFullYear();
+ const month = formData.get('month') ?? now.getMonth() + 1;
+ const day = formData.get('day') ?? now.getDate();
+ const date = formatDate(year, month, day);
+
+ const frequency = formData.get('frequency');
+ const interval = formData.get('interval');
+ const unitOfMeasurement = formData.get('unitOfMeasurement');
+ const days_of_month = formData.getAll('days-of-month') ?? [];
+ const active = !!formData.get('active');
+
+ return {
+ amount,
+ category,
+ description,
+ date,
+ frequency: frequency === 'custom' ? `${interval}-${unitOfMeasurement}` : frequency,
+ days_of_month,
+ active,
+ };
+};
+
+export const formatEntry = (entry) => {
+ let { frequency } = entry;
+
+ let interval = null;
+ let unitOfMeasurement = null;
+
+ switch (frequency) {
+ case '1-month':
+ case '3-month':
+ case '6-month':
+ case '1-year':
+ case '1-week':
+ case '2-week':
+ case 'twice-per-month':
+ break;
+ default:
+ [interval, unitOfMeasurement] = frequency.split('-');
+ frequency = 'custom';
+ break;
+ }
+
+ return {
+ ...entry,
+ frequency,
+ interval,
+ unitOfMeasurement,
+ };
+};
diff --git a/src/routes/recurring-expenses/expense/+page.server.js b/src/routes/recurring-expenses/expense/+page.server.js
index e6009d6..a1eb745 100644
--- a/src/routes/recurring-expenses/expense/+page.server.js
+++ b/src/routes/recurring-expenses/expense/+page.server.js
@@ -1,5 +1,5 @@
import { fail, redirect } from '@sveltejs/kit';
-import { formatAmount, formatDate } from '$lib/format-inputs.js';
+import { getRecurringEntryFormData } from '$lib/format-recurring.js';
import { gateDynamicPage } from '$lib/gate-dynamic-page.js';
export const load = async ({ locals: { supabase } }) => {
@@ -19,23 +19,8 @@ export const load = async ({ locals: { supabase } }) => {
export const actions = {
default: async ({ request, locals: { supabase } }) => {
const formData = await request.formData();
- const amount = formatAmount(formData.get('amount'));
-
- const selectedCategory = formData.get('category');
- const newCategory = formData.get('new-category');
- const category = newCategory ?? selectedCategory;
-
- const description = formData.get('description') || category;
-
- const now = new Date();
- const year = formData.get('year') ?? now.getFullYear();
- const month = formData.get('month') ?? now.getMonth() + 1;
- const day = formData.get('day') ?? now.getDate();
- const date = formatDate(year, month, day);
-
- const frequency = formData.get('frequency');
- const days_of_month = formData.getAll('days-of-month') ?? [];
- const active = !!formData.get('active');
+ const { amount, category, description, date, frequency, days_of_month, active } =
+ getRecurringEntryFormData(formData);
const {
data: { user },
diff --git a/src/routes/recurring-expenses/expense/[id]/+page.server.js b/src/routes/recurring-expenses/expense/[id]/+page.server.js
index b076024..e3e4a18 100644
--- a/src/routes/recurring-expenses/expense/[id]/+page.server.js
+++ b/src/routes/recurring-expenses/expense/[id]/+page.server.js
@@ -1,5 +1,5 @@
import { fail, redirect } from '@sveltejs/kit';
-import { formatAmount, formatDate } from '$lib/format-inputs.js';
+import { getRecurringEntryFormData, formatEntry } from '$lib/format-recurring.js';
import { gateDynamicPage } from '$lib/gate-dynamic-page.js';
const getCategories = async (supabase) => {
@@ -21,7 +21,7 @@ const getExpense = async (supabase, id) => {
throw new Error('Could not find the specified expense.');
}
- return expense[0];
+ return formatEntry(expense[0]);
};
export const load = async ({ params: { id }, locals: { supabase } }) => {
@@ -44,23 +44,8 @@ export const actions = {
const formData = await request.formData();
const id = formData.get('id');
- const amount = formatAmount(formData.get('amount'));
-
- const selectedCategory = formData.get('category');
- const newCategory = formData.get('new-category');
- const category = newCategory ?? selectedCategory;
-
- const description = formData.get('description') || category;
-
- const now = new Date();
- const year = formData.get('year') ?? now.getFullYear();
- const month = formData.get('month') ?? now.getMonth() + 1;
- const day = formData.get('day') ?? now.getDate();
- const date = formatDate(year, month, day);
-
- const frequency = formData.get('frequency');
- const days_of_month = formData.getAll('days-of-month') ?? [];
- const active = !!formData.get('active');
+ const { amount, category, description, date, frequency, days_of_month, active } =
+ getRecurringEntryFormData(formData);
const updated_at = new Date();
diff --git a/src/routes/recurring-expenses/expense/[id]/+page.svelte b/src/routes/recurring-expenses/expense/[id]/+page.svelte
index 9f7f950..d030825 100644
--- a/src/routes/recurring-expenses/expense/[id]/+page.svelte
+++ b/src/routes/recurring-expenses/expense/[id]/+page.svelte
@@ -17,6 +17,8 @@
diff --git a/src/routes/recurring-income/income/+page.server.js b/src/routes/recurring-income/income/+page.server.js
index 98ccab1..e77a62b 100644
--- a/src/routes/recurring-income/income/+page.server.js
+++ b/src/routes/recurring-income/income/+page.server.js
@@ -1,5 +1,5 @@
import { fail, redirect } from '@sveltejs/kit';
-import { formatAmount, formatDate } from '$lib/format-inputs.js';
+import { getRecurringEntryFormData } from '$lib/format-recurring.js';
import { gateDynamicPage } from '$lib/gate-dynamic-page.js';
export const load = async ({ locals: { supabase } }) => {
@@ -19,23 +19,8 @@ export const load = async ({ locals: { supabase } }) => {
export const actions = {
default: async ({ request, locals: { supabase } }) => {
const formData = await request.formData();
- const amount = formatAmount(formData.get('amount'));
-
- const selectedCategory = formData.get('category');
- const newCategory = formData.get('new-category');
- const category = newCategory ?? selectedCategory;
-
- const description = formData.get('description') || category;
-
- const now = new Date();
- const year = formData.get('year') ?? now.getFullYear();
- const month = formData.get('month') ?? now.getMonth() + 1;
- const day = formData.get('day') ?? now.getDate();
- const date = formatDate(year, month, day);
-
- const frequency = formData.get('frequency');
- const days_of_month = formData.getAll('days-of-month') ?? [];
- const active = !!formData.get('active');
+ const { amount, category, description, date, frequency, days_of_month, active } =
+ getRecurringEntryFormData(formData);
const {
data: { user },
diff --git a/src/routes/recurring-income/income/[id]/+page.server.js b/src/routes/recurring-income/income/[id]/+page.server.js
index c5cd194..fa786c0 100644
--- a/src/routes/recurring-income/income/[id]/+page.server.js
+++ b/src/routes/recurring-income/income/[id]/+page.server.js
@@ -1,5 +1,5 @@
import { fail, redirect } from '@sveltejs/kit';
-import { formatAmount, formatDate } from '$lib/format-inputs.js';
+import { getRecurringEntryFormData, formatEntry } from '$lib/format-recurring.js';
import { gateDynamicPage } from '$lib/gate-dynamic-page.js';
const getCategories = async (supabase) => {
@@ -21,7 +21,7 @@ const getIncome = async (supabase, id) => {
throw new Error('Could not find the specified income.');
}
- return income[0];
+ return formatEntry(income[0]);
};
export const load = async ({ params: { id }, locals: { supabase } }) => {
@@ -44,23 +44,8 @@ export const actions = {
const formData = await request.formData();
const id = formData.get('id');
- const amount = formatAmount(formData.get('amount'));
-
- const selectedCategory = formData.get('category');
- const newCategory = formData.get('new-category');
- const category = newCategory ?? selectedCategory;
-
- const description = formData.get('description') || category;
-
- const now = new Date();
- const year = formData.get('year') ?? now.getFullYear();
- const month = formData.get('month') ?? now.getMonth() + 1;
- const day = formData.get('day') ?? now.getDate();
- const date = formatDate(year, month, day);
-
- const frequency = formData.get('frequency');
- const days_of_month = formData.getAll('days-of-month') ?? [];
- const active = !!formData.get('active');
+ const { amount, category, description, date, frequency, days_of_month, active } =
+ getRecurringEntryFormData(formData);
const updated_at = new Date();
diff --git a/src/routes/recurring-income/income/[id]/+page.svelte b/src/routes/recurring-income/income/[id]/+page.svelte
index dd6a5a6..025cfc9 100644
--- a/src/routes/recurring-income/income/[id]/+page.svelte
+++ b/src/routes/recurring-income/income/[id]/+page.svelte
@@ -17,6 +17,8 @@