Merge branch 'feature/fixing-bugs' into develop

This commit is contained in:
Knee Cola
2025-11-23 22:51:06 +01:00
12 changed files with 104 additions and 95 deletions

View File

@@ -435,6 +435,7 @@ export const fetchAllLocations = withUser(async (user:AuthenticatedUser, year:nu
// "bills.hub3aText": 1, // "bills.hub3aText": 1,
// project only file name - leave out file content so that // project only file name - leave out file content so that
// less data is transferred to the client // less data is transferred to the client
"utilBillsProofOfPaymentUploadedAt": 1,
"utilBillsProofOfPaymentAttachment.fileName": 1, "utilBillsProofOfPaymentAttachment.fileName": 1,
}, },
}, },
@@ -630,6 +631,17 @@ export const uploadUtilBillsProofOfPayment = async (locationID: string, formData
noStore(); noStore();
try { try {
// check if attachment already exists for the location
const dbClient = await getDbClient();
const existingLocation = await dbClient.collection<BillingLocation>("lokacije")
.findOne({ _id: locationID }, { projection: { utilBillsProofOfPaymentAttachment: 1 } });
if (existingLocation?.utilBillsProofOfPaymentAttachment) {
return { success: false, error: 'An attachment already exists for this location' };
}
const file = formData.get('utilBillsProofOfPaymentAttachment') as File; const file = formData.get('utilBillsProofOfPaymentAttachment') as File;
// Validate file type // Validate file type
@@ -643,13 +655,14 @@ export const uploadUtilBillsProofOfPayment = async (locationID: string, formData
return { success: false, error: 'Invalid file' }; return { success: false, error: 'Invalid file' };
} }
const dbClient = await getDbClient();
// Update the location with the attachment // Update the location with the attachment
await dbClient.collection<BillingLocation>("lokacije") await dbClient.collection<BillingLocation>("lokacije")
.updateOne( .updateOne(
{ _id: locationID }, { _id: locationID },
{ $set: { utilBillsProofOfPaymentAttachment: attachment } } { $set: {
utilBillsProofOfPaymentAttachment: attachment,
utilBillsProofOfPaymentUploadedAt: new Date()
} }
); );
return { success: true }; return { success: true };

View File

@@ -41,6 +41,7 @@ export const addMonth = withUser(async (user:AuthenticatedUser, { year, month }:
...prevLocation, ...prevLocation,
// clear properties specific to the month // clear properties specific to the month
seenByTenant: undefined, seenByTenant: undefined,
utilBillsProofOfPaymentUploadedAt: undefined,
utilBillsProofOfPaymentAttachment: undefined, utilBillsProofOfPaymentAttachment: undefined,
// assign a new ID // assign a new ID
_id: (new ObjectId()).toHexString(), _id: (new ObjectId()).toHexString(),

View File

@@ -71,6 +71,8 @@ export interface BillingLocation {
seenByTenant?: boolean | null; seenByTenant?: boolean | null;
/** (optional) utility bills proof of payment attachment */ /** (optional) utility bills proof of payment attachment */
utilBillsProofOfPaymentAttachment?: BillAttachment|null; utilBillsProofOfPaymentAttachment?: BillAttachment|null;
/** (optional) date when utility bills proof of payment was uploaded */
utilBillsProofOfPaymentUploadedAt?: Date|null;
}; };
export enum BilledTo { export enum BilledTo {

View File

@@ -10,6 +10,7 @@ import { formatYearMonth } from "../lib/format";
import { decodeFromImage, DecodeResult, findDecodePdf417 } from "../lib/pdf/barcodeDecoder"; import { decodeFromImage, DecodeResult, findDecodePdf417 } from "../lib/pdf/barcodeDecoder";
import { useLocale, useTranslations } from "next-intl"; import { useLocale, useTranslations } from "next-intl";
import { Pdf417Barcode } from "./Pdf417Barcode"; import { Pdf417Barcode } from "./Pdf417Barcode";
import { InfoBox } from "./InfoBox";
// Next.js does not encode an utf-8 file name correctly when sending a form with a file attachment // Next.js does not encode an utf-8 file name correctly when sending a form with a file attachment
// This is a workaround for that // This is a workaround for that
@@ -67,7 +68,7 @@ export const BillEditForm: FC<BillEditFormProps> = ({ location, bill }) => {
}, [bill?.barcodeImage, hub3aText]); }, [bill?.barcodeImage, hub3aText]);
const billedTo_handleChange = (event: React.ChangeEvent<HTMLInputElement>) => { const billedTo_handleChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
setBilledToValue(event.target.value as BilledTo); setBilledToValue(event.target.value as BilledTo);
} }
@@ -227,33 +228,14 @@ export const BillEditForm: FC<BillEditFormProps> = ({ location, bill }) => {
))} ))}
</div> </div>
<div className="form-control mt-4"> <fieldset className="fieldset bg-base-200 border-base-300 rounded-box w-xs border p-4 pb-2 mt-4">
<div className="flex items-center gap-4"> <legend className="fieldset-legend font-semibold uppercase">{t("billed-to-legend")}</legend>
<span className="label-text">{t("billed-to-label")}</span> <select className="select select-bordered w-full" name="billedTo" defaultValue={billedToValue} onChange={billedTo_handleChange}>
<label className="label cursor-pointer gap-2"> <option value={BilledTo.Tenant}>{t("billed-to-tenant-option")}</option>
<input <option value={BilledTo.Landlord}>{t("billed-to-landlord-option")}</option>
type="radio" </select>
name="billedTo" <InfoBox className="m-0 mt-3 mb-1 p-0">{t("billed-to-info")}</InfoBox>
value={BilledTo.Tenant} </fieldset>
className="radio radio-primary"
checked={billedToValue === BilledTo.Tenant}
onChange={billedTo_handleChange}
/>
<span className="label-text">{t("billed-to-tenant-option")}</span>
</label>
<label className="label cursor-pointer gap-2">
<input
type="radio"
name="billedTo"
value={BilledTo.Landlord}
className="radio radio-primary"
checked={billedToValue === BilledTo.Landlord}
onChange={billedTo_handleChange}
/>
<span className="label-text">{t("billed-to-landlord-option")}</span>
</label>
</div>
</div>
{/* Show toggle only when adding a new bill (not editing) */} {/* Show toggle only when adding a new bill (not editing) */}
{!bill && ( {!bill && (

View File

@@ -1,6 +1,6 @@
'use client'; 'use client';
import { CheckCircleIcon, Cog8ToothIcon, PlusCircleIcon, ShareIcon, BanknotesIcon, DocumentIcon, EnvelopeIcon, LinkIcon, EyeIcon } from "@heroicons/react/24/outline"; import { CheckCircleIcon, Cog8ToothIcon, PlusCircleIcon, ShareIcon, BanknotesIcon, EyeIcon, TicketIcon } from "@heroicons/react/24/outline";
import { FC } from "react"; import { FC } from "react";
import { BillBadge } from "./BillBadge"; import { BillBadge } from "./BillBadge";
import { BillingLocation } from "../lib/db-types"; import { BillingLocation } from "../lib/db-types";
@@ -23,7 +23,7 @@ export const LocationCard: FC<LocationCardProps> = ({ location, currency }) => {
bills, bills,
seenByTenant, seenByTenant,
// NOTE: only the fileName is projected from the DB to reduce data transfer // NOTE: only the fileName is projected from the DB to reduce data transfer
utilBillsProofOfPaymentAttachment utilBillsProofOfPaymentUploadedAt
} = location; } = location;
const t = useTranslations("home-page.location-card"); const t = useTranslations("home-page.location-card");
@@ -58,7 +58,7 @@ export const LocationCard: FC<LocationCardProps> = ({ location, currency }) => {
</div> </div>
{monthlyExpense > 0 || seenByTenant || utilBillsProofOfPaymentAttachment ? {monthlyExpense > 0 || seenByTenant || utilBillsProofOfPaymentUploadedAt ?
<fieldset className="card card-compact card-bordered border-1 border-neutral p-3 mt-2 mr-[3.5rem]"> <fieldset className="card card-compact card-bordered border-1 border-neutral p-3 mt-2 mr-[3.5rem]">
<legend className="fieldset-legend px-2 text-sm font-semibold uppercase">{t("monthly-statement-legend")}</legend> <legend className="fieldset-legend px-2 text-sm font-semibold uppercase">{t("monthly-statement-legend")}</legend>
@@ -78,13 +78,13 @@ export const LocationCard: FC<LocationCardProps> = ({ location, currency }) => {
<CheckCircleIcon className="h-5 w-5 text-success" /> <CheckCircleIcon className="h-5 w-5 text-success" />
</div> </div>
)} )}
{utilBillsProofOfPaymentAttachment && ( {utilBillsProofOfPaymentUploadedAt && (
<Link <Link
href={`/share/proof-of-payment/${_id}/`} href={`/share/proof-of-payment/${_id}/`}
target="_blank" target="_blank"
className="flex items-center gap-2 mt-2 ml-4" className="flex items-center gap-2 mt-2 ml-4"
> >
<LinkIcon className="h-5 w-5" /> <TicketIcon className="h-5 w-5" />
<span className="text-sm">{t("download-proof-of-payment-label")}</span> <span className="text-sm">{t("download-proof-of-payment-label")}</span>
<CheckCircleIcon className="h-5 w-5 text-success" /> <CheckCircleIcon className="h-5 w-5 text-success" />
</Link> </Link>

View File

@@ -81,7 +81,6 @@ export const LocationEditForm: FC<LocationEditFormProps> = ({ location, yearMont
</fieldset> </fieldset>
<fieldset className="fieldset bg-base-200 border-base-300 rounded-box w-xs border p-4 pb-2 mt-4"> <fieldset className="fieldset bg-base-200 border-base-300 rounded-box w-xs border p-4 pb-2 mt-4">
<legend className="fieldset-legend font-semibold uppercase">{t("tenant-2d-code-legend")}</legend> <legend className="fieldset-legend font-semibold uppercase">{t("tenant-2d-code-legend")}</legend>
<InfoBox className="p-1 mb-1">{t("tenant-2d-code-info")}</InfoBox> <InfoBox className="p-1 mb-1">{t("tenant-2d-code-info")}</InfoBox>
<fieldset className="fieldset"> <fieldset className="fieldset">

View File

@@ -3,7 +3,6 @@
import { useState, useEffect, FC } from 'react'; import { useState, useEffect, FC } from 'react';
import { generateBarcode } from '../lib/pdf/pdf417'; import { generateBarcode } from '../lib/pdf/pdf417';
import { renderBarcode } from '../lib/pdf/renderBarcode'; import { renderBarcode } from '../lib/pdf/renderBarcode';
import { EncodePayment, PaymentParams } from 'hub-3a-payment-encoder';
export const Pdf417Barcode:FC<{hub3aText:string, className?: string}> = ({hub3aText: hub3a_text, className}) => { export const Pdf417Barcode:FC<{hub3aText:string, className?: string}> = ({hub3aText: hub3a_text, className}) => {
const [bitmapData, setBitmapData] = useState<string | undefined>(undefined); const [bitmapData, setBitmapData] = useState<string | undefined>(undefined);

View File

@@ -37,13 +37,6 @@ export const PrintPreview: React.FC<PrintPreviewProps> = ({ data, year, month, t
print-color-adjust: exact !important; print-color-adjust: exact !important;
} }
.print-barcode-img {
width: 69.6mm !important;
max-width: 69.6mm !important;
height: auto !important;
max-height: 85px !important;
}
.print-table { .print-table {
page-break-inside: avoid; page-break-inside: avoid;
} }
@@ -79,10 +72,6 @@ export const PrintPreview: React.FC<PrintPreviewProps> = ({ data, year, month, t
.print-table thead tr { .print-table thead tr {
background: #f5f5f5 !important; background: #f5f5f5 !important;
} }
.print-table td img {
margin: 5em auto;
}
} }
`}</style> `}</style>
@@ -144,15 +133,14 @@ export const PrintPreview: React.FC<PrintPreviewProps> = ({ data, year, month, t
<div className="flex justify-center items-center"> <div className="flex justify-center items-center">
{ {
item.hub3aText ? item.hub3aText ?
<Pdf417Barcode hub3aText={item.hub3aText} /> <Pdf417Barcode hub3aText={item.hub3aText} className="max-h-28 w-auto max-w-[270px] print:m-[5em_auto] print:h-[auto] print:max-h-[85px] print:w-[69.6mm] print:max-w-[69.6mm]" />
: ( : (
// LEGACY SUPPORT ... untill all bills have been migrated // LEGACY SUPPORT ... untill all bills have been migrated
item.barcodeImage ? item.barcodeImage ?
<img <img
src={item.barcodeImage.startsWith('data:') ? item.barcodeImage : `data:image/png;base64,${item.barcodeImage}`} src={item.barcodeImage.startsWith('data:') ? item.barcodeImage : `data:image/png;base64,${item.barcodeImage}`}
alt={`Barcode for ${item.billName}`} alt={`Barcode for ${item.billName}`}
className="max-h-28 w-auto border border-gray-300 rounded print-barcode-img" className="max-h-28 w-auto max-w-[270px] print:m-[5em_auto] print:h-[auto] print:max-h-[85px] print:w-[69.6mm] print:max-w-[69.6mm]"
style={{ maxWidth: '270px' }}
/> : null /> : null
) )
} }

View File

@@ -61,7 +61,7 @@ export const ViewBillCard:FC<ViewBillCardProps> = ({ location, bill }) => {
{ {
hub3aText ? hub3aText ?
<div className="form-control p-1"> <div className="form-control p-1">
<label className="cursor-pointer label p-2 grow bg-white justify-center"> <label className="label p-2 grow bg-white border border-gray-300 rounded-box justify-center">
<Pdf417Barcode hub3aText={hub3aText} /> <Pdf417Barcode hub3aText={hub3aText} />
</label> </label>
<p className="text-xs my-1">{t.rich('barcode-disclaimer', { br: () => <br /> })}</p> <p className="text-xs my-1">{t.rich('barcode-disclaimer', { br: () => <br /> })}</p>
@@ -70,7 +70,7 @@ export const ViewBillCard:FC<ViewBillCardProps> = ({ location, bill }) => {
// LEGACY SUPPORT ... untill all bills have been migrated // LEGACY SUPPORT ... untill all bills have been migrated
barcodeImage ? barcodeImage ?
<div className="p-1"> <div className="p-1">
<label className="label p-2 grow bg-white"> <label className="label p-2 grow bg-white border border-gray-300 rounded-box justify-center">
<img src={barcodeImage} className="grow sm:max-w-[350px]" alt="2D Barcode" /> <img src={barcodeImage} className="grow sm:max-w-[350px]" alt="2D Barcode" />
</label> </label>
<p className="text-xs my-1">{t.rich('barcode-disclaimer', { br: () => <br /> })}</p> <p className="text-xs my-1">{t.rich('barcode-disclaimer', { br: () => <br /> })}</p>

View File

@@ -1,6 +1,6 @@
'use client'; 'use client';
import { FC, useState } from "react"; import { FC, useEffect, useMemo, useState } from "react";
import { BillAttachment, BilledTo, BillingLocation, UserSettings } from "../lib/db-types"; import { BillAttachment, BilledTo, BillingLocation, UserSettings } from "../lib/db-types";
import { formatYearMonth } from "../lib/format"; import { formatYearMonth } from "../lib/format";
import { formatCurrency } from "../lib/formatStrings"; import { formatCurrency } from "../lib/formatStrings";
@@ -29,13 +29,15 @@ export const ViewLocationCard:FC<ViewLocationCardProps> = ({location, userSettin
tenantTown, tenantTown,
generateTenantCode, generateTenantCode,
// NOTE: only the fileName is projected from the DB to reduce data transfer // NOTE: only the fileName is projected from the DB to reduce data transfer
utilBillsProofOfPaymentAttachment utilBillsProofOfPaymentAttachment,
utilBillsProofOfPaymentUploadedAt,
} = location; } = location;
const t = useTranslations("home-page.location-card"); const t = useTranslations("home-page.location-card");
const [isUploading, setIsUploading] = useState(false); const [isUploading, setIsUploading] = useState(false);
const [uploadError, setUploadError] = useState<string | null>(null); const [uploadError, setUploadError] = useState<string | null>(null);
const [attachmentUploadedAt, setAttachmentUploadedAt ] = useState<Date | null>(utilBillsProofOfPaymentUploadedAt ?? null);
const [attachmentFilename, setAttachmentFilename] = useState(utilBillsProofOfPaymentAttachment?.fileName); const [attachmentFilename, setAttachmentFilename] = useState(utilBillsProofOfPaymentAttachment?.fileName);
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => { const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
@@ -60,6 +62,7 @@ export const ViewLocationCard:FC<ViewLocationCardProps> = ({location, userSettin
if (result.success) { if (result.success) {
setAttachmentFilename(file.name); setAttachmentFilename(file.name);
setAttachmentUploadedAt(new Date());
} else { } else {
setUploadError(result.error || 'Upload failed'); setUploadError(result.error || 'Upload failed');
} }
@@ -74,27 +77,40 @@ export const ViewLocationCard:FC<ViewLocationCardProps> = ({location, userSettin
// sum all the billAmounts (only for bills billed to tenant) // sum all the billAmounts (only for bills billed to tenant)
const monthlyExpense = bills.reduce((acc, bill) => (bill.paid && (bill.billedTo ?? BilledTo.Tenant) === BilledTo.Tenant) ? acc + (bill.payedAmount ?? 0) : acc, 0); const monthlyExpense = bills.reduce((acc, bill) => (bill.paid && (bill.billedTo ?? BilledTo.Tenant) === BilledTo.Tenant) ? acc + (bill.payedAmount ?? 0) : acc, 0);
const locationNameTrimmed_max20 = locationName.trimEnd().trimEnd().substring(0,19); const { hub3aText, paymentParams } = useMemo(() => {
const paymentParams:PaymentParams = { if(!userSettings?.show2dCodeInMonthlyStatement || !generateTenantCode) {
Iznos: (monthlyExpense/100).toFixed(2).replace(".",","), return {
ImePlatitelja: tenantName ?? "", hub3aText: "",
AdresaPlatitelja: tenantStreet ?? "", paymentParams: {} as PaymentParams
SjedistePlatitelja: tenantTown ?? "", };
Primatelj: userSettings?.ownerName ?? "", }
AdresaPrimatelja: userSettings?.ownerStreet ?? "",
SjedistePrimatelja: userSettings?.ownerTown ?? "",
IBAN: userSettings?.ownerIBAN ?? "",
ModelPlacanja: "HR00",
PozivNaBroj: formatYearMonth(yearMonth),
SifraNamjene: "",
OpisPlacanja: `Režije-${locationNameTrimmed_max20}-${formatYearMonth(yearMonth)}`, // max length 35 = "Režije-" (7) + locationName (20) + "-" (1) + "YYYY-MM" (7)
};
const hub3a_text = EncodePayment(paymentParams); const locationNameTrimmed_max20 = locationName.trimEnd().trimEnd().substring(0,19);
const paymentParams:PaymentParams = {
Iznos: (monthlyExpense/100).toFixed(2).replace(".",","),
ImePlatitelja: tenantName ?? "",
AdresaPlatitelja: tenantStreet ?? "",
SjedistePlatitelja: tenantTown ?? "",
Primatelj: userSettings?.ownerName ?? "",
AdresaPrimatelja: userSettings?.ownerStreet ?? "",
SjedistePrimatelja: userSettings?.ownerTown ?? "",
IBAN: userSettings?.ownerIBAN ?? "",
ModelPlacanja: "HR00",
PozivNaBroj: formatYearMonth(yearMonth),
SifraNamjene: "",
OpisPlacanja: `Režije-${locationNameTrimmed_max20}-${formatYearMonth(yearMonth)}`, // max length 35 = "Režije-" (7) + locationName (20) + "-" (1) + "YYYY-MM" (7)
};
return({
hub3aText: EncodePayment(paymentParams),
paymentParams
});
}, [userSettings?.show2dCodeInMonthlyStatement, generateTenantCode, locationName, tenantName, tenantStreet, tenantTown, userSettings, monthlyExpense, yearMonth]);
return( return(
<div data-key={_id } className="card card-compact card-bordered max-w-[30em] min-w-[350px] bg-base-100 border-1 border-neutral my-1"> <div data-key={_id } className="card card-compact card-bordered max-w-[30em] min-w-[330px] bg-base-100 border-1 border-neutral my-1">
<div className="card-body"> <div className="card-body">
<h2 className="card-title mr-[2em] text-[1.3rem]">{formatYearMonth(yearMonth)} {locationName}</h2> <h2 className="card-title mr-[2em] text-[1.3rem]">{formatYearMonth(yearMonth)} {locationName}</h2>
<div className="card-actions mt-[1em] mb-[1em]"> <div className="card-actions mt-[1em] mb-[1em]">
@@ -118,31 +134,38 @@ export const ViewLocationCard:FC<ViewLocationCardProps> = ({location, userSettin
<li><strong>{t("payment-recipient-label")}</strong> <pre className="inline pl-1">{paymentParams.Primatelj}</pre></li> <li><strong>{t("payment-recipient-label")}</strong> <pre className="inline pl-1">{paymentParams.Primatelj}</pre></li>
<li><strong>{t("payment-recipient-address-label")}</strong><pre className="inline pl-1">{paymentParams.AdresaPrimatelja}</pre></li> <li><strong>{t("payment-recipient-address-label")}</strong><pre className="inline pl-1">{paymentParams.AdresaPrimatelja}</pre></li>
<li><strong>{t("payment-recipient-city-label")}</strong><pre className="inline pl-1">{paymentParams.SjedistePrimatelja}</pre></li> <li><strong>{t("payment-recipient-city-label")}</strong><pre className="inline pl-1">{paymentParams.SjedistePrimatelja}</pre></li>
<li><strong>{t("payment-amount-label")}</strong> <pre className="inline pl-1">{paymentParams.Iznos}</pre></li> <li><strong>{t("payment-amount-label")}</strong> <pre className="inline pl-1">{paymentParams.Iznos} { userSettings?.currency }</pre></li>
<li><strong>{t("payment-description-label")}</strong><pre className="inline pl-1">{paymentParams.OpisPlacanja}</pre></li> <li><strong>{t("payment-description-label")}</strong><pre className="inline pl-1">{paymentParams.OpisPlacanja}</pre></li>
<li><strong>{t("payment-model-label")}</strong><pre className="inline pl-1">{paymentParams.ModelPlacanja}</pre></li> <li><strong>{t("payment-model-label")}</strong><pre className="inline pl-1">{paymentParams.ModelPlacanja}</pre></li>
<li><strong>{t("payment-reference-label")}</strong><pre className="inline pl-1">{paymentParams.PozivNaBroj}</pre></li> <li><strong>{t("payment-reference-label")}</strong><pre className="inline pl-1">{paymentParams.PozivNaBroj}</pre></li>
<li><strong>{t("payment-purpose-code-label")}</strong><pre className="inline pl-1">{paymentParams.SifraNamjene}</pre></li>
</ul> </ul>
<Pdf417Barcode hub3aText={hub3a_text} /> <label className="label p-2 grow bg-white border border-gray-300 rounded-box justify-center">
<Pdf417Barcode hub3aText={hub3aText} />
</label>
</> </>
: null : null
} }
<fieldset className="fieldset bg-base-200 border-base-300 rounded-box w-xs border p-4 pb-2 pt-0 mt-2"> <fieldset className="fieldset bg-base-200 border-base-300 rounded-box w-xs border p-4 pb-2 pt-0 mt-2">
<legend className="fieldset-legend font-semibold uppercase">{t("upload-proof-of-payment-legend")}</legend> <legend className="fieldset-legend font-semibold uppercase">{t("upload-proof-of-payment-legend")}</legend>
{
{attachmentFilename ? ( // IF proof of payment was uploaded
<div className="mt-3 ml-[-.5rem]"> attachmentUploadedAt ? (
<Link // IF file name is available, show link to download
href={`/share/proof-of-payment/${_id}/`} // ELSE it's not available that means that the uploaded file was purged by housekeeping
target="_blank" // -> don't show anything
className='text-center w-full max-w-[20rem] text-nowrap truncate inline-block' attachmentFilename ? (
> <div className="mt-3 ml-[-.5rem]">
<DocumentIcon className="h-[1em] w-[1em] text-2xl inline-block mr-1" /> <Link
{decodeURIComponent(attachmentFilename)} href={`/share/proof-of-payment/${_id}/`}
</Link> target="_blank"
</div> className='text-center w-full max-w-[20rem] text-nowrap truncate inline-block'
) : ( >
<DocumentIcon className="h-[1em] w-[1em] text-2xl inline-block mr-1" />
{decodeURIComponent(attachmentFilename)}
</Link>
</div>
) : null
) : /* ELSE show upload input */ (
<div className="form-control w-full"> <div className="form-control w-full">
<label className="label"> <label className="label">
<span className="label-text">{t("upload-proof-of-payment-label")}</span> <span className="label-text">{t("upload-proof-of-payment-label")}</span>

View File

@@ -124,9 +124,10 @@
}, },
"attachment": "Attachment", "attachment": "Attachment",
"back-button": "Back", "back-button": "Back",
"billed-to-label": "Billed to", "billed-to-legend": "Who bears the cost?",
"billed-to-tenant-option": "tenant", "billed-to-tenant-option": "the tenant bears this cost",
"billed-to-landlord-option": "landlord" "billed-to-landlord-option": "the landlord bears this cost",
"billed-to-info": "This option is intended for cases where part of the utility costs are not charged to the tenant. If 'the landlord bears this cost' is selected, this bill will not be included in the monthly statement shown to the tenant."
}, },
"location-delete-form": { "location-delete-form": {
"text": "Please confirm deletion of realestate \"<strong>{name}</strong>\".", "text": "Please confirm deletion of realestate \"<strong>{name}</strong>\".",

View File

@@ -123,9 +123,10 @@
}, },
"attachment": "Privitak", "attachment": "Privitak",
"back-button": "Nazad", "back-button": "Nazad",
"billed-to-label": "Račun plaća", "billed-to-legend": "Tko snosi trošak?",
"billed-to-tenant-option": "podstanar", "billed-to-tenant-option": "ovaj trošak snosi podstanar",
"billed-to-landlord-option": "vlasnik" "billed-to-landlord-option": "ovaj trošak snosi vlasnik",
"billed-to-info": "Ova opcija je predviđena za slučaj kada se dio režija ne naplaćuje od podstanara. Ako je odabrano 'trošak snosi vlasnik', ovaj račun neće biti uključen u mjesečni obračun koji se prikazuje podstanaru."
}, },
"location-delete-form": { "location-delete-form": {
"text": "Molim potvrdi brisanje nekretnine \"<strong>{name}</strong>\".", "text": "Molim potvrdi brisanje nekretnine \"<strong>{name}</strong>\".",