-
+
+
{/* Warning Indicator Context Accent Strip */}
- {mode === "SINGLE" ?
:
}
+ {mode === "SINGLE" ? (
+
+ ) : (
+
+ )}
-
- {mode === "SINGLE" ? "Purge Circulation Record" : "Purge Completed History"}
+
+ {mode === "SINGLE"
+ ? "Purge Circulation Record"
+ : "Purge Completed History"}
-
{/* Control Desk Actions Footer */}
-
-
-
);
-};
\ No newline at end of file
+};
diff --git a/client/src/features/issues/components/IssueDetailsModal.tsx b/client/src/features/issues/components/IssueDetailsModal.tsx
index 418cc2c..e83e8ed 100644
--- a/client/src/features/issues/components/IssueDetailsModal.tsx
+++ b/client/src/features/issues/components/IssueDetailsModal.tsx
@@ -5,7 +5,17 @@ import type { BookIssueRecord } from "../../../types/transactions";
import { useNavigate } from "react-router-dom"; // For redirecting to payments
// Lucide Icons for the professional popup layout
-import { ShieldAlert, AlertTriangle, ArrowRight, X, User, BookOpen, Calendar, Edit2, CheckCircle2 } from "lucide-react";
+import {
+ ShieldAlert,
+ AlertTriangle,
+ ArrowRight,
+ X,
+ User,
+ BookOpen,
+ Calendar,
+ Edit2,
+ CheckCircle2,
+} from "lucide-react";
interface IssueDetailsModalProps {
isOpen: boolean;
@@ -15,9 +25,15 @@ interface IssueDetailsModalProps {
onTriggerEdit: () => void;
}
-export const IssueDetailsModal = ({ isOpen, onClose, record, onMarkAsReturned, onTriggerEdit }: IssueDetailsModalProps) => {
+export const IssueDetailsModal = ({
+ isOpen,
+ onClose,
+ record,
+ onMarkAsReturned,
+ onTriggerEdit,
+}: IssueDetailsModalProps) => {
const navigate = useNavigate();
-
+
// 🟢 State control for our new embedded warning modal layout
const [showFineBlockModal, setShowFineBlockModal] = useState(false);
@@ -26,7 +42,9 @@ export const IssueDetailsModal = ({ isOpen, onClose, record, onMarkAsReturned, o
queryKey: ["memberHistoricalReturnsCount", record?.memberId],
queryFn: async () => {
if (!record?.memberId) return null;
- const res = await axiosClient.get(`/issues/member-stats/${record.memberId}`);
+ const res = await axiosClient.get(
+ `/issues/member-stats/${record.memberId}`,
+ );
return res.data?.data || res.data;
},
enabled: !!record?.memberId && isOpen,
@@ -35,15 +53,17 @@ export const IssueDetailsModal = ({ isOpen, onClose, record, onMarkAsReturned, o
if (!isOpen || !record) return null;
// ✨ Feature: Safely parse and isolate the last 4 characters of the transaction UUID
- const formattedIssueId = record.id && record.id.length >= 4
- ? `ISSUE-${record.id.slice(-4).toUpperCase()}`
- : `ISSUE-${record.id}`;
+ const formattedIssueId =
+ record.id && record.id.length >= 4
+ ? `ISSUE-${record.id.slice(-4).toUpperCase()}`
+ : `ISSUE-${record.id}`;
// 🟢 Intercept Return Action to validate financial status
const handleReturnClick = () => {
// Check if there is an active outstanding fine on this specific record
- const hasUnpaidFine = record.fineAmount && record.fineAmount > 0 && !record.finePaidStatus;
-
+ const hasUnpaidFine =
+ record.fineAmount && record.fineAmount > 0 && !record.finePaidStatus;
+
if (hasUnpaidFine) {
// Catch the restriction instantly and display the professional layout block modal
setShowFineBlockModal(true);
@@ -56,19 +76,22 @@ export const IssueDetailsModal = ({ isOpen, onClose, record, onMarkAsReturned, o
return (
<>
{/* Primary Issue Details Window Desk Layer */}
-
-
-
+
+
{/* Header Block Panel */}
-
Issue Details
- {formattedIssueId}
+
+ Issue Details
+
+
+ {formattedIssueId}
+
-
@@ -77,20 +100,30 @@ export const IssueDetailsModal = ({ isOpen, onClose, record, onMarkAsReturned, o
{/* Member Meta Information Sub-Card */}
-
+
- Profile Account Context
+
+ Profile Account Context
+
+
+
+ {record.memberName}
+
+
+ ✉️ {record.memberEmail || "No Email Provided"}
-
{record.memberName}
-
✉️ {record.memberEmail || "No Email Provided"}
-
📞 {record.memberPhone || "No Phone Contact Registered"}
-
-
-
+
+ 📞 {record.memberPhone || "No Phone Contact Registered"}
+
+
+
+
Active Resource Holdings:{" "}
{isLoadingStats ? (
- Calculating...
+
+ Calculating...
+
) : (
{memberStats?.currentBorrows ?? 0} books outstanding
@@ -104,27 +137,41 @@ export const IssueDetailsModal = ({ isOpen, onClose, record, onMarkAsReturned, o
- Checked Inventory Volume
+
+ Checked Inventory Volume
+
+
+
+ 📖 {record.bookTitle}
+
+
+ Catalog Author: {record.bookAuthor || "Unknown Reference"}
-
📖 {record.bookTitle}
-
Catalog Author: {record.bookAuthor || "Unknown Reference"}
{/* Timeline Parameters Matrix */}
-
+
- Checkout Signature
+
+ Checkout Signature
+
+
+
+ {record.borrowedDate}
-
{record.borrowedDate}
- Target Return Due
+
+ Target Return Due
+
+
+
+ {record.dueDate}
-
{record.dueDate}
@@ -133,15 +180,15 @@ export const IssueDetailsModal = ({ isOpen, onClose, record, onMarkAsReturned, o
Edit
-
Cancel
@@ -155,22 +202,22 @@ export const IssueDetailsModal = ({ isOpen, onClose, record, onMarkAsReturned, o
-
{/* 🟢 NEW SECONDARY PORTAL LAYER: Professional Unpaid Fine Blocking Warning Pop-Up */}
{showFineBlockModal && (
-
-
+
{/* Warning Header block matches standard system alerts */}
-
Return Blocked: Pending Balance
+
+ Return Blocked: Pending Balance
+
Financial Validation Exception Bound
@@ -178,43 +225,55 @@ export const IssueDetailsModal = ({ isOpen, onClose, record, onMarkAsReturned, o
{/* Warning Body Parameters */}
-
+
- The library core system cannot authorize this inventory shelf check-in sequence because an unpaid fine liability matches this active operation.
+ The library core system cannot authorize this inventory shelf
+ check-in sequence because an unpaid fine liability matches this
+ active operation.
{/* Data Summary Box */}
-
+
- Account Holder:
- {record.memberName}
+
+ Account Holder:
+
+
+ {record.memberName}
+
- Asset Volume:
- {record.bookTitle}
+
+ Asset Volume:
+
+
+ {record.bookTitle}
+
Overdue Debt:
-
+
₹{record.fineAmount}.00
-
- Policy Rule: Outstanding debt liabilities must clear through the cash registration counter desk before restoring book items back into system catalog slots.
+
+ Policy Rule: Outstanding debt liabilities must clear through the
+ cash registration counter desk before restoring book items back
+ into system catalog slots.
{/* Footer Control Panel */}
-
+
setShowFineBlockModal(false)}
- className="px-4 py-2 bg-white text-slate-500 border border-slate-200 hover:text-slate-900 font-bold text-xs uppercase tracking-wider rounded-xl transition-all cursor-pointer"
+ className="px-4 py-2 bg-card-bg text-slate-500 border border-border-main hover:text-text-main font-bold text-xs uppercase tracking-wider rounded-xl transition-all cursor-pointer"
>
Dismiss Alert
@@ -230,10 +289,9 @@ export const IssueDetailsModal = ({ isOpen, onClose, record, onMarkAsReturned, o
Collect Fine Counter
-
)}
>
);
-};
\ No newline at end of file
+};
diff --git a/client/src/features/issues/components/TransactionModal.tsx b/client/src/features/issues/components/TransactionModal.tsx
index e0a60ef..bf35a7d 100644
--- a/client/src/features/issues/components/TransactionModal.tsx
+++ b/client/src/features/issues/components/TransactionModal.tsx
@@ -12,7 +12,13 @@ interface ExtendedBookIssueRecord extends BookIssueRecord {
}
interface SearchCompliance {
- status: "GOOD" | "EXPIRED" | "LIMIT_EXCEEDED" | "WARNING_LAST_SLOT" | "AVAILABLE" | "OUT_OF_STOCK";
+ status:
+ | "GOOD"
+ | "EXPIRED"
+ | "LIMIT_EXCEEDED"
+ | "WARNING_LAST_SLOT"
+ | "AVAILABLE"
+ | "OUT_OF_STOCK";
message: string;
isBlocked: boolean;
}
@@ -49,11 +55,21 @@ interface LocalBorrowMetrics {
interface TransactionModalProps {
isOpen: boolean;
onClose: () => void;
- onSubmit: (payload: { memberId: string; bookId: string; borrowDate: string; dueDate: string }) => void;
+ onSubmit: (payload: {
+ memberId: string;
+ bookId: string;
+ borrowDate: string;
+ dueDate: string;
+ }) => void;
editingRecord?: ExtendedBookIssueRecord | null;
}
-export const TransactionModal = ({ isOpen, onClose, onSubmit, editingRecord }: TransactionModalProps) => {
+export const TransactionModal = ({
+ isOpen,
+ onClose,
+ onSubmit,
+ editingRecord,
+}: TransactionModalProps) => {
const getTodayString = () => new Date().toISOString().split("T")[0];
const getTomorrowString = () => {
const tomorrow = new Date();
@@ -68,10 +84,14 @@ export const TransactionModal = ({ isOpen, onClose, onSubmit, editingRecord }: T
};
// ✨ Structural fallback for edit sessions
- const fixedRecordExpiry = editingRecord ? (editingRecord.membershipExpiryDate || editingRecord.expiryDate || "") : "";
+ const fixedRecordExpiry = editingRecord
+ ? editingRecord.membershipExpiryDate || editingRecord.expiryDate || ""
+ : "";
// ✨ Initial State Configuration
- const [memberSearch, setMemberSearch] = useState(editingRecord ? editingRecord.memberName : "");
+ const [memberSearch, setMemberSearch] = useState(
+ editingRecord ? editingRecord.memberName : "",
+ );
const [selectedMember, setSelectedMember] = useState
(
editingRecord
? {
@@ -83,13 +103,15 @@ export const TransactionModal = ({ isOpen, onClose, onSubmit, editingRecord }: T
plan_name: "Active Loan Lifecycle",
maxAllowed: 20,
currentBorrows: 0,
- compliance: { status: "GOOD", message: "", isBlocked: false }
+ compliance: { status: "GOOD", message: "", isBlocked: false },
}
- : null
+ : null,
);
const [showMemberDropdown, setShowMemberDropdown] = useState(false);
- const [bookSearch, setBookSearch] = useState(editingRecord ? editingRecord.bookTitle : "");
+ const [bookSearch, setBookSearch] = useState(
+ editingRecord ? editingRecord.bookTitle : "",
+ );
const [selectedBook, setSelectedBook] = useState(
editingRecord
? {
@@ -97,13 +119,15 @@ export const TransactionModal = ({ isOpen, onClose, onSubmit, editingRecord }: T
title: editingRecord.bookTitle,
author: editingRecord.bookAuthor || "",
available_copies: 1,
- compliance: { status: "AVAILABLE", message: "", isBlocked: false }
+ compliance: { status: "AVAILABLE", message: "", isBlocked: false },
}
- : null
+ : null,
);
const [showBookDropdown, setShowBookDropdown] = useState(false);
- const [dueDate, setDueDate] = useState(editingRecord ? editingRecord.dueDate : "");
+ const [dueDate, setDueDate] = useState(
+ editingRecord ? editingRecord.dueDate : "",
+ );
const [borrowMetrics, setBorrowMetrics] = useState({
currentBorrows: 0,
maxAllowed: 20,
@@ -116,17 +140,24 @@ export const TransactionModal = ({ isOpen, onClose, onSubmit, editingRecord }: T
// ✨ Async Network Lifecycle Synchronization
useEffect(() => {
if (selectedMember && isOpen) {
- axiosClient.get(`/issues/member-allowance/${selectedMember.member_id}`)
+ axiosClient
+ .get(`/issues/member-allowance/${selectedMember.member_id}`)
.then((res) => {
const metrics = res.data?.data || res.data;
-
+
// Fallback parsing priority order for custom API schemas
- const dynamicExpiry = metrics.expiryDate || metrics.membershipExpiryDate || selectedMember.expiry_date || fixedRecordExpiry;
+ const dynamicExpiry =
+ metrics.expiryDate ||
+ metrics.membershipExpiryDate ||
+ selectedMember.expiry_date ||
+ fixedRecordExpiry;
setBorrowMetrics({
currentBorrows: metrics.currentBorrows || 0,
maxAllowed: metrics.maxAllowed || 20,
- isExpired: selectedMember.membership_status === "EXPIRED" || selectedMember.compliance?.status === "EXPIRED",
+ isExpired:
+ selectedMember.membership_status === "EXPIRED" ||
+ selectedMember.compliance?.status === "EXPIRED",
expiryDate: dynamicExpiry,
complianceStatus: selectedMember.compliance?.status || "GOOD",
complianceMessage: selectedMember.compliance?.message || "",
@@ -134,9 +165,9 @@ export const TransactionModal = ({ isOpen, onClose, onSubmit, editingRecord }: T
})
.catch(() => {
// Keep the baseline structural record expiration values safe even if server fails
- setBorrowMetrics(prev => ({
- ...prev,
- expiryDate: fixedRecordExpiry
+ setBorrowMetrics((prev) => ({
+ ...prev,
+ expiryDate: fixedRecordExpiry,
}));
});
}
@@ -166,26 +197,37 @@ export const TransactionModal = ({ isOpen, onClose, onSubmit, editingRecord }: T
if (!isOpen) return null;
const hasMemberText = memberSearch.trim().length > 0;
- const isSelectedMemberMatchingInput = selectedMember && selectedMember.name === memberSearch;
-
+ const isSelectedMemberMatchingInput =
+ selectedMember && selectedMember.name === memberSearch;
+
// Ensure info card stays loaded instantly on edit parameters
- const showMemberInfoCard = !!editingRecord || (hasMemberText && isSelectedMemberMatchingInput);
+ const showMemberInfoCard =
+ !!editingRecord || (hasMemberText && isSelectedMemberMatchingInput);
+
+ const reachedPlanCap =
+ borrowMetrics.currentBorrows >= borrowMetrics.maxAllowed ||
+ borrowMetrics.complianceStatus === "LIMIT_EXCEEDED";
- const reachedPlanCap = borrowMetrics.currentBorrows >= borrowMetrics.maxAllowed || borrowMetrics.complianceStatus === "LIMIT_EXCEEDED";
-
// 🔒 Fixed Absolute Calendaring System
const cleanDueDate = formatToISODate(dueDate);
const maxAllowedDate = formatToISODate(borrowMetrics.expiryDate);
- const minAllowedDate = editingRecord ? formatToISODate(editingRecord.borrowedDate) : getTomorrowString();
+ const minAllowedDate = editingRecord
+ ? formatToISODate(editingRecord.borrowedDate)
+ : getTomorrowString();
- const isDateRangeInvalid = !!(maxAllowedDate && cleanDueDate > maxAllowedDate) || (cleanDueDate < minAllowedDate);
+ const isDateRangeInvalid =
+ !!(maxAllowedDate && cleanDueDate > maxAllowedDate) ||
+ cleanDueDate < minAllowedDate;
- const isSubmissionBlocked =
- !selectedMember ||
- !selectedBook ||
+ const isSubmissionBlocked =
+ !selectedMember ||
+ !selectedBook ||
!dueDate ||
isDateRangeInvalid ||
- (!editingRecord && (borrowMetrics.isExpired || reachedPlanCap || (selectedBook && selectedBook.available_copies <= 0)));
+ (!editingRecord &&
+ (borrowMetrics.isExpired ||
+ reachedPlanCap ||
+ (selectedBook && selectedBook.available_copies <= 0)));
const handleFormSubmit = (e: React.FormEvent) => {
e.preventDefault();
@@ -200,104 +242,139 @@ export const TransactionModal = ({ isOpen, onClose, onSubmit, editingRecord }: T
};
return (
-
-
-
+
+
{/* HEADER BLOCK BRANDED SYSTEM */}
-
Circulation Terminal
- {editingRecord ? "Edit issue details" : "Issue New Book"}
+
+ Circulation Terminal
+
+
+ {editingRecord ? "Edit issue details" : "Issue New Book"}
+
-