Skip to content

Commit d0fb3ef

Browse files
committed
feat: Add Employees management page and update navigation - Introduced Employees page with CRUD functionality - Added Employees route in App component - Updated Navbar and Sidebar to include Employees link - Enhanced page headers across various pages for consistency
1 parent 45aaa60 commit d0fb3ef

File tree

10 files changed

+329
-10
lines changed

10 files changed

+329
-10
lines changed

src/App.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import Hotels from './pages/Hotels';
1818
import Rooms from './pages/Rooms';
1919
import Bookings from './pages/Bookings';
2020
import Guests from './pages/Guests';
21+
import Employees from './pages/Employees';
2122
import Services from './pages/Services';
2223
import Billing from './pages/Billing';
2324
import Reports from './pages/Reports';
@@ -64,6 +65,7 @@ function App() {
6465
<Route path="/rooms" element={<Rooms />} />
6566
<Route path="/bookings" element={<Bookings />} />
6667
<Route path="/guests" element={<Guests />} />
68+
<Route path="/employees" element={<Employees />} />
6769
<Route path="/services" element={<Services />} />
6870
<Route path="/billing" element={<Billing />} />
6971
<Route path="/reports" element={<Reports />} />

src/components/Layout/Navbar.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ const Navbar = () => {
5353
<BootstrapNavbar bg="dark" variant="dark" expand="lg" fixed="top" className="navbar-custom">
5454
<Container fluid>
5555
<BootstrapNavbar.Brand href="/admin" className="d-flex align-items-center">
56-
<FaBed className="me-2" />
57-
<strong>SkyNest Hotels</strong>
56+
<FaBed style={{ color: '#87CEEB' }} className="me-2" />
57+
<strong style={{ color: '#87CEEB' }}>SkyNest Hotels</strong>
5858
</BootstrapNavbar.Brand>
5959
<BootstrapNavbar.Toggle aria-controls="basic-navbar-nav" />
6060
<BootstrapNavbar.Collapse id="basic-navbar-nav">

src/components/Layout/Sidebar.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
FaBed,
88
FaCalendarCheck,
99
FaUsers,
10+
FaUserTie,
1011
FaConciergeBell,
1112
FaReceipt,
1213
FaChartBar
@@ -21,6 +22,7 @@ const Sidebar = () => {
2122
{ path: '/admin/rooms', icon: FaBed, label: 'Rooms' },
2223
{ path: '/admin/bookings', icon: FaCalendarCheck, label: 'Bookings' },
2324
{ path: '/admin/guests', icon: FaUsers, label: 'Guests' },
25+
{ path: '/admin/employees', icon: FaUserTie, label: 'Employees' },
2426
{ path: '/admin/services', icon: FaConciergeBell, label: 'Services' },
2527
{ path: '/admin/billing', icon: FaReceipt, label: 'Billing' },
2628
{ path: '/admin/reports', icon: FaChartBar, label: 'Reports' }

src/pages/Billing.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,13 @@ const Billing = () => {
7878

7979
return (
8080
<div>
81+
{/* Page Header */}
8182
<Row className="mb-4">
8283
<Col>
83-
<h2>Billing & Payments</h2>
84+
<div className="page-header">
85+
<h2>Billing & Payments</h2>
86+
<p style={{ marginBottom: 0 }}>Manage payments, transactions, and billing adjustments</p>
87+
</div>
8488
</Col>
8589
</Row>
8690

src/pages/Employees.js

Lines changed: 301 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,301 @@
1+
import React, { useState, useEffect } from 'react';
2+
import { Row, Col, Card, Button, Modal, Form, Table, Badge, Spinner, Alert } from 'react-bootstrap';
3+
import { FaPlus, FaEdit, FaTrash, FaUserTie } from 'react-icons/fa';
4+
import { useBranch } from '../context/BranchContext';
5+
import { apiUrl } from '../utils/api';
6+
7+
const Employees = () => {
8+
const { selectedBranchId } = useBranch();
9+
const [employees, setEmployees] = useState([]);
10+
const [loading, setLoading] = useState(true);
11+
const [error, setError] = useState(null);
12+
const [showModal, setShowModal] = useState(false);
13+
const [selectedEmployee, setSelectedEmployee] = useState(null);
14+
15+
useEffect(() => {
16+
fetchEmployees();
17+
}, [selectedBranchId]);
18+
19+
const fetchEmployees = async () => {
20+
try {
21+
setLoading(true);
22+
setError(null);
23+
24+
let url = '/api/employees?limit=1000';
25+
if (selectedBranchId !== 'All') {
26+
url += `&branch_id=${selectedBranchId}`;
27+
}
28+
29+
const response = await fetch(apiUrl(url));
30+
const data = await response.json();
31+
32+
if (data.success && data.data && data.data.employees) {
33+
setEmployees(data.data.employees);
34+
} else {
35+
setError('No employees found');
36+
}
37+
} catch (err) {
38+
console.error('Error fetching employees:', err);
39+
setError('Failed to load employees. Please check backend connection.');
40+
} finally {
41+
setLoading(false);
42+
}
43+
};
44+
45+
const handleShowModal = (employee = null) => {
46+
setSelectedEmployee(employee);
47+
setShowModal(true);
48+
};
49+
50+
const handleCloseModal = () => {
51+
setShowModal(false);
52+
setSelectedEmployee(null);
53+
};
54+
55+
const getRoleBadgeColor = (role) => {
56+
const roleColors = {
57+
'Admin': 'danger',
58+
'Manager': 'primary',
59+
'Receptionist': 'info',
60+
'Accountant': 'success',
61+
'Housekeeping': 'warning'
62+
};
63+
return roleColors[role] || 'secondary';
64+
};
65+
66+
const getEmployeeStats = () => {
67+
const stats = {
68+
total: employees.length,
69+
managers: employees.filter(e => e.role === 'Manager').length,
70+
receptionists: employees.filter(e => e.role === 'Receptionist').length,
71+
accountants: employees.filter(e => e.role === 'Accountant').length,
72+
housekeeping: employees.filter(e => e.role === 'Housekeeping').length
73+
};
74+
return stats;
75+
};
76+
77+
const stats = getEmployeeStats();
78+
79+
return (
80+
<div>
81+
{/* Page Header */}
82+
<Row className="mb-4">
83+
<Col>
84+
<div className="page-header">
85+
<div className="d-flex justify-content-between align-items-center">
86+
<div>
87+
<h2>Employee Management</h2>
88+
<p style={{ marginBottom: 0 }}>
89+
Manage employees working at {selectedBranchId === 'All' ? 'all branches' : `Branch ${selectedBranchId}`}
90+
</p>
91+
</div>
92+
<Button variant="primary" onClick={() => handleShowModal()}>
93+
<FaPlus className="me-2" />
94+
Add New Employee
95+
</Button>
96+
</div>
97+
</div>
98+
</Col>
99+
</Row>
100+
101+
{error && <Alert variant="warning">{error}</Alert>}
102+
103+
{/* Employee Statistics */}
104+
<Row className="mb-4">
105+
<Col md={2}>
106+
<Card className="stat-card text-center h-100">
107+
<Card.Body style={{ padding: '24px' }}>
108+
<FaUserTie style={{ color: 'white', marginBottom: '12px' }} size={32} />
109+
<h3 style={{ color: 'white', fontWeight: 'bold', fontSize: '2rem', marginBottom: '8px' }}>
110+
{stats.total}
111+
</h3>
112+
<p style={{ color: 'rgba(255, 255, 255, 0.9)', marginBottom: 0 }}>Total Employees</p>
113+
</Card.Body>
114+
</Card>
115+
</Col>
116+
<Col md={2}>
117+
<Card className="stat-card text-center h-100">
118+
<Card.Body style={{ padding: '24px' }}>
119+
<h3 style={{ color: 'white', fontWeight: 'bold', fontSize: '2rem', marginBottom: '8px' }}>
120+
{stats.managers}
121+
</h3>
122+
<p style={{ color: 'rgba(255, 255, 255, 0.9)', marginBottom: 0 }}>Managers</p>
123+
</Card.Body>
124+
</Card>
125+
</Col>
126+
<Col md={2}>
127+
<Card className="stat-card text-center h-100">
128+
<Card.Body style={{ padding: '24px' }}>
129+
<h3 style={{ color: 'white', fontWeight: 'bold', fontSize: '2rem', marginBottom: '8px' }}>
130+
{stats.receptionists}
131+
</h3>
132+
<p style={{ color: 'rgba(255, 255, 255, 0.9)', marginBottom: 0 }}>Receptionists</p>
133+
</Card.Body>
134+
</Card>
135+
</Col>
136+
<Col md={2}>
137+
<Card className="stat-card text-center h-100">
138+
<Card.Body style={{ padding: '24px' }}>
139+
<h3 style={{ color: 'white', fontWeight: 'bold', fontSize: '2rem', marginBottom: '8px' }}>
140+
{stats.accountants}
141+
</h3>
142+
<p style={{ color: 'rgba(255, 255, 255, 0.9)', marginBottom: 0 }}>Accountants</p>
143+
</Card.Body>
144+
</Card>
145+
</Col>
146+
<Col md={2}>
147+
<Card className="stat-card text-center h-100">
148+
<Card.Body style={{ padding: '24px' }}>
149+
<h3 style={{ color: 'white', fontWeight: 'bold', fontSize: '2rem', marginBottom: '8px' }}>
150+
{stats.housekeeping}
151+
</h3>
152+
<p style={{ color: 'rgba(255, 255, 255, 0.9)', marginBottom: 0 }}>Housekeeping</p>
153+
</Card.Body>
154+
</Card>
155+
</Col>
156+
</Row>
157+
158+
{/* Employee List */}
159+
<Card>
160+
<Card.Header style={{ background: '#f8f9fa', borderBottom: '1px solid #e0e6ed' }}>
161+
<h5 className="mb-0" style={{ fontWeight: '700', color: '#2c3e50' }}>
162+
Employee List ({employees.length})
163+
</h5>
164+
</Card.Header>
165+
<Card.Body className="p-0">
166+
{loading ? (
167+
<div className="text-center py-5">
168+
<Spinner animation="border" variant="primary" />
169+
<p className="mt-2 text-muted">Loading employees...</p>
170+
</div>
171+
) : (
172+
<Table responsive hover className="mb-0">
173+
<thead style={{ backgroundColor: '#f8f9fa', borderBottom: '2px solid #e0e6ed' }}>
174+
<tr>
175+
<th style={{ padding: '16px', fontWeight: '600', color: '#5a6c7d', fontSize: '0.85rem', letterSpacing: '0.5px', textTransform: 'uppercase', border: 'none' }}>ID</th>
176+
<th style={{ padding: '16px', fontWeight: '600', color: '#5a6c7d', fontSize: '0.85rem', letterSpacing: '0.5px', textTransform: 'uppercase', border: 'none' }}>Name</th>
177+
<th style={{ padding: '16px', fontWeight: '600', color: '#5a6c7d', fontSize: '0.85rem', letterSpacing: '0.5px', textTransform: 'uppercase', border: 'none' }}>Email</th>
178+
<th style={{ padding: '16px', fontWeight: '600', color: '#5a6c7d', fontSize: '0.85rem', letterSpacing: '0.5px', textTransform: 'uppercase', border: 'none' }}>Phone</th>
179+
<th style={{ padding: '16px', fontWeight: '600', color: '#5a6c7d', fontSize: '0.85rem', letterSpacing: '0.5px', textTransform: 'uppercase', border: 'none' }}>Branch</th>
180+
<th style={{ padding: '16px', fontWeight: '600', color: '#5a6c7d', fontSize: '0.85rem', letterSpacing: '0.5px', textTransform: 'uppercase', border: 'none' }}>Role</th>
181+
<th style={{ padding: '16px', fontWeight: '600', color: '#5a6c7d', fontSize: '0.85rem', letterSpacing: '0.5px', textTransform: 'uppercase', border: 'none' }}>Hire Date</th>
182+
<th style={{ padding: '16px', fontWeight: '600', color: '#5a6c7d', fontSize: '0.85rem', letterSpacing: '0.5px', textTransform: 'uppercase', border: 'none' }}>Actions</th>
183+
</tr>
184+
</thead>
185+
<tbody>
186+
{employees.map((employee) => (
187+
<tr key={employee.employee_id} style={{ borderBottom: '1px solid #e0e6ed' }}>
188+
<td style={{ padding: '16px', color: '#2c3e50', fontWeight: '500' }}>
189+
EMP{String(employee.employee_id).padStart(3, '0')}
190+
</td>
191+
<td style={{ padding: '16px', color: '#2c3e50', fontWeight: '500' }}>{employee.name}</td>
192+
<td style={{ padding: '16px', color: '#2c3e50', fontWeight: '500' }}>{employee.email || 'N/A'}</td>
193+
<td style={{ padding: '16px', color: '#2c3e50', fontWeight: '500' }}>{employee.contact_no || 'N/A'}</td>
194+
<td style={{ padding: '16px', color: '#2c3e50', fontWeight: '500' }}>
195+
{employee.branch_name || `Branch ${employee.branch_id}`}
196+
</td>
197+
<td style={{ padding: '16px' }}>
198+
<Badge bg={getRoleBadgeColor(employee.role || 'Staff')}>
199+
{employee.role || 'Staff'}
200+
</Badge>
201+
</td>
202+
<td style={{ padding: '16px', color: '#2c3e50', fontWeight: '500' }}>
203+
{employee.hire_date ? new Date(employee.hire_date).toLocaleDateString() : 'N/A'}
204+
</td>
205+
<td style={{ padding: '16px' }}>
206+
<Button
207+
variant="outline-primary"
208+
size="sm"
209+
onClick={() => handleShowModal(employee)}
210+
className="me-2"
211+
>
212+
<FaEdit />
213+
</Button>
214+
<Button variant="outline-danger" size="sm">
215+
<FaTrash />
216+
</Button>
217+
</td>
218+
</tr>
219+
))}
220+
</tbody>
221+
</Table>
222+
)}
223+
</Card.Body>
224+
</Card>
225+
226+
{/* Add/Edit Employee Modal */}
227+
<Modal show={showModal} onHide={handleCloseModal} size="lg">
228+
<Modal.Header closeButton>
229+
<Modal.Title>{selectedEmployee ? 'Edit Employee' : 'Add New Employee'}</Modal.Title>
230+
</Modal.Header>
231+
<Modal.Body>
232+
<Form>
233+
<Row>
234+
<Col md={6}>
235+
<Form.Group className="mb-3">
236+
<Form.Label>Full Name</Form.Label>
237+
<Form.Control type="text" placeholder="Enter full name" />
238+
</Form.Group>
239+
</Col>
240+
<Col md={6}>
241+
<Form.Group className="mb-3">
242+
<Form.Label>Email</Form.Label>
243+
<Form.Control type="email" placeholder="Enter email" />
244+
</Form.Group>
245+
</Col>
246+
</Row>
247+
<Row>
248+
<Col md={6}>
249+
<Form.Group className="mb-3">
250+
<Form.Label>Phone Number</Form.Label>
251+
<Form.Control type="tel" placeholder="Enter phone number" />
252+
</Form.Group>
253+
</Col>
254+
<Col md={6}>
255+
<Form.Group className="mb-3">
256+
<Form.Label>Branch</Form.Label>
257+
<Form.Select>
258+
<option value="">Select Branch</option>
259+
<option value="1">Colombo</option>
260+
<option value="2">Kandy</option>
261+
<option value="3">Galle</option>
262+
</Form.Select>
263+
</Form.Group>
264+
</Col>
265+
</Row>
266+
<Row>
267+
<Col md={6}>
268+
<Form.Group className="mb-3">
269+
<Form.Label>Role</Form.Label>
270+
<Form.Select>
271+
<option value="">Select Role</option>
272+
<option value="Manager">Manager</option>
273+
<option value="Receptionist">Receptionist</option>
274+
<option value="Accountant">Accountant</option>
275+
<option value="Housekeeping">Housekeeping</option>
276+
</Form.Select>
277+
</Form.Group>
278+
</Col>
279+
<Col md={6}>
280+
<Form.Group className="mb-3">
281+
<Form.Label>Hire Date</Form.Label>
282+
<Form.Control type="date" />
283+
</Form.Group>
284+
</Col>
285+
</Row>
286+
</Form>
287+
</Modal.Body>
288+
<Modal.Footer>
289+
<Button variant="secondary" onClick={handleCloseModal}>
290+
Cancel
291+
</Button>
292+
<Button variant="primary">
293+
{selectedEmployee ? 'Update Employee' : 'Add Employee'}
294+
</Button>
295+
</Modal.Footer>
296+
</Modal>
297+
</div>
298+
);
299+
};
300+
301+
export default Employees;

src/pages/Guests.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,9 @@ const Guests = () => {
122122
<div>
123123
<Row className="mb-4">
124124
<Col>
125-
<h2>Guest Management</h2>
125+
<div className="page-header">
126+
<h2 >Guest Management</h2>
127+
</div>
126128
</Col>
127129
<Col xs="auto">
128130
<Button variant="primary" onClick={() => handleShowModal()}>

src/pages/Hotels.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,9 @@ const Hotels = () => {
9999
<Row className="mb-4">
100100
<Col>
101101
<div className="d-flex justify-content-between align-items-center">
102-
<div>
103-
<h2 className="mb-1" style={{ color: '#2c3e50' }}>Hotel Branches</h2>
104-
<p className="mb-2" style={{ color: '#2c3e50' }}>Manage SkyNest Hotels locations across Sri Lanka</p>
102+
<div className="page-header">
103+
<h2>Hotel Branches</h2>
104+
<p style={{ marginBottom: 0 }}>Manage SkyNest Hotels locations across Sri Lanka</p>
105105
</div>
106106
<Button
107107
variant="primary"

src/pages/Landing.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ const Landing = () => {
9191
fontWeight: 'bold'
9292
}}
9393
>
94-
Staff Login
94+
Login
9595
</Button>
9696
</div>
9797
</Container>

0 commit comments

Comments
 (0)