Faizur Rahman Zunayed commited on
Commit
58668c3
Β·
0 Parent(s):

Initial commit: Library Management System

Browse files
.gitignore ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ __pycache__/
2
+ *.pyc
3
+ *.pyo
4
+ *.pyd
5
+ .Python
6
+ *.so
7
+ *.egg
8
+ *.egg-info/
9
+ dist/
10
+ build/
11
+ venv/
12
+ .venv/
13
+ *.log
14
+ .DS_Store
Dockerfile ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.12-slim
2
+
3
+ WORKDIR /app
4
+
5
+ # Install system dependencies
6
+ RUN apt-get update && apt-get install -y \
7
+ && rm -rf /var/lib/apt/lists/*
8
+
9
+ # Copy requirements and install Python dependencies
10
+ COPY requirements.txt .
11
+ RUN pip install --no-cache-dir -r requirements.txt
12
+
13
+ # Copy application files
14
+ COPY . .
15
+
16
+ # Create data directory
17
+ RUN mkdir -p data
18
+
19
+ # Initialize database
20
+ RUN python3 -c "import database as db; db.init_db()"
21
+
22
+ # Expose port
23
+ EXPOSE 7860
24
+
25
+ # Run the application
26
+ CMD ["python3", "app.py"]
README.md ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Library Management System
3
+ emoji: πŸ“š
4
+ colorFrom: blue
5
+ colorTo: green
6
+ sdk: docker
7
+ pinned: false
8
+ ---
9
+
10
+ # Library Management System
11
+
12
+ A comprehensive Flask-based Library Management System with role-based access control.
13
+
14
+ ## Features
15
+
16
+ - **User Roles**: Admin, Librarian, Student
17
+ - **Book Management**: Add, edit, delete, search books
18
+ - **Student Management**: Manage student records
19
+ - **Issue/Return System**: Track book borrowing and returns
20
+ - **Overdue Tracking**: Monitor overdue books
21
+ - **Reports**: System statistics and analytics
22
+
23
+ ## Login Credentials
24
+
25
+ - **Admin**: admin@lms.com / admin123
26
+ - **Student**: student@test.com / student123
Start Library System.command ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ #!/bin/bash
2
+ cd "$(dirname "$0")"
3
+ ./run.sh
app.py ADDED
@@ -0,0 +1,420 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Library Management System - Main Application
3
+ """
4
+ from flask import Flask, render_template, request, redirect, url_for, flash, jsonify
5
+ from flask_login import LoginManager, login_user, logout_user, login_required, current_user
6
+ from functools import wraps
7
+ import bcrypt
8
+ from datetime import datetime
9
+ import uuid
10
+
11
+ from models import User, Book, Issue
12
+ import database as db
13
+
14
+ app = Flask(__name__)
15
+ app.secret_key = 'your-secret-key-here-change-in-production'
16
+
17
+ # Initialize Flask-Login
18
+ login_manager = LoginManager()
19
+ login_manager.init_app(app)
20
+ login_manager.login_view = 'login'
21
+
22
+ # Initialize database
23
+ db.init_db()
24
+
25
+
26
+ @login_manager.user_loader
27
+ def load_user(user_id):
28
+ return db.get_user(user_id)
29
+
30
+
31
+ # Role-based access decorators
32
+ def admin_required(f):
33
+ @wraps(f)
34
+ @login_required
35
+ def decorated_function(*args, **kwargs):
36
+ if not current_user.is_admin():
37
+ flash('Access denied. Admin privileges required.', 'danger')
38
+ return redirect(url_for('dashboard'))
39
+ return f(*args, **kwargs)
40
+ return decorated_function
41
+
42
+
43
+ def librarian_required(f):
44
+ @wraps(f)
45
+ @login_required
46
+ def decorated_function(*args, **kwargs):
47
+ if not current_user.can_manage_books():
48
+ flash('Access denied. Librarian or Admin privileges required.', 'danger')
49
+ return redirect(url_for('dashboard'))
50
+ return f(*args, **kwargs)
51
+ return decorated_function
52
+
53
+
54
+ # Authentication routes
55
+ @app.route('/login', methods=['GET', 'POST'])
56
+ def login():
57
+ if current_user.is_authenticated:
58
+ return redirect(url_for('dashboard'))
59
+
60
+ if request.method == 'POST':
61
+ email = request.form.get('email')
62
+ password = request.form.get('password')
63
+
64
+ user = db.get_user_by_email(email)
65
+ if user and bcrypt.checkpw(password.encode(), user.password.encode()):
66
+ login_user(user)
67
+ flash(f'Welcome back, {user.name}!', 'success')
68
+ return redirect(url_for('dashboard'))
69
+
70
+ flash('Invalid email or password', 'danger')
71
+
72
+ return render_template('login.html')
73
+
74
+
75
+ @app.route('/logout')
76
+ @login_required
77
+ def logout():
78
+ logout_user()
79
+ flash('You have been logged out successfully', 'success')
80
+ return redirect(url_for('login'))
81
+
82
+
83
+ # Dashboard
84
+ @app.route('/')
85
+ @app.route('/dashboard')
86
+ @login_required
87
+ def dashboard():
88
+ stats = db.get_stats()
89
+
90
+ if current_user.is_student():
91
+ # Student dashboard
92
+ books = db.get_all_books()
93
+ my_issues = db.get_user_issues(current_user.id)
94
+ return render_template('student_dashboard.html',
95
+ stats=stats, books=books,
96
+ my_issues=my_issues)
97
+ else:
98
+ # Admin/Librarian dashboard
99
+ recent_issues = db.get_active_issues()[:10]
100
+ overdue = db.get_overdue_issues()
101
+ return render_template('dashboard.html',
102
+ stats=stats, recent_issues=recent_issues,
103
+ overdue=overdue)
104
+
105
+
106
+ # Student Management
107
+ @app.route('/students')
108
+ @librarian_required
109
+ def students():
110
+ # Get search query
111
+ search_query = request.args.get('q', '').strip().lower()
112
+
113
+ # Get all students
114
+ all_students = db.get_students()
115
+
116
+ # Filter by search query if provided
117
+ if search_query:
118
+ students = [s for s in all_students if
119
+ search_query in s.id.lower() or
120
+ search_query in s.name.lower()]
121
+ else:
122
+ students = all_students
123
+
124
+ # Get ONLY ACTIVE issued books for each student
125
+ student_active_issues = {}
126
+ for student in students:
127
+ all_issues = db.get_user_issues(student.id)
128
+ # Filter only active (not returned)
129
+ active_issues = [issue for issue in all_issues if issue.status == 'issued']
130
+ student_active_issues[student.id] = active_issues
131
+
132
+ return render_template('students.html', students=students,
133
+ student_issues=student_active_issues, search_query=search_query)
134
+
135
+
136
+ @app.route('/students/add', methods=['GET', 'POST'])
137
+ @librarian_required
138
+ def add_student():
139
+ if request.method == 'POST':
140
+ student_id = request.form.get('student_id')
141
+ name = request.form.get('name')
142
+ email = request.form.get('email')
143
+ role = request.form.get('role', 'student') # Get role from form
144
+ department = request.form.get('department')
145
+ contact = request.form.get('contact')
146
+ password = request.form.get('password', 'student123')
147
+
148
+ # Check if student ID already exists
149
+ if db.get_user(student_id):
150
+ flash('User ID already exists', 'danger')
151
+ return redirect(url_for('add_student'))
152
+
153
+ # Hash password
154
+ hashed_password = bcrypt.hashpw(password.encode(), bcrypt.gensalt())
155
+
156
+ student = User(
157
+ id=student_id,
158
+ name=name,
159
+ email=email,
160
+ password=hashed_password.decode(),
161
+ role=role, # Use role from form
162
+ department=department,
163
+ contact=contact
164
+ )
165
+
166
+ db.add_user(student)
167
+ flash(f'{role.capitalize()} {name} added successfully', 'success')
168
+ return redirect(url_for('students'))
169
+
170
+ return render_template('add_student.html')
171
+
172
+
173
+ @app.route('/students/edit/<student_id>', methods=['GET', 'POST'])
174
+ @librarian_required
175
+ def edit_student(student_id):
176
+ student = db.get_user(student_id)
177
+ if not student:
178
+ flash('User not found', 'danger')
179
+ return redirect(url_for('students'))
180
+
181
+ if request.method == 'POST':
182
+ student.name = request.form.get('name')
183
+ student.email = request.form.get('email')
184
+ student.role = request.form.get('role', 'student') # Get role from form
185
+ student.department = request.form.get('department')
186
+ student.contact = request.form.get('contact')
187
+
188
+ db.update_user(student)
189
+ flash(f'{student.role.capitalize()} {student.name} updated successfully', 'success')
190
+ return redirect(url_for('students'))
191
+
192
+ return render_template('edit_student.html', student=student)
193
+
194
+
195
+ @app.route('/students/delete/<student_id>', methods=['POST'])
196
+ @librarian_required
197
+ def delete_student(student_id):
198
+ db.delete_user(student_id)
199
+ flash('Student deleted successfully', 'success')
200
+ return redirect(url_for('students'))
201
+
202
+
203
+ @app.route('/students/view/<student_id>')
204
+ @librarian_required
205
+ def view_student(student_id):
206
+ student = db.get_user(student_id)
207
+ if not student:
208
+ flash('Student not found', 'danger')
209
+ return redirect(url_for('students'))
210
+
211
+ # Get all issues for this student
212
+ all_issues = db.get_user_issues(student_id)
213
+
214
+ # Categorize issues
215
+ active_issues = [i for i in all_issues if i.status == 'issued']
216
+ returned_issues = [i for i in all_issues if i.status == 'returned']
217
+ current_overdue = len([i for i in active_issues if i.is_overdue()])
218
+
219
+ # Calculate total times student has been overdue (including past returned books)
220
+ total_overdue_times = len([i for i in all_issues if i.is_overdue() or (i.status == 'returned' and i.days_overdue() > 0)])
221
+
222
+ # Get books info for issues
223
+ issues_with_books = []
224
+ for issue in all_issues:
225
+ book = db.get_book(issue.book_id)
226
+ issues_with_books.append({
227
+ 'issue': issue,
228
+ 'book': book
229
+ })
230
+
231
+ return render_template('view_student.html',
232
+ student=student,
233
+ issues_with_books=issues_with_books,
234
+ active_count=len(active_issues),
235
+ returned_count=len(returned_issues),
236
+ overdue_count=current_overdue,
237
+ total_overdue_times=total_overdue_times)
238
+
239
+
240
+ # Book Management
241
+ @app.route('/books')
242
+ @login_required
243
+ def books():
244
+ query = request.args.get('q', '')
245
+ category = request.args.get('category', '')
246
+ available_only = request.args.get('available') == 'true'
247
+
248
+ books = db.search_books(query, category, available_only)
249
+ categories = list(set(b.category for b in db.get_all_books()))
250
+
251
+ return render_template('books.html', books=books, categories=categories,
252
+ query=query, selected_category=category,
253
+ available_only=available_only)
254
+
255
+
256
+ @app.route('/books/add', methods=['GET', 'POST'])
257
+ @librarian_required
258
+ def add_book():
259
+ if request.method == 'POST':
260
+ book = Book(
261
+ book_id=request.form.get('book_id'),
262
+ title=request.form.get('title'),
263
+ author=request.form.get('author'),
264
+ isbn=request.form.get('isbn'),
265
+ category=request.form.get('category'),
266
+ publisher=request.form.get('publisher'),
267
+ year=int(request.form.get('year')),
268
+ total_copies=int(request.form.get('total_copies')),
269
+ shelf_no=request.form.get('shelf_no'),
270
+ cover_image=request.form.get('cover_image')
271
+ )
272
+
273
+ db.add_book(book)
274
+ flash(f'Book "{book.title}" added successfully', 'success')
275
+ return redirect(url_for('books'))
276
+
277
+ return render_template('add_book.html')
278
+
279
+
280
+ @app.route('/books/edit/<book_id>', methods=['GET', 'POST'])
281
+ @librarian_required
282
+ def edit_book(book_id):
283
+ book = db.get_book(book_id)
284
+ if not book:
285
+ flash('Book not found', 'danger')
286
+ return redirect(url_for('books'))
287
+
288
+ if request.method == 'POST':
289
+ book.title = request.form.get('title')
290
+ book.author = request.form.get('author')
291
+ book.isbn = request.form.get('isbn')
292
+ book.category = request.form.get('category')
293
+ book.publisher = request.form.get('publisher')
294
+ book.year = int(request.form.get('year'))
295
+ book.total_copies = int(request.form.get('total_copies'))
296
+ book.shelf_no = request.form.get('shelf_no')
297
+ book.cover_image = request.form.get('cover_image')
298
+
299
+ db.update_book(book)
300
+ flash(f'Book "{book.title}" updated successfully', 'success')
301
+ return redirect(url_for('books'))
302
+
303
+ return render_template('edit_book.html', book=book)
304
+
305
+
306
+ @app.route('/books/delete/<book_id>', methods=['POST'])
307
+ @librarian_required
308
+ def delete_book(book_id):
309
+ book = db.get_book(book_id)
310
+ if book:
311
+ db.delete_book(book_id)
312
+ flash(f'Book "{book.title}" deleted successfully', 'success')
313
+ return redirect(url_for('books'))
314
+
315
+
316
+ # Issue/Borrow System
317
+ @app.route('/issue', methods=['GET', 'POST'])
318
+ @librarian_required
319
+ def issue_book():
320
+ if request.method == 'POST':
321
+ student_id = request.form.get('student_id')
322
+ book_id = request.form.get('book_id')
323
+
324
+ student = db.get_user(student_id)
325
+ book = db.get_book(book_id)
326
+
327
+ if not student:
328
+ flash('Student not found', 'danger')
329
+ return redirect(url_for('issue_book'))
330
+
331
+ if not book:
332
+ flash('Book not found', 'danger')
333
+ return redirect(url_for('issue_book'))
334
+
335
+ if not book.is_available():
336
+ flash('No copies available for this book', 'danger')
337
+ return redirect(url_for('issue_book'))
338
+
339
+ # Create issue record
340
+ issue = Issue(
341
+ issue_id=str(uuid.uuid4())[:8],
342
+ student_id=student_id,
343
+ book_id=book_id
344
+ )
345
+
346
+ db.add_issue(issue)
347
+
348
+ # Update book availability
349
+ db.update_book_copies(book_id, book.available_copies - 1)
350
+
351
+ flash(f'Book "{book.title}" issued to {student.name}', 'success')
352
+ return redirect(url_for('dashboard'))
353
+
354
+ students = db.get_students()
355
+ books = db.search_books(available_only=True)
356
+ return render_template('issue_book.html', students=students, books=books)
357
+
358
+
359
+ # Return System
360
+ @app.route('/return', methods=['GET', 'POST'])
361
+ @librarian_required
362
+ def return_book():
363
+ if request.method == 'POST':
364
+ student_id = request.form.get('student_id')
365
+ book_id = request.form.get('book_id')
366
+
367
+ # Find the active issue
368
+ issues = [i for i in db.get_user_issues(student_id)
369
+ if i.book_id == book_id and i.status == 'issued']
370
+
371
+ if not issues:
372
+ flash('No active issue found for this book and student', 'danger')
373
+ return redirect(url_for('return_book'))
374
+
375
+ issue = issues[0]
376
+ issue.return_date = datetime.now()
377
+ issue.status = 'returned'
378
+
379
+ # Calculate fine if overdue
380
+ if issue.is_overdue():
381
+ fine_amount = issue.calculate_fine()
382
+ fine = Fine(
383
+ fine_id=str(uuid.uuid4())[:8],
384
+ issue_id=issue.issue_id,
385
+ student_id=student_id,
386
+ amount=fine_amount
387
+ )
388
+ db.add_fine(fine)
389
+ flash(f'Book returned. Fine: {fine_amount} BDT for {issue.days_overdue()} days overdue', 'warning')
390
+ else:
391
+ flash('Book returned successfully', 'success')
392
+
393
+ db.update_issue(issue)
394
+
395
+ # Update book availability
396
+ book = db.get_book(book_id)
397
+ db.update_book_copies(book_id, book.available_copies + 1)
398
+
399
+ return redirect(url_for('dashboard'))
400
+
401
+ active_issues = db.get_active_issues()
402
+ return render_template('return_book.html', issues=active_issues)
403
+
404
+
405
+ # Reports
406
+ @app.route('/reports')
407
+ @librarian_required
408
+ def reports():
409
+ stats = db.get_stats()
410
+ all_issues = db.get_all_issues()
411
+ overdue = db.get_overdue_issues()
412
+
413
+ return render_template('reports.html', stats=stats, issues=all_issues,
414
+ overdue=overdue)
415
+
416
+
417
+ if __name__ == '__main__':
418
+ import os
419
+ port = int(os.environ.get('PORT', 7860))
420
+ app.run(debug=False, host='0.0.0.0', port=port)
data/books.json ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "3332": {
3
+ "book_id": "3332",
4
+ "title": "dds",
5
+ "author": "sdf",
6
+ "isbn": "333w",
7
+ "category": "wrrw",
8
+ "publisher": "wrrw",
9
+ "year": 2324,
10
+ "total_copies": 24,
11
+ "available_copies": 23,
12
+ "shelf_no": "24",
13
+ "cover_image": "",
14
+ "created_at": "2025-11-13T19:02:12.227703"
15
+ },
16
+ "BK001": {
17
+ "book_id": "BK001",
18
+ "title": "Python Programming",
19
+ "author": "John Doe",
20
+ "isbn": "978-123",
21
+ "category": "Programming",
22
+ "publisher": "Tech Books",
23
+ "year": 2023,
24
+ "total_copies": 5,
25
+ "available_copies": 4,
26
+ "shelf_no": "A1",
27
+ "cover_image": null,
28
+ "created_at": "2025-11-13T18:50:02.162456"
29
+ },
30
+ "250001": {
31
+ "book_id": "250001",
32
+ "title": "T/O",
33
+ "author": "Osmani",
34
+ "isbn": "876543",
35
+ "category": "Tafsir",
36
+ "publisher": "Thanvi Publ.",
37
+ "year": 2017,
38
+ "total_copies": 5,
39
+ "available_copies": 5,
40
+ "shelf_no": "1B",
41
+ "cover_image": "",
42
+ "created_at": "2025-11-15T20:47:34.767088"
43
+ }
44
+ }
data/fines.json ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "45bcfbaf": {
3
+ "fine_id": "45bcfbaf",
4
+ "issue_id": "974e6573",
5
+ "student_id": "STU001",
6
+ "amount": 50,
7
+ "paid": false,
8
+ "paid_date": null,
9
+ "created_at": "2025-11-13T18:41:36.870469"
10
+ }
11
+ }
data/issues.json ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "974e6573": {
3
+ "issue_id": "974e6573",
4
+ "student_id": "STU001",
5
+ "book_id": "BK001",
6
+ "issue_date": "2025-11-13T18:41:36.869792",
7
+ "due_date": "2025-11-08T18:41:36.870443",
8
+ "return_date": "2025-11-13T18:41:36.870753",
9
+ "status": "returned"
10
+ },
11
+ "69e000b1": {
12
+ "issue_id": "69e000b1",
13
+ "student_id": "123",
14
+ "book_id": "3332",
15
+ "issue_date": "2025-11-13T19:02:12.226945",
16
+ "due_date": "2025-11-27T19:02:12.226947",
17
+ "return_date": null,
18
+ "status": "issued"
19
+ },
20
+ "fa74d48a": {
21
+ "issue_id": "fa74d48a",
22
+ "student_id": "STU001",
23
+ "book_id": "BK001",
24
+ "issue_date": "2025-11-13T19:02:59.338752",
25
+ "due_date": "2025-11-27T19:02:59.338773",
26
+ "return_date": null,
27
+ "status": "issued"
28
+ },
29
+ "894a4b97": {
30
+ "issue_id": "894a4b97",
31
+ "student_id": "250001",
32
+ "book_id": "250001",
33
+ "issue_date": "2025-11-15T20:49:47.878206",
34
+ "due_date": "2025-11-29T20:49:47.878228",
35
+ "return_date": "2025-11-15T20:50:31.959295",
36
+ "status": "returned"
37
+ }
38
+ }
data/users.json ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "ADMIN001": {
3
+ "id": "ADMIN001",
4
+ "name": "System Admin",
5
+ "email": "admin@lms.com",
6
+ "password": "$2b$12$fHhvbTH/kuz5ORiJlVDj6.38QOQa.6Eyr0Qp9zpImoHpUtXcSuXme",
7
+ "role": "admin",
8
+ "department": null,
9
+ "contact": null,
10
+ "created_at": "2025-11-11T20:59:30.219380"
11
+ },
12
+ "123": {
13
+ "id": "123",
14
+ "name": "abc",
15
+ "email": "gghg@gmail.com",
16
+ "password": "$2b$12$HmLWaXVJ0rVca6WgTi0qneD1ln2WIcW9Hp1rRLxbWACaEA55h6FG2",
17
+ "role": "student",
18
+ "department": "asd",
19
+ "contact": "asds",
20
+ "created_at": "2025-11-13T03:30:41.405388"
21
+ },
22
+ "STU001": {
23
+ "id": "STU001",
24
+ "name": "Test Student",
25
+ "email": "student@test.com",
26
+ "password": "$2b$12$f4nnfEFbtzBIg7CYisfz9eY6lBwjhFVMa4m1m536.HC5DyH8qEHoC",
27
+ "role": "student",
28
+ "department": "Computer Science",
29
+ "contact": "01712345678",
30
+ "created_at": "2025-11-13T19:58:12.293294"
31
+ },
32
+ "250001": {
33
+ "id": "250001",
34
+ "name": "Abdullah",
35
+ "email": "abdullah@lms.com",
36
+ "password": "$2b$12$YYK8u2F5m/No6Vaybgb7qOCzgqtEfz0xq3oPt//gJg.f1KzMdEbL6",
37
+ "role": "student",
38
+ "department": "Sharyiah",
39
+ "contact": "01711111111",
40
+ "created_at": "2025-11-15T20:40:22.624008"
41
+ }
42
+ }
database.py ADDED
@@ -0,0 +1,273 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Database layer - JSON file-based storage
3
+ """
4
+ import json
5
+ import os
6
+ from datetime import datetime
7
+ from models import User, Book, Issue
8
+
9
+ DATA_DIR = 'data'
10
+ USERS_FILE = os.path.join(DATA_DIR, 'users.json')
11
+ BOOKS_FILE = os.path.join(DATA_DIR, 'books.json')
12
+ ISSUES_FILE = os.path.join(DATA_DIR, 'issues.json')
13
+
14
+ # Ensure data directory exists
15
+ os.makedirs(DATA_DIR, exist_ok=True)
16
+
17
+
18
+ def init_db():
19
+ """Initialize database with default admin user"""
20
+ import bcrypt
21
+
22
+ if not os.path.exists(USERS_FILE):
23
+ admin_password = bcrypt.hashpw('admin123'.encode(), bcrypt.gensalt())
24
+ admin = User(
25
+ id='ADMIN001',
26
+ name='System Admin',
27
+ email='admin@lms.com',
28
+ password=admin_password.decode(),
29
+ role='admin'
30
+ )
31
+ save_data(USERS_FILE, {'ADMIN001': admin.to_dict()})
32
+
33
+ for file in [BOOKS_FILE, ISSUES_FILE]:
34
+ if not os.path.exists(file):
35
+ save_data(file, {})
36
+
37
+
38
+ def load_data(filename):
39
+ """Load data from JSON file"""
40
+ try:
41
+ with open(filename, 'r') as f:
42
+ return json.load(f)
43
+ except (FileNotFoundError, json.JSONDecodeError):
44
+ return {}
45
+
46
+
47
+ def save_data(filename, data):
48
+ """Save data to JSON file"""
49
+ with open(filename, 'w') as f:
50
+ json.dump(data, f, indent=2, default=str)
51
+
52
+
53
+ # User operations
54
+ def get_user(user_id):
55
+ """Get user by ID"""
56
+ users = load_data(USERS_FILE)
57
+ user_data = users.get(user_id)
58
+ if user_data:
59
+ return User(**user_data)
60
+ return None
61
+
62
+
63
+ def get_user_by_email(email):
64
+ """Get user by email"""
65
+ users = load_data(USERS_FILE)
66
+ for user_data in users.values():
67
+ if user_data['email'] == email:
68
+ return User(**user_data)
69
+ return None
70
+
71
+
72
+ def get_all_users():
73
+ """Get all users"""
74
+ users = load_data(USERS_FILE)
75
+ return [User(**data) for data in users.values()]
76
+
77
+
78
+ def get_students():
79
+ """Get all students"""
80
+ users = load_data(USERS_FILE)
81
+ return [User(**data) for data in users.values() if data['role'] == 'student']
82
+
83
+
84
+ def add_user(user):
85
+ """Add new user"""
86
+ users = load_data(USERS_FILE)
87
+ users[user.id] = user.to_dict()
88
+ save_data(USERS_FILE, users)
89
+ return user
90
+
91
+
92
+ def update_user(user):
93
+ """Update existing user"""
94
+ users = load_data(USERS_FILE)
95
+ if user.id in users:
96
+ users[user.id] = user.to_dict()
97
+ save_data(USERS_FILE, users)
98
+ return True
99
+ return False
100
+
101
+
102
+ def delete_user(user_id):
103
+ """Delete user"""
104
+ users = load_data(USERS_FILE)
105
+ if user_id in users:
106
+ del users[user_id]
107
+ save_data(USERS_FILE, users)
108
+ return True
109
+ return False
110
+
111
+
112
+ # Book operations
113
+ def get_book(book_id):
114
+ """Get book by ID"""
115
+ books = load_data(BOOKS_FILE)
116
+ book_data = books.get(book_id)
117
+ if book_data:
118
+ # Remove computed fields that shouldn't be in __init__
119
+ book_data.pop('is_available', None)
120
+ return Book(**book_data)
121
+ return None
122
+
123
+
124
+ def get_all_books():
125
+ """Get all books"""
126
+ books = load_data(BOOKS_FILE)
127
+ result = []
128
+ for data in books.values():
129
+ # Remove computed fields that shouldn't be in __init__
130
+ data.pop('is_available', None)
131
+ result.append(Book(**data))
132
+ return result
133
+
134
+
135
+ def search_books(query=None, category=None, available_only=False):
136
+ """Search books by title, author, ISBN or category"""
137
+ books = get_all_books()
138
+
139
+ if query:
140
+ query = query.lower()
141
+ books = [b for b in books if
142
+ query in b.title.lower() or
143
+ query in b.author.lower() or
144
+ query in b.isbn.lower()]
145
+
146
+ if category:
147
+ books = [b for b in books if b.category == category]
148
+
149
+ if available_only:
150
+ books = [b for b in books if b.is_available()]
151
+
152
+ return books
153
+
154
+
155
+ def add_book(book):
156
+ """Add new book"""
157
+ books = load_data(BOOKS_FILE)
158
+ books[book.book_id] = book.to_dict()
159
+ save_data(BOOKS_FILE, books)
160
+ return book
161
+
162
+
163
+ def update_book(book):
164
+ """Update existing book"""
165
+ books = load_data(BOOKS_FILE)
166
+ if book.book_id in books:
167
+ books[book.book_id] = book.to_dict()
168
+ save_data(BOOKS_FILE, books)
169
+ return True
170
+ return False
171
+
172
+
173
+ def delete_book(book_id):
174
+ """Delete book"""
175
+ books = load_data(BOOKS_FILE)
176
+ if book_id in books:
177
+ del books[book_id]
178
+ save_data(BOOKS_FILE, books)
179
+ return True
180
+ return False
181
+
182
+
183
+ def update_book_copies(book_id, available_copies):
184
+ """Update available copies count"""
185
+ book = get_book(book_id)
186
+ if book:
187
+ book.available_copies = available_copies
188
+ update_book(book)
189
+ return True
190
+ return False
191
+
192
+
193
+ # Issue operations
194
+ def get_issue(issue_id):
195
+ """Get issue by ID"""
196
+ issues = load_data(ISSUES_FILE)
197
+ issue_data = issues.get(issue_id)
198
+ if issue_data:
199
+ # Convert date strings back to datetime
200
+ if 'issue_date' in issue_data and issue_data['issue_date']:
201
+ issue_data['issue_date'] = datetime.fromisoformat(issue_data['issue_date'])
202
+ if 'due_date' in issue_data and issue_data['due_date']:
203
+ issue_data['due_date'] = datetime.fromisoformat(issue_data['due_date'])
204
+ if 'return_date' in issue_data and issue_data['return_date']:
205
+ issue_data['return_date'] = datetime.fromisoformat(issue_data['return_date'])
206
+ return Issue(**issue_data)
207
+ return None
208
+
209
+
210
+ def get_all_issues():
211
+ """Get all issues"""
212
+ issues = load_data(ISSUES_FILE)
213
+ result = []
214
+ for data in issues.values():
215
+ if 'issue_date' in data and data['issue_date']:
216
+ data['issue_date'] = datetime.fromisoformat(data['issue_date'])
217
+ if 'due_date' in data and data['due_date']:
218
+ data['due_date'] = datetime.fromisoformat(data['due_date'])
219
+ if 'return_date' in data and data['return_date']:
220
+ data['return_date'] = datetime.fromisoformat(data['return_date'])
221
+ result.append(Issue(**data))
222
+ return result
223
+
224
+
225
+ def get_active_issues():
226
+ """Get all currently issued books (not returned)"""
227
+ return [i for i in get_all_issues() if i.status == 'issued']
228
+
229
+
230
+ def get_overdue_issues():
231
+ """Get all overdue issues"""
232
+ return [i for i in get_active_issues() if i.is_overdue()]
233
+
234
+
235
+ def get_user_issues(user_id):
236
+ """Get all issues for a specific user"""
237
+ return [i for i in get_all_issues() if i.student_id == user_id]
238
+
239
+
240
+ def add_issue(issue):
241
+ """Add new issue"""
242
+ issues = load_data(ISSUES_FILE)
243
+ issues[issue.issue_id] = issue.to_dict()
244
+ save_data(ISSUES_FILE, issues)
245
+ return issue
246
+
247
+
248
+ def update_issue(issue):
249
+ """Update existing issue"""
250
+ issues = load_data(ISSUES_FILE)
251
+ if issue.issue_id in issues:
252
+ issues[issue.issue_id] = issue.to_dict()
253
+ save_data(ISSUES_FILE, issues)
254
+ return True
255
+ return False
256
+
257
+
258
+ # Statistics
259
+ def get_stats():
260
+ """Get system statistics"""
261
+ books = get_all_books()
262
+ users = get_students()
263
+ issues = get_active_issues()
264
+ overdue = get_overdue_issues()
265
+
266
+ return {
267
+ 'total_books': len(books),
268
+ 'total_copies': sum(b.total_copies for b in books),
269
+ 'available_copies': sum(b.available_copies for b in books),
270
+ 'total_students': len(users),
271
+ 'books_issued': len(issues),
272
+ 'overdue_books': len(overdue)
273
+ }
models.py ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Database models for Library Management System
3
+ """
4
+ from datetime import datetime, timedelta
5
+ from flask_login import UserMixin
6
+
7
+ class User(UserMixin):
8
+ """User model for authentication and role management"""
9
+
10
+ ROLES = {
11
+ 'admin': 'Admin',
12
+ 'librarian': 'Librarian',
13
+ 'student': 'Student'
14
+ }
15
+
16
+ def __init__(self, id, name, email, password, role='student', department=None, contact=None, created_at=None):
17
+ self.id = id
18
+ self.name = name
19
+ self.email = email
20
+ self.password = password # Should be hashed
21
+ self.role = role
22
+ self.department = department
23
+ self.contact = contact
24
+ self.created_at = created_at if created_at else datetime.now()
25
+
26
+ def is_admin(self):
27
+ return self.role == 'admin'
28
+
29
+ def is_librarian(self):
30
+ return self.role == 'librarian'
31
+
32
+ def is_student(self):
33
+ return self.role == 'student'
34
+
35
+ def can_manage_books(self):
36
+ return self.role in ['admin', 'librarian']
37
+
38
+ def can_manage_users(self):
39
+ return self.role in ['admin', 'librarian']
40
+
41
+ def to_dict(self):
42
+ return {
43
+ 'id': self.id,
44
+ 'name': self.name,
45
+ 'email': self.email,
46
+ 'password': self.password,
47
+ 'role': self.role,
48
+ 'department': self.department,
49
+ 'contact': self.contact,
50
+ 'created_at': self.created_at.isoformat() if isinstance(self.created_at, datetime) else self.created_at
51
+ }
52
+
53
+
54
+ class Book:
55
+ """Book model"""
56
+
57
+ def __init__(self, book_id, title, author, isbn, category, publisher,
58
+ year, total_copies, shelf_no, cover_image=None, available_copies=None, created_at=None):
59
+ self.book_id = book_id
60
+ self.title = title
61
+ self.author = author
62
+ self.isbn = isbn
63
+ self.category = category
64
+ self.publisher = publisher
65
+ self.year = year
66
+ self.total_copies = total_copies
67
+ self.available_copies = available_copies if available_copies is not None else total_copies
68
+ self.shelf_no = shelf_no
69
+ self.cover_image = cover_image
70
+ self.created_at = created_at if created_at else datetime.now()
71
+
72
+ def is_available(self):
73
+ return self.available_copies > 0
74
+
75
+ def to_dict(self):
76
+ return {
77
+ 'book_id': self.book_id,
78
+ 'title': self.title,
79
+ 'author': self.author,
80
+ 'isbn': self.isbn,
81
+ 'category': self.category,
82
+ 'publisher': self.publisher,
83
+ 'year': self.year,
84
+ 'total_copies': self.total_copies,
85
+ 'available_copies': self.available_copies,
86
+ 'shelf_no': self.shelf_no,
87
+ 'cover_image': self.cover_image,
88
+ 'created_at': self.created_at.isoformat() if isinstance(self.created_at, datetime) else self.created_at
89
+ }
90
+
91
+
92
+ class Issue:
93
+ """Book issue/borrowing record"""
94
+
95
+ def __init__(self, issue_id, student_id, book_id, issue_date=None, due_date=None,
96
+ return_date=None, status='issued'):
97
+ self.issue_id = issue_id
98
+ self.student_id = student_id
99
+ self.book_id = book_id
100
+ self.issue_date = issue_date or datetime.now()
101
+ self.due_date = due_date or (datetime.now() + timedelta(days=14)) # 14 days default
102
+ self.return_date = return_date
103
+ self.status = status # 'issued', 'returned', 'overdue'
104
+
105
+ def is_overdue(self):
106
+ if self.status == 'returned':
107
+ return False
108
+ return datetime.now() > self.due_date
109
+
110
+ def days_overdue(self):
111
+ if not self.is_overdue():
112
+ return 0
113
+ if self.return_date:
114
+ return (self.return_date - self.due_date).days
115
+ return (datetime.now() - self.due_date).days
116
+
117
+ def to_dict(self):
118
+ return {
119
+ 'issue_id': self.issue_id,
120
+ 'student_id': self.student_id,
121
+ 'book_id': self.book_id,
122
+ 'issue_date': self.issue_date.isoformat() if isinstance(self.issue_date, datetime) else self.issue_date,
123
+ 'due_date': self.due_date.isoformat() if isinstance(self.due_date, datetime) else self.due_date,
124
+ 'return_date': self.return_date.isoformat() if isinstance(self.return_date, datetime) else self.return_date,
125
+ 'status': self.status
126
+ }
127
+
128
+
129
+
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ flask==3.1.2
2
+ flask-login==0.6.3
3
+ bcrypt==4.1.2
4
+ werkzeug==3.1.3
run.sh ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+
3
+ # Library Management System Launcher
4
+ echo "πŸš€ Starting Library Management System..."
5
+ echo ""
6
+
7
+ # Navigate to LMS directory
8
+ cd "/Users/faizur.zunayed/Gwtf/LMS"
9
+
10
+ # Check if virtual environment exists, create if not
11
+ if [ ! -d "venv" ]; then
12
+ echo "πŸ“¦ Setting up environment for first time..."
13
+ python3.12 -m venv venv
14
+ source venv/bin/activate
15
+ pip install -r requirements.txt
16
+ else
17
+ source venv/bin/activate
18
+ fi
19
+
20
+ # Install dependencies if needed
21
+ echo "βœ… Environment ready!"
22
+ echo ""
23
+
24
+ # Run the application
25
+ echo "🌐 Starting server on http://127.0.0.1:5001"
26
+ echo "πŸ“§ Login: admin@lms.com / admin123"
27
+ echo ""
28
+ echo "Press Ctrl+C to stop the server"
29
+ echo "================================================"
30
+ echo ""
31
+
32
+ python3.12 app.py
templates/add_book.html ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+ {% block content %}
3
+ <h2>Add Book</h2>
4
+ <div class="card">
5
+ <form method="POST">
6
+ <label>Book ID:</label><input name="book_id" required>
7
+ <label>Title:</label><input name="title" required>
8
+ <label>Author:</label><input name="author" required>
9
+ <label>ISBN:</label><input name="isbn" required>
10
+ <label>Category:</label><input name="category" required>
11
+ <label>Publisher:</label><input name="publisher" required>
12
+ <label>Year:</label><input type="number" name="year" required>
13
+ <label>Total Copies:</label><input type="number" name="total_copies" required>
14
+ <label>Shelf No:</label><input name="shelf_no" required>
15
+ <label>Cover Image URL (optional):</label><input name="cover_image">
16
+ <button type="submit" class="btn">Add Book</button>
17
+ <a href="{{ url_for('books') }}" class="btn btn-danger">Cancel</a>
18
+ </form>
19
+ </div>
20
+ {% endblock %}
templates/add_student.html ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+ {% block content %}
3
+ <h2>Add User</h2>
4
+ <div class="card">
5
+ <form method="POST">
6
+ <label>User ID:</label><input name="student_id" required>
7
+ <label>Name:</label><input name="name" required>
8
+ <label>Email:</label><input type="email" name="email" required>
9
+ <label>Role:</label>
10
+ <select name="role" required style="padding: 0.5rem; border: 1px solid #d1d5db; border-radius: 4px; width: 100%; margin-bottom: 1rem;">
11
+ <option value="student">Student</option>
12
+ <option value="librarian">Librarian</option>
13
+ <option value="admin">Admin</option>
14
+ </select>
15
+ <label>Department:</label><input name="department">
16
+ <label>Contact:</label><input name="contact">
17
+ <label>Password (default: student123):</label><input type="password" name="password">
18
+ <button type="submit" class="btn">Add User</button>
19
+ <a href="{{ url_for('students') }}" class="btn btn-danger">Cancel</a>
20
+ </form>
21
+ </div>
22
+ {% endblock %}
templates/base.html ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>{% block title %}Library Management System{% endblock %}</title>
7
+ <style>
8
+ * { margin: 0; padding: 0; box-sizing: border-box; }
9
+ body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: #f5f5f5; }
10
+ nav { background: #2c3e50; color: white; padding: 1rem 2rem; display: flex; justify-content: space-between; align-items: center; }
11
+ nav h1 { font-size: 1.5rem; }
12
+ nav ul { list-style: none; display: flex; gap: 2rem; }
13
+ nav a { color: white; text-decoration: none; padding: 0.5rem 1rem; border-radius: 4px; }
14
+ nav a:hover { background: #34495e; }
15
+ .container { max-width: 1200px; margin: 2rem auto; padding: 0 2rem; }
16
+ .flash { padding: 1rem; margin-bottom: 1rem; border-radius: 4px; }
17
+ .flash.success { background: #d4edda; color: #155724; border: 1px solid #c3e6cb; }
18
+ .flash.danger { background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }
19
+ .flash.warning { background: #fff3cd; color: #856404; border: 1px solid #ffeaa7; }
20
+ .card { background: white; padding: 2rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); margin-bottom: 2rem; }
21
+ .btn { display: inline-block; padding: 0.75rem 1.5rem; background: #3498db; color: white; text-decoration: none; border-radius: 4px; border: none; cursor: pointer; }
22
+ .btn:hover { background: #2980b9; }
23
+ .btn-danger { background: #e74c3c; }
24
+ .btn-danger:hover { background: #c0392b; }
25
+ .btn-success { background: #27ae60; }
26
+ .btn-success:hover { background: #229954; }
27
+ table { width: 100%; border-collapse: collapse; }
28
+ table th, table td { padding: 1rem; text-align: left; border-bottom: 1px solid #ddd; }
29
+ table th { background: #ecf0f1; font-weight: 600; }
30
+ form label { display: block; margin-bottom: 0.5rem; font-weight: 500; }
31
+ form input, form select { width: 100%; padding: 0.75rem; margin-bottom: 1rem; border: 1px solid #ddd; border-radius: 4px; }
32
+ .stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; margin-bottom: 2rem; }
33
+ .stat-card { background: white; padding: 1.5rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
34
+ .stat-card h3 { color: #7f8c8d; font-size: 0.9rem; margin-bottom: 0.5rem; }
35
+ .stat-card p { font-size: 2rem; font-weight: bold; color: #2c3e50; }
36
+ </style>
37
+ </head>
38
+ <body>
39
+ {% if current_user.is_authenticated %}
40
+ <nav>
41
+ <h1>πŸ“š Library Management System</h1>
42
+ <ul>
43
+ <li><a href="{{ url_for('dashboard') }}">Dashboard</a></li>
44
+ {% if current_user.can_manage_books() %}
45
+ <li><a href="{{ url_for('students') }}">Students</a></li>
46
+ <li><a href="{{ url_for('books') }}">Books</a></li>
47
+ <li><a href="{{ url_for('issue_book') }}">Issue</a></li>
48
+ <li><a href="{{ url_for('return_book') }}">Return</a></li>
49
+ <li><a href="{{ url_for('reports') }}">Reports</a></li>
50
+ {% else %}
51
+ <li><a href="{{ url_for('books') }}">Browse Books</a></li>
52
+ {% endif %}
53
+ <li><a href="{{ url_for('logout') }}">Logout ({{ current_user.name }})</a></li>
54
+ </ul>
55
+ </nav>
56
+ {% endif %}
57
+
58
+ <div class="container">
59
+ {% with messages = get_flashed_messages(with_categories=true) %}
60
+ {% if messages %}
61
+ {% for category, message in messages %}
62
+ <div class="flash {{ category }}">{{ message }}</div>
63
+ {% endfor %}
64
+ {% endif %}
65
+ {% endwith %}
66
+
67
+ {% block content %}{% endblock %}
68
+ </div>
69
+ </body>
70
+ </html>
templates/books.html ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+ {% block content %}
3
+ <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 2rem;">
4
+ <h2>πŸ“š Books Library</h2>
5
+ {% if current_user.can_manage_books() %}
6
+ <a href="{{ url_for('add_book') }}" class="btn btn-success">+ Add New Book</a>
7
+ {% endif %}
8
+ </div>
9
+
10
+ <div class="card">
11
+ <form method="GET" style="display: grid; grid-template-columns: 1fr 200px 200px auto; gap: 1rem;">
12
+ <input type="text" name="q" placeholder="Search by title, author, or ISBN..." value="{{ query }}">
13
+ <select name="category">
14
+ <option value="">All Categories</option>
15
+ {% for cat in categories %}
16
+ <option value="{{ cat }}" {% if cat == selected_category %}selected{% endif %}>{{ cat }}</option>
17
+ {% endfor %}
18
+ </select>
19
+ <select name="available">
20
+ <option value="">All Books</option>
21
+ <option value="true" {% if available_only %}selected{% endif %}>Available Only</option>
22
+ </select>
23
+ <button type="submit" class="btn">Search</button>
24
+ </form>
25
+ </div>
26
+
27
+ <div class="card">
28
+ <table>
29
+ <tr>
30
+ <th>Book ID</th>
31
+ <th>Title</th>
32
+ <th>Author</th>
33
+ <th>Category</th>
34
+ <th>Shelf No</th>
35
+ <th>Total</th>
36
+ <th>Available</th>
37
+ <th>Status</th>
38
+ {% if current_user.can_manage_books() %}
39
+ <th>Actions</th>
40
+ {% endif %}
41
+ </tr>
42
+ {% for book in books %}
43
+ <tr>
44
+ <td>{{ book.book_id }}</td>
45
+ <td><strong>{{ book.title }}</strong></td>
46
+ <td>{{ book.author }}</td>
47
+ <td>{{ book.category }}</td>
48
+ <td>πŸ—‚οΈ {{ book.shelf_no }}</td>
49
+ <td>{{ book.total_copies }}</td>
50
+ <td>{{ book.available_copies }}</td>
51
+ <td>
52
+ {% if book.is_available() %}
53
+ <span style="color: #27ae60;">βœ“ Available</span>
54
+ {% else %}
55
+ <span style="color: #e74c3c;">βœ— Not Available</span>
56
+ {% endif %}
57
+ </td>
58
+ {% if current_user.can_manage_books() %}
59
+ <td>
60
+ <a href="{{ url_for('edit_book', book_id=book.book_id) }}" class="btn" style="padding: 0.5rem 1rem;">Edit</a>
61
+ <form method="POST" action="{{ url_for('delete_book', book_id=book.book_id) }}" style="display: inline;">
62
+ <button type="submit" class="btn btn-danger" style="padding: 0.5rem 1rem;" onclick="return confirm('Delete this book?')">Delete</button>
63
+ </form>
64
+ </td>
65
+ {% endif %}
66
+ </tr>
67
+ {% else %}
68
+ <tr><td colspan="9" style="text-align: center;">No books found</td></tr>
69
+ {% endfor %}
70
+ </table>
71
+ </div>
72
+ {% endblock %}
templates/dashboard.html ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+ {% block content %}
3
+ <h2>πŸ“Š Dashboard</h2>
4
+
5
+ <div class="stats">
6
+ <div class="stat-card">
7
+ <h3>Total Books</h3>
8
+ <p>{{ stats.total_books }}</p>
9
+ </div>
10
+ <div class="stat-card">
11
+ <h3>Total Copies</h3>
12
+ <p>{{ stats.total_copies }}</p>
13
+ </div>
14
+ <div class="stat-card">
15
+ <h3>Available Copies</h3>
16
+ <p>{{ stats.available_copies }}</p>
17
+ </div>
18
+ <div class="stat-card">
19
+ <h3>Total Students</h3>
20
+ <p>{{ stats.total_students }}</p>
21
+ </div>
22
+ <div class="stat-card">
23
+ <h3>Books Issued</h3>
24
+ <p>{{ stats.books_issued }}</p>
25
+ </div>
26
+ <div class="stat-card">
27
+ <h3>Overdue Books</h3>
28
+ <p style="color: #e74c3c;">{{ stats.overdue_books }}</p>
29
+ </div>
30
+ <div class="stat-card">
31
+ <h3>Total Fines</h3>
32
+ <p>{{ stats.total_fines }} BDT</p>
33
+ </div>
34
+ <div class="stat-card">
35
+ <h3>Collected Fines</h3>
36
+ <p style="color: #27ae60;">{{ stats.collected_fines }} BDT</p>
37
+ </div>
38
+ </div>
39
+
40
+ {% if overdue %}
41
+ <div class="card">
42
+ <h3 style="color: #e74c3c;">⚠️ Overdue Books ({{ overdue|length }})</h3>
43
+ <table>
44
+ <tr>
45
+ <th>Student ID</th>
46
+ <th>Book ID</th>
47
+ <th>Due Date</th>
48
+ <th>Days Overdue</th>
49
+ <th>Fine</th>
50
+ </tr>
51
+ {% for issue in overdue %}
52
+ <tr>
53
+ <td>{{ issue.student_id }}</td>
54
+ <td>{{ issue.book_id }}</td>
55
+ <td>{{ issue.due_date.strftime('%Y-%m-%d') }}</td>
56
+ <td>{{ issue.days_overdue() }}</td>
57
+ <td>{{ issue.calculate_fine() }} BDT</td>
58
+ </tr>
59
+ {% endfor %}
60
+ </table>
61
+ </div>
62
+ {% endif %}
63
+ {% endblock %}
templates/edit_book.html ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+ {% block content %}
3
+ <h2>Edit Book</h2>
4
+ <div class="card">
5
+ <form method="POST">
6
+ <label>Title:</label><input name="title" value="{{ book.title }}" required>
7
+ <label>Author:</label><input name="author" value="{{ book.author }}" required>
8
+ <label>ISBN:</label><input name="isbn" value="{{ book.isbn }}" required>
9
+ <label>Category:</label><input name="category" value="{{ book.category }}" required>
10
+ <label>Publisher:</label><input name="publisher" value="{{ book.publisher }}" required>
11
+ <label>Year:</label><input type="number" name="year" value="{{ book.year }}" required>
12
+ <label>Total Copies:</label><input type="number" name="total_copies" value="{{ book.total_copies }}" required>
13
+ <label>Shelf No:</label><input name="shelf_no" value="{{ book.shelf_no }}" required>
14
+ <button type="submit" class="btn">Update</button>
15
+ <a href="{{ url_for('books') }}" class="btn btn-danger">Cancel</a>
16
+ </form>
17
+ </div>
18
+ {% endblock %}
templates/edit_student.html ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+ {% block content %}
3
+ <h2>Edit User</h2>
4
+ <div class="card">
5
+ <form method="POST">
6
+ <label>Name:</label><input name="name" value="{{ student.name }}" required>
7
+ <label>Email:</label><input type="email" name="email" value="{{ student.email }}" required>
8
+ <label>Role:</label>
9
+ <select name="role" required style="padding: 0.5rem; border: 1px solid #d1d5db; border-radius: 4px; width: 100%; margin-bottom: 1rem;">
10
+ <option value="student" {% if student.role == 'student' %}selected{% endif %}>Student</option>
11
+ <option value="librarian" {% if student.role == 'librarian' %}selected{% endif %}>Librarian</option>
12
+ <option value="admin" {% if student.role == 'admin' %}selected{% endif %}>Admin</option>
13
+ </select>
14
+ <label>Department:</label><input name="department" value="{{ student.department }}">
15
+ <label>Contact:</label><input name="contact" value="{{ student.contact }}">
16
+ <button type="submit" class="btn">Update</button>
17
+ <a href="{{ url_for('students') }}" class="btn btn-danger">Cancel</a>
18
+ </form>
19
+ </div>
20
+ {% endblock %}
templates/issue_book.html ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+ {% block content %}
3
+ <h2>Issue Book</h2>
4
+ <div class="card">
5
+ <form method="POST">
6
+ <label>Student ID:</label>
7
+ <select name="student_id" required>
8
+ {% for s in students %}<option value="{{ s.id }}">{{ s.id }} - {{ s.name }}</option>{% endfor %}
9
+ </select>
10
+ <label>Book ID:</label>
11
+ <select name="book_id" required>
12
+ {% for b in books %}<option value="{{ b.book_id }}">{{ b.book_id }} - {{ b.title }} ({{ b.available_copies }} available)</option>{% endfor %}
13
+ </select>
14
+ <button type="submit" class="btn">Issue Book</button>
15
+ <a href="{{ url_for('dashboard') }}" class="btn btn-danger">Cancel</a>
16
+ </form>
17
+ </div>
18
+ {% endblock %}
templates/login.html ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+
3
+ {% block title %}Login - LMS{% endblock %}
4
+
5
+ {% block content %}
6
+ <div class="card" style="max-width: 400px; margin: 4rem auto;">
7
+ <h2 style="text-align: center; margin-bottom: 2rem;">πŸ“š Library Login</h2>
8
+ <form method="POST">
9
+ <label>Email:</label>
10
+ <input type="email" name="email" required>
11
+
12
+ <label>Password:</label>
13
+ <input type="password" name="password" required>
14
+
15
+ <button type="submit" class="btn" style="width: 100%;">Login</button>
16
+ </form>
17
+ <p style="margin-top: 1rem; text-align: center; color: #7f8c8d;">
18
+ Default: admin@lms.com / admin123
19
+ </p>
20
+ </div>
21
+ {% endblock %}
templates/reports.html ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+ {% block content %}
3
+ <h2>πŸ“Š Reports</h2>
4
+
5
+ <div class="card" style="margin-bottom: 2rem;">
6
+ <h3>System Statistics</h3>
7
+ <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.5rem; margin-top: 1rem;">
8
+ <div style="text-align: center; padding: 1rem; background: #f3f4f6; border-radius: 8px;">
9
+ <div style="font-size: 2rem; font-weight: bold; color: #2563eb;">{{ stats.total_books }}</div>
10
+ <div style="color: #6b7280;">Total Books</div>
11
+ </div>
12
+ <div style="text-align: center; padding: 1rem; background: #f3f4f6; border-radius: 8px;">
13
+ <div style="font-size: 2rem; font-weight: bold; color: #f59e0b;">{{ stats.books_issued }}</div>
14
+ <div style="color: #6b7280;">Books Issued</div>
15
+ </div>
16
+ <div style="text-align: center; padding: 1rem; background: #f3f4f6; border-radius: 8px;">
17
+ <div style="font-size: 2rem; font-weight: bold; color: #ef4444;">{{ stats.overdue_books }}</div>
18
+ <div style="color: #6b7280;">Overdue Books</div>
19
+ </div>
20
+ </div>
21
+ </div>
22
+
23
+ <div class="card" style="margin-bottom: 2rem;">
24
+ <h3>πŸ“š All Issues</h3>
25
+ {% if issues %}
26
+ <table>
27
+ <tr>
28
+ <th>Issue ID</th>
29
+ <th>Student ID</th>
30
+ <th>Book ID</th>
31
+ <th>Issue Date</th>
32
+ <th>Due Date</th>
33
+ <th>Status</th>
34
+ </tr>
35
+ {% for issue in issues %}
36
+ <tr>
37
+ <td>{{ issue.issue_id }}</td>
38
+ <td>{{ issue.student_id }}</td>
39
+ <td>{{ issue.book_id }}</td>
40
+ <td>{{ issue.issue_date.strftime('%Y-%m-%d') }}</td>
41
+ <td>{{ issue.due_date.strftime('%Y-%m-%d') }}</td>
42
+ <td>
43
+ {% if issue.status == 'returned' %}
44
+ <span style="background: #d1fae5; color: #065f46; padding: 0.25rem 0.75rem; border-radius: 12px; font-size: 0.875rem;">βœ“ Returned</span>
45
+ {% elif issue.is_overdue() %}
46
+ <span style="background: #fee2e2; color: #991b1b; padding: 0.25rem 0.75rem; border-radius: 12px; font-size: 0.875rem;">⚠ Overdue</span>
47
+ {% else %}
48
+ <span style="background: #fef3c7; color: #92400e; padding: 0.25rem 0.75rem; border-radius: 12px; font-size: 0.875rem;">πŸ“– Active</span>
49
+ {% endif %}
50
+ </td>
51
+ </tr>
52
+ {% endfor %}
53
+ </table>
54
+ {% else %}
55
+ <p style="text-align: center; color: #9ca3af; padding: 2rem;">No issues found</p>
56
+ {% endif %}
57
+ </div>
58
+
59
+ <div class="card">
60
+ <h3>⚠ Overdue Books</h3>
61
+ {% if overdue %}
62
+ <table>
63
+ <tr>
64
+ <th>Issue ID</th>
65
+ <th>Student ID</th>
66
+ <th>Book ID</th>
67
+ <th>Due Date</th>
68
+ <th>Days Overdue</th>
69
+ </tr>
70
+ {% for issue in overdue %}
71
+ <tr>
72
+ <td>{{ issue.issue_id }}</td>
73
+ <td>{{ issue.student_id }}</td>
74
+ <td>{{ issue.book_id }}</td>
75
+ <td>{{ issue.due_date.strftime('%Y-%m-%d') }}</td>
76
+ <td><strong style="color: #ef4444;">{{ issue.days_overdue() }} days</strong></td>
77
+ </tr>
78
+ {% endfor %}
79
+ </table>
80
+ {% else %}
81
+ <p style="text-align: center; color: #9ca3af; padding: 2rem;">No overdue books</p>
82
+ {% endif %}
83
+ </div>
84
+
85
+ {% endblock %}
templates/return_book.html ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+ {% block content %}
3
+ <h2>Return Book</h2>
4
+ <div class="card">
5
+ <form method="POST">
6
+ <label>Select Issue to Return:</label>
7
+ <select name="student_id" required onchange="this.form.book_id.value=this.options[this.selectedIndex].dataset.bookid">
8
+ {% for i in issues %}<option value="{{ i.student_id }}" data-bookid="{{ i.book_id }}">{{ i.student_id }} - {{ i.book_id }}</option>{% endfor %}
9
+ </select>
10
+ <input type="hidden" name="book_id">
11
+ <button type="submit" class="btn">Return Book</button>
12
+ <a href="{{ url_for('dashboard') }}" class="btn btn-danger">Cancel</a>
13
+ </form>
14
+ </div>
15
+ {% endblock %}
templates/student_dashboard.html ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+ {% block content %}
3
+ <h2>πŸ“š Welcome, {{ current_user.name }}!</h2>
4
+
5
+ <div class="stats">
6
+ <div class="stat-card">
7
+ <h3>Total Books in Library</h3>
8
+ <p>{{ stats.total_books }}</p>
9
+ </div>
10
+ <div class="stat-card">
11
+ <h3>Available Books</h3>
12
+ <p>{{ stats.available_copies }}</p>
13
+ </div>
14
+ <div class="stat-card">
15
+ <h3>Books I Borrowed</h3>
16
+ <p>{{ my_issues|length }}</p>
17
+ </div>
18
+ <div class="stat-card">
19
+ <h3>My Fines</h3>
20
+ <p style="color: {% if my_fines %}#e74c3c{% else %}#27ae60{% endif %};">
21
+ {{ my_fines|sum(attribute='amount')|default(0) }} BDT
22
+ </p>
23
+ </div>
24
+ </div>
25
+
26
+ <div class="card">
27
+ <h3>πŸ“– Browse All Books</h3>
28
+ <p><a href="{{ url_for('books') }}" class="btn">View All Books β†’</a></p>
29
+ </div>
30
+
31
+ {% if my_issues %}
32
+ <div class="card">
33
+ <h3>πŸ“š My Borrowed Books</h3>
34
+ <table>
35
+ <tr>
36
+ <th>Book ID</th>
37
+ <th>Issue Date</th>
38
+ <th>Due Date</th>
39
+ <th>Status</th>
40
+ </tr>
41
+ {% for issue in my_issues %}
42
+ <tr>
43
+ <td>{{ issue.book_id }}</td>
44
+ <td>{{ issue.issue_date.strftime('%Y-%m-%d') }}</td>
45
+ <td>{{ issue.due_date.strftime('%Y-%m-%d') }}</td>
46
+ <td>
47
+ {% if issue.status == 'returned' %}
48
+ <span style="color: #27ae60;">βœ“ Returned</span>
49
+ {% elif issue.is_overdue() %}
50
+ <span style="color: #e74c3c;">⚠️ Overdue ({{ issue.days_overdue() }} days)</span>
51
+ {% else %}
52
+ <span style="color: #3498db;">πŸ“– Issued</span>
53
+ {% endif %}
54
+ </td>
55
+ </tr>
56
+ {% endfor %}
57
+ </table>
58
+ </div>
59
+ {% endif %}
60
+ {% endblock %}
templates/students.html ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+ {% block content %}
3
+ <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 2rem;">
4
+ <h2>πŸ‘¨β€πŸŽ“ Students</h2>
5
+ <a href="{{ url_for('add_student') }}" class="btn btn-success">+ Add Student</a>
6
+ </div>
7
+
8
+ <!-- Search Bar -->
9
+ <div class="card" style="margin-bottom: 1.5rem; padding: 1rem;">
10
+ <form method="GET" action="{{ url_for('students') }}" style="display: flex; gap: 1rem; align-items: center;">
11
+ <input type="text"
12
+ name="q"
13
+ value="{{ search_query or '' }}"
14
+ placeholder="πŸ” Search by Student ID or Name..."
15
+ style="flex: 1; padding: 0.75rem; border: 1px solid #d1d5db; border-radius: 6px; font-size: 1rem;">
16
+ <button type="submit" class="btn" style="padding: 0.75rem 1.5rem;">Search</button>
17
+ {% if search_query %}
18
+ <a href="{{ url_for('students') }}" class="btn" style="padding: 0.75rem 1.5rem; background: #6b7280;">Clear</a>
19
+ {% endif %}
20
+ </form>
21
+ {% if search_query %}
22
+ <div style="margin-top: 0.5rem; color: #6b7280; font-size: 0.9rem;">
23
+ Found {{ students|length }} student(s) matching "{{ search_query }}"
24
+ </div>
25
+ {% endif %}
26
+ </div>
27
+ <div class="card">
28
+ <table>
29
+ <tr>
30
+ <th>Student ID</th>
31
+ <th>Name</th>
32
+ <th>Email</th>
33
+ <th>Department</th>
34
+ <th>Contact</th>
35
+ <th>Issued Books</th>
36
+ <th>Actions</th>
37
+ </tr>
38
+ {% for student in students %}
39
+ <tr>
40
+ <td>{{ student.id }}</td>
41
+ <td>
42
+ <a href="{{ url_for('view_student', student_id=student.id) }}"
43
+ style="color: #2563eb; text-decoration: none; font-weight: 600;">
44
+ {{ student.name }}
45
+ </a>
46
+ </td>
47
+ <td>{{ student.email }}</td>
48
+ <td>{{ student.department }}</td>
49
+ <td>{{ student.contact }}</td>
50
+ <td>
51
+ {% set issues = student_issues.get(student.id, []) %}
52
+ {% if issues %}
53
+ <span style="color: #2563eb; font-weight: 600;">{{ issues|length }} active book(s)</span>
54
+ <div style="margin-top: 0.5rem; font-size: 0.85rem; color: #6b7280;">
55
+ {% for issue in issues %}
56
+ <div style="padding: 0.25rem 0;">
57
+ πŸ“š {{ issue.book_id }}
58
+ {% if issue.is_overdue() %}
59
+ <span style="color: red; font-weight: bold;">⚠ Overdue</span>
60
+ {% endif %}
61
+ </div>
62
+ {% endfor %}
63
+ </div>
64
+ {% else %}
65
+ <span style="color: #9ca3af;">No active books</span>
66
+ {% endif %}
67
+ </td>
68
+ <td>
69
+ <a href="{{ url_for('edit_student', student_id=student.id) }}" class="btn">Edit</a>
70
+ <form method="POST" action="{{ url_for('delete_student', student_id=student.id) }}" style="display: inline;">
71
+ <button type="submit" class="btn btn-danger" onclick="return confirm('Delete?')">Delete</button>
72
+ </form>
73
+ </td>
74
+ </tr>
75
+ {% endfor %}
76
+ </table>
77
+ </div>
78
+ {% endblock %}
templates/view_student.html ADDED
@@ -0,0 +1,205 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+ {% block content %}
3
+ <div style="margin-bottom: 2rem;">
4
+ <a href="{{ url_for('students') }}" style="color: #2563eb; text-decoration: none;">&larr; Back to Students</a>
5
+ </div>
6
+
7
+ <div class="card" style="margin-bottom: 2rem;">
8
+ <h2 style="margin-bottom: 1.5rem;">πŸ‘¨β€πŸŽ“ Student Details</h2>
9
+ <div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 1rem;">
10
+ <div>
11
+ <strong>Student ID:</strong> {{ student.id }}
12
+ </div>
13
+ <div>
14
+ <strong>Name:</strong> {{ student.name }}
15
+ </div>
16
+ <div>
17
+ <strong>Email:</strong> {{ student.email }}
18
+ </div>
19
+ <div>
20
+ <strong>Department:</strong> {{ student.department or 'N/A' }}
21
+ </div>
22
+ <div>
23
+ <strong>Contact:</strong> {{ student.contact or 'N/A' }}
24
+ </div>
25
+ </div>
26
+ </div>
27
+
28
+ <div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 1rem; margin-bottom: 2rem;">
29
+ <a href="#all-section" style="text-decoration: none;">
30
+ <div class="card" style="text-align: center; padding: 1.5rem; cursor: pointer; transition: transform 0.2s;"
31
+ onmouseover="this.style.transform='scale(1.05)'" onmouseout="this.style.transform='scale(1)'">
32
+ <div style="font-size: 2rem; font-weight: bold; color: #2563eb;">{{ issues_with_books|length }}</div>
33
+ <div style="color: #6b7280;">Total Issues</div>
34
+ <small style="color: #9ca3af; font-size: 0.8rem;">Click to view all</small>
35
+ </div>
36
+ </a>
37
+ <a href="#active-section" style="text-decoration: none;">
38
+ <div class="card" style="text-align: center; padding: 1.5rem; cursor: pointer; transition: transform 0.2s;"
39
+ onmouseover="this.style.transform='scale(1.05)'" onmouseout="this.style.transform='scale(1)'">
40
+ <div style="font-size: 2rem; font-weight: bold; color: #f59e0b;">{{ active_count }}</div>
41
+ <div style="color: #6b7280;">Active Books</div>
42
+ <small style="color: #9ca3af; font-size: 0.8rem;">Click to view</small>
43
+ </div>
44
+ </a>
45
+ <a href="#returned-section" style="text-decoration: none;">
46
+ <div class="card" style="text-align: center; padding: 1.5rem; cursor: pointer; transition: transform 0.2s;"
47
+ onmouseover="this.style.transform='scale(1.05)'" onmouseout="this.style.transform='scale(1)'">
48
+ <div style="font-size: 2rem; font-weight: bold; color: #10b981;">{{ returned_count }}</div>
49
+ <div style="color: #6b7280;">Returned</div>
50
+ <small style="color: #9ca3af; font-size: 0.8rem;">Click to view</small>
51
+ </div>
52
+ </a>
53
+ <div class="card" style="text-align: center; padding: 1.5rem;">
54
+ <div style="font-size: 2rem; font-weight: bold; color: #ef4444;">{{ overdue_count }}</div>
55
+ <div style="color: #6b7280;">Currently Overdue</div>
56
+ <small style="color: #ef4444; font-weight: 600; font-size: 0.9rem; margin-top: 0.5rem; display: block;">
57
+ Total overdue times: {{ total_overdue_times }}
58
+ </small>
59
+ </div>
60
+ </div>
61
+
62
+ <div class="card" id="all-section" style="margin-bottom: 2rem;">
63
+ <h3 style="margin-bottom: 1rem;">πŸ“š All Issue History</h3>
64
+ {% if issues_with_books %}
65
+ <table>
66
+ <tr>
67
+ <th>Book</th>
68
+ <th>Issue Date</th>
69
+ <th>Due Date</th>
70
+ <th>Return Date</th>
71
+ <th>Status</th>
72
+ </tr>
73
+ {% for item in issues_with_books %}
74
+ {% set issue = item.issue %}
75
+ {% set book = item.book %}
76
+ {% set is_overdue = issue.is_overdue() %}
77
+ {% set was_overdue = issue.status == 'returned' and issue.days_overdue() > 0 %}
78
+ <tr>
79
+ <td>
80
+ <strong>{{ book.title if book else 'Unknown' }}</strong><br>
81
+ <small style="color: #6b7280;">ID: {{ issue.book_id }}</small>
82
+ </td>
83
+ <td>{{ issue.issue_date.strftime('%Y-%m-%d %H:%M') }}</td>
84
+ <td>{{ issue.due_date.strftime('%Y-%m-%d') }}</td>
85
+ <td>
86
+ {% if issue.return_date %}
87
+ {{ issue.return_date.strftime('%Y-%m-%d %H:%M') }}
88
+ {% else %}
89
+ <span style="color: #9ca3af;">Not returned</span>
90
+ {% endif %}
91
+ </td>
92
+ <td>
93
+ {% if issue.status == 'returned' %}
94
+ {% if was_overdue %}
95
+ <span style="background: #fee2e2; color: #991b1b; padding: 0.25rem 0.75rem; border-radius: 12px; font-size: 0.875rem;">
96
+ ⚠ Returned Late ({{ issue.days_overdue() }} days)
97
+ </span>
98
+ {% else %}
99
+ <span style="background: #d1fae5; color: #065f46; padding: 0.25rem 0.75rem; border-radius: 12px; font-size: 0.875rem;">
100
+ βœ“ Returned On Time
101
+ </span>
102
+ {% endif %}
103
+ {% elif is_overdue %}
104
+ <span style="background: #fee2e2; color: #991b1b; padding: 0.25rem 0.75rem; border-radius: 12px; font-size: 0.875rem; font-weight: bold;">
105
+ ⚠ OVERDUE ({{ issue.days_overdue() }} days)
106
+ </span>
107
+ {% else %}
108
+ <span style="background: #fef3c7; color: #92400e; padding: 0.25rem 0.75rem; border-radius: 12px; font-size: 0.875rem;">
109
+ πŸ“– Active
110
+ </span>
111
+ {% endif %}
112
+ </td>
113
+ </tr>
114
+ {% endfor %}
115
+ </table>
116
+ {% else %}
117
+ <p style="text-align: center; color: #9ca3af; padding: 2rem;">No issue history found</p>
118
+ {% endif %}
119
+ </div>
120
+
121
+ <div class="card" id="active-section" style="margin-bottom: 2rem;">
122
+ <h3 style="margin-bottom: 1rem;">πŸ“– Active Books</h3>
123
+ {% set active_books = issues_with_books|selectattr('issue.status', 'equalto', 'issued')|list %}
124
+ {% if active_books %}
125
+ <table>
126
+ <tr>
127
+ <th>Book</th>
128
+ <th>Issue Date</th>
129
+ <th>Due Date</th>
130
+ <th>Status</th>
131
+ </tr>
132
+ {% for item in active_books %}
133
+ {% set issue = item.issue %}
134
+ {% set book = item.book %}
135
+ {% set is_overdue = issue.is_overdue() %}
136
+ <tr>
137
+ <td>
138
+ <strong>{{ book.title if book else 'Unknown' }}</strong><br>
139
+ <small style="color: #6b7280;">ID: {{ issue.book_id }}</small>
140
+ </td>
141
+ <td>{{ issue.issue_date.strftime('%Y-%m-%d %H:%M') }}</td>
142
+ <td>{{ issue.due_date.strftime('%Y-%m-%d') }}</td>
143
+ <td>
144
+ {% if is_overdue %}
145
+ <span style="background: #fee2e2; color: #991b1b; padding: 0.25rem 0.75rem; border-radius: 12px; font-size: 0.875rem; font-weight: bold;">
146
+ ⚠ OVERDUE ({{ issue.days_overdue() }} days)
147
+ </span>
148
+ {% else %}
149
+ <span style="background: #fef3c7; color: #92400e; padding: 0.25rem 0.75rem; border-radius: 12px; font-size: 0.875rem;">
150
+ πŸ“– Active
151
+ </span>
152
+ {% endif %}
153
+ </td>
154
+ </tr>
155
+ {% endfor %}
156
+ </table>
157
+ {% else %}
158
+ <p style="text-align: center; color: #9ca3af; padding: 2rem;">No active books</p>
159
+ {% endif %}
160
+ </div>
161
+
162
+ <div class="card" id="returned-section">
163
+ <h3 style="margin-bottom: 1rem;">βœ“ Returned Books</h3>
164
+ {% set returned_books = issues_with_books|selectattr('issue.status', 'equalto', 'returned')|list %}
165
+ {% if returned_books %}
166
+ <table>
167
+ <tr>
168
+ <th>Book</th>
169
+ <th>Issue Date</th>
170
+ <th>Due Date</th>
171
+ <th>Return Date</th>
172
+ <th>Status</th>
173
+ </tr>
174
+ {% for item in returned_books %}
175
+ {% set issue = item.issue %}
176
+ {% set book = item.book %}
177
+ {% set was_overdue = issue.days_overdue() > 0 %}
178
+ <tr>
179
+ <td>
180
+ <strong>{{ book.title if book else 'Unknown' }}</strong><br>
181
+ <small style="color: #6b7280;">ID: {{ issue.book_id }}</small>
182
+ </td>
183
+ <td>{{ issue.issue_date.strftime('%Y-%m-%d %H:%M') }}</td>
184
+ <td>{{ issue.due_date.strftime('%Y-%m-%d') }}</td>
185
+ <td>{{ issue.return_date.strftime('%Y-%m-%d %H:%M') }}</td>
186
+ <td>
187
+ {% if was_overdue %}
188
+ <span style="background: #fee2e2; color: #991b1b; padding: 0.25rem 0.75rem; border-radius: 12px; font-size: 0.875rem;">
189
+ ⚠ Returned Late ({{ issue.days_overdue() }} days)
190
+ </span>
191
+ {% else %}
192
+ <span style="background: #d1fae5; color: #065f46; padding: 0.25rem 0.75rem; border-radius: 12px; font-size: 0.875rem;">
193
+ βœ“ Returned On Time
194
+ </span>
195
+ {% endif %}
196
+ </td>
197
+ </tr>
198
+ {% endfor %}
199
+ </table>
200
+ {% else %}
201
+ <p style="text-align: center; color: #9ca3af; padding: 2rem;">No returned books</p>
202
+ {% endif %}
203
+ </div>
204
+
205
+ {% endblock %}