From cbd13cbae3ea5e6af21edd6103364b68aef17a7d Mon Sep 17 00:00:00 2001 From: Knee Cola Date: Tue, 18 Nov 2025 23:51:23 +0100 Subject: [PATCH] Add seenByTenant tracking feature MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add seenByTenant field to BillingLocation interface - Implement setSeenByTenant function to mark locations as viewed by tenant - Checks if flag is already set to avoid unnecessary DB updates - Includes TypeDoc documentation - Update LocationViewPage to call setSeenByTenant when non-owner visits - Add seenByTenant to fetchAllLocations projection - Update LocationCard to show "seen by tenant" status indicator - Displays in "Monthly statement" fieldset with checkmark icon - Shows alongside monthly expense total - Add localization strings for monthly statement and seen status 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../share/location/[id]/LocationViewPage.tsx | 12 ++++++- app/lib/actions/locationActions.ts | 35 ++++++++++++++++++- app/lib/db-types.ts | 2 ++ app/ui/LocationCard.tsx | 34 +++++++++++++----- messages/en.json | 4 ++- messages/hr.json | 4 ++- 6 files changed, 79 insertions(+), 12 deletions(-) diff --git a/app/[locale]/share/location/[id]/LocationViewPage.tsx b/app/[locale]/share/location/[id]/LocationViewPage.tsx index d155069..60fcf12 100644 --- a/app/[locale]/share/location/[id]/LocationViewPage.tsx +++ b/app/[locale]/share/location/[id]/LocationViewPage.tsx @@ -1,6 +1,7 @@ import { ViewLocationCard } from '@/app/ui/ViewLocationCard'; -import { fetchLocationById } from '@/app/lib/actions/locationActions'; +import { fetchLocationById, setSeenByTenant } from '@/app/lib/actions/locationActions'; import { notFound } from 'next/navigation'; +import { myAuth } from '@/app/lib/auth'; export default async function LocationViewPage({ locationId }: { locationId:string }) { const location = await fetchLocationById(locationId); @@ -9,5 +10,14 @@ export default async function LocationViewPage({ locationId }: { locationId:stri return(notFound()); } + // Check if the page was accessed by an authenticated user who is the owner + const session = await myAuth(); + const isOwner = session?.user?.id === location.userId; + + // If the page is not visited by the owner, mark it as seen by tenant + if (!isOwner) { + await setSeenByTenant(locationId); + } + return (); } \ No newline at end of file diff --git a/app/lib/actions/locationActions.ts b/app/lib/actions/locationActions.ts index ae36b81..74493de 100644 --- a/app/lib/actions/locationActions.ts +++ b/app/lib/actions/locationActions.ts @@ -411,6 +411,7 @@ export const fetchAllLocations = withUser(async (user:AuthenticatedUser, year:nu "yearMonth.year": 1, "yearMonth.month": 1, "bills": 1, + "seenByTenant": 1, // "bills.attachment": 0, // "bills.notes": 0, // "bills.barcodeImage": 1, @@ -529,4 +530,36 @@ export const deleteLocationById = withUser(async (user:AuthenticatedUser, locati message: null, errors: undefined, }; -}) \ No newline at end of file +}) + +/** + * Sets the `seenByTenant` flag to true for a specific location. + * + * This function marks a location as viewed by the tenant. It first checks if the flag + * is already set to true to avoid unnecessary database updates. + * + * @param {string} locationID - The ID of the location to update + * @returns {Promise} + * + * @example + * await setSeenByTenant("507f1f77bcf86cd799439011"); + */ +export const setSeenByTenant = async (locationID: string): Promise => { + const dbClient = await getDbClient(); + + // First check if the location exists and if seenByTenant is already true + const location = await dbClient.collection("lokacije") + .findOne({ _id: locationID }); + + // If location doesn't exist or seenByTenant is already true, no update needed + if (!location || location.seenByTenant === true) { + return; + } + + // Update the location to mark it as seen by tenant + await dbClient.collection("lokacije") + .updateOne( + { _id: locationID }, + { $set: { seenByTenant: true } } + ); +} \ No newline at end of file diff --git a/app/lib/db-types.ts b/app/lib/db-types.ts index ad1982c..fda8898 100644 --- a/app/lib/db-types.ts +++ b/app/lib/db-types.ts @@ -63,6 +63,8 @@ export interface BillingLocation { rentDueDay?: number | null; /** (optional) monthly rent amount in cents */ rentAmount?: number | null; + /** (optional) whether the location has been seen by tenant */ + seenByTenant?: boolean | null; }; export enum BilledTo { diff --git a/app/ui/LocationCard.tsx b/app/ui/LocationCard.tsx index d6099e1..1e807f6 100644 --- a/app/ui/LocationCard.tsx +++ b/app/ui/LocationCard.tsx @@ -1,6 +1,6 @@ 'use client'; -import { Cog8ToothIcon, PlusCircleIcon, ShareIcon } from "@heroicons/react/24/outline"; +import { CheckCircleIcon, Cog8ToothIcon, PlusCircleIcon, ShareIcon, BanknotesIcon } from "@heroicons/react/24/outline"; import { FC } from "react"; import { BillBadge } from "./BillBadge"; import { BillingLocation } from "../lib/db-types"; @@ -14,7 +14,10 @@ export interface LocationCardProps { location: BillingLocation } -export const LocationCard:FC = ({location: { _id, name, yearMonth, bills }}) => { +export const LocationCard:FC = ({location}) => { + const { _id, name, yearMonth, bills, seenByTenant } = location; + + console.log("seenByTenant:", seenByTenant); const t = useTranslations("home-page.location-card"); const currentLocale = useLocale(); @@ -46,12 +49,27 @@ export const LocationCard:FC = ({location: { _id, name, yearM {t("add-bill-button-tooltip")} - { - monthlyExpense > 0 ? -

- { t("payed-total-label") } ${formatCurrency(monthlyExpense)} -

- : null + + + { monthlyExpense > 0 || seenByTenant ? + +
+ {t("monthly-statement-legend")} + { + monthlyExpense > 0 ? +
+ + { t("payed-total-label") } ${formatCurrency(monthlyExpense)} +
+ : null + } + {seenByTenant && ( +
+ + {t("seen-by-tenant-label")} +
+ )} +
: null } diff --git a/messages/en.json b/messages/en.json index 6e71016..379ca49 100644 --- a/messages/en.json +++ b/messages/en.json @@ -55,7 +55,9 @@ "edit-card-tooltip": "Edit realestate", "add-bill-button-tooltip": "Add a new bill", "payed-total-label": "Payed total:", - "link-copy-message": "Link copied to clipboard" + "link-copy-message": "Link copied to clipboard", + "monthly-statement-legend": "Monthly statement", + "seen-by-tenant-label": "Seen by tenant" }, "month-card": { "payed-total-label": "Total monthly expenditure:", diff --git a/messages/hr.json b/messages/hr.json index e809bf1..0e15f87 100644 --- a/messages/hr.json +++ b/messages/hr.json @@ -55,7 +55,9 @@ "edit-card-tooltip": "Izmjeni nekretninu", "add-bill-button-tooltip": "Dodaj novi račun", "payed-total-label": "Ukupno plaćeno:", - "link-copy-message": "Link kopiran na clipboard" + "link-copy-message": "Link kopiran na clipboard", + "monthly-statement-legend": "Obračun", + "seen-by-tenant-label": "Viđeno od strane podstanara" }, "month-card": { "payed-total-label": "Ukupni mjesečni trošak:",