feat: refine admin forms, attendance and finance workflows

Squash merge PR #23.

Included changes:
- complete occupancy check-in required fields/default payload
- improve responsive admin management pages
- fix attendance edge cases and attendance period config
- refine wallet/finance-related workflow handling

Checks:
- npm run typecheck -w apps/admin
- npm run typecheck -w apps/server
This commit is contained in:
2026-07-18 12:54:10 +00:00
parent 92d303ed01
commit 375c7ec60b
64 changed files with 5169 additions and 2404 deletions

View File

@@ -34,16 +34,20 @@ const NotificationBell: React.FC = () => {
const fetchNotifications = async () => {
try {
const data = await api.get('/notifications?limit=20') as unknown as NotificationItem[];
const data = (await api.get('/notifications?limit=20')) as unknown as NotificationItem[];
setNotifications(data);
} catch { /* ignore */ }
} catch {
/* ignore */
}
};
const fetchUnread = async () => {
try {
const data = await api.get('/notifications/unread-count') as unknown as { count: number };
const data = (await api.get('/notifications/unread-count')) as unknown as { count: number };
setUnreadCount(data.count);
} catch { /* ignore */ }
} catch {
/* ignore */
}
};
const openRef = useRef(open);
openRef.current = open;
@@ -60,7 +64,9 @@ const NotificationBell: React.FC = () => {
JSON.parse(event.data);
setUnreadCount((c) => c + 1);
if (openRef.current) fetchNotifications();
} catch { /* ignore */ }
} catch {
/* ignore */
}
};
es.onerror = () => {
es.close();
@@ -84,7 +90,9 @@ const NotificationBell: React.FC = () => {
try {
await api.put(`/notifications/${item.id}/read`);
setUnreadCount((c) => Math.max(0, c - 1));
} catch { /* ignore */ }
} catch {
/* ignore */
}
}
setOpen(false);
if (item.link) navigate(item.link);
@@ -94,10 +102,10 @@ const NotificationBell: React.FC = () => {
try {
await api.put('/notifications/read-all');
setUnreadCount(0);
setNotifications((prev) =>
prev.map((n) => ({ ...n, isRead: true })),
);
} catch { /* ignore */ }
setNotifications((prev) => prev.map((n) => ({ ...n, isRead: true })));
} catch {
/* ignore */
}
};
const content = (
@@ -148,18 +156,13 @@ const NotificationBell: React.FC = () => {
)
}
title={
<Typography.Text
strong={!item.isRead}
style={{ fontSize: 14 }}
>
[{notificationTypeLabels[item.type] || item.type}] {formatNotificationText(item.title)}
<Typography.Text strong={!item.isRead} style={{ fontSize: 14 }}>
[{notificationTypeLabels[item.type] || item.type}]{' '}
{formatNotificationText(item.title)}
</Typography.Text>
}
description={
<Typography.Text
type="secondary"
style={{ fontSize: 12 }}
>
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
{timeAgo(item.createdAt)}
</Typography.Text>
}