Merge branch 'feature/location-card-simplification' into develop

This commit is contained in:
Knee Cola
2025-11-26 20:32:34 +01:00
10 changed files with 78 additions and 54 deletions

View File

@@ -17,7 +17,7 @@ const Page: FC = async () => {
<div className="card card-compact card-bordered min-w-[20em] max-w-[90em] bg-base-100 shadow-s my-1">
<div className="card-body">
<h2 className="card-title"><AccountCircle /> {t('title')}</h2>
<Link href={`/${locale}/home`} className='btn btn-neutral'><HomeIcon className='text-green-300' /> {t('goto-home-button-label')}</Link>
<Link href={`/${locale}/home`} className='btn btn-neutral'><HomeIcon className='text-white'/> {t('goto-home-button-label')}</Link>
<Link href={`/${locale}/home/account/settings`} className='btn btn-neutral'><SettingsIcon className='text-blue-400' /> {t('goto-settings-button-label')}</Link>
<LogoutButton />
</div>

View File

@@ -1,5 +1,5 @@
import { ViewLocationCard } from '@/app/ui/ViewLocationCard';
import { fetchLocationById, setSeenByTenant } from '@/app/lib/actions/locationActions';
import { fetchLocationById, setSeenByTenantAt } from '@/app/lib/actions/locationActions';
import { getUserSettingsByUserId } from '@/app/lib/actions/userSettingsActions';
import { notFound } from 'next/navigation';
import { myAuth } from '@/app/lib/auth';
@@ -20,7 +20,7 @@ export default async function LocationViewPage({ locationId }: { locationId:stri
// If the page is not visited by the owner, mark it as seen by tenant
if (!isOwner) {
await setSeenByTenant(locationId);
await setSeenByTenantAt(locationId);
}
return (<ViewLocationCard location={location} userSettings={userSettings} />);

View File

@@ -428,7 +428,7 @@ export const fetchAllLocations = withUser(async (user:AuthenticatedUser, year:nu
"yearMonth.year": 1,
"yearMonth.month": 1,
"bills": 1,
"seenByTenant": 1,
"seenByTenantAt": 1,
// "bills.attachment": 0,
// "bills.notes": 0,
// "bills.hub3aText": 1,
@@ -555,7 +555,7 @@ export const deleteLocationById = withUser(async (user:AuthenticatedUser, locati
})
/**
* Sets the `seenByTenant` flag to true for a specific location.
* Sets the `seenByTenantAt` 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.
@@ -564,17 +564,17 @@ export const deleteLocationById = withUser(async (user:AuthenticatedUser, locati
* @returns {Promise<void>}
*
* @example
* await setSeenByTenant("507f1f77bcf86cd799439011");
* await setseenByTenantAt("507f1f77bcf86cd799439011");
*/
export const setSeenByTenant = async (locationID: string): Promise<void> => {
export const setSeenByTenantAt = async (locationID: string): Promise<void> => {
const dbClient = await getDbClient();
// First check if the location exists and if seenByTenant is already true
// First check if the location exists and if seenByTenantAt is already true
const location = await dbClient.collection<BillingLocation>("lokacije")
.findOne({ _id: locationID });
// If location doesn't exist or seenByTenant is already true, no update needed
if (!location || location.seenByTenant === true) {
// If location doesn't exist or seenByTenantAt is already true, no update needed
if (!location || location.seenByTenantAt) {
return;
}
@@ -582,7 +582,7 @@ export const setSeenByTenant = async (locationID: string): Promise<void> => {
await dbClient.collection<BillingLocation>("lokacije")
.updateOne(
{ _id: locationID },
{ $set: { seenByTenant: true } }
{ $set: { seenByTenantAt: new Date() } }
);
}

View File

@@ -40,7 +40,7 @@ export const addMonth = withUser(async (user:AuthenticatedUser, { year, month }:
// copy all the properties from the previous location
...prevLocation,
// clear properties specific to the month
seenByTenant: undefined,
seenByTenantAt: undefined,
utilBillsProofOfPaymentUploadedAt: undefined,
utilBillsProofOfPaymentAttachment: undefined,
// assign a new ID

View File

@@ -74,7 +74,7 @@ export interface BillingLocation {
/** (optional) monthly rent amount in cents */
rentAmount?: number | null;
/** (optional) whether the location has been seen by tenant */
seenByTenant?: boolean | null;
seenByTenantAt?: Date | null;
/** (optional) utility bills proof of payment attachment */
utilBillsProofOfPaymentAttachment?: BillAttachment|null;
/** (optional) date when utility bills proof of payment was uploaded */

View File

@@ -9,6 +9,7 @@ import { formatCurrency } from "../lib/formatStrings";
import Link from "next/link";
import { useLocale, useTranslations } from "next-intl";
import { toast } from "react-toastify";
import { get } from "http";
export interface LocationCardProps {
location: BillingLocation;
@@ -21,7 +22,7 @@ export const LocationCard: FC<LocationCardProps> = ({ location, currency }) => {
name,
yearMonth,
bills,
seenByTenant,
seenByTenantAt,
// NOTE: only the fileName is projected from the DB to reduce data transfer
utilBillsProofOfPaymentUploadedAt
} = location;
@@ -63,37 +64,47 @@ export const LocationCard: FC<LocationCardProps> = ({ location, currency }) => {
</Link>
<ShareIcon className="h-[1em] w-[1em] cursor-pointer text-2xl inline hover:text-red-500" title="create sharable link" onClick={handleCopyLinkClick} />
</div>
{monthlyExpense > 0 || seenByTenant || utilBillsProofOfPaymentUploadedAt ?
<>
<div className="divider m-0 font-bold uppercase">{t("monthly-statement-legend")}</div>
{
monthlyExpense > 0 ?
<div className="flex items-center gap-2 ml-2">
<BanknotesIcon className="h-5 w-5" />
{t("payed-total-label")} <strong>{formatCurrency(monthlyExpense, currency ?? "EUR")}</strong>
<CheckCircleIcon className="h-5 w-5 text-success" />
</div>
: null
}
{seenByTenant && (
<div className="flex items-center gap-2 mt-[-0.2rem] ml-2">
<EyeIcon className="h-5 w-5" />
<span className="text-sm">{t("seen-by-tenant-label")}</span>
<CheckCircleIcon className="h-5 w-5 text-success" />
{ monthlyExpense > 0 || seenByTenantAt || utilBillsProofOfPaymentUploadedAt ?
<>
<div className="flex ml-1">
<div className="divider divider-horizontal p-0 m-0"></div>
<div className="card rounded-box grid grow place-items-left place-items-top p-0">
{
monthlyExpense > 0 ?
<div className="flex ml-1">
<span className="w-5 min-w-5 mr-2"><BanknotesIcon className="mt-[.1rem]" /></span>
<span>
{t("payed-total-label")}&nbsp;<strong>{formatCurrency(monthlyExpense, currency ?? "EUR")}</strong>
<CheckCircleIcon className="h-5 w-5 ml-1 mt-[-.2rem] text-success inline-block" />
</span>
</div>
: null
}
{seenByTenantAt && (
<div className="flex mt-1 ml-1">
<span className="w-5 mr-2 min-w-5"><EyeIcon className="mt-[.1rem]" /></span>
<span>
<span>{t("seen-by-tenant-label")} at {seenByTenantAt.toLocaleString()}</span>
<CheckCircleIcon className="h-5 w-5 ml-1 mt-[-.2rem] text-success inline-block" />
</span>
</div>
)}
{utilBillsProofOfPaymentUploadedAt && (
<Link
href={`/share/proof-of-payment/${_id}/`}
target="_blank"
className="flex mt-1 ml-1">
<span className="w-5 min-w-5 mr-2"><TicketIcon className="mt-[.1rem]" /></span>
<span>
<span className="underline">{t("download-proof-of-payment-label")}</span>
<CheckCircleIcon className="h-5 w-5 ml-2 mt-[-.2rem] text-success inline-block" />
</span>
</Link>
)}
</div>
)}
{utilBillsProofOfPaymentUploadedAt && (
<Link
href={`/share/proof-of-payment/${_id}/`}
target="_blank"
className="flex items-center gap-2 mt-[-0.2rem] ml-2"
>
<TicketIcon className="h-5 w-5" />
<span className="text-sm">{t("download-proof-of-payment-label")}</span>
<CheckCircleIcon className="h-5 w-5 text-success" />
</Link>
)}
</>: null
</div>
</> : null
}
</div>

View File

@@ -119,7 +119,7 @@ export const LocationEditForm: FC<LocationEditFormProps> = ({ location, yearMont
</fieldset>
{formValues.tenantPaymentMethod === "iban" && userSettings?.enableIbanPayment ? (
<>
<div className="animate-expand-fade-in origin-top">
<div className="divider mt-4 mb-2 font-bold uppercase">{t("iban-payment--form-title")}</div>
<div className="form-control w-full">
<label className="label">
@@ -192,7 +192,7 @@ export const LocationEditForm: FC<LocationEditFormProps> = ({ location, yearMont
))}
</div>
</div>
</>
</div>
) : // ELSE include hidden inputs to preserve existing values
<>
<input
@@ -266,7 +266,7 @@ export const LocationEditForm: FC<LocationEditFormProps> = ({ location, yearMont
</fieldset>
{formValues.rentDueNotification && (
<>
<div className="animate-expand-fade-in origin-top">
<fieldset className="fieldset mt-2 p-2">
<legend className="fieldset-legend">{t("rent-due-day-label")}</legend>
<select defaultValue={formValues.rentDueDay}
@@ -301,7 +301,7 @@ export const LocationEditForm: FC<LocationEditFormProps> = ({ location, yearMont
))}
</div>
</fieldset>
</>
</div>
)}
</fieldset>

View File

@@ -129,7 +129,7 @@ const FormFields: FC<FormFieldsProps> = ({ userSettings, errors, message }) => {
</fieldset>
{ formValues.enableIbanPayment ? (
<>
<div className="animate-expand-fade-in origin-top">
<div className="divider mt-2 mb-2 font-bold uppercase">{t("iban-form-title")}</div>
<div className="form-control w-full">
<label className="label">
@@ -231,7 +231,7 @@ const FormFields: FC<FormFieldsProps> = ({ userSettings, errors, message }) => {
</div>
<NoteBox>{t("payment-additional-notes")}</NoteBox>
</>
</div>
) : // ELSE include hidden inputs to preserve existing values
<>
<input
@@ -281,7 +281,7 @@ const FormFields: FC<FormFieldsProps> = ({ userSettings, errors, message }) => {
</fieldset>
{ formValues.enableRevolutPayment ? (
<>
<div className="animate-expand-fade-in origin-top">
<div className="divider mt-2 mb-2 font-bold uppercase">{t("revolut-form-title")}</div>
<div className="form-control w-full">
<label className="label">
@@ -326,7 +326,7 @@ const FormFields: FC<FormFieldsProps> = ({ userSettings, errors, message }) => {
}
</div>
<NoteBox>{t("payment-additional-notes")}</NoteBox>
</>
</div>
)
: // ELSE include hidden input to preserve existing value
<>

View File

@@ -1,7 +1,7 @@
'use client';
import { FC, useEffect, useMemo, useState } from "react";
import { BillAttachment, BilledTo, BillingLocation, UserSettings } from "../lib/db-types";
import { FC, useMemo, useState } from "react";
import { BilledTo, BillingLocation, UserSettings } from "../lib/db-types";
import { formatYearMonth } from "../lib/format";
import { formatCurrency, formatIban } from "../lib/formatStrings";
import { useTranslations } from "next-intl";

View File

@@ -18,6 +18,9 @@ const config: Config = {
600: '#2F6FEB',
},
},
animation: {
'expand-fade-in': 'expandFadeIn 0.3s ease-in-out forwards',
},
},
keyframes: {
shimmer: {
@@ -37,6 +40,16 @@ const config: Config = {
maxHeight: '200px',
},
},
expandFadeIn: {
'0%': {
opacity: '0',
transform: 'scaleY(0.95)',
},
'100%': {
opacity: '1',
transform: 'scaleY(1)',
},
},
},
},
plugins: [