Merge branch 'dev' into debian12
This commit is contained in:
@@ -112,6 +112,86 @@ export const MyLoansPage = () => {
|
||||
return `${d}.${M}.${y} ${h}:${min}`;
|
||||
};
|
||||
|
||||
const handleTakeAction = async (loanCode: string) => {
|
||||
try {
|
||||
const res = await fetch(
|
||||
`${API_BASE}/api/loans/set-take-date/${loanCode}`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${Cookies.get("token")}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
setMsgStatus("error");
|
||||
setMsgTitle(t("error"));
|
||||
setMsgDescription(t("error-take-loan"));
|
||||
setIsMsg(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the loan in state
|
||||
setLoans((prev) =>
|
||||
prev.map((loan) =>
|
||||
loan.loan_code === loanCode
|
||||
? { ...loan, take_date: new Date().toISOString() }
|
||||
: loan,
|
||||
),
|
||||
);
|
||||
setMsgStatus("success");
|
||||
setMsgTitle(t("success"));
|
||||
setMsgDescription(t("take-loan-success"));
|
||||
setIsMsg(true);
|
||||
} catch (e) {
|
||||
setMsgStatus("error");
|
||||
setMsgTitle(t("error"));
|
||||
setMsgDescription(t("network-error"));
|
||||
setIsMsg(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleReturnAction = async (loanCode: string) => {
|
||||
try {
|
||||
const res = await fetch(
|
||||
`${API_BASE}/api/loans/set-return-date/${loanCode}`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${Cookies.get("token")}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
setMsgStatus("error");
|
||||
setMsgTitle(t("error"));
|
||||
setMsgDescription(t("error-return-loan"));
|
||||
setIsMsg(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the loan in state
|
||||
setLoans((prev) =>
|
||||
prev.map((loan) =>
|
||||
loan.loan_code === loanCode
|
||||
? { ...loan, returned_date: new Date().toISOString() }
|
||||
: loan,
|
||||
),
|
||||
);
|
||||
setMsgStatus("success");
|
||||
setMsgTitle(t("success"));
|
||||
setMsgDescription(t("return-loan-success"));
|
||||
setIsMsg(true);
|
||||
} catch (e) {
|
||||
setMsgStatus("error");
|
||||
setMsgTitle(t("error"));
|
||||
setMsgDescription(t("network-error"));
|
||||
setIsMsg(true);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Container className="px-6 sm:px-8 pt-10">
|
||||
@@ -190,8 +270,33 @@ export const MyLoansPage = () => {
|
||||
: "-"}
|
||||
</Text>
|
||||
</Table.Cell>
|
||||
<Table.Cell>{formatDate(loan.take_date)}</Table.Cell>
|
||||
<Table.Cell>{formatDate(loan.returned_date)}</Table.Cell>
|
||||
<Table.Cell>
|
||||
{loan.take_date ? (
|
||||
formatDate(loan.take_date)
|
||||
) : (
|
||||
<Button
|
||||
size="xs"
|
||||
colorPalette="teal"
|
||||
onClick={() => handleTakeAction(loan.loan_code)}
|
||||
>
|
||||
{t("take")}
|
||||
</Button>
|
||||
)}
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
{loan.returned_date ? (
|
||||
formatDate(loan.returned_date)
|
||||
) : (
|
||||
<Button
|
||||
size="xs"
|
||||
colorPalette="blue"
|
||||
onClick={() => handleReturnAction(loan.loan_code)}
|
||||
disabled={!loan.take_date}
|
||||
>
|
||||
{t("return")}
|
||||
</Button>
|
||||
)}
|
||||
</Table.Cell>
|
||||
<Table.Cell>{loan.note}</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Dialog.Root role="alertdialog">
|
||||
|
||||
@@ -81,5 +81,10 @@
|
||||
"contactPage_messageLabel": "Nachricht",
|
||||
"contactPage_messagePlaceholder": "Geben Sie hier Ihre Nachricht ein...",
|
||||
"contactPage_messageErrorText": "Dieses Feld darf nicht leer sein.",
|
||||
"contact": "Kontakt"
|
||||
"contact": "Kontakt",
|
||||
"take": "Abholen",
|
||||
"return": "Zurückgeben",
|
||||
"take-loan-success": "Ausleihe erfolgreich abgeholt",
|
||||
"return-loan-success": "Ausleihe erfolgreich zurückgegeben",
|
||||
"network-error": "Netzwerkfehler. Kontaktieren Sie den Administrator."
|
||||
}
|
||||
@@ -16,7 +16,7 @@ export const createLoanInDatabase = async (
|
||||
startDate,
|
||||
endDate,
|
||||
note,
|
||||
itemIds
|
||||
itemIds,
|
||||
) => {
|
||||
if (!username)
|
||||
return { success: false, code: "BAD_REQUEST", message: "Missing username" };
|
||||
@@ -52,7 +52,7 @@ export const createLoanInDatabase = async (
|
||||
// Ensure all items exist and collect names + lockers
|
||||
const [itemsRows] = await conn.query(
|
||||
"SELECT id, item_name, safe_nr FROM items WHERE id IN (?)",
|
||||
[itemIds]
|
||||
[itemIds],
|
||||
);
|
||||
if (!itemsRows || itemsRows.length !== itemIds.length) {
|
||||
await conn.rollback();
|
||||
@@ -65,7 +65,7 @@ export const createLoanInDatabase = async (
|
||||
|
||||
const itemNames = itemIds
|
||||
.map(
|
||||
(id) => itemsRows.find((r) => Number(r.id) === Number(id))?.item_name
|
||||
(id) => itemsRows.find((r) => Number(r.id) === Number(id))?.item_name,
|
||||
)
|
||||
.filter(Boolean);
|
||||
|
||||
@@ -80,9 +80,9 @@ export const createLoanInDatabase = async (
|
||||
sn !== undefined &&
|
||||
Number.isInteger(Number(sn)) &&
|
||||
Number(sn) >= 0 &&
|
||||
Number(sn) <= 99
|
||||
Number(sn) <= 99,
|
||||
)
|
||||
.map((sn) => Number(sn))
|
||||
.map((sn) => Number(sn)),
|
||||
),
|
||||
];
|
||||
|
||||
@@ -98,7 +98,7 @@ export const createLoanInDatabase = async (
|
||||
AND l.start_date < ?
|
||||
AND COALESCE(l.returned_date, l.end_date) > ?
|
||||
`,
|
||||
[itemIds, end, start]
|
||||
[itemIds, end, start],
|
||||
);
|
||||
if (confRows?.[0]?.conflicts > 0) {
|
||||
await conn.rollback();
|
||||
@@ -115,7 +115,7 @@ export const createLoanInDatabase = async (
|
||||
const candidate = Math.floor(100000 + Math.random() * 899999); // 6 digits
|
||||
const [exists] = await conn.query(
|
||||
"SELECT 1 FROM loans WHERE loan_code = ? LIMIT 1",
|
||||
[candidate]
|
||||
[candidate],
|
||||
);
|
||||
if (exists.length === 0) {
|
||||
loanCode = candidate;
|
||||
@@ -146,7 +146,7 @@ export const createLoanInDatabase = async (
|
||||
JSON.stringify(itemIds.map((n) => Number(n))),
|
||||
JSON.stringify(itemNames),
|
||||
note,
|
||||
]
|
||||
],
|
||||
);
|
||||
|
||||
await conn.commit();
|
||||
@@ -189,7 +189,7 @@ export const getLoanInfoWithID = async (loanId) => {
|
||||
export const getLoansFromDatabase = async (username) => {
|
||||
const [result] = await pool.query(
|
||||
"SELECT * FROM loans WHERE username = ? AND deleted = 0;",
|
||||
[username]
|
||||
[username],
|
||||
);
|
||||
if (result.length > 0) {
|
||||
return { success: true, status: true, data: result };
|
||||
@@ -202,7 +202,7 @@ export const getLoansFromDatabase = async (username) => {
|
||||
export const getBorrowableItemsFromDatabase = async (
|
||||
startDate,
|
||||
endDate,
|
||||
role = 0
|
||||
role = 0,
|
||||
) => {
|
||||
// Overlap if: loan.start < end AND effective_end > start
|
||||
// effective_end is returned_date if set, otherwise end_date
|
||||
@@ -236,7 +236,7 @@ export const getBorrowableItemsFromDatabase = async (
|
||||
export const SETdeleteLoanFromDatabase = async (loanId) => {
|
||||
const [result] = await pool.query(
|
||||
"UPDATE loans SET deleted = 1 WHERE id = ?;",
|
||||
[loanId]
|
||||
[loanId],
|
||||
);
|
||||
if (result.affectedRows > 0) {
|
||||
return { success: true };
|
||||
@@ -260,3 +260,69 @@ export const getItems = async () => {
|
||||
}
|
||||
return { success: false };
|
||||
};
|
||||
|
||||
export const setReturnDate = async (loanCode) => {
|
||||
const [items] = await pool.query(
|
||||
"SELECT loaned_items_id FROM loans WHERE loan_code = ?",
|
||||
[loanCode],
|
||||
);
|
||||
|
||||
const [owner] = await pool.query(
|
||||
"SELECT username FROM loans WHERE loan_code = ?",
|
||||
[loanCode],
|
||||
);
|
||||
|
||||
if (items.length === 0) return { success: false };
|
||||
|
||||
const itemIds = Array.isArray(items[0].loaned_items_id)
|
||||
? items[0].loaned_items_id
|
||||
: JSON.parse(items[0].loaned_items_id || "[]");
|
||||
|
||||
const [setItemStates] = await pool.query(
|
||||
"UPDATE items SET in_safe = 1, currently_borrowing = NULL, last_borrowed_person = (?) WHERE id IN (?)",
|
||||
[owner[0].username, itemIds],
|
||||
);
|
||||
|
||||
const [result] = await pool.query(
|
||||
"UPDATE loans SET returned_date = NOW() WHERE loan_code = ?",
|
||||
[loanCode],
|
||||
);
|
||||
|
||||
if (result.affectedRows > 0 && setItemStates.affectedRows > 0) {
|
||||
return { success: true };
|
||||
}
|
||||
return { success: false };
|
||||
};
|
||||
|
||||
export const setTakeDate = async (loanCode) => {
|
||||
const [items] = await pool.query(
|
||||
"SELECT loaned_items_id FROM loans WHERE loan_code = ?",
|
||||
[loanCode],
|
||||
);
|
||||
|
||||
const [owner] = await pool.query(
|
||||
"SELECT username FROM loans WHERE loan_code = ?",
|
||||
[loanCode],
|
||||
);
|
||||
|
||||
if (items.length === 0) return { success: false };
|
||||
|
||||
const itemIds = Array.isArray(items[0].loaned_items_id)
|
||||
? items[0].loaned_items_id
|
||||
: JSON.parse(items[0].loaned_items_id || "[]");
|
||||
|
||||
const [setItemStates] = await pool.query(
|
||||
"UPDATE items SET in_safe = 0, currently_borrowing = (?) WHERE id IN (?)",
|
||||
[owner[0].username, itemIds],
|
||||
);
|
||||
|
||||
const [result] = await pool.query(
|
||||
"UPDATE loans SET take_date = NOW() WHERE loan_code = ?",
|
||||
[loanCode],
|
||||
);
|
||||
|
||||
if (result.affectedRows > 0 && setItemStates.affectedRows > 0) {
|
||||
return { success: true };
|
||||
}
|
||||
return { success: false };
|
||||
};
|
||||
|
||||
@@ -13,6 +13,8 @@ import {
|
||||
getALLLoans,
|
||||
getItems,
|
||||
SETdeleteLoanFromDatabase,
|
||||
setReturnDate,
|
||||
setTakeDate,
|
||||
} from "./database/loansMgmt.database.js";
|
||||
import { sendMailLoan } from "./services/mailer.js";
|
||||
|
||||
@@ -48,7 +50,7 @@ router.post("/createLoan", authenticate, async (req, res) => {
|
||||
start,
|
||||
end,
|
||||
note,
|
||||
itemIds
|
||||
itemIds,
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
@@ -59,7 +61,7 @@ router.post("/createLoan", authenticate, async (req, res) => {
|
||||
mailInfo.data.loaned_items_name,
|
||||
mailInfo.data.start_date,
|
||||
mailInfo.data.end_date,
|
||||
mailInfo.data.created_at
|
||||
mailInfo.data.created_at,
|
||||
);
|
||||
return res.status(201).json({
|
||||
message: "Loan created successfully",
|
||||
@@ -96,6 +98,26 @@ router.get("/loans", authenticate, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/set-return-date/:loan_code", authenticate, async (req, res) => {
|
||||
const loanCode = req.params.loan_code;
|
||||
const result = await setReturnDate(loanCode);
|
||||
if (result.success) {
|
||||
res.status(200).json({ data: result.data });
|
||||
} else {
|
||||
res.status(500).json({ message: "Failed to set return date" });
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/set-take-date/:loan_code", authenticate, async (req, res) => {
|
||||
const loanCode = req.params.loan_code;
|
||||
const result = await setTakeDate(loanCode);
|
||||
if (result.success) {
|
||||
res.status(200).json({ data: result.data });
|
||||
} else {
|
||||
res.status(500).json({ message: "Failed to set take date" });
|
||||
}
|
||||
});
|
||||
|
||||
router.get("/all-items", authenticate, async (req, res) => {
|
||||
const result = await getItems();
|
||||
if (result.success) {
|
||||
@@ -135,7 +157,7 @@ router.post("/borrowable-items", authenticate, async (req, res) => {
|
||||
const result = await getBorrowableItemsFromDatabase(
|
||||
startDate,
|
||||
endDate,
|
||||
req.user.role
|
||||
req.user.role,
|
||||
);
|
||||
if (result.success) {
|
||||
// return the array directly for consistency with /items
|
||||
|
||||
Reference in New Issue
Block a user