// Name: Proportional Rent Calculator // Description: Calculate proportional rent between two dates with additional expenses // Author: BeSpunky // GitHub: BeSpunky import "@johnlindquist/kit" // Get rent calculation parameters const fullRent = parseFloat(await arg("Enter full monthly rent amount:")) const additionalExpenses = parseFloat(await arg("Enter additional monthly expenses:") || "0") const startDate = new Date(await arg("Enter start date (YYYY-MM-DD):")) const endDate = new Date(await arg("Enter end date (YYYY-MM-DD):")) // Validate dates if (startDate >= endDate) { await div(md("❌ **Error:** Start date must be before end date")) exit() } // Calculate proportional rent function calculateProportionalRent(startDate, endDate, monthlyRent, monthlyExpenses) { const totalMonthlyAmount = monthlyRent + monthlyExpenses let totalAmount = 0 const current = new Date(startDate) const calculations = [] while (current < endDate) { const year = current.getFullYear() const month = current.getMonth() // Get first and last day of current month const firstDayOfMonth = new Date(year, month, 1) const lastDayOfMonth = new Date(year, month + 1, 0) // Determine the actual start and end dates for this month const periodStart = current < firstDayOfMonth ? firstDayOfMonth : current const periodEnd = endDate > lastDayOfMonth ? lastDayOfMonth : new Date(endDate.getTime() - 24 * 60 * 60 * 1000) // Subtract 1 day since end date is exclusive if (periodStart <= periodEnd) { const daysInMonth = lastDayOfMonth.getDate() const daysInPeriod = Math.floor((periodEnd - periodStart) / (24 * 60 * 60 * 1000)) + 1 const proportionalAmount = (totalMonthlyAmount / daysInMonth) * daysInPeriod totalAmount += proportionalAmount calculations.push({ month: `${year}-${String(month + 1).padStart(2, '0')}`, daysInMonth, daysInPeriod, dailyRate: totalMonthlyAmount / daysInMonth, amount: proportionalAmount, startDate: periodStart.toISOString().split('T')[0], endDate: periodEnd.toISOString().split('T')[0] }) } // Move to next month current.setMonth(current.getMonth() + 1) current.setDate(1) } return { totalAmount, calculations } } const result = calculateProportionalRent(startDate, endDate, fullRent, additionalExpenses) // Format results const formatCurrency = (amount) => `$${amount.toFixed(2)}` let reportContent = `# Proportional Rent Calculation ## Summary - **Full Monthly Rent:** ${formatCurrency(fullRent)} - **Additional Monthly Expenses:** ${formatCurrency(additionalExpenses)} - **Total Monthly Amount:** ${formatCurrency(fullRent + additionalExpenses)} - **Period:** ${startDate.toISOString().split('T')[0]} to ${endDate.toISOString().split('T')[0]} - **Total Days:** ${Math.floor((endDate - startDate) / (24 * 60 * 60 * 1000))} ## **Total Proportional Amount: ${formatCurrency(result.totalAmount)}** ## Monthly Breakdown | Month | Period | Days Used | Days in Month | Daily Rate | Amount | |-------|--------|-----------|---------------|------------|--------| ` result.calculations.forEach(calc => { reportContent += `| ${calc.month} | ${calc.startDate} to ${calc.endDate} | ${calc.daysInPeriod} | ${calc.daysInMonth} | ${formatCurrency(calc.dailyRate)} | ${formatCurrency(calc.amount)} |\n` }) // Display results await div({ html: md(reportContent), shortcuts: [ { name: 'Copy Total Amount', key: `${cmd}+c`, onPress: () => { copy(formatCurrency(result.totalAmount)) toast('Total amount copied to clipboard!') }, bar: 'right' }, { name: 'Copy Full Report', key: `${cmd}+shift+c`, onPress: () => { copy(reportContent) toast('Full report copied to clipboard!') }, bar: 'right' } ] })// Name: Proportional Rent Calculator // Description: Calculate proportional rent between two dates with additional expenses // Author: BeSpunky // GitHub: BeSpunky import "@johnlindquist/kit" interface ExpenseItem { name: string amount: number } interface MonthlyCalculation { month: string daysInMonth: number daysInPeriod: number dailyRate: number amount: number startDate: string endDate: string } interface RentCalculationResult { totalAmount: number calculations: MonthlyCalculation[] } /** * Validates that a date string is in correct format and creates a valid Date object */ function parseAndValidateDate(dateString: string, fieldName: string): Date { const date = new Date(dateString) if (isNaN(date.getTime())) { throw new Error(`Invalid ${fieldName}. Please use YYYY-MM-DD format.`) } return date } /** * Validates that a number string is a valid positive number */ function parseAndValidateAmount(amountString: string, fieldName: string): number { const amount = parseFloat(amountString) if (isNaN(amount) || amount < 0) { throw new Error(`Invalid ${fieldName}. Please enter a positive number.`) } return amount } /** * Calculates the number of days between two dates (inclusive of start, exclusive of end) */ function getDaysBetween(startDate: Date, endDate: Date): number { return Math.floor((endDate.getTime() - startDate.getTime()) / (24 * 60 * 60 * 1000)) } /** * Gets the last day of a given month */ function getLastDayOfMonth(year: number, month: number): Date { return new Date(year, month + 1, 0) } /** * Formats a date as YYYY-MM-DD string */ function formatDate(date: Date): string { return date.toISOString().split('T')[0] } /** * Formats a number as currency string */ function formatCurrency(amount: number): string { return `$${amount.toFixed(2)}` } /** * Calculates proportional rent across multiple months */ function calculateProportionalRent( startDate: Date, endDate: Date, totalMonthlyAmount: number ): RentCalculationResult { let totalAmount = 0 const calculations: MonthlyCalculation[] = [] // Create a working date starting from the first day of the start month const currentDate = new Date(startDate) while (currentDate < endDate) { const year = currentDate.getFullYear() const month = currentDate.getMonth() // Calculate the period boundaries for this month const firstDayOfMonth = new Date(year, month, 1) const lastDayOfMonth = getLastDayOfMonth(year, month) // Determine actual start and end dates for this month's calculation const periodStart = currentDate < firstDayOfMonth ? firstDayOfMonth : new Date(currentDate) const periodEnd = endDate > lastDayOfMonth ? lastDayOfMonth : new Date(endDate.getTime() - 24 * 60 * 60 * 1000) // Only calculate if we have a valid period if (periodStart <= periodEnd) { const daysInMonth = lastDayOfMonth.getDate() const daysInPeriod = getDaysBetween(periodStart, periodEnd) + 1 // +1 because we want inclusive count const dailyRate = totalMonthlyAmount / daysInMonth const proportionalAmount = dailyRate * daysInPeriod totalAmount += proportionalAmount calculations.push({ month: `${year}-${String(month + 1).padStart(2, '0')}`, daysInMonth, daysInPeriod, dailyRate, amount: proportionalAmount, startDate: formatDate(periodStart), endDate: formatDate(periodEnd) }) } // Move to the first day of the next month currentDate.setMonth(currentDate.getMonth() + 1) currentDate.setDate(1) } return { totalAmount, calculations } } /** * Collects additional expenses from user input */ async function collectAdditionalExpenses(): Promise<ExpenseItem[]> { const expenses: ExpenseItem[] = [] while (true) { const action = await arg({ placeholder: expenses.length === 0 ? 'Add additional expenses (or press Enter to skip)' : 'Add another expense or press Enter to continue', strict: false, hint: expenses.length > 0 ? `Current expenses: ${expenses.map(e => `${e.name}: ${formatCurrency(e.amount)}`).join(', ')}` : 'Enter expense name or press Enter to skip' }) // If user presses Enter without typing anything, we're done if (!action || action.trim() === '') { break } try { const expenseName = action.trim() const amountString = await arg(`Enter amount for "${expenseName}":`) const amount = parseAndValidateAmount(amountString, `amount for ${expenseName}`) expenses.push({ name: expenseName, amount }) } catch (error) { await div(md(`❌ **Error:** ${error.message}`)) // Continue the loop to let user try again } } return expenses } /** * Generates the markdown report content */ function generateReport( fullRent: number, expenses: ExpenseItem[], startDate: Date, endDate: Date, result: RentCalculationResult ): string { const totalExpenses = expenses.reduce((sum, expense) => sum + expense.amount, 0) const totalMonthlyAmount = fullRent + totalExpenses const totalDays = getDaysBetween(startDate, endDate) let reportContent = `# Proportional Rent Calculation ## Summary - **Full Monthly Rent:** ${formatCurrency(fullRent)} - **Additional Monthly Expenses:** ${formatCurrency(totalExpenses)}` if (expenses.length > 0) { reportContent += `\n - ${expenses.map(e => `${e.name}: ${formatCurrency(e.amount)}`).join('\n - ')}` } reportContent += ` - **Total Monthly Amount:** ${formatCurrency(totalMonthlyAmount)} - **Period:** ${formatDate(startDate)} to ${formatDate(endDate)} - **Total Days:** ${totalDays} ## **Total Proportional Amount: ${formatCurrency(result.totalAmount)}** ## Monthly Breakdown | Month | Period | Days Used | Days in Month | Daily Rate | Amount | |-------|--------|-----------|---------------|------------|--------| ` result.calculations.forEach(calc => { reportContent += `| ${calc.month} | ${calc.startDate} to ${calc.endDate} | ${calc.daysInPeriod} | ${calc.daysInMonth} | ${formatCurrency(calc.dailyRate)} | ${formatCurrency(calc.amount)} |\n` }) return reportContent } // Main execution try { // Collect input data with validation const fullRentString = await arg("Enter full monthly rent amount:") const fullRent = parseAndValidateAmount(fullRentString, "monthly rent") const expenses = await collectAdditionalExpenses() const startDateString = await arg("Enter start date (YYYY-MM-DD):") const startDate = parseAndValidateDate(startDateString, "start date") const endDateString = await arg("Enter end date (YYYY-MM-DD):") const endDate = parseAndValidateDate(endDateString, "end date") // Validate date range if (startDate >= endDate) { await div(md("❌ **Error:** Start date must be before end date")) exit() } // Calculate total monthly amount const totalExpenses = expenses.reduce((sum, expense) => sum + expense.amount, 0) const totalMonthlyAmount = fullRent + totalExpenses // Perform calculation const result = calculateProportionalRent(startDate, endDate, totalMonthlyAmount) // Generate and display report const reportContent = generateReport(fullRent, expenses, startDate, endDate, result) await div({ html: md(reportContent), shortcuts: [ { name: 'Copy Total Amount', key: `${cmd}+c`, onPress: () => { copy(formatCurrency(result.totalAmount)) toast('Total amount copied to clipboard!') }, bar: 'right' }, { name: 'Copy Full Report', key: `${cmd}+shift+c`, onPress: () => { copy(reportContent) toast('Full report copied to clipboard!') }, bar: 'right' } ] }) } catch (error) { await div(md(`❌ **Error:** ${error.message}`)) exit() }