@kernhq/module-hr 0.13.0 → 0.13.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kernhq/module-hr",
3
- "version": "0.13.0",
3
+ "version": "0.13.2",
4
4
  "description": "Kern HR module: people, offices, org chart, calendars",
5
5
  "license": "AGPL-3.0-only",
6
6
  "type": "module",
@@ -45,9 +45,12 @@ export const en: Record<string, Message> = {
45
45
  'hr.accr_col_policy': 'Policy',
46
46
  'hr.accr_col_when': 'When',
47
47
  'hr.accr_config_unreadable': "This policy's settings cannot be read",
48
+ 'hr.accr_config_unreadable_form':
49
+ "This policy's stored settings could not be read, so the fields below are defaults rather than what is saved. Saving replaces the stored settings with these.",
48
50
  'hr.accr_copy_of': '{name} (copy)',
49
51
  'hr.accr_create_title': 'New accrual policy',
50
52
  'hr.accr_created': '"{name}" created',
53
+ 'hr.accr_day_is': 'a day is {length}',
51
54
  'hr.accr_day_length': 'Length of a leave day',
52
55
  'hr.accr_day_length_hint':
53
56
  'In minutes — {length}. Balances are kept in minutes so half days and hourly leave stay exact.',
@@ -71,6 +74,8 @@ export const en: Record<string, Message> = {
71
74
  'hr.accr_error_name': 'Give the policy a name.',
72
75
  'hr.accr_error_pick_policy': 'Pick a policy.',
73
76
  'hr.accr_error_pick_subject': 'Pick who it applies to.',
77
+ 'hr.accr_error_round_cap':
78
+ 'A rounding step cannot be longer than {max}, so a whole day is not one on a day this long.',
74
79
  'hr.accr_error_tier_clash': 'Two tiers start at the same number of years.',
75
80
  'hr.accr_error_to_before_from': 'The end date is before the start date.',
76
81
  'hr.accr_freq_anniversary': 'On each work anniversary',
@@ -117,9 +122,9 @@ export const en: Record<string, Message> = {
117
122
  'A policy says how much a year earns, when it appears and how it rounds. Nobody accrues until a policy is assigned to them.',
118
123
  'hr.accr_preview_error': 'The preview could not be computed',
119
124
  'hr.accr_readonly': 'You can read this page, but changing anything needs permission to manage policies.',
120
- 'hr.accr_round_day': 'A whole day',
125
+ 'hr.accr_round_day': 'A whole day ({length})',
121
126
  'hr.accr_round_exact': 'No rounding',
122
- 'hr.accr_round_half_day': 'Half a day',
127
+ 'hr.accr_round_half_day': 'Half a day ({length})',
123
128
  'hr.accr_round_minutes': { one: '{count} minute', other: '{count} minutes' },
124
129
  'hr.accr_round_short': 'rounded to {step}',
125
130
  'hr.accr_rounding': 'Rounding',
@@ -148,6 +153,7 @@ export const en: Record<string, Message> = {
148
153
  'hr.accr_run_title': 'Accrual run',
149
154
  'hr.accr_run_to': 'Period to',
150
155
  'hr.accr_run_total': 'Total',
156
+ 'hr.accr_run_total_note': 'Time, not days: one run can cover policies with different day lengths.',
151
157
  'hr.accr_run_writes':
152
158
  'Crediting writes to the leave ledger. It cannot be undone here — a wrong credit is corrected with a leave adjustment.',
153
159
  'hr.accr_rung_empty': 'Nothing on this rung.',
@@ -652,6 +658,10 @@ export const en: Record<string, Message> = {
652
658
  'hr.employment_intern': 'Intern',
653
659
  'hr.employment_part_time': 'Part time',
654
660
  'hr.employment_temporary': 'Temporary',
661
+ 'hr.filter_clear': 'Show everyone',
662
+ 'hr.filter_department': '{name} and the departments under it',
663
+ 'hr.filter_department_unnamed': 'One department and the departments under it',
664
+ 'hr.filter_office_unnamed': 'One office',
655
665
  'hr.general_country_desc':
656
666
  'Used when the module is switched on: the first office is created in this country and its holiday calendar is seeded from the pack Kern ships for it.',
657
667
  'hr.general_country_hint': 'Offices that already exist keep their own country; change one under Offices.',
@@ -889,6 +899,10 @@ export const en: Record<string, Message> = {
889
899
  'hr.nav': 'People',
890
900
  'hr.no_people': 'Nobody here yet',
891
901
  'hr.no_people_desc': 'Add the first person to start the directory.',
902
+ 'hr.no_people_match': 'Nobody matches',
903
+ 'hr.no_people_match_filtered':
904
+ 'Nobody in this part of the company matches. Show everyone to search the whole directory.',
905
+ 'hr.no_people_match_search': 'No name matches what you typed.',
892
906
  'hr.not_clocked_in': 'Not clocked in',
893
907
  'hr.offboard': 'End employment',
894
908
  'hr.offboard_already': 'This person has already left',
@@ -1001,6 +1015,7 @@ export const en: Record<string, Message> = {
1001
1015
  'hr.offices_timezones': 'Timezones',
1002
1016
  'hr.offices_title': 'Offices',
1003
1017
  'hr.on_break_since': 'On break since {time}',
1018
+ 'hr.on_leave_note': 'Away right now',
1004
1019
  'hr.org_archive_blocked_children': {
1005
1020
  one: 'Its {count} sub-department would be left with no parent. Move or archive it first.',
1006
1021
  other: 'Its {count} sub-departments would be left with no parent. Move or archive them first.',
@@ -1072,7 +1087,7 @@ export const en: Record<string, Message> = {
1072
1087
  'hr.org_root': 'Top level',
1073
1088
  'hr.org_search_label': 'Search departments by name or code',
1074
1089
  'hr.org_search_placeholder': 'Search departments',
1075
- 'hr.org_see_people': { one: 'See the {count} person here', other: 'See the {count} people here' },
1090
+ 'hr.org_see_people': { one: 'See the {count} person', other: 'See the {count} people' },
1076
1091
  'hr.org_stat_levels': 'Levels deep',
1077
1092
  'hr.org_stat_people': 'People placed',
1078
1093
  'hr.org_stat_positions': 'Positions',
@@ -1242,13 +1257,8 @@ export const en: Record<string, Message> = {
1242
1257
  'hr.schedule_saved': 'Schedule saved',
1243
1258
  'hr.schedule_start': 'Start',
1244
1259
  'hr.schedule_time_missing': 'Every working day needs a start and an end.',
1245
- 'hr.schedule_tz_fixed': 'One fixed zone',
1246
- 'hr.schedule_tz_hint': '09:00 is a reading on a clock. This says whose clock.',
1247
- 'hr.schedule_tz_label': 'Zone',
1248
- 'hr.schedule_tz_mode': 'Time zone',
1249
- 'hr.schedule_tz_office': "The person's office",
1250
- 'hr.schedule_tz_person': "The person's own time zone",
1251
- 'hr.schedule_tz_required': 'Choose the zone this schedule is read in.',
1260
+ 'hr.schedule_tz_note':
1261
+ "09:00 means nine o'clock where the person is their primary office's time zone, unless their own profile sets one.",
1252
1262
  'hr.schedule_week': 'Working week',
1253
1263
  'hr.schedule_week_empty': 'Every day is off — there would be nothing to measure.',
1254
1264
  'hr.schedule_week_total': '{hours} a week',
@@ -1270,7 +1280,8 @@ export const en: Record<string, Message> = {
1270
1280
  'hr.sensitive_contact_hint': 'Who to call if something happens at work.',
1271
1281
  'hr.sensitive_contact_name': 'Emergency contact',
1272
1282
  'hr.sensitive_contact_partial': 'A name and a number, or neither.',
1273
- 'hr.sensitive_edit_body': 'Stored encrypted. The record says these changed, never what they say.',
1283
+ 'hr.sensitive_edit_body':
1284
+ 'The identity number and bank details are encrypted. Everything here is restricted, and the record says these changed, never what they say.',
1274
1285
  'hr.sensitive_edit_title': 'Personal details for {name}',
1275
1286
  'hr.sensitive_emergency': 'In an emergency, call',
1276
1287
  'hr.sensitive_emergency_none': 'No emergency contact recorded',
@@ -1356,9 +1367,12 @@ export const ar: Record<string, Message> = {
1356
1367
  'hr.accr_col_policy': 'السياسة',
1357
1368
  'hr.accr_col_when': 'متى',
1358
1369
  'hr.accr_config_unreadable': 'تعذّرت قراءة إعدادات هذه السياسة',
1370
+ 'hr.accr_config_unreadable_form':
1371
+ 'تعذّرت قراءة الإعدادات المحفوظة لهذه السياسة، لذا فالحقول أدناه قيم افتراضية لا ما هو محفوظ. الحفظ سيستبدل المحفوظ بها.',
1359
1372
  'hr.accr_copy_of': '{name} (نسخة)',
1360
1373
  'hr.accr_create_title': 'سياسة استحقاق جديدة',
1361
1374
  'hr.accr_created': 'أُنشئت «{name}»',
1375
+ 'hr.accr_day_is': 'اليوم {length}',
1362
1376
  'hr.accr_day_length': 'طول يوم الإجازة',
1363
1377
  'hr.accr_day_length_hint':
1364
1378
  'بالدقائق — {length}. تُحفظ الأرصدة بالدقائق كي تبقى أنصاف الأيام والإجازة الساعية دقيقة.',
@@ -1389,6 +1403,8 @@ export const ar: Record<string, Message> = {
1389
1403
  'hr.accr_error_name': 'امنح السياسة اسمًا.',
1390
1404
  'hr.accr_error_pick_policy': 'اختر سياسة.',
1391
1405
  'hr.accr_error_pick_subject': 'اختر من تنطبق عليه.',
1406
+ 'hr.accr_error_round_cap':
1407
+ 'لا يمكن أن تتجاوز خطوة التقريب {max}، لذا لا يصلح اليوم الكامل خطوةً في يوم بهذا الطول.',
1392
1408
  'hr.accr_error_tier_clash': 'درجتان تبدآن عند العدد نفسه من السنوات.',
1393
1409
  'hr.accr_error_to_before_from': 'تاريخ الانتهاء قبل تاريخ البدء.',
1394
1410
  'hr.accr_freq_anniversary': 'في كل ذكرى سنوية للتعيين',
@@ -1435,9 +1451,9 @@ export const ar: Record<string, Message> = {
1435
1451
  'تحدّد السياسة كم يمنح العام من أيام، ومتى تظهر، وكيف تُقرَّب. ولا يستحق أحد شيئًا قبل أن تُسنَد إليه سياسة.',
1436
1452
  'hr.accr_preview_error': 'تعذّر حساب المعاينة',
1437
1453
  'hr.accr_readonly': 'يمكنك قراءة هذه الصفحة، لكن تغيير أي شيء يحتاج صلاحية إدارة السياسات.',
1438
- 'hr.accr_round_day': 'يوم كامل',
1454
+ 'hr.accr_round_day': 'يوم كامل ({length})',
1439
1455
  'hr.accr_round_exact': 'بلا تقريب',
1440
- 'hr.accr_round_half_day': 'نصف يوم',
1456
+ 'hr.accr_round_half_day': 'نصف يوم ({length})',
1441
1457
  'hr.accr_round_minutes': {
1442
1458
  zero: '{count} دقيقة',
1443
1459
  one: 'دقيقة واحدة',
@@ -1498,6 +1514,7 @@ export const ar: Record<string, Message> = {
1498
1514
  'hr.accr_run_title': 'تشغيل الاستحقاق',
1499
1515
  'hr.accr_run_to': 'الفترة إلى',
1500
1516
  'hr.accr_run_total': 'الإجمالي',
1517
+ 'hr.accr_run_total_note': 'وقت لا أيام: قد يشمل التشغيل الواحد سياسات بأطوال أيام مختلفة.',
1501
1518
  'hr.accr_run_writes':
1502
1519
  'الإضافة تُكتب في دفتر الإجازات. ولا يمكن التراجع عنها من هنا — تُصحَّح الإضافة الخاطئة بتسوية إجازة.',
1503
1520
  'hr.accr_rung_empty': 'لا شيء على هذه الدرجة.',
@@ -2039,6 +2056,10 @@ export const ar: Record<string, Message> = {
2039
2056
  'hr.employment_intern': 'متدرب',
2040
2057
  'hr.employment_part_time': 'دوام جزئي',
2041
2058
  'hr.employment_temporary': 'مؤقت',
2059
+ 'hr.filter_clear': 'عرض الجميع',
2060
+ 'hr.filter_department': '{name} والأقسام التابعة لها',
2061
+ 'hr.filter_department_unnamed': 'قسم واحد والأقسام التابعة له',
2062
+ 'hr.filter_office_unnamed': 'مكتب واحد',
2042
2063
  'hr.general_country_desc':
2043
2064
  'يُستخدم عند تشغيل الوحدة: يُنشأ المكتب الأول في هذه الدولة ويُملأ تقويم عطلاته من حزمة الدولة نفسها.',
2044
2065
  'hr.general_country_hint': 'المكاتب الموجودة تحتفظ بدولتها؛ لتغيير أحدها انتقل إلى المكاتب.',
@@ -2327,6 +2348,9 @@ export const ar: Record<string, Message> = {
2327
2348
  'hr.nav': 'الأشخاص',
2328
2349
  'hr.no_people': 'لا يوجد أحد هنا بعد',
2329
2350
  'hr.no_people_desc': 'أضف أول شخص لتبدأ القائمة.',
2351
+ 'hr.no_people_match': 'لا أحد مطابق',
2352
+ 'hr.no_people_match_filtered': 'لا أحد في هذا الجزء من الشركة مطابق. اعرض الجميع للبحث في الدليل كله.',
2353
+ 'hr.no_people_match_search': 'لا يوجد اسم مطابق لما كتبته.',
2330
2354
  'hr.not_clocked_in': 'لم يُسجَّل الحضور',
2331
2355
  'hr.offboard': 'إنهاء التوظيف',
2332
2356
  'hr.offboard_already': 'غادر هذا الشخص بالفعل',
@@ -2440,6 +2464,7 @@ export const ar: Record<string, Message> = {
2440
2464
  'hr.offices_timezones': 'المناطق الزمنية',
2441
2465
  'hr.offices_title': 'المكاتب',
2442
2466
  'hr.on_break_since': 'في استراحة منذ {time}',
2467
+ 'hr.on_leave_note': 'غائبون الآن',
2443
2468
  'hr.org_archive_blocked_children': {
2444
2469
  zero: 'سيبقى {count} أقسام فرعية بلا قسم أعلى. انقلها أو أرشفها أولًا.',
2445
2470
  one: 'سيبقى قسم فرعي واحد بلا قسم أعلى. انقله أو أرشفه أولًا.',
@@ -2533,12 +2558,12 @@ export const ar: Record<string, Message> = {
2533
2558
  'hr.org_search_label': 'ابحث في الأقسام بالاسم أو الرمز',
2534
2559
  'hr.org_search_placeholder': 'ابحث في الأقسام',
2535
2560
  'hr.org_see_people': {
2536
- zero: 'عرض {count} أشخاص هنا',
2537
- one: 'عرض شخص واحد هنا',
2538
- two: 'عرض شخصين هنا',
2539
- few: 'عرض {count} أشخاص هنا',
2540
- many: 'عرض {count} شخصًا هنا',
2541
- other: 'عرض {count} شخص هنا',
2561
+ zero: 'عرض {count} أشخاص',
2562
+ one: 'عرض شخص واحد',
2563
+ two: 'عرض شخصين',
2564
+ few: 'عرض {count} أشخاص',
2565
+ many: 'عرض {count} شخصًا',
2566
+ other: 'عرض {count} شخص',
2542
2567
  },
2543
2568
  'hr.org_stat_levels': 'المستويات',
2544
2569
  'hr.org_stat_people': 'الأشخاص المُسنَدون',
@@ -2713,13 +2738,8 @@ export const ar: Record<string, Message> = {
2713
2738
  'hr.schedule_saved': 'تم حفظ المناوبة',
2714
2739
  'hr.schedule_start': 'البداية',
2715
2740
  'hr.schedule_time_missing': 'كل يوم عمل يحتاج إلى بداية ونهاية.',
2716
- 'hr.schedule_tz_fixed': 'منطقة واحدة ثابتة',
2717
- 'hr.schedule_tz_hint': '٠٩:٠٠ قراءة على ساعة. وهذا يحدد ساعة مَن.',
2718
- 'hr.schedule_tz_label': 'المنطقة',
2719
- 'hr.schedule_tz_mode': 'المنطقة الزمنية',
2720
- 'hr.schedule_tz_office': 'مكتب الشخص',
2721
- 'hr.schedule_tz_person': 'المنطقة الزمنية الخاصة بالشخص',
2722
- 'hr.schedule_tz_required': 'اختر المنطقة التي تُقرأ بها هذه المناوبة.',
2741
+ 'hr.schedule_tz_note':
2742
+ '٠٩:٠٠ تعني التاسعة في المكان الذي يوجد فيه الشخص: المنطقة الزمنية لمكتبه الأساسي، ما لم يحدّد ملفه منطقة خاصة به.',
2723
2743
  'hr.schedule_week': 'أسبوع العمل',
2724
2744
  'hr.schedule_week_empty': 'كل الأيام متوقفة — لن يبقى شيء يُقاس.',
2725
2745
  'hr.schedule_week_total': '{hours} في الأسبوع',
@@ -2739,7 +2759,8 @@ export const ar: Record<string, Message> = {
2739
2759
  'hr.sensitive_contact_hint': 'من يُتَّصل به إذا وقع شيء في العمل.',
2740
2760
  'hr.sensitive_contact_name': 'جهة اتصال للطوارئ',
2741
2761
  'hr.sensitive_contact_partial': 'اسم ورقم معًا، أو لا شيء منهما.',
2742
- 'hr.sensitive_edit_body': 'تُحفَظ مشفَّرة، ويُسجَّل أنها تغيّرت لا ما تحتويه.',
2762
+ 'hr.sensitive_edit_body':
2763
+ 'رقم الهوية والبيانات المصرفية تُحفَظ مشفَّرة. وكل ما هنا مقيَّد الوصول، ويُسجَّل أنها تغيّرت لا ما تحتويه.',
2743
2764
  'hr.sensitive_edit_title': 'البيانات الشخصية لـ {name}',
2744
2765
  'hr.sensitive_emergency': 'في حالة الطوارئ، اتصل بـ',
2745
2766
  'hr.sensitive_emergency_none': 'لا توجد جهة اتصال للطوارئ',
@@ -2821,9 +2842,12 @@ export const de: Record<string, Message> = {
2821
2842
  'hr.accr_col_policy': 'Regel',
2822
2843
  'hr.accr_col_when': 'Wann',
2823
2844
  'hr.accr_config_unreadable': 'Die Einstellungen dieser Regel sind nicht lesbar',
2845
+ 'hr.accr_config_unreadable_form':
2846
+ 'Die gespeicherten Einstellungen dieser Regel konnten nicht gelesen werden. Die Felder unten zeigen Standardwerte, nicht das Gespeicherte — Speichern ersetzt es dadurch.',
2824
2847
  'hr.accr_copy_of': '{name} (Kopie)',
2825
2848
  'hr.accr_create_title': 'Neue Anspruchsregel',
2826
2849
  'hr.accr_created': '„{name}“ wurde angelegt',
2850
+ 'hr.accr_day_is': 'ein Tag sind {length}',
2827
2851
  'hr.accr_day_length': 'Länge eines Urlaubstags',
2828
2852
  'hr.accr_day_length_hint':
2829
2853
  'In Minuten — {length}. Guthaben werden in Minuten geführt, damit halbe Tage und stundenweiser Urlaub exakt bleiben.',
@@ -2847,6 +2871,8 @@ export const de: Record<string, Message> = {
2847
2871
  'hr.accr_error_name': 'Geben Sie der Regel einen Namen.',
2848
2872
  'hr.accr_error_pick_policy': 'Wählen Sie eine Regel.',
2849
2873
  'hr.accr_error_pick_subject': 'Wählen Sie, für wen sie gilt.',
2874
+ 'hr.accr_error_round_cap':
2875
+ 'Ein Rundungsschritt darf nicht länger als {max} sein — bei einem so langen Tag ist ein ganzer Tag daher keiner.',
2850
2876
  'hr.accr_error_tier_clash': 'Zwei Stufen beginnen bei derselben Anzahl Jahre.',
2851
2877
  'hr.accr_error_to_before_from': 'Das Enddatum liegt vor dem Startdatum.',
2852
2878
  'hr.accr_freq_anniversary': 'An jedem Eintrittsjahrestag',
@@ -2896,9 +2922,9 @@ export const de: Record<string, Message> = {
2896
2922
  'hr.accr_preview_error': 'Die Vorschau konnte nicht berechnet werden',
2897
2923
  'hr.accr_readonly':
2898
2924
  'Sie können diese Seite lesen; zum Ändern brauchen Sie die Berechtigung, Regeln zu verwalten.',
2899
- 'hr.accr_round_day': 'Ein ganzer Tag',
2925
+ 'hr.accr_round_day': 'Ein ganzer Tag ({length})',
2900
2926
  'hr.accr_round_exact': 'Keine Rundung',
2901
- 'hr.accr_round_half_day': 'Ein halber Tag',
2927
+ 'hr.accr_round_half_day': 'Ein halber Tag ({length})',
2902
2928
  'hr.accr_round_minutes': { one: '{count} Minute', other: '{count} Minuten' },
2903
2929
  'hr.accr_round_short': 'auf {step} gerundet',
2904
2930
  'hr.accr_rounding': 'Rundung',
@@ -2927,6 +2953,7 @@ export const de: Record<string, Message> = {
2927
2953
  'hr.accr_run_title': 'Anspruchslauf',
2928
2954
  'hr.accr_run_to': 'Zeitraum bis',
2929
2955
  'hr.accr_run_total': 'Gesamt',
2956
+ 'hr.accr_run_total_note': 'Zeit, keine Tage: ein Lauf kann Regeln mit verschiedenen Taglängen umfassen.',
2930
2957
  'hr.accr_run_writes':
2931
2958
  'Die Gutschrift wird in das Urlaubskonto geschrieben. Hier ist sie nicht rückgängig zu machen — eine falsche Gutschrift wird über eine Urlaubskorrektur berichtigt.',
2932
2959
  'hr.accr_rung_empty': 'Auf dieser Stufe liegt nichts.',
@@ -3455,6 +3482,10 @@ export const de: Record<string, Message> = {
3455
3482
  'hr.employment_intern': 'Praktikum',
3456
3483
  'hr.employment_part_time': 'Teilzeit',
3457
3484
  'hr.employment_temporary': 'Befristet',
3485
+ 'hr.filter_clear': 'Alle anzeigen',
3486
+ 'hr.filter_department': '{name} und die Abteilungen darunter',
3487
+ 'hr.filter_department_unnamed': 'Eine Abteilung und die Abteilungen darunter',
3488
+ 'hr.filter_office_unnamed': 'Ein Standort',
3458
3489
  'hr.general_country_desc':
3459
3490
  'Wird beim Einschalten des Moduls verwendet: Der erste Standort entsteht in diesem Land, und sein Feiertagskalender wird aus dem Paket für dieses Land befüllt.',
3460
3491
  'hr.general_country_hint':
@@ -3701,6 +3732,10 @@ export const de: Record<string, Message> = {
3701
3732
  'hr.nav': 'Personen',
3702
3733
  'hr.no_people': 'Noch keine Personen',
3703
3734
  'hr.no_people_desc': 'Fügen Sie die erste Person hinzu, um das Verzeichnis aufzubauen.',
3735
+ 'hr.no_people_match': 'Niemand passt',
3736
+ 'hr.no_people_match_filtered':
3737
+ 'In diesem Teil des Unternehmens passt niemand. Zeigen Sie alle an, um das ganze Verzeichnis zu durchsuchen.',
3738
+ 'hr.no_people_match_search': 'Kein Name passt zu Ihrer Eingabe.',
3704
3739
  'hr.not_clocked_in': 'Nicht eingestempelt',
3705
3740
  'hr.offboard': 'Beschäftigung beenden',
3706
3741
  'hr.offboard_already': 'Diese Person ist bereits ausgeschieden',
@@ -3816,6 +3851,7 @@ export const de: Record<string, Message> = {
3816
3851
  'hr.offices_timezones': 'Zeitzonen',
3817
3852
  'hr.offices_title': 'Standorte',
3818
3853
  'hr.on_break_since': 'Seit {time} in der Pause',
3854
+ 'hr.on_leave_note': 'Gerade abwesend',
3819
3855
  'hr.org_archive_blocked_children': {
3820
3856
  one: 'Ihre {count} Unterabteilung bliebe ohne übergeordnete Abteilung. Verschieben oder archivieren Sie sie zuerst.',
3821
3857
  other:
@@ -3895,7 +3931,7 @@ export const de: Record<string, Message> = {
3895
3931
  'hr.org_root': 'Oberste Ebene',
3896
3932
  'hr.org_search_label': 'Abteilungen nach Name oder Kürzel durchsuchen',
3897
3933
  'hr.org_search_placeholder': 'Abteilungen durchsuchen',
3898
- 'hr.org_see_people': { one: 'Die {count} Person hier ansehen', other: 'Die {count} Personen hier ansehen' },
3934
+ 'hr.org_see_people': { one: 'Die {count} Person ansehen', other: 'Die {count} Personen ansehen' },
3899
3935
  'hr.org_stat_levels': 'Ebenen',
3900
3936
  'hr.org_stat_people': 'Zugeordnete Personen',
3901
3937
  'hr.org_stat_positions': 'Stellen',
@@ -4068,13 +4104,8 @@ export const de: Record<string, Message> = {
4068
4104
  'hr.schedule_saved': 'Arbeitszeitmodell gespeichert',
4069
4105
  'hr.schedule_start': 'Beginn',
4070
4106
  'hr.schedule_time_missing': 'Jeder Arbeitstag braucht einen Beginn und ein Ende.',
4071
- 'hr.schedule_tz_fixed': 'Eine feste Zone',
4072
- 'hr.schedule_tz_hint': '09:00 ist eine Uhrzeit. Hier steht, wessen Uhr gemeint ist.',
4073
- 'hr.schedule_tz_label': 'Zone',
4074
- 'hr.schedule_tz_mode': 'Zeitzone',
4075
- 'hr.schedule_tz_office': 'Der Standort der Person',
4076
- 'hr.schedule_tz_person': 'Die eigene Zeitzone der Person',
4077
- 'hr.schedule_tz_required': 'Wählen Sie die Zone, in der dieses Modell gelesen wird.',
4107
+ 'hr.schedule_tz_note':
4108
+ '09:00 heißt neun Uhr dort, wo die Person ist die Zeitzone ihres primären Standorts, sofern ihr Profil keine eigene angibt.',
4078
4109
  'hr.schedule_week': 'Arbeitswoche',
4079
4110
  'hr.schedule_week_empty': 'Alle Tage sind frei — es gäbe nichts zu messen.',
4080
4111
  'hr.schedule_week_total': '{hours} pro Woche',
@@ -4097,7 +4128,7 @@ export const de: Record<string, Message> = {
4097
4128
  'hr.sensitive_contact_name': 'Notfallkontakt',
4098
4129
  'hr.sensitive_contact_partial': 'Name und Nummer zusammen — oder beides leer.',
4099
4130
  'hr.sensitive_edit_body':
4100
- 'Verschlüsselt gespeichert. Protokolliert wird, dass sich diese Angaben geändert haben — nie ihr Inhalt.',
4131
+ 'Ausweisnummer und Bankverbindung werden verschlüsselt gespeichert. Alles hier ist zugriffsbeschränkt, und protokolliert wird, dass sich diese Angaben geändert haben — nie ihr Inhalt.',
4101
4132
  'hr.sensitive_edit_title': 'Persönliche Angaben von {name}',
4102
4133
  'hr.sensitive_emergency': 'Im Notfall anrufen',
4103
4134
  'hr.sensitive_emergency_none': 'Kein Notfallkontakt hinterlegt',
@@ -4180,9 +4211,12 @@ export const fa: Record<string, Message> = {
4180
4211
  'hr.accr_col_policy': 'قاعده',
4181
4212
  'hr.accr_col_when': 'کِی',
4182
4213
  'hr.accr_config_unreadable': 'تنظیمات این قاعده خوانده نمی‌شود',
4214
+ 'hr.accr_config_unreadable_form':
4215
+ 'تنظیمات ذخیره‌شدهٔ این سیاست خوانده نشد؛ بنابراین فیلدهای زیر مقدارهای پیش‌فرض‌اند، نه آنچه ذخیره شده. ذخیره‌کردن، تنظیمات ذخیره‌شده را با همین‌ها جایگزین می‌کند.',
4183
4216
  'hr.accr_copy_of': '{name} (رونوشت)',
4184
4217
  'hr.accr_create_title': 'قاعدهٔ انباشت تازه',
4185
4218
  'hr.accr_created': '«{name}» ساخته شد',
4219
+ 'hr.accr_day_is': 'هر روز {length}',
4186
4220
  'hr.accr_day_length': 'طول یک روز مرخصی',
4187
4221
  'hr.accr_day_length_hint':
4188
4222
  'به دقیقه — {length}. مانده‌ها به دقیقه نگه داشته می‌شوند تا نیم‌روز و مرخصی ساعتی دقیق بماند.',
@@ -4206,6 +4240,8 @@ export const fa: Record<string, Message> = {
4206
4240
  'hr.accr_error_name': 'به قاعده نامی بدهید.',
4207
4241
  'hr.accr_error_pick_policy': 'یک قاعده انتخاب کنید.',
4208
4242
  'hr.accr_error_pick_subject': 'انتخاب کنید برای چه کسی است.',
4243
+ 'hr.accr_error_round_cap':
4244
+ 'گام گِردکردن نمی‌تواند بیش از {max} باشد؛ پس در روزی به این بلندی، یک روز کامل گام معتبری نیست.',
4209
4245
  'hr.accr_error_tier_clash': 'دو پله از یک تعداد سال یکسان شروع می‌شوند.',
4210
4246
  'hr.accr_error_to_before_from': 'تاریخ پایان پیش از تاریخ شروع است.',
4211
4247
  'hr.accr_freq_anniversary': 'در هر سالگرد استخدام',
@@ -4252,9 +4288,9 @@ export const fa: Record<string, Message> = {
4252
4288
  'هر قاعده می‌گوید یک سال چقدر مرخصی می‌آورد، کِی اضافه می‌شود و چطور گرد می‌شود. تا قاعده‌ای به کسی داده نشود، چیزی برایش انباشته نمی‌شود.',
4253
4289
  'hr.accr_preview_error': 'پیش‌نمایش محاسبه نشد',
4254
4290
  'hr.accr_readonly': 'این صفحه را می‌بینید، اما تغییر هر چیزی به دسترسی مدیریت قاعده‌ها نیاز دارد.',
4255
- 'hr.accr_round_day': 'یک روز کامل',
4291
+ 'hr.accr_round_day': 'یک روز کامل ({length})',
4256
4292
  'hr.accr_round_exact': 'بدون گردکردن',
4257
- 'hr.accr_round_half_day': 'نیم‌روز',
4293
+ 'hr.accr_round_half_day': 'نیم‌روز ({length})',
4258
4294
  'hr.accr_round_minutes': { one: '{count} دقیقه', other: '{count} دقیقه' },
4259
4295
  'hr.accr_round_short': 'گرد شده به {step}',
4260
4296
  'hr.accr_rounding': 'گردکردن',
@@ -4283,6 +4319,7 @@ export const fa: Record<string, Message> = {
4283
4319
  'hr.accr_run_title': 'اجرای انباشت',
4284
4320
  'hr.accr_run_to': 'دوره تا',
4285
4321
  'hr.accr_run_total': 'جمع',
4322
+ 'hr.accr_run_total_note': 'زمان است، نه روز: یک اجرا می‌تواند سیاست‌هایی با طول روز متفاوت را دربر بگیرد.',
4286
4323
  'hr.accr_run_writes':
4287
4324
  'اضافه‌کردن در دفتر مرخصی نوشته می‌شود. از اینجا برگشت‌پذیر نیست — اضافهٔ اشتباه با یک اصلاح مرخصی جبران می‌شود.',
4288
4325
  'hr.accr_rung_empty': 'روی این پله چیزی نیست.',
@@ -4780,6 +4817,10 @@ export const fa: Record<string, Message> = {
4780
4817
  'hr.employment_intern': 'کارآموز',
4781
4818
  'hr.employment_part_time': 'پاره‌وقت',
4782
4819
  'hr.employment_temporary': 'موقت',
4820
+ 'hr.filter_clear': 'نمایش همه',
4821
+ 'hr.filter_department': '{name} و واحدهای زیرمجموعه',
4822
+ 'hr.filter_department_unnamed': 'یک واحد و واحدهای زیرمجموعه',
4823
+ 'hr.filter_office_unnamed': 'یک دفتر',
4783
4824
  'hr.general_country_desc':
4784
4825
  'وقتی ماژول روشن می‌شود از این مقدار استفاده می‌شود: نخستین دفتر در همین کشور ساخته و تقویم تعطیلاتش از بستهٔ همان کشور پر می‌شود.',
4785
4826
  'hr.general_country_hint':
@@ -5017,6 +5058,10 @@ export const fa: Record<string, Message> = {
5017
5058
  'hr.nav': 'افراد',
5018
5059
  'hr.no_people': 'هنوز کسی نیست',
5019
5060
  'hr.no_people_desc': 'اولین نفر را اضافه کنید تا فهرست شروع شود.',
5061
+ 'hr.no_people_match': 'کسی پیدا نشد',
5062
+ 'hr.no_people_match_filtered':
5063
+ 'در این بخش از شرکت کسی پیدا نشد. برای جست‌وجو در کل فهرست، همه را نمایش دهید.',
5064
+ 'hr.no_people_match_search': 'هیچ نامی با آنچه نوشتید مطابقت ندارد.',
5020
5065
  'hr.not_clocked_in': 'ورود نزده',
5021
5066
  'hr.offboard': 'پایان همکاری',
5022
5067
  'hr.offboard_already': 'این فرد قبلاً رفته است',
@@ -5128,6 +5173,7 @@ export const fa: Record<string, Message> = {
5128
5173
  'hr.offices_timezones': 'منطقه‌های زمانی',
5129
5174
  'hr.offices_title': 'دفاتر',
5130
5175
  'hr.on_break_since': 'از {time} در استراحت',
5176
+ 'hr.on_leave_note': 'همین حالا در مرخصی',
5131
5177
  'hr.org_archive_blocked_children': {
5132
5178
  one: '{count} زیرواحد بی‌سرپرست می‌ماند. اول آن‌ها را جابه‌جا یا بایگانی کنید.',
5133
5179
  other: '{count} زیرواحد بی‌سرپرست می‌ماند. اول آن‌ها را جابه‌جا یا بایگانی کنید.',
@@ -5201,7 +5247,7 @@ export const fa: Record<string, Message> = {
5201
5247
  'hr.org_root': 'بالاترین سطح',
5202
5248
  'hr.org_search_label': 'جست‌وجوی واحدها بر پایهٔ نام یا کد',
5203
5249
  'hr.org_search_placeholder': 'جست‌وجوی واحدها',
5204
- 'hr.org_see_people': { one: 'دیدن {count} نفر اینجا', other: 'دیدن {count} نفر اینجا' },
5250
+ 'hr.org_see_people': { one: 'دیدن {count} نفر', other: 'دیدن {count} نفر' },
5205
5251
  'hr.org_stat_levels': 'عمق چارت',
5206
5252
  'hr.org_stat_people': 'افراد جای‌گرفته',
5207
5253
  'hr.org_stat_positions': 'سمت‌ها',
@@ -5371,13 +5417,8 @@ export const fa: Record<string, Message> = {
5371
5417
  'hr.schedule_saved': 'شیفت ذخیره شد',
5372
5418
  'hr.schedule_start': 'شروع',
5373
5419
  'hr.schedule_time_missing': 'هر روز کاری باید شروع و پایان داشته باشد.',
5374
- 'hr.schedule_tz_fixed': 'یک منطقهٔ ثابت',
5375
- 'hr.schedule_tz_hint': '۰۹:۰۰ خواندن یک ساعت است. این می‌گوید ساعتِ چه کسی.',
5376
- 'hr.schedule_tz_label': 'منطقه',
5377
- 'hr.schedule_tz_mode': 'منطقهٔ زمانی',
5378
- 'hr.schedule_tz_office': 'دفتر فرد',
5379
- 'hr.schedule_tz_person': 'منطقهٔ زمانی خود فرد',
5380
- 'hr.schedule_tz_required': 'منطقه‌ای را که این شیفت با آن خوانده می‌شود انتخاب کنید.',
5420
+ 'hr.schedule_tz_note':
5421
+ '۰۹:۰۰ یعنی ساعت نُه در جایی که فرد هست: منطقهٔ زمانی دفتر اصلی او، مگر آنکه در نمایه‌اش منطقهٔ خودش تعیین شده باشد.',
5381
5422
  'hr.schedule_week': 'هفتهٔ کاری',
5382
5423
  'hr.schedule_week_empty': 'همهٔ روزها خاموش‌اند — چیزی برای سنجیدن نمی‌ماند.',
5383
5424
  'hr.schedule_week_total': 'هفته‌ای {hours}',
@@ -5397,7 +5438,8 @@ export const fa: Record<string, Message> = {
5397
5438
  'hr.sensitive_contact_hint': 'اگر سرِ کار اتفاقی افتاد، با چه کسی تماس بگیریم.',
5398
5439
  'hr.sensitive_contact_name': 'تماس اضطراری',
5399
5440
  'hr.sensitive_contact_partial': 'هم نام و هم شماره، یا هیچ‌کدام.',
5400
- 'hr.sensitive_edit_body': 'رمزگذاری‌شده نگهداری می‌شود. سابقه می‌گوید این‌ها عوض شده‌اند، نه اینکه چه بوده‌اند.',
5441
+ 'hr.sensitive_edit_body':
5442
+ 'شمارهٔ ملی و اطلاعات بانکی رمزگذاری‌شده نگهداری می‌شوند. دسترسی به همهٔ این‌ها محدود است و سابقه می‌گوید این‌ها عوض شده‌اند، نه اینکه چه بوده‌اند.',
5401
5443
  'hr.sensitive_edit_title': 'اطلاعات شخصی {name}',
5402
5444
  'hr.sensitive_emergency': 'در وضعیت اضطراری، تماس با',
5403
5445
  'hr.sensitive_emergency_none': 'تماس اضطراری ثبت نشده است',
@@ -5478,9 +5520,12 @@ export const tr: Record<string, Message> = {
5478
5520
  'hr.accr_col_policy': 'Kural',
5479
5521
  'hr.accr_col_when': 'Ne zaman',
5480
5522
  'hr.accr_config_unreadable': 'Bu kuralın ayarları okunamıyor',
5523
+ 'hr.accr_config_unreadable_form':
5524
+ 'Bu politikanın kayıtlı ayarları okunamadı; aşağıdaki alanlar kayıtlı değerler değil, varsayılanlardır. Kaydetmek, kayıtlı ayarların yerine bunları yazar.',
5481
5525
  'hr.accr_copy_of': '{name} (kopya)',
5482
5526
  'hr.accr_create_title': 'Yeni hakediş kuralı',
5483
5527
  'hr.accr_created': '"{name}" oluşturuldu',
5528
+ 'hr.accr_day_is': 'bir gün {length}',
5484
5529
  'hr.accr_day_length': 'Bir izin gününün uzunluğu',
5485
5530
  'hr.accr_day_length_hint':
5486
5531
  'Dakika olarak — {length}. Bakiyeler dakika tutulur; böylece yarım günler ve saatlik izin tam kalır.',
@@ -5504,6 +5549,8 @@ export const tr: Record<string, Message> = {
5504
5549
  'hr.accr_error_name': 'Kurala bir ad verin.',
5505
5550
  'hr.accr_error_pick_policy': 'Bir kural seçin.',
5506
5551
  'hr.accr_error_pick_subject': 'Kime uygulanacağını seçin.',
5552
+ 'hr.accr_error_round_cap':
5553
+ 'Yuvarlama adımı {max} değerinden uzun olamaz; bu kadar uzun bir günde tam gün adım olamaz.',
5507
5554
  'hr.accr_error_tier_clash': 'İki kademe aynı yıl sayısında başlıyor.',
5508
5555
  'hr.accr_error_to_before_from': 'Bitiş tarihi başlangıç tarihinden önce.',
5509
5556
  'hr.accr_freq_anniversary': 'Her işe giriş yıl dönümünde',
@@ -5551,9 +5598,9 @@ export const tr: Record<string, Message> = {
5551
5598
  'hr.accr_preview_error': 'Önizleme hesaplanamadı',
5552
5599
  'hr.accr_readonly':
5553
5600
  'Bu sayfayı okuyabilirsiniz, ama bir şeyi değiştirmek için kuralları yönetme yetkisi gerekir.',
5554
- 'hr.accr_round_day': 'Tam gün',
5601
+ 'hr.accr_round_day': 'Tam bir gün ({length})',
5555
5602
  'hr.accr_round_exact': 'Yuvarlama yok',
5556
- 'hr.accr_round_half_day': 'Yarım gün',
5603
+ 'hr.accr_round_half_day': 'Yarım gün ({length})',
5557
5604
  'hr.accr_round_minutes': { one: '{count} dakika', other: '{count} dakika' },
5558
5605
  'hr.accr_round_short': '{step} adımına yuvarlanır',
5559
5606
  'hr.accr_rounding': 'Yuvarlama',
@@ -5582,6 +5629,8 @@ export const tr: Record<string, Message> = {
5582
5629
  'hr.accr_run_title': 'Hakediş çalıştırma',
5583
5630
  'hr.accr_run_to': 'Dönem sonu',
5584
5631
  'hr.accr_run_total': 'Toplam',
5632
+ 'hr.accr_run_total_note':
5633
+ 'Gün değil, süre: tek bir çalıştırma farklı gün uzunluklarına sahip politikaları kapsayabilir.',
5585
5634
  'hr.accr_run_writes':
5586
5635
  'Ekleme, izin defterine yazar. Buradan geri alınamaz; yanlış bir ekleme izin düzeltmesiyle giderilir.',
5587
5636
  'hr.accr_rung_empty': 'Bu basamakta bir şey yok.',
@@ -6081,6 +6130,10 @@ export const tr: Record<string, Message> = {
6081
6130
  'hr.employment_intern': 'Stajyer',
6082
6131
  'hr.employment_part_time': 'Yarı zamanlı',
6083
6132
  'hr.employment_temporary': 'Geçici',
6133
+ 'hr.filter_clear': 'Herkesi göster',
6134
+ 'hr.filter_department': '{name} ve altındaki departmanlar',
6135
+ 'hr.filter_department_unnamed': 'Bir departman ve altındaki departmanlar',
6136
+ 'hr.filter_office_unnamed': 'Bir ofis',
6084
6137
  'hr.general_country_desc':
6085
6138
  'Modül açıldığında kullanılır: ilk ofis bu ülkede oluşturulur ve tatil takvimi bu ülke için hazırlanan paketten doldurulur.',
6086
6139
  'hr.general_country_hint':
@@ -6323,6 +6376,10 @@ export const tr: Record<string, Message> = {
6323
6376
  'hr.nav': 'Kişiler',
6324
6377
  'hr.no_people': 'Henüz kimse yok',
6325
6378
  'hr.no_people_desc': 'Rehberi başlatmak için ilk kişiyi ekleyin.',
6379
+ 'hr.no_people_match': 'Eşleşen kimse yok',
6380
+ 'hr.no_people_match_filtered':
6381
+ 'Şirketin bu bölümünde eşleşen kimse yok. Tüm rehberde aramak için herkesi gösterin.',
6382
+ 'hr.no_people_match_search': 'Yazdığınızla eşleşen bir ad yok.',
6326
6383
  'hr.not_clocked_in': 'Giriş yapılmadı',
6327
6384
  'hr.offboard': 'İşi bitir',
6328
6385
  'hr.offboard_already': 'Bu kişi zaten ayrıldı',
@@ -6435,6 +6492,7 @@ export const tr: Record<string, Message> = {
6435
6492
  'hr.offices_timezones': 'Saat dilimi',
6436
6493
  'hr.offices_title': 'Ofisler',
6437
6494
  'hr.on_break_since': '{time} saatinden beri molada',
6495
+ 'hr.on_leave_note': 'Şu anda izinde',
6438
6496
  'hr.org_archive_blocked_children': {
6439
6497
  one: 'Altındaki {count} departman bağlısız kalır. Önce onu taşıyın veya arşivleyin.',
6440
6498
  other: 'Altındaki {count} departman bağlısız kalır. Önce onları taşıyın veya arşivleyin.',
@@ -6506,7 +6564,7 @@ export const tr: Record<string, Message> = {
6506
6564
  'hr.org_root': 'En üst kademe',
6507
6565
  'hr.org_search_label': 'Departmanları ada veya koda göre ara',
6508
6566
  'hr.org_search_placeholder': 'Departman ara',
6509
- 'hr.org_see_people': { one: 'Buradaki {count} kişiyi gör', other: 'Buradaki {count} kişiyi gör' },
6567
+ 'hr.org_see_people': { one: '{count} kişiyi gör', other: '{count} kişiyi gör' },
6510
6568
  'hr.org_stat_levels': 'Kademe',
6511
6569
  'hr.org_stat_people': 'Yerleştirilen kişi',
6512
6570
  'hr.org_stat_positions': 'Pozisyonlar',
@@ -6676,13 +6734,8 @@ export const tr: Record<string, Message> = {
6676
6734
  'hr.schedule_saved': 'Vardiya kaydedildi',
6677
6735
  'hr.schedule_start': 'Başlangıç',
6678
6736
  'hr.schedule_time_missing': 'Her çalışma gününün bir başlangıcı ve bitişi olmalı.',
6679
- 'hr.schedule_tz_fixed': 'Tek bir sabit dilim',
6680
- 'hr.schedule_tz_hint': '09:00 bir saat okumasıdır. Bu, kimin saati olduğunu söyler.',
6681
- 'hr.schedule_tz_label': 'Dilim',
6682
- 'hr.schedule_tz_mode': 'Saat dilimi',
6683
- 'hr.schedule_tz_office': 'Kişinin ofisi',
6684
- 'hr.schedule_tz_person': 'Kişinin kendi saat dilimi',
6685
- 'hr.schedule_tz_required': 'Bu vardiyanın okunacağı dilimi seçin.',
6737
+ 'hr.schedule_tz_note':
6738
+ '09:00, kişinin bulunduğu yerde saat dokuz demektir: profilinde kendi saat dilimi yazılı değilse birincil ofisinin dilimi geçerlidir.',
6686
6739
  'hr.schedule_week': 'Çalışma haftası',
6687
6740
  'hr.schedule_week_empty': 'Bütün günler kapalı — ölçülecek bir şey kalmaz.',
6688
6741
  'hr.schedule_week_total': 'Haftada {hours}',
@@ -6704,7 +6757,8 @@ export const tr: Record<string, Message> = {
6704
6757
  'hr.sensitive_contact_hint': 'İşyerinde bir şey olursa aranacak kişi.',
6705
6758
  'hr.sensitive_contact_name': 'Acil durum kişisi',
6706
6759
  'hr.sensitive_contact_partial': 'Ya ad ve numara, ya da hiçbiri.',
6707
- 'hr.sensitive_edit_body': 'Şifreli saklanır. Kayıt, bunların değiştiğini söyler; ne yazdığını asla.',
6760
+ 'hr.sensitive_edit_body':
6761
+ 'Kimlik numarası ve banka bilgileri şifreli saklanır. Buradaki her şeye erişim kısıtlıdır ve kayıt, bunların değiştiğini söyler; ne yazdığını asla.',
6708
6762
  'hr.sensitive_edit_title': '{name} için kişisel bilgiler',
6709
6763
  'hr.sensitive_emergency': 'Acil durumda arayın',
6710
6764
  'hr.sensitive_emergency_none': 'Kayıtlı acil durum kişisi yok',
@@ -18,6 +18,7 @@ import { getHrApi } from '../api-instance.js'
18
18
  import ClockControls from '../components/ClockControls.svelte'
19
19
  import DayDetail from '../components/DayDetail.svelte'
20
20
  import { t } from '../i18n.js'
21
+ import { canHr } from '../permissions.js'
21
22
  import { formatDuration, hrKeys, monthRange } from '../query.js'
22
23
 
23
24
  /**
@@ -47,6 +48,26 @@ const workspaceId = $derived(workspace?.id ?? '')
47
48
 
48
49
  const range = $derived(monthRange())
49
50
 
51
+ /**
52
+ * The clock is the punch permission, and nothing else on this page is.
53
+ *
54
+ * `hr.attendance.view` is what opens this route — reading your own month — while every one of the
55
+ * four transitions behind `ClockControls` is `requires('hr.attendance.punch')` on the server. A
56
+ * workspace revokes that from somebody whose punches arrive from a badge terminal or from their
57
+ * manager, and for them the clock row was a set of buttons that can only answer 403 — an offer the
58
+ * product cannot honour.
59
+ *
60
+ * Hidden rather than disabled-with-a-reason, because this is a permission and not a state: they may
61
+ * never do it, there is nothing to wait for, and "you cannot clock in" repeated on every visit
62
+ * teaches nothing. It is also the same answer the dashboard already gives — `module.ts` declares the
63
+ * clock widget with `permission: attendancePunch`, so the shell has always hidden this exact
64
+ * component for these people. The page was the one place it leaked.
65
+ *
66
+ * The status line goes with the buttons it captions; the month underneath is what a viewer keeps,
67
+ * and today is a row in it.
68
+ */
69
+ const canPunch = $derived(canHr('attendancePunch'))
70
+
50
71
  const daysQuery = createQuery(() => ({
51
72
  queryKey: hrKeys.attendanceDays(workspaceId, undefined, range.from, range.to),
52
73
  enabled: Boolean(workspaceId),
@@ -168,7 +189,9 @@ const dayLabel = (iso: string) =>
168
189
  {/snippet}
169
190
 
170
191
  <Page>
171
- <ClockControls {workspaceId} />
192
+ {#if canPunch}
193
+ <ClockControls {workspaceId} />
194
+ {/if}
172
195
 
173
196
  {#if totalsUnknown}
174
197
  {@render tilesUnknown()}