Auto commit: 2026-01-03T08:58:39.154Z

This commit is contained in:
Flatlogic Bot 2026-01-03 08:58:39 +00:00
parent 7dde39bcd5
commit 5d42eee08c
5 changed files with 546 additions and 111 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@ -1,79 +1,318 @@
:root {
--primary-color: #4F46E5;
--sidebar-bg: #111827;
--sidebar-text: #9CA3AF;
--sidebar-active-bg: #374151;
--sidebar-active-text: #FFFFFF;
--main-bg: #F9FAFB;
--text-primary: #1F2937;
--text-secondary: #6B7280;
--card-bg: #FFFFFF;
--border-color: #E5E7EB;
--green-light: #ECFDF5;
--green-dark: #065F46;
--red-light: #FEF2F2;
--red-dark: #991B1B;
--blue-light: #EFF6FF;
--blue-dark: #1E40AF;
--yellow-light: #FFFBEB;
--yellow-dark: #92400E;
}
body { body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; font-family: 'Inter', sans-serif;
background-color: var(--main-bg);
color: var(--text-primary);
margin: 0; margin: 0;
background-color: #f4f7fa; display: flex;
color: #333;
} }
#root { #root {
display: flex; display: flex;
flex-direction: column; width: 100%;
min-height: 100vh; min-height: 100vh;
} }
.dashboard { .dashboard {
display: flex; display: flex;
flex: 1; width: 100%;
} }
.sidebar { .sidebar {
width: 240px; width: 260px;
background-color: #fff; background-color: var(--sidebar-bg);
border-right: 1px solid #e0e0e0; color: var(--sidebar-text);
padding: 20px; padding: 24px;
display: flex;
flex-direction: column;
flex-shrink: 0;
} }
.sidebar h2 { .sidebar-header {
font-size: 1.5rem; display: flex;
margin-bottom: 20px; align-items: center;
gap: 12px;
margin-bottom: 32px;
} }
.sidebar ul { .sidebar-logo {
list-style: none; background-color: var(--primary-color);
padding: 0; border-radius: 8px;
margin: 0; padding: 8px;
color: white;
} }
.sidebar li a { .sidebar-school-name {
display: block; display: flex;
padding: 10px 15px; flex-direction: column;
}
.sidebar-school-name span:first-child {
font-weight: 600;
color: var(--sidebar-active-text);
}
.sidebar-school-name span:last-child {
font-size: 0.875rem;
}
.nav-section {
margin-bottom: 24px;
}
.nav-section-title {
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: 8px;
}
.nav-item {
display: flex;
align-items: center;
padding: 10px 12px;
border-radius: 6px;
text-decoration: none; text-decoration: none;
color: #555; color: var(--sidebar-text);
border-radius: 4px; font-weight: 500;
margin-bottom: 5px; margin-bottom: 4px;
gap: 12px;
} }
.sidebar li a:hover, .sidebar li a.active { .nav-item:hover {
background-color: #eef2f5; background-color: #1F2937;
color: #007bff; color: var(--sidebar-active-text);
}
.nav-item.active {
background-color: var(--sidebar-active-bg);
color: var(--sidebar-active-text);
} }
.main-content { .main-content {
flex: 1; flex-grow: 1;
padding: 20px; display: flex;
flex-direction: column;
} }
.header { .header {
background-color: #fff; padding: 24px 32px;
border-bottom: 1px solid #e0e0e0; background-color: var(--card-bg);
padding: 20px; border-bottom: 1px solid var(--border-color);
margin-bottom: 20px; display: flex;
justify-content: space-between;
align-items: center;
} }
.header h1 { .search-bar {
display: flex;
align-items: center;
gap: 8px;
width: 400px;
}
.search-bar input {
width: 100%;
border: 1px solid var(--border-color);
border-radius: 6px;
padding: 10px 14px;
font-size: 1rem;
}
.header-actions {
display: flex;
align-items: center;
gap: 16px;
}
.user-profile {
display: flex;
align-items: center;
gap: 12px;
}
.user-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
background-color: var(--primary-color);
color: white;
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
}
.user-info span {
display: block;
}
.user-info span:first-child {
font-weight: 600;
}
.user-info span:last-child {
font-size: 0.875rem;
color: var(--text-secondary);
}
.page-content {
padding: 32px;
flex-grow: 1;
}
.welcome-header {
margin-bottom: 32px;
}
.welcome-header h1 {
font-size: 1.875rem;
font-weight: 700;
margin: 0 0 4px 0;
}
.welcome-header p {
font-size: 1rem;
color: var(--text-secondary);
margin: 0; margin: 0;
font-size: 1.8rem;
} }
.card { .stats-grid {
background-color: #fff; display: grid;
border-radius: 8px; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
box-shadow: 0 2px 4px rgba(0,0,0,0.05); gap: 24px;
padding: 20px; margin-bottom: 32px;
margin-bottom: 20px;
} }
.card h3 { .stat-card {
margin-top: 0; background-color: var(--card-bg);
border-radius: 12px;
padding: 24px;
border: 1px solid var(--border-color);
box-shadow: 0 1px 3px 0 rgba(0,0,0,0.05);
}
.stat-card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.stat-card-title {
font-weight: 500;
color: var(--text-secondary);
}
.stat-card-icon {
width: 40px;
height: 40px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.stat-card-icon.blue { background-color: var(--blue-light); color: var(--blue-dark); }
.stat-card-icon.green { background-color: var(--green-light); color: var(--green-dark); }
.stat-card-icon.red { background-color: var(--red-light); color: var(--red-dark); }
.stat-card-value {
font-size: 2.25rem;
font-weight: 700;
margin-bottom: 8px;
}
.stat-card-footer {
font-size: 0.875rem;
}
.stat-card-footer .increase { color: var(--green-dark); }
.stat-card-footer .decrease { color: var(--red-dark); }
.charts-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 24px;
margin-bottom: 32px;
}
.chart-card {
background-color: var(--card-bg);
border-radius: 12px;
padding: 24px;
border: 1px solid var(--border-color);
box-shadow: 0 1px 3px 0 rgba(0,0,0,0.05);
}
.chart-card-header h3 {
margin: 0 0 4px 0;
font-size: 1.125rem;
font-weight: 600;
}
.chart-card-header p {
margin: 0;
color: var(--text-secondary);
font-size: 0.875rem;
}
.chart-placeholder {
height: 250px;
display: flex;
align-items: center;
justify-content: center;
}
.table-card {
background-color: var(--card-bg);
border-radius: 12px;
padding: 24px;
border: 1px solid var(--border-color);
box-shadow: 0 1px 3px 0 rgba(0,0,0,0.05);
}
.table-card h3 {
margin: 0 0 16px 0;
font-size: 1.125rem;
font-weight: 600;
}
table {
width: 100%;
border-collapse: collapse;
}
th, td {
padding: 12px 16px;
text-align: left;
border-bottom: 1px solid var(--border-color);
}
thead th {
font-weight: 600;
color: var(--text-secondary);
font-size: 0.875rem;
} }

View File

@ -3,18 +3,23 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>School Accounting Dashboard</title> <title>Mother Providencia International School</title>
<link rel="stylesheet" href="dashboard/assets/css/style.css"> <link rel="stylesheet" href="assets/css/style.css">
<!-- React and Babel for JSX transpilation --> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<script src="https://unpkg.com/feather-icons"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://unpkg.com/react@17/umd/react.development.js"></script> <script src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script> <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>
<script type="text/babel" src="src/App.js"></script>
<!-- React Application --> <script type="text/babel" src="src/index.js"></script>
<script type="text/babel" src="dashboard/src/App.js"></script> <script>
<script type="text/babel" src="dashboard/src/index.js"></script> feather.replace();
</script>
</body> </body>
</html> </html>

View File

@ -1,74 +1,265 @@
const App = () => {
// State to hold trial balance data
const [trialBalance, setTrialBalance] = React.useState(null);
const [error, setError] = React.useState(null);
const App = () => {
React.useEffect(() => { React.useEffect(() => {
// Fetch trial balance data feather.replace();
fetch('/api/reports/trial_balance.php')
.then(response => { // Mock Chart for Income vs Expenses
if (!response.ok) { const incomeExpenseCtx = document.getElementById('incomeExpenseChart');
throw new Error('Network response was not ok'); if (incomeExpenseCtx) {
new Chart(incomeExpenseCtx, {
type: 'line',
data: {
labels: ['Sep', 'Oct', 'Nov', 'Dec', 'Jan', 'Feb'],
datasets: [{
label: 'Income',
data: [120000, 130000, 140000, 135000, 150000, 160000],
borderColor: '#10B981',
backgroundColor: 'rgba(16, 185, 129, 0.1)',
fill: true,
tension: 0.4
}, {
label: 'Expenses',
data: [90000, 95000, 105000, 100000, 110000, 115000],
borderColor: '#EF4444',
backgroundColor: 'rgba(239, 68, 68, 0.1)',
fill: true,
tension: 0.4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
ticks: {
callback: function(value) {
return '$' + value / 1000 + 'k';
}
}
}
}
} }
return response.json();
})
.then(data => {
if (data.error) {
throw new Error(data.error);
}
setTrialBalance(data);
})
.catch(error => {
console.error("Error fetching trial balance:", error);
setError(error.toString());
}); });
}
// Mock Chart for Fee Collection
const feeCollectionCtx = document.getElementById('feeCollectionChart');
if (feeCollectionCtx) {
new Chart(feeCollectionCtx, {
type: 'doughnut',
data: {
labels: ['Collected', 'Pending', 'Overdue'],
datasets: [{
data: [85, 10, 5],
backgroundColor: ['#10B981', '#F59E0B', '#EF4444'],
borderWidth: 0
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
cutout: '70%',
plugins: {
legend: {
display: false
}
}
}
});
}
}, []); }, []);
const navItems = {
Overview: [
{ name: 'Dashboard', icon: 'grid', active: true },
],
Academic: [
{ name: 'Students', icon: 'users' },
{ name: 'Teachers', icon: 'users' },
{ name: 'Classes', icon: 'book' },
{ name: 'Timetable', icon: 'calendar' },
{ name: 'Attendance', icon: 'check-square' },
{ name: 'Examinations', icon: 'file-text' },
],
Finance: [
{ name: 'Chart of Accounts', icon: 'file' },
{ name: 'Journal Entries', icon: 'book-open' },
{ name: 'Invoices (AR)', icon: 'dollar-sign' },
{ name: 'Payments', icon: 'trending-up' },
{ name: 'Bills (AP)', icon: 'file-minus' },
{ name: 'Vendors', icon: 'briefcase' },
{ name: 'Inventory', icon: 'archive' },
]
};
const stats = [
{ title: 'Total Students', value: '487', change: '+12 this month', changeType: 'increase', icon: 'users', iconBg: 'blue' },
{ title: 'Total Teachers', value: '32', change: '2 on leave', icon: 'users', iconBg: 'blue' },
{ title: 'Total Revenue', value: 'GH₵1,085,500.00', change: '+8.2% from last year', changeType: 'increase', icon: 'dollar-sign', iconBg: 'green' },
{ title: 'Pending Fees', value: 'GH₵85,000.00', change: '12 invoices overdue', changeType: 'decrease', icon: 'alert-triangle', iconBg: 'red' },
];
const Sidebar = () => (
<div className="sidebar">
<div className="sidebar-header">
<div className="sidebar-logo">
<i data-feather="award"></i>
</div>
<div className="sidebar-school-name">
<span>Mother Providencia</span>
<span>International School</span>
</div>
</div>
<nav>
{Object.entries(navItems).map(([section, items]) => (
<div className="nav-section" key={section}>
<div className="nav-section-title">{section}</div>
{items.map(item => (
<a href="#" className={`nav-item ${item.active ? 'active' : ''}`} key={item.name}>
<i data-feather={item.icon}></i>
<span>{item.name}</span>
</a>
))}
</div>
))}
</nav>
<div style={{ marginTop: 'auto' }}>
<a href="#" className="nav-item">
<i data-feather="settings"></i>
<span>Settings</span>
</a>
<a href="#" className="nav-item">
<i data-feather="log-out"></i>
<span>Logout</span>
</a>
</div>
</div>
);
const Header = () => (
<header className="header">
<div className="search-bar">
<i data-feather="search" style={{ color: 'var(--text-secondary)' }}></i>
<input type="text" placeholder="Search students, invoices, accounts..." />
</div>
<div className="header-actions">
<button className="admin-btn" style={{background: '#f8d7da', border: 'none', padding: '5px 10px', borderRadius: '5px'}}>admin</button>
<span>Switch Role</span>
<i data-feather="bell"></i>
<div className="user-profile">
<div className="user-avatar">JA</div>
<div className="user-info">
<span>John Administrator</span>
<span>Admin</span>
</div>
</div>
</div>
</header>
);
const StatCard = ({ stat }) => (
<div className="stat-card">
<div className="stat-card-header">
<span className="stat-card-title">{stat.title}</span>
<div className={`stat-card-icon ${stat.iconBg}`}>
<i data-feather={stat.icon}></i>
</div>
</div>
<div className="stat-card-value">{stat.value}</div>
<div className={`stat-card-footer ${stat.changeType}`}>{stat.change}</div>
</div>
);
return ( return (
<div className="dashboard"> <div className="dashboard">
<aside className="sidebar"> <Sidebar />
<h2>Dashboard</h2>
<ul>
<li><a href="#" className="active">Overview</a></li>
<li><a href="#">Reports</a></li>
<li><a href="#">Students</a></li>
<li><a href="#">Invoices</a></li>
<li><a href="#">Payments</a></li>
</ul>
</aside>
<main className="main-content"> <main className="main-content">
<header className="header"> <Header />
<h1>Financial Overview</h1> <div className="page-content">
</header> <div className="welcome-header">
<div className="card"> <h1>Welcome back, John!</h1>
<h3>Trial Balance</h3> <p>Here's your administrative overview of the school.</p>
{error && <p>Error loading data: {error}</p>} </div>
{!trialBalance && !error && <p>Loading...</p>} <div className="stats-grid">
{trialBalance && ( {stats.map(stat => <StatCard stat={stat} key={stat.title} />)}
<table> </div>
<thead> <div className="charts-grid">
<tr> <div className="chart-card">
<th>Account</th> <div className="chart-card-header">
<th>Debit</th> <h3>Cash Position</h3>
<th>Credit</th> <p>Current balances</p>
</tr> </div>
</thead> <div style={{height: '250px', display:'flex', flexDirection: 'column', justifyContent:'center', gap: '20px'}}>
<tbody> <div style={{display: 'flex', alignItems: 'center', gap: '15px'}}>
{trialBalance.lines.map((line, index) => ( <div style={{padding: '10px', background: 'var(--blue-light)', borderRadius: '8px'}}><i data-feather="briefcase"></i></div>
<tr key={index}> <div>
<td>{line.account_name}</td> <div>Bank Account</div>
<td>{line.debit}</td> <div style={{fontSize: '1.5rem', fontWeight: '600'}}>GH650,000.00</div>
<td>{line.credit}</td> <div style={{fontSize: '0.8rem', color: 'var(--text-secondary)'}}>Operating + Savings</div>
</tr> </div>
))} </div>
<tr> <div style={{display: 'flex', alignItems: 'center', gap: '15px'}}>
<td><b>{trialBalance.totals.account_name}</b></td> <div style={{padding: '10px', background: 'var(--green-light)', borderRadius: '8px'}}><i data-feather="dollar-sign"></i></div>
<td><b>{trialBalance.totals.debit}</b></td> <div>
<td><b>{trialBalance.totals.credit}</b></td> <div>Petty Cash</div>
</tr> <div style={{fontSize: '1.5rem', fontWeight: '600'}}>GH2,500.00</div>
</tbody> <div style={{fontSize: '0.8rem', color: 'var(--text-secondary)'}}>On hand</div>
</table> </div>
)} </div>
<div style={{color: 'var(--red-dark)', fontWeight:'500', display:'flex', alignItems:'center', gap: '5px'}}><i data-feather="trending-down"></i>Upcoming payables GH31,000.00</div>
</div>
</div>
<div className="chart-card">
<div className="chart-card-header">
<h3>Fee Collection</h3>
<p>Current academic year</p>
</div>
<div className="chart-placeholder">
<canvas id="feeCollectionChart"></canvas>
</div>
<div style={{display: 'flex', justifyContent: 'center', gap: '15px', marginTop: '15px'}}>
<span style={{display:'flex', alignItems: 'center', gap: '5px'}}><div style={{width:'10px', height: '10px', borderRadius: '50%', background: '#10B981'}}></div>Collected</span>
<span style={{display:'flex', alignItems: 'center', gap: '5px'}}><div style={{width:'10px', height: '10px', borderRadius: '50%', background: '#F59E0B'}}></div>Pending</span>
<span style={{display:'flex', alignItems: 'center', gap: '5px'}}><div style={{width:'10px', height: '10px', borderRadius: '50%', background: '#EF4444'}}></div>Overdue</span>
</div>
</div>
<div className="chart-card">
<div className="chart-card-header">
<h3>Income vs Expenses</h3>
<p>Last 6 months trend</p>
</div>
<div className="chart-placeholder">
<canvas id="incomeExpenseChart"></canvas>
</div>
</div>
</div>
<div className="charts-grid" style={{gridTemplateColumns: '1fr 1fr'}}>
<div className="table-card">
<h3>Recent Invoices</h3>
<table>
<thead>
<tr><th>Student</th><th>Amount</th><th>Status</th></tr>
</thead>
<tbody>
<tr><td>Kwabena Adu</td><td>GH1,200.00</td><td>Paid</td></tr>
<tr><td>Afiya Owusu</td><td>GH1,200.00</td><td>Pending</td></tr>
</tbody>
</table>
</div>
<div className="table-card">
<h3>Recent Payments</h3>
<table>
<thead>
<tr><th>Student</th><th>Amount</th><th>Date</th></tr>
</thead>
<tbody>
<tr><td>Kwabena Adu</td><td>GH1,200.00</td><td>2024-02-15</td></tr>
<tr><td>Esi Mensah</td><td>GH1,000.00</td><td>2024-02-14</td></tr>
</tbody>
</table>
</div>
</div>
</div> </div>
</main> </main>
</div> </div>