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'} +
+ + +
+
+ Unit of measurement +
+
+ + +
+
+ + +
+
+ + +
+
+
+ {: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 @@