Merge pull request #44 from nathanAndrinoid/open-swe/11a3ae30-7b0d-4b7e-937c-38d32a878cac

[WIP]: Update TODO.md and add Create New Lease button to landlord dashboard
This commit is contained in:
nathanAndrinoid 2026-01-21 20:25:58 -06:00 committed by GitHub
commit 3c27be316b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 77 additions and 28 deletions

View File

@ -5,10 +5,6 @@ development plans for the application.
## High Priority ## High Priority
- **Implement Payment Processing**: The current rent payment system in
`routes/tenant/rent/pay/index.tsx` is a placeholder. It needs to be fully
implemented with a payment provider like Stripe to handle real transactions
securely.
## Backend & API ## Backend & API
@ -27,6 +23,7 @@ development plans for the application.
- Tenant invitation and management (`islands/LandlordInviteTenant.tsx`) - Tenant invitation and management (`islands/LandlordInviteTenant.tsx`)
- Messaging with tenants (`islands/LandlordMessageThreads.tsx`) - Messaging with tenants (`islands/LandlordMessageThreads.tsx`)
- Financial tracking and reporting - Financial tracking and reporting
- **Complete Tenant Dashboard**: Finalize all features under the - **Complete Tenant Dashboard**: Finalize all features under the
`routes/tenant/` directory. This includes: `routes/tenant/` directory. This includes:
- Viewing lease documents - Viewing lease documents

View File

@ -29,7 +29,6 @@ export default function AddLeaseDialog(
.toISOString().split("T")[0], .toISOString().split("T")[0],
rent: propertyCost !== undefined ? String(propertyCost) : "", rent: propertyCost !== undefined ? String(propertyCost) : "",
security_deposit: "", security_deposit: "",
lease_status: "",
}); });
return ( return (
@ -102,14 +101,6 @@ export default function AddLeaseDialog(
onInput={(e) => onInput={(e) =>
setForm({ ...form, security_deposit: e.currentTarget.value })} setForm({ ...form, security_deposit: e.currentTarget.value })}
/> />
<input
name="lease_status"
placeholder="Lease Status"
class="border p-2 rounded w-full"
value={form.lease_status}
onInput={(e) =>
setForm({ ...form, lease_status: e.currentTarget.value })}
/>
<div class="flex justify-end gap-2 mt-4"> <div class="flex justify-end gap-2 mt-4">
<button <button
type="button" type="button"

View File

@ -1,4 +1,3 @@
// islands/AsyncYesNoPop.ts
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import AddLeaseDialog from "./AddLeaseDialog.tsx"; import AddLeaseDialog from "./AddLeaseDialog.tsx";
import { Lease, Property } from "../../db/interfaces.ts"; import { Lease, Property } from "../../db/interfaces.ts";
@ -6,11 +5,13 @@ import { Lease, Property } from "../../db/interfaces.ts";
export function PopAddLeaseDialog(leases: Lease[], properties: Property[]) { export function PopAddLeaseDialog(leases: Lease[], properties: Property[]) {
const [popup, setPopup] = useState< const [popup, setPopup] = useState<
{ {
propertyId: string; propertyId: string | null;
popupKey: number; popupKey: number;
resolve: (value: boolean) => void; resolve: (value: boolean) => void;
} | null } | null
>(null); >(null);
const [selectedPropertyId, setSelectedPropertyId] = useState("");
const [showPropertySelection, setShowPropertySelection] = useState(false);
function getPropertyInfo(propertyId: string) { function getPropertyInfo(propertyId: string) {
const property = properties.find((p) => p.id === propertyId); const property = properties.find((p) => p.id === propertyId);
@ -25,16 +26,21 @@ export function PopAddLeaseDialog(leases: Lease[], properties: Property[]) {
// Show the popup and return a Promise for the answer // Show the popup and return a Promise for the answer
const popAddLease = ( const popAddLease = (
propertyId: string, propertyId?: string,
popupKey: number, popupKey?: number,
): Promise<boolean> => { ): Promise<boolean> => {
return new Promise((resolve) => { return new Promise((resolve) => {
setPopup({ propertyId, popupKey, resolve }); if (propertyId) {
setPopup({ propertyId, popupKey: popupKey || 0, resolve });
} else {
setShowPropertySelection(true);
setPopup({ propertyId: null, popupKey: popupKey || 0, resolve });
}
}); });
}; };
// Render the popup if needed // Render the popup if needed
const popupAddLeaseDialog = popup const popupAddLeaseDialog = popup && popup.propertyId
? (() => { ? (() => {
const { title, address, price } = getPropertyInfo(popup.propertyId); const { title, address, price } = getPropertyInfo(popup.propertyId);
return ( return (
@ -48,11 +54,63 @@ export function PopAddLeaseDialog(leases: Lease[], properties: Property[]) {
onClose={() => { onClose={() => {
popup.resolve(false); popup.resolve(false);
setPopup(null); setPopup(null);
setShowPropertySelection(false);
setSelectedPropertyId("");
}} }}
/> />
); );
})() })()
: null; : null;
return [popAddLease, popupAddLeaseDialog] as const; // Render property selection dialog if no propertyId provided
const propertySelectionDialog = showPropertySelection
? (() => {
return (
<div class="fixed inset-0 flex items-center justify-center bg-black bg-opacity-30 z-50">
<div class="bg-white p-6 rounded shadow-lg w-full max-w-md space-y-4">
<h3 class="text-xl font-bold mb-2">Select Property</h3>
<select
class="border p-2 rounded w-full"
value={selectedPropertyId}
onInput={(e) => setSelectedPropertyId(e.currentTarget.value)}
>
<option value="">-- Select a property --</option>
{properties.map((p) => (
<option key={p.id} value={p.id}>
{p.title} - {p.address_1}
</option>
))}
</select>
<div class="flex justify-end gap-2 mt-4">
<button
type="button"
class="px-4 py-2 rounded border"
onClick={() => {
if (popup) popup.resolve(false);
setPopup(null);
setShowPropertySelection(false);
setSelectedPropertyId("");
}}
>
Cancel
</button>
<button
type="button"
class="px-4 py-2 rounded bg-blue-600 text-white"
disabled={!selectedPropertyId}
onClick={() => {
setPopup({ ...popup!, propertyId: selectedPropertyId });
setShowPropertySelection(false);
}}
>
Continue
</button>
</div>
</div>
</div>
);
})()
: null;
return [popAddLease, popupAddLeaseDialog || propertySelectionDialog] as const;
} }

View File

@ -1,15 +1,16 @@
// routes/api/create/lease.ts // routes/api/create/lease.ts
import { define } from "../../../utils.ts"; import { define } from "../../../utils.ts";
import { createLease, getProperty } from "../../../services/propertyService.ts"; import { createLease } from "../../../services/leaseService.ts";
import { getProperty } from "../../../services/propertyService.ts";
import { SessionCookie } from "../../auth/callback.ts"; import { SessionCookie } from "../../auth/callback.ts";
export const handler = define.handlers({ export const handler = define.handlers({
async POST(ctx) { async POST(ctx) {
try { try {
const stateData: SessionCookie = ctx.state.data; const _stateData: SessionCookie = ctx.state.data;
const form = await ctx.req.formData(); const form = await ctx.req.formData();
const sender = form.get("sender"); const sender = form.get("sender");
const tenant_id = stateData.user_id; const tenant_id = form.get("tenant_id")?.toString();
const propertyId = form.get("property_id")?.toString() || ""; const propertyId = form.get("property_id")?.toString() || "";
const title = form.get("title")?.toString() || ""; const title = form.get("title")?.toString() || "";
const rent = Number(form.get("rent")); const rent = Number(form.get("rent"));
@ -19,18 +20,20 @@ export const handler = define.handlers({
const end_date = form.get("end_date") const end_date = form.get("end_date")
? Date.parse(form.get("end_date")?.toString() + "T00:00:00") ? Date.parse(form.get("end_date")?.toString() + "T00:00:00")
: null; : null;
const security_deposit = Number(form.get("security_deposit")) || 0;
const property = await getProperty(propertyId); const property = await getProperty(propertyId);
if (property.length == 1 && start_date && end_date) { if (property.length == 1 && start_date && end_date) {
const owner_id = property[0].owner_id as string; const owner_id = property[0].owner_id as string;
await createLease( await createLease(
propertyId,
tenant_id,
owner_id,
title, title,
rent, propertyId,
owner_id,
start_date, start_date,
end_date, end_date,
rent,
security_deposit,
tenant_id,
); );
} }

View File

@ -57,7 +57,7 @@ export async function createLease(
end_date, end_date,
rent, rent,
security_deposit, security_deposit,
lease_status: "draft", lease_status: "new",
lease_document_url: null, lease_document_url: null,
payment_schedule_id: payment_schedule_id || null, payment_schedule_id: payment_schedule_id || null,
late_fee_rules: null, late_fee_rules: null,