const PRIORITY_TONE = { High: 'red', Medium: 'amber', Low: 'grey' };
const NEXT_STATUS = { todo: 'in_progress', in_progress: 'done', done: 'todo' };
function assignableFor(S, me) {
const admin = ['hr_admin', 'super_admin'].includes(me.role);
return S.users.filter((u) => u.status === 'active' && u.id !== me.id && (admin || u.managerId === me.id));
}
function AssignTaskModal({ open, onClose, preset }) {
const S = useStore(); const { me, toast } = useApp();
const people = assignableFor(S, me);
const blank = () => ({ assigneeId: preset || (people[0] || {}).id || '', title: '', desc: '', due: AKH.iso(AKH.addDays(AKH.today(), 2)), priority: 'Medium' });
const [f, setF] = React.useState(blank);
const [err, setErr] = React.useState('');
React.useEffect(() => { setF(blank()); setErr(''); }, [open, preset]);
const submit = () => {
if (!f.assigneeId) return setErr('Choose who this is for.');
if (!f.title.trim()) return setErr('Give the task a title.');
AKStore.act.addTask(me.id, f); onClose(); toast('Task assigned · email sent to ' + AKH.user(f.assigneeId).name);
};
return (
);
}
function TaskRow({ x, first, showAssignee, onOpen }) {
const t = AKH.todayIso(); const over = x.status !== 'done' && x.due < t; const done = x.status === 'done';
return (
{x.priority}
);
}
function TasksPage() {
const S = useStore(); const { me, m } = useApp();
const canAssign = me.role !== 'employee';
const [scope, setScope] = React.useState('mine');
const [filter, setFilter] = React.useState('open');
const [openId, setOpenId] = React.useState(null);
const [assign, setAssign] = React.useState(false);
const base = S.tasks.filter((x) => (scope === 'mine' ? x.assigneeId === me.id : x.byId === me.id && x.assigneeId !== me.id));
const groups = { open: base.filter((x) => x.status !== 'done'), todo: base.filter((x) => x.status === 'todo'), in_progress: base.filter((x) => x.status === 'in_progress'), done: base.filter((x) => x.status === 'done') };
const list = groups[filter].slice().sort((a, b) => a.due.localeCompare(b.due));
const sel = S.tasks.find((x) => x.id === openId);
return (
<>
setAssign(true)}>Assign task : null} />
{list.length === 0 ? : list.map((x, i) => setOpenId(x.id)} />)}
setOpenId(null)} eyebrow={sel ? sel.priority + ' priority' : ''} title={sel ? sel.title : ''}>
{sel ? (
{sel.desc ?
{sel.desc}
: null}
AKStore.act.setTaskStatus(sel.id, v)} />
) : null}
setAssign(false)} />
>
);
}
const RATINGS = [[1, 'Needs improvement'], [2, 'Developing'], [3, 'Meets expectations'], [4, 'Exceeds expectations'], [5, 'Outstanding']];
function PerformancePage() {
const S = useStore(); const { me, m, toast } = useApp();
const goals = S.goals.filter((g) => g.userId === me.id);
const overall = goals.length ? Math.round(goals.reduce((a, g) => a + g.progress * g.weight, 0) / goals.reduce((a, g) => a + g.weight, 0)) : 0;
const sub = S.assessments[me.id];
const [edit, setEdit] = React.useState(null);
const [val, setVal] = React.useState({ progress: 0, note: '' });
const [f, setF] = React.useState({ rating: 0, achievements: '', challenges: '', next: '' });
const [err, setErr] = React.useState('');
const g = goals.find((x) => x.id === edit);
const daysLeft = Math.round((AKH.parse(S.cycle.due) - AKH.today()) / 864e5);
const submit = () => {
if (!f.rating) return setErr('Choose an overall rating.');
if (!f.achievements.trim() || !f.next.trim()) return setErr('Fill in your achievements and your focus for next period.');
AKStore.act.submitAssessment(me.id, f); toast('Self-assessment submitted to ' + (me.managerId ? AKH.user(me.managerId).name : 'HR'));
};
return (
<>
My goals
Weighted progress across {goals.length} goals
{overall}%
{goals.length === 0 ? : goals.map((x, i) => (
{x.title}
Weight {x.weight}% · due {fmt.d(x.due)} · updated {fmt.d(x.updatedAt)}
{ setEdit(x.id); setVal({ progress: x.progress, note: '' }); }}>Update
))}
= 0 ? ' · ' + daysLeft + ' days left' : '')} action={sub ? Submitted : Open} />
{sub ? (
{sub.rating} · {RATINGS[sub.rating - 1][1]}
{[['Achievements', sub.achievements], ['Challenges', sub.challenges], ['Focus for next period', sub.next]].map(([l, v]) => v ?
: null)}
) : (
{RATINGS.map(([n, l]) => (
))}
)}
setEdit(null)} eyebrow="Update goal" title={g ? g.title : ''}>
{g ? (
) : null}
>
);
}
const HOL_TONE = { National: 'blue', Festival: 'amber', Telangana: 'green' };
function HolidaysPage() {
const S = useStore(); const { m } = useApp();
const [type, setType] = React.useState('all');
const t = AKH.todayIso(); const now = AKH.today();
const year = now.getFullYear();
const list = S.holidays.filter((h) => h.date.startsWith(String(year)) || (h.date > t && h.date.startsWith(String(year + 1))));
const next = S.holidays.find((h) => h.date >= t && !AKH.isWeekend(AKH.parse(h.date))) || S.holidays.find((h) => h.date >= t);
const shown = list.filter((h) => type === 'all' || h.type === type);
const months = {};
shown.forEach((h) => { const k = h.date.slice(0, 7); (months[k] = months[k] || []).push(h); });
const off = list.filter((h) => !AKH.isWeekend(AKH.parse(h.date)) && h.date.startsWith(String(year))).length;
return (
<>
{next ? (() => {
const d = AKH.parse(next.date); const days = Math.round((d - now) / 864e5);
return (
Next holiday
{next.name}
{fmt.long(next.date)}
{days}
{days === 1 ? 'day to go' : 'days to go'}
);
})() : null}
{Object.entries(months).map(([k, hs]) => (
{AKH.parse(k + '-01').toLocaleDateString('en-IN', { month: 'long', year: 'numeric' })}
{hs.map((h, i) => {
const d = AKH.parse(h.date); const past = h.date < t; const we = AKH.isWeekend(d);
return (
{d.getDate()}
{d.toLocaleDateString('en-IN', { weekday: 'short' })}
{h.name}
{we ?
Falls on a weekend
: null}
{h.type}
);
})}
))}
Dates for Eid, Bakrid, Muharram and Milad-un-Nabi depend on the moon sighting and may move by a day. HR will confirm.
>
);
}
function DecideModal({ item, onClose }) {
const { me, toast } = useApp(); const [note, setNote] = React.useState('');
React.useEffect(() => setNote(''), [item]);
if (!item) return null;
const { leave, approve } = item; const u = AKH.user(leave.userId);
return (
1 ? 's' : '')}>
{AKStore.LEAVE_TYPES[leave.type]} · {fmt.range(leave.from, leave.to)}
“{leave.reason}”
);
}
function LeaveRequestCard({ l, onDecide }) {
const u = AKH.user(l.userId); const bal = u.leave[l.type];
return (
{l.reason}{bal.total - bal.used} of {bal.total} days left
onDecide({ leave: l, approve: false })}>Decline onDecide({ leave: l, approve: true })}>Approve
);
}
function CorrectionCard({ c }) {
const { me, toast } = useApp(); const S = useStore(); const u = AKH.user(c.userId);
const orig = (S.attendance[c.userId] || {})[c.date];
return (
{c.reason}Recorded: {orig && orig.in ? fmt.t(orig.in) + ' – ' + (orig.out ? fmt.t(orig.out) : 'no check-out') : 'no check-in'}
{c.status === 'pending'
?
{ AKStore.act.decideCorrection(c.id, false, me.id); toast('Correction declined'); }}>Decline { AKStore.act.decideCorrection(c.id, true, me.id); toast('Correction approved · attendance updated'); }}>Approve
:
}
);
}
function TeamPage() {
const S = useStore(); const { me, m } = useApp();
const [decide, setDecide] = React.useState(null);
const [assign, setAssign] = React.useState(null);
const team = S.users.filter((u) => u.managerId === me.id && u.status === 'active');
const ids = team.map((u) => u.id); const t = AKH.todayIso();
const leaves = S.leaves.filter((l) => l.status === 'pending' && l.approverId === me.id);
const corrections = S.corrections.filter((c) => c.status === 'pending' && ids.includes(c.userId));
const status = (u) => { const di = dayInfo(S, u.id, t); if (di.kind === 'office' || di.kind === 'present') return ['blue', 'Office · ' + fmt.t(di.rec.in)]; if (di.kind === 'wfh') return ['grey', 'WFH · ' + fmt.t(di.rec.in)]; if (di.kind === 'leave') return ['amber', 'On leave']; if (di.kind === 'holiday') return ['navy', 'Holiday']; if (di.kind === 'weekend') return ['grey', 'Weekend']; return ['red', 'Not checked in']; };
return (
<>
setAssign(team[0].id)}>Assign task : null} />
{team.length === 0 ? : team.map((u, i) => {
const [tone, label] = status(u); const open = S.tasks.filter((x) => x.assigneeId === u.id && x.status !== 'done').length; const a = S.assessments[u.id];
return (
{label}
);
})}
{leaves.length === 0 ? : leaves.map((l) => )}
{corrections.length === 0 ? : corrections.map((c) => )}
setDecide(null)} />
setAssign(null)} />
>
);
}
Object.assign(window, { TasksPage, PerformancePage, HolidaysPage, TeamPage, AssignTaskModal, DecideModal, LeaveRequestCard, CorrectionCard, HOL_TONE });