Merge branch 'release/2.9.0'
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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} />);
|
||||
|
||||
@@ -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() } }
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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")} <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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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
|
||||
<>
|
||||
|
||||
@@ -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";
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "evidencija-rezija",
|
||||
"version": "2.8.0",
|
||||
"version": "2.9.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"version": "2.8.0",
|
||||
"version": "2.9.0",
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.1",
|
||||
|
||||
@@ -59,5 +59,5 @@
|
||||
"engines": {
|
||||
"node": ">=18.17.0"
|
||||
},
|
||||
"version": "2.8.0"
|
||||
"version": "2.9.0"
|
||||
}
|
||||
|
||||
@@ -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: [
|
||||
|
||||
Reference in New Issue
Block a user