go('admin-outbox')}>Email outbox} />
{[['Active employees', active.length, S.users.filter((u) => u.status === 'suspended').length + ' suspended'], ['Checked in', inNow, Math.round((inNow / Math.max(1, active.length - onLeave)) * 100) + '% of expected'], ['On leave today', onLeave, null], ['Pending approvals', pending, 'access, leave & corrections']].map(([l, v, s]) => (
))}
Today's attendance
{rows.map(({ u, di, badge }) => (
|
{u.dept} |
{badge[1]}{di.late ? Late : null} |
{di.rec && di.rec.in ? fmt.t(di.rec.in) : '—'} |
{di.rec && di.rec.out ? fmt.t(di.rec.out) : '—'} |
))}
go('admin-employees')} style={{ border: 'none', background: 'none', padding: 0, color: '#2F80EC', fontSize: '13.5px', fontWeight: 700, cursor: 'pointer', fontFamily: 'inherit' }}>All →} />
{pendingUsers.length === 0 ? : pendingUsers.map((u, i) => (
))}
{upcoming.map((h, i) => (
{h.greetingSent ?
Greeting sent :
{ AKStore.act.sendGreeting(h.id, me.id); toast(h.name + ' greeting emailed to all employees'); }}>Send}
))}
setApprove(null)} />
>
);
}
function EmployeesPage() {
const S = useStore(); const { me, m, toast } = useApp();
const [tab, setTab] = React.useState(S.users.some((u) => u.status === 'pending') ? 'pending' : 'active');
const [q, setQ] = React.useState('');
const [approve, setApprove] = React.useState(null); const [suspend, setSuspend] = React.useState(null); const [detail, setDetail] = React.useState(null);
const by = (s) => S.users.filter((u) => (s === 'all' ? u.status !== 'rejected' : u.status === s));
const list = by(tab).filter((u) => !q || (u.name + u.email + u.phone + (u.empId || '') + u.dept).toLowerCase().includes(q.toLowerCase()));
const actions = (u) => {
if (u.status === 'pending') return { AKStore.act.rejectUser(u.id); toast('Request rejected'); }}>Reject setApprove(u)}>Approve
;
if (u.id === me.id) return You;
if (u.role === 'super_admin' && me.role !== 'super_admin') return null;
if (u.status === 'active') return setSuspend(u)}>Suspend;
if (u.status === 'suspended') return { AKStore.act.reactivateUser(u.id); toast(u.name + ' reactivated'); }}>Reactivate;
return null;
};
return (
<>
{list.length === 0 ? : m ? (
{list.map((u) => (
{actions(u)}
))}
) : (
Actions']} minWidth={860}>
{list.map((u) => (
|
{u.empId || '—'} |
{u.dept}{u.designation} |
{AKStore.ROLES[u.role]} |
{u.managerId ? AKH.user(u.managerId).name : '—'} |
|
{actions(u)} |
))}
)}
setApprove(null)} />
setSuspend(null)} />
setDetail(null)} />
>
);
}
function ApprovalsPage() {
const S = useStore();
const [tab, setTab] = React.useState('corrections');
const [decide, setDecide] = React.useState(null);
const cP = S.corrections.filter((c) => c.status === 'pending'); const cD = S.corrections.filter((c) => c.status !== 'pending');
const lP = S.leaves.filter((l) => l.status === 'pending'); const lD = S.leaves.filter((l) => ['approved', 'rejected'].includes(l.status)).slice(0, 8);
return (
<>
{tab === 'corrections' ? (
<>
{cP.length === 0 ? : cP.map((c) => )}
{cD.length === 0 ? : cD.map((c) => )}
>
) : (
<>
{lP.length === 0 ? : lP.map((l) => Approver: {l.approverId ? AKH.user(l.approverId).name : '—'}
)}
{lD.map((l) => (
by {(l.decidedBy || l.approverId) ? AKH.user(l.decidedBy || l.approverId).name : '—'}
))}
>
)}
setDecide(null)} />
>
);
}
function AdminHolidays() {
const S = useStore(); const { me, m, toast } = useApp();
const [f, setF] = React.useState({ name: '', date: '', type: 'Festival', greeting: true });
const [err, setErr] = React.useState('');
const t = AKH.todayIso(); const [show, setShow] = React.useState('upcoming');
const list = S.holidays.filter((h) => (show === 'upcoming' ? h.date >= t : true));
const add = () => {
if (!f.name.trim() || !f.date) return setErr('Add a name and a date.');
if (S.holidays.some((h) => h.date === f.date)) return setErr('There is already a holiday on that date.');
AKStore.act.addHoliday(f, me.id); setF({ name: '', date: '', type: 'Festival', greeting: true }); setErr('');
toast('Holiday added' + (f.greeting ? ' · greeting emailed to all employees' : ''));
};
return (
<>
setF({ ...f, name: e.target.value })} placeholder="e.g. Company foundation day" />
setF({ ...f, date: e.target.value })} />
{list.map((h) => (
| {fmt.dw(h.date)} |
{h.name} |
{h.type} |
{h.greetingSent ? Sent : h.date >= t ? { AKStore.act.sendGreeting(h.id, me.id); toast('Greeting emailed to all employees'); }}>Send : —} |
|
))}
In production, greetings are scheduled automatically at 10:00 am the day before each holiday.
>
);
}
const EMAIL_TONE = { account_approved: 'green', account_suspended: 'red', leave_decision: 'amber', task_assigned: 'blue', holiday_greeting: 'navy', monthly_summary: 'grey' };
function OutboxPage() {
const S = useStore(); const { me, m, toast } = useApp();
const [type, setType] = React.useState('all');
const list = S.outbox.filter((e) => type === 'all' || e.type === type);
const [sel, setSel] = React.useState(null);
const cur = list.find((e) => e.id === sel) || (!m ? list[0] : null);
const preview = cur ? : null;
return (
<>
{ const n = AKStore.act.runMonthlySummary(me.id); toast('Monthly summary sent to ' + n + ' employees'); }}>Run monthly summary} />
setType('all')}>All
{Object.entries(AKEmail.TYPES).map(([k, l]) => e.type === k).length} onClick={() => setType(k)}>{l})}
{list.length === 0 ? : list.map((e) => {
const on = cur && cur.id === e.id;
return (
);
})}
{!m ?
{preview}
: null}
{m ? setSel(null)} eyebrow={cur ? AKEmail.TYPES[cur.type] : ''} title={cur ? 'To ' + cur.toName : ''} width={640}>{preview} : null}
>
);
}
const PERMS = [
['Check in / out, leave, tasks, goals', [1, 1, 1, 1]],
['Approve team leave & corrections', [0, 1, 1, 1]],
['Assign tasks to direct reports', [0, 1, 1, 1]],
['Approve / reject new employees', [0, 0, 1, 1]],
['Suspend / reactivate accounts', [0, 0, 1, 1]],
['Manage holidays & send greetings', [0, 0, 1, 1]],
['View email outbox, run summaries', [0, 0, 1, 1]],
['Change roles, manage admins', [0, 0, 0, 1]],
];
function RolesPage() {
const S = useStore(); const { me, m, toast } = useApp();
const people = S.users.filter((u) => u.status === 'active');
const roles = Object.keys(AKStore.ROLES);
return (
<>
AKStore.ROLES[r])]} minWidth={640}>
{PERMS.map(([p, v]) => (
| {p} | {v.map((on, i) => {on ? : '—'} | )}
))}
People
{people.map((u, i) => (
))}
>
);
}
Object.assign(window, { AdminOverview, EmployeesPage, ApprovalsPage, AdminHolidays, OutboxPage, RolesPage });