241 lines
8.5 KiB
TypeScript
241 lines
8.5 KiB
TypeScript
// routes/tenant/documents/index.tsx
|
|
import { define } from "../../../utils.ts";
|
|
import { page } from "fresh";
|
|
import { getLeasesByTenant } from "../../../services/leaseService.ts";
|
|
import {
|
|
createLeaseDocument,
|
|
getDocumentsByLease,
|
|
signDocumentAsTenant,
|
|
} from "../../../services/leaseDocumentService.ts";
|
|
import { isE2EEnabled } from "../../../services/e2eFixtures.ts";
|
|
import { Lease, LeaseDocument } from "../../../db/interfaces.ts";
|
|
|
|
function formatDate(value?: number | null): string {
|
|
if (!value) return "-";
|
|
return new Date(value).toLocaleDateString();
|
|
}
|
|
|
|
export const handler = define.handlers({
|
|
async GET(ctx) {
|
|
const { user_id } = ctx.state.data;
|
|
|
|
if (!user_id) {
|
|
return new Response("", {
|
|
status: 302,
|
|
headers: { Location: "/auth/login" },
|
|
});
|
|
}
|
|
|
|
try {
|
|
const leases = await getLeasesByTenant(user_id);
|
|
const documents: Record<string, LeaseDocument[]> = {};
|
|
|
|
for (const lease of leases || []) {
|
|
if (!lease.id) continue;
|
|
documents[lease.id] = await getDocumentsByLease(lease.id);
|
|
}
|
|
|
|
return page({
|
|
leases,
|
|
documents,
|
|
isE2E: isE2EEnabled(),
|
|
});
|
|
} catch (error) {
|
|
console.error("Error loading tenant documents:", error);
|
|
return page({
|
|
error: "Failed to load documents",
|
|
});
|
|
}
|
|
},
|
|
|
|
async POST(ctx) {
|
|
const { user_id } = ctx.state.data;
|
|
|
|
if (!user_id) {
|
|
return new Response("", {
|
|
status: 302,
|
|
headers: { Location: "/auth/login" },
|
|
});
|
|
}
|
|
|
|
const formData = await ctx.req.formData();
|
|
const action = (formData.get("action") || "").toString();
|
|
const documentId = (formData.get("document_id") || "").toString();
|
|
const leaseId = (formData.get("lease_id") || "").toString();
|
|
|
|
if (action === "sign" && documentId) {
|
|
await signDocumentAsTenant(documentId);
|
|
}
|
|
|
|
if (action === "add_sample" && leaseId) {
|
|
await createLeaseDocument(
|
|
leaseId,
|
|
"receipt",
|
|
"/static/sample-receipt.pdf",
|
|
"sample-receipt.pdf",
|
|
);
|
|
}
|
|
|
|
return new Response("", {
|
|
status: 302,
|
|
headers: { Location: "/tenant/documents" },
|
|
});
|
|
},
|
|
});
|
|
|
|
export default define.page(function TenantDocuments({ data }) {
|
|
const { leases, documents, error, isE2E } = data as {
|
|
leases: Lease[];
|
|
documents: Record<string, LeaseDocument[]>;
|
|
error?: string;
|
|
isE2E?: boolean;
|
|
};
|
|
|
|
return (
|
|
<div class="min-h-screen bg-gray-50">
|
|
<div class="bg-white shadow">
|
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
<div class="flex justify-between items-center py-6">
|
|
<div>
|
|
<h1 class="text-3xl font-bold text-gray-900">Documents</h1>
|
|
<p class="mt-1 text-sm text-gray-500">
|
|
Access your lease agreements, receipts, and notices
|
|
</p>
|
|
</div>
|
|
<div class="flex space-x-3">
|
|
<a
|
|
href="/tenant/dashboard"
|
|
class="inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50"
|
|
>
|
|
Back to Dashboard
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
|
{error && (
|
|
<div class="mb-6 bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded">
|
|
{error}
|
|
</div>
|
|
)}
|
|
|
|
{(leases || []).map((lease) => {
|
|
const leaseDocs = documents?.[lease.id] || [];
|
|
return (
|
|
<div key={lease.id} class="mb-8 bg-white shadow rounded-lg">
|
|
<div class="px-6 py-4 border-b border-gray-200">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<h2 class="text-lg font-semibold text-gray-900">
|
|
{lease.property_address || "Lease Documents"}
|
|
</h2>
|
|
<p class="text-sm text-gray-500">
|
|
Lease ID: {lease.id}
|
|
</p>
|
|
</div>
|
|
{isE2E && (
|
|
<form method="POST">
|
|
<input type="hidden" name="action" value="add_sample" />
|
|
<input type="hidden" name="lease_id" value={lease.id} />
|
|
<button
|
|
type="submit"
|
|
class="inline-flex items-center px-3 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700"
|
|
>
|
|
Add Sample Receipt
|
|
</button>
|
|
</form>
|
|
)}
|
|
</div>
|
|
</div>
|
|
<div class="p-6">
|
|
<table class="min-w-full divide-y divide-gray-200">
|
|
<thead class="bg-gray-50">
|
|
<tr>
|
|
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">
|
|
Type
|
|
</th>
|
|
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">
|
|
File
|
|
</th>
|
|
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">
|
|
Tenant Status
|
|
</th>
|
|
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">
|
|
Landlord Status
|
|
</th>
|
|
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">
|
|
Signed
|
|
</th>
|
|
<th class="px-4 py-2"></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="divide-y divide-gray-200">
|
|
{leaseDocs.length > 0
|
|
? (
|
|
leaseDocs.map((doc) => (
|
|
<tr key={doc.id}>
|
|
<td class="px-4 py-2 text-sm text-gray-900 capitalize">
|
|
{doc.document_type || "document"}
|
|
</td>
|
|
<td class="px-4 py-2 text-sm text-blue-600">
|
|
<a href={doc.file_url || "#"}>
|
|
{doc.file_name || "View"}
|
|
</a>
|
|
</td>
|
|
<td class="px-4 py-2 text-sm text-gray-900">
|
|
{doc.signed_by_tenant ? "Signed" : "Pending"}
|
|
</td>
|
|
<td class="px-4 py-2 text-sm text-gray-900">
|
|
{doc.signed_by_landlord ? "Signed" : "Pending"}
|
|
</td>
|
|
<td class="px-4 py-2 text-sm text-gray-900">
|
|
{formatDate(doc.signature_date)}
|
|
</td>
|
|
<td class="px-4 py-2 text-sm">
|
|
{!doc.signed_by_tenant && (
|
|
<form method="POST">
|
|
<input
|
|
type="hidden"
|
|
name="action"
|
|
value="sign"
|
|
/>
|
|
<input
|
|
type="hidden"
|
|
name="document_id"
|
|
value={doc.id}
|
|
/>
|
|
<button
|
|
type="submit"
|
|
class="text-blue-600 hover:text-blue-500"
|
|
>
|
|
Sign as Tenant
|
|
</button>
|
|
</form>
|
|
)}
|
|
</td>
|
|
</tr>
|
|
))
|
|
)
|
|
: (
|
|
<tr>
|
|
<td
|
|
colspan={6}
|
|
class="px-4 py-4 text-center text-sm text-gray-500"
|
|
>
|
|
No documents available yet
|
|
</td>
|
|
</tr>
|
|
)}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
);
|
|
});
|