diff --git a/app/modules/employees/templates/employees/portal_dashboard.html b/app/modules/employees/templates/employees/portal_dashboard.html index e539b0a..01b4b82 100644 --- a/app/modules/employees/templates/employees/portal_dashboard.html +++ b/app/modules/employees/templates/employees/portal_dashboard.html @@ -1,20 +1,18 @@ {% extends "ui/templates/base/layout.html" %} {% block content %} -
- {% include "modules/employees/templates/employees/_my_workspace_tabs.html" %} - +

My Workspace

-

Today’s work, attendance and personal profile

-

Use this dashboard for your own assignments, attendance, leave, documents, payslips and alerts.

+

Employee Portal

+

Use this dashboard for your assigned work, attendance, leave, documents, payslips, profile and alerts.

{% if employee %} Open My Work Board - Punch / Attendance + Full Attendance {% else %} Request Employee Linkage {% endif %} @@ -23,310 +21,88 @@
- {% if employee %} -
- -
Open Work
-
{{ work_payload.summary.open if work_payload else 0 }}
-
Assigned to me
-
- -
In Progress
-
{{ work_payload.summary.in_progress if work_payload else 0 }}
-
Currently active
-
- -
Blocked
-
{{ work_payload.summary.blocked if work_payload else 0 }}
-
Need clarification
-
- -
Overdue
-
{{ work_payload.summary.overdue if work_payload else 0 }}
-
Immediate action
-
- -
Attendance
-
{% if today_attendance %}Marked{% else %}Not Marked{% endif %}
-
Today
-
- -
Payslips
-
{{ payslips|length }}
-
Available slips
-
+ {% set tabs = [ + ('overview','Overview'), + ('work','My Work Board'), + ('attendance','My Attendance'), + ('profile','My Profile'), + ('leave','My Leave'), + ('documents','My Documents'), + ('payslips','My Payslips'), + ('offboarding','My Offboarding'), + ('alerts','My Alert') + ] %} + +
+
My Workspace
+
+
+ {% for code, label in tabs %} + + {% endfor %} +
+
-
-
-
-
-
-

My Assignment Board Snapshot

-

Your work is grouped by practical status so you can start from urgent items first.

-
- Open My Work Board -
-
- {% if work_payload %} - {% for column in work_payload.columns %} - -
{{ column.label }}
-
{{ column.cards|length }}
-
assignment card(s)
-
- {% endfor %} - {% else %} -
No work board data available yet.
- {% endif %} -
-
- -
-
-
-

Leave & Availability

-

Balances and recent leave requests for your profile.

-
- Open Leave -
-
- {% for bal in leave_balances %} -
-
{{ bal.leave_type.name if bal.leave_type else bal.leave_type_id }}
-
{{ bal.balance_days }}
-
days balance
-
- {% else %} -
No leave balance available yet.
- {% endfor %} -
-
-
- - +
+ {% include "modules/employees/templates/employees/portal_partials/overview.html" %}
- {% elif pending_request %} -

Employee profile request is pending

Your request #{{ pending_request.id }} is waiting for approval.

- {% else %} -

Your login is not linked to an employee master

Submit an employee link request or ask Firm Admin/Partner/Branch Manager to link your user account from Employee Master.

Request Employee Link
- {% endif %} -
- - - {% endblock %} diff --git a/app/modules/employees/templates/employees/portal_partials/alerts.html b/app/modules/employees/templates/employees/portal_partials/alerts.html new file mode 100644 index 0000000..584a98d --- /dev/null +++ b/app/modules/employees/templates/employees/portal_partials/alerts.html @@ -0,0 +1,12 @@ +
+

My Alert

Open your alert centre to view notifications and follow-ups.

+ Open Alerts +
+
+
{% if unread_alert_count is defined and unread_alert_count > 0 %}You have {{ unread_alert_count }} unread alert(s).{% else %}No unread alert count is pending in this workspace context.{% endif %}
+ +
diff --git a/app/modules/employees/templates/employees/portal_partials/attendance.html b/app/modules/employees/templates/employees/portal_partials/attendance.html new file mode 100644 index 0000000..3840cf8 --- /dev/null +++ b/app/modules/employees/templates/employees/portal_partials/attendance.html @@ -0,0 +1,42 @@ +
+
+

My Attendance

+

Punch in/out and view your recent attendance records.

+
+ Open Full Attendance Page +
+ +{% if not employee %} +
Your login is not linked to an employee profile yet. Please request employee linkage first.
+{% else %} +
+
Today
{{ today_attendance.attendance_date if today_attendance else 'Not marked' }}
+
Punch In
{{ today_attendance.punch_in_local_at.strftime('%H:%M') if today_attendance and today_attendance.punch_in_local_at else (today_attendance.punch_in_utc.strftime('%H:%M UTC') if today_attendance and today_attendance.punch_in_utc else '-') }}
+
Punch Out
{{ today_attendance.punch_out_local_at.strftime('%H:%M') if today_attendance and today_attendance.punch_out_local_at else (today_attendance.punch_out_utc.strftime('%H:%M UTC') if today_attendance and today_attendance.punch_out_utc else '-') }}
+
+ +{% include "modules/employees/templates/employees/portal_partials/attendance_punch_card.html" %} + +
+ + + + + + {% for row in attendance_rows %} + + + + + + + + + + {% else %} + + {% endfor %} + +
DatePunch InPunch OutDurationStatusApprovalGeo/IP
{{ row.attendance_date }}{{ row.punch_in_local_at.strftime('%H:%M') if row.punch_in_local_at else (row.punch_in_utc.strftime('%H:%M UTC') if row.punch_in_utc else '-') }}{{ row.punch_out_local_at.strftime('%H:%M') if row.punch_out_local_at else (row.punch_out_utc.strftime('%H:%M UTC') if row.punch_out_utc else '-') }}{{ (row.work_duration_minutes ~ ' min') if row.work_duration_minutes else '-' }}{{ row.status.replace('_',' ').title() }}{{ row.approval_status.replace('_',' ').title() }}
Geo: {{ (row.punch_in_geo_status or '-').replace('_',' ').title() }}
IP: {{ (row.punch_in_ip_status or '-').replace('_',' ').title() }}
No attendance records available yet.
+
+{% endif %} diff --git a/app/modules/employees/templates/employees/portal_partials/attendance_punch_card.html b/app/modules/employees/templates/employees/portal_partials/attendance_punch_card.html new file mode 100644 index 0000000..ecfccf3 --- /dev/null +++ b/app/modules/employees/templates/employees/portal_partials/attendance_punch_card.html @@ -0,0 +1,94 @@ +
+
+
+

Today’s Attendance

+

+ {% if today_attendance %} + Punch In: {{ today_attendance.punch_in_local_at.strftime('%H:%M') if today_attendance.punch_in_local_at else (today_attendance.punch_in_utc.strftime('%H:%M UTC') if today_attendance.punch_in_utc else '-') }} · + Punch Out: {{ today_attendance.punch_out_local_at.strftime('%H:%M') if today_attendance.punch_out_local_at else (today_attendance.punch_out_utc.strftime('%H:%M UTC') if today_attendance.punch_out_utc else '-') }} + {% else %} + Attendance not marked for today. + {% endif %} +

+
+ Full View +
+ + {% if employee %} +
Location not captured yet.
+
+
+ + + + + + + +
+
+ + + + + + + +
+
+ {% endif %} +
+ + diff --git a/app/modules/employees/templates/employees/portal_partials/documents.html b/app/modules/employees/templates/employees/portal_partials/documents.html new file mode 100644 index 0000000..afe47cd --- /dev/null +++ b/app/modules/employees/templates/employees/portal_partials/documents.html @@ -0,0 +1,9 @@ +
+

My Documents

Recently uploaded employee documents and verification status.

+ Open Documents Page +
+
+ + {% for doc in own_documents %}{% else %}{% endfor %} +
DocumentTypeStatusUploadedAction
{{ doc.title or doc.file_name or ('Document #' ~ doc.id) }}{{ doc.document_type.name if doc.document_type else '-' }}{{ doc.status.replace('_',' ').title() if doc.status else '-' }}{{ doc.created_at.strftime('%d-%m-%Y') if doc.created_at else '-' }}View
No employee documents available yet.
+
diff --git a/app/modules/employees/templates/employees/portal_partials/leave.html b/app/modules/employees/templates/employees/portal_partials/leave.html new file mode 100644 index 0000000..9f7b17a --- /dev/null +++ b/app/modules/employees/templates/employees/portal_partials/leave.html @@ -0,0 +1,21 @@ +
+

My Leave

Leave balances and recent applications.

+ Open Leave Page +
+
+ {% for bal in leave_balances %} +
+
{{ bal.leave_type.name if bal.leave_type else bal.leave_type_id }}
+
{{ bal.balance_days }}
+
days balance
+
+ {% else %} +
No leave balance available yet.
+ {% endfor %} +
+
+

Recent Leave Requests

+ + {% for row in leave_requests %}{% else %}{% endfor %} +
TypeFromToDaysStatus
{{ row.leave_type.name if row.leave_type else row.leave_type_id }}{{ row.from_date }}{{ row.to_date }}{{ row.days }}{{ row.status.replace('_',' ').title() }}
No leave requests available.
+
diff --git a/app/modules/employees/templates/employees/portal_partials/offboarding.html b/app/modules/employees/templates/employees/portal_partials/offboarding.html new file mode 100644 index 0000000..c187c36 --- /dev/null +++ b/app/modules/employees/templates/employees/portal_partials/offboarding.html @@ -0,0 +1,9 @@ +
+

My Offboarding

Offboarding requests and status.

+ Open Offboarding Page +
+
+ + {% for row in offboarding_requests %}{% else %}{% endfor %} +
RequestReasonStatusCreated
#{{ row.id }}{{ row.reason or '-' }}{{ row.status.replace('_',' ').title() if row.status else '-' }}{{ row.created_at.strftime('%d-%m-%Y') if row.created_at else '-' }}
No offboarding requests available.
+
diff --git a/app/modules/employees/templates/employees/portal_partials/overview.html b/app/modules/employees/templates/employees/portal_partials/overview.html new file mode 100644 index 0000000..1aeb72b --- /dev/null +++ b/app/modules/employees/templates/employees/portal_partials/overview.html @@ -0,0 +1,111 @@ +{% if employee %} +
+ +
Open Work
+
{{ work_payload.summary.open if work_payload else 0 }}
+
Assigned to me
+
+ +
In Progress
+
{{ work_payload.summary.in_progress if work_payload else 0 }}
+
Currently active
+
+ +
Blocked
+
{{ work_payload.summary.blocked if work_payload else 0 }}
+
Need clarification
+
+ +
Overdue
+
{{ work_payload.summary.overdue if work_payload else 0 }}
+
Immediate action
+
+ +
Attendance
+
{% if today_attendance %}{% if today_attendance.punch_out_utc %}Completed{% elif today_attendance.punch_in_utc %}Punched In{% else %}Marked{% endif %}{% else %}Not Marked{% endif %}
+
Today
+
+ +
Payslips
+
{{ payslips|length }}
+
Available slips
+
+
+ +
+
+
+
+
+

My Assignment Board Snapshot

+

Your work is grouped by practical status. Open the full board for detailed updates.

+
+ Open My Work Board +
+
+ {% if work_payload %} + {% for column in work_payload.columns %} + +
{{ column.label }}
+
{{ column.cards|length }}
+
assignment card(s)
+
+ {% endfor %} + {% else %} +
No work board data available yet.
+ {% endif %} +
+
+ +
+
+
+

Leave & Availability

+

Balances and recent leave requests for your profile.

+
+ Open Leave +
+
+ {% for bal in leave_balances %} +
+
{{ bal.leave_type.name if bal.leave_type else bal.leave_type_id }}
+
{{ bal.balance_days }}
+
days balance
+
+ {% else %} +
No leave balance available yet.
+ {% endfor %} +
+
+
+ + +
+{% elif pending_request %} +

Employee profile request is pending

Your request #{{ pending_request.id }} is waiting for approval.

+{% else %} +

Your login is not linked to an employee master

Submit an employee link request or ask Firm Admin/Partner/Branch Manager to link your user account from Employee Master.

Request Employee Link
+{% endif %} diff --git a/app/modules/employees/templates/employees/portal_partials/payslips.html b/app/modules/employees/templates/employees/portal_partials/payslips.html new file mode 100644 index 0000000..72f03f7 --- /dev/null +++ b/app/modules/employees/templates/employees/portal_partials/payslips.html @@ -0,0 +1,16 @@ +
+

My Payslips

Recently available payslips.

+ Open Payslips Page +
+
+ {% for slip in payslips %} +
+
Payslip
+

{{ slip.period_label if slip.period_label is defined else ('Payslip #' ~ slip.id) }}

+

Status: {{ slip.status.replace('_',' ').title() if slip.status else '-' }}

+ View +
+ {% else %} +
No payslips available yet.
+ {% endfor %} +
diff --git a/app/modules/employees/templates/employees/portal_partials/profile.html b/app/modules/employees/templates/employees/portal_partials/profile.html new file mode 100644 index 0000000..405c0dd --- /dev/null +++ b/app/modules/employees/templates/employees/portal_partials/profile.html @@ -0,0 +1,31 @@ +{% if not employee %} +
Your login is not linked to an employee profile yet. Request employee profile.
+{% else %} +
+

My Profile

Your employee master and public profile details.

+ Open / Update Profile +
+
+
+

Employment Details

+
+
Name
{{ employee.full_name }}
+
Employee Code
{{ employee.employee_code }}
+
Status
{{ employee.status.replace('_',' ').title() }}
+
Designation
{{ employee.designation or '-' }}
+
Department
{{ employee.department or '-' }}
+
Date of Joining
{{ employee.date_of_joining or '-' }}
+
+
+
+

Contact Details

+
+
Email
{{ employee.email or current_user.email }}
+
Mobile
{{ employee.mobile or current_user.mobile or '-' }}
+
Alternate Mobile
{{ employee.alternate_mobile or '-' }}
+
Address
{{ employee.address or '-' }}
+
Emergency Contact
{{ employee.emergency_contact_name or '-' }} {{ employee.emergency_contact_mobile or '' }}
+
+
+
+{% endif %} diff --git a/app/modules/employees/templates/employees/portal_partials/work.html b/app/modules/employees/templates/employees/portal_partials/work.html new file mode 100644 index 0000000..b98217f --- /dev/null +++ b/app/modules/employees/templates/employees/portal_partials/work.html @@ -0,0 +1,55 @@ +{% if not employee %} +
+

Your login is not linked to an employee master

+

You may still see tasks assigned directly to your user ID, but full self-service requires employee master linkage.

+ Request Employee Link +
+{% endif %} + +
+
+

My Work Board

+

Snapshot of your assigned engagements. Use the full work board to update task status and communication.

+
+ Open Full Work Board +
+ +
+
Total
{{ work_payload.summary.total if work_payload else 0 }}
+
Open
{{ work_payload.summary.open if work_payload else 0 }}
+
In Progress
{{ work_payload.summary.in_progress if work_payload else 0 }}
+
Blocked
{{ work_payload.summary.blocked if work_payload else 0 }}
+
Overdue
{{ work_payload.summary.overdue if work_payload else 0 }}
+
Completed
{{ work_payload.summary.completed if work_payload else 0 }}
+
+ +
+ {% if work_payload %} + {% for column in work_payload.columns %} +
+
+

{{ column.label }}

+ {{ column.cards|length }} +
+
+ {% for card in column.cards[:5] %} +
+
{{ card.client_code or 'Client' }}
+

{{ card.client_name }}

+
+
{{ card.service_name }}
+
FY {{ card.financial_year }} · Due {{ card.due_date or '-' }}
+
+ Open Work Details +
+ {% else %} +
No cards in this column.
+ {% endfor %} + {% if column.cards|length > 5 %}View all {{ column.cards|length }}{% endif %} +
+
+ {% endfor %} + {% else %} +
No work board data available yet.
+ {% endif %} +
diff --git a/app/modules/employees/ui.py b/app/modules/employees/ui.py index d041382..bba5288 100644 --- a/app/modules/employees/ui.py +++ b/app/modules/employees/ui.py @@ -2472,8 +2472,56 @@ def employee_status_submit( db.close() + +EMPLOYEE_PORTAL_TABS = { + "overview": "modules/employees/templates/employees/portal_partials/overview.html", + "work": "modules/employees/templates/employees/portal_partials/work.html", + "attendance": "modules/employees/templates/employees/portal_partials/attendance.html", + "profile": "modules/employees/templates/employees/portal_partials/profile.html", + "leave": "modules/employees/templates/employees/portal_partials/leave.html", + "documents": "modules/employees/templates/employees/portal_partials/documents.html", + "payslips": "modules/employees/templates/employees/portal_partials/payslips.html", + "offboarding": "modules/employees/templates/employees/portal_partials/offboarding.html", + "alerts": "modules/employees/templates/employees/portal_partials/alerts.html", +} + + +def _employee_portal_context(request: Request, db, current_user, *, active_tab: str = "overview"): + employee = get_employee_for_user(db, current_user) + pending_request = None if employee else get_pending_registration_for_user(db, current_user) + work_payload = None + if employee: + scope = build_employee_scope( + db, + current_user, + tenant_id=request.session.get("active_tenant_id") or current_user.tenant_id, + branch_id=request.session.get("active_branch_id"), + ) + work_payload = list_employee_work_kanban( + db, + scope, + q="", + status="open", + financial_year=_active_financial_year(request), + ) + return { + "title": "Employee Portal", + "active_tab": active_tab if active_tab in EMPLOYEE_PORTAL_TABS else "overview", + "employee": employee, + "pending_request": pending_request, + "today_attendance": get_today_attendance_for_user(db, current_user), + "attendance_rows": list_own_attendance(db, current_user, limit=10), + "leave_balances": list_own_leave_balances(db, current_user), + "leave_requests": list_own_leave_requests(db, current_user)[:5], + "offboarding_requests": list_own_offboarding_requests(db, current_user)[:5], + "payslips": list_own_payslips(db, current_user)[:5], + "own_documents": list_own_employee_documents(db, current_user)[:8], + "work_payload": work_payload, + } + + @portal_router.get("/dashboard") -def employee_portal_dashboard(request: Request): +def employee_portal_dashboard(request: Request, tab: str = "overview"): db = CommonSessionLocal() try: current_user = get_current_user(request, db=db) @@ -2483,32 +2531,40 @@ def employee_portal_dashboard(request: Request): require_permission(db, current_user, "employees.ess.view") except Exception: return _redirect_denied() - employee = get_employee_for_user(db, current_user) - pending_request = None if employee else get_pending_registration_for_user(db, current_user) - work_payload = None - if employee: - scope = build_employee_scope(db, current_user, tenant_id=request.session.get("active_tenant_id") or current_user.tenant_id, branch_id=request.session.get("active_branch_id")) - work_payload = list_employee_work_kanban(db, scope, q="", status="open", financial_year=_active_financial_year(request)) + active_tab = tab if tab in EMPLOYEE_PORTAL_TABS else "overview" return _render( request, "modules/employees/templates/employees/portal_dashboard.html", db, current_user, - title="Employee Portal", - employee=employee, - pending_request=pending_request, - today_attendance=get_today_attendance_for_user(db, current_user), - attendance_rows=list_own_attendance(db, current_user)[:7], - leave_balances=list_own_leave_balances(db, current_user), - leave_requests=list_own_leave_requests(db, current_user)[:5], - offboarding_requests=list_own_offboarding_requests(db, current_user)[:5], - payslips=list_own_payslips(db, current_user)[:5], - work_payload=work_payload, + **_employee_portal_context(request, db, current_user, active_tab=active_tab), ) finally: db.close() +@portal_router.get("/dashboard/tab/{tab_name}") +def employee_portal_dashboard_tab(request: Request, tab_name: str): + db = CommonSessionLocal() + try: + current_user = get_current_user(request, db=db) + if not current_user: + return _redirect_login() + try: + require_permission(db, current_user, "employees.ess.view") + except Exception: + return _redirect_denied() + active_tab = tab_name if tab_name in EMPLOYEE_PORTAL_TABS else "overview" + return _render( + request, + EMPLOYEE_PORTAL_TABS[active_tab], + db, + current_user, + **_employee_portal_context(request, db, current_user, active_tab=active_tab), + ) + finally: + db.close() + @portal_router.get("/attendance") def employee_self_attendance(request: Request): db = CommonSessionLocal()