Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 107
0.00% covered (danger)
0.00%
0 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
SchedulingPageController
0.00% covered (danger)
0.00%
0 / 107
0.00% covered (danger)
0.00%
0 / 8
650
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 checkAccess
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
20
 calendar
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
12
 getRoleConfigMap
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 settings
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
20
 timesheets
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
12
 timesheetDetail
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
12
 mySchedule
0.00% covered (danger)
0.00%
0 / 30
0.00% covered (danger)
0.00%
0 / 1
30
1<?php
2
3namespace BuyerKiosk\Scheduling\Controllers;
4
5use BuyerKiosk\TeamMember\Services\RoleConfigService;
6use PDO;
7
8/**
9 * SchedulingPageController - Admin Page Rendering
10 *
11 * Handles rendering of scheduling admin pages:
12 * - Calendar view (main schedule interface)
13 * - Settings page (scheduling configuration)
14 * - Timesheets page (coming in Phase 5)
15 *
16 * @package BuyerKiosk\Scheduling\Controllers
17 */
18class SchedulingPageController
19{
20    /**
21     * @var \Slim\Slim Slim application instance
22     */
23    private $app;
24
25    /**
26     * @var \Store Store object
27     */
28    private $store;
29
30    /**
31     * @var string Store type number
32     */
33    private string $typeNum;
34
35    /**
36     * Constructor
37     *
38     * @param \Slim\Slim $app Slim application instance
39     * @param \Store $store Store object (validated)
40     */
41    public function __construct($app, \Store $store)
42    {
43        $this->app = $app;
44        $this->store = $store;
45        $this->typeNum = $store->getTypeNum();
46    }
47
48    /**
49     * Check if user has scheduling access
50     *
51     * @return bool True if authorized
52     */
53    private function checkAccess(): bool
54    {
55        if (!isset($this->app->user) || !$this->app->user) {
56            $this->app->redirect('/login');
57            return false;
58        }
59
60        if (!$this->app->user->checkAccess('uri_schedule')) {
61            $this->app->notAuthorized();
62            return false;
63        }
64
65        return true;
66    }
67
68    /**
69     * GET /admin/:typeNum/schedule
70     *
71     * Render the main scheduling calendar page
72     *
73     * Passes server-provided store hours for shift type classification.
74     * Database hours are the source of truth; localStorage is only used
75     * for a one-time migration prompt when storeHoursLastUpdated is null.
76     *
77     * @see docs/specs/018-store-configuration/solution-design.md - localStorage Migration Flow
78     */
79    public function calendar(): void
80    {
81        if (!$this->checkAccess()) {
82            return;
83        }
84
85        // Get role configuration for the store
86        $roleConfigMap = $this->getRoleConfigMap();
87
88        // Get store hours for shift type classification (source of truth from database)
89        // null indicates hours have not been explicitly configured yet
90        $storeHoursLastUpdated = $this->store->getHoursLastUpdated();
91
92        $this->app->render('scheduling/calendar.html', [
93            'page' => [
94                'title' => 'Employee Schedule',
95                'author' => $this->app->site->author,
96                'description' => 'Manage employee schedules and shifts'
97            ],
98            'store' => \getStoreInfo($this->store),
99            'typeNum' => $this->typeNum,
100            'storeName' => $this->store->getCompanyName(),
101            'storeTimezone' => $this->store->getTimeZone() ?: 'America/Los_Angeles',
102            'roleConfigMap' => json_encode($roleConfigMap),
103            // Store hours for shift type classification (Spec 018 Calendar Integration)
104            'storeOpenTime' => $this->store->getDefaultOpenTime(),
105            'storeCloseTime' => $this->store->getDefaultCloseTime(),
106            'storeHoursLastUpdated' => $storeHoursLastUpdated,
107        ]);
108    }
109
110    /**
111     * Get role configuration map for the store
112     *
113     * Note: storeRoles table is in kiosk_buykiosk (central DB), not the store DB
114     *
115     * @return array Role configuration map keyed by role ID
116     */
117    private function getRoleConfigMap(): array
118    {
119        try {
120            // storeRoles table is in the central database (kiosk_buykiosk)
121            /** @var PDO $db */
122            $db = dbConnectByName($_ENV['DB_NAME']);
123            $roleService = new RoleConfigService($db, $this->typeNum);
124            return $roleService->toMap();
125        } catch (\Exception $e) {
126            // Fall back to defaults if role config fails
127            return RoleConfigService::getDefaultsAsMap();
128        }
129    }
130
131    /**
132     * GET /admin/:typeNum/schedule/settings
133     *
134     * Render the scheduling settings page
135     */
136    public function settings(): void
137    {
138        if (!$this->checkAccess()) {
139            return;
140        }
141
142        // Settings page requires elevated permission
143        if (!$this->app->user->checkAccess('uri_schedule_config')) {
144            $this->app->notAuthorized();
145            return;
146        }
147
148        $this->app->render('scheduling/settings.html', [
149            'page' => [
150                'title' => 'Scheduling Settings',
151                'author' => $this->app->site->author,
152                'description' => 'Configure scheduling policies and overtime rules'
153            ],
154            'store' => \getStoreInfo($this->store),
155            'typeNum' => $this->typeNum,
156            'storeName' => $this->store->getCompanyName(),
157            'storeTimezone' => $this->store->getTimeZone() ?: 'America/Los_Angeles',
158        ]);
159    }
160
161    /**
162     * GET /admin/:typeNum/schedule/timesheets
163     *
164     * Render the timesheets management page
165     */
166    public function timesheets(): void
167    {
168        if (!$this->checkAccess()) {
169            return;
170        }
171
172        $this->app->render('scheduling/timesheets.html', [
173            'page' => [
174                'title' => 'Timesheets',
175                'author' => $this->app->site->author,
176                'description' => 'Review and approve employee timesheets'
177            ],
178            'store' => \getStoreInfo($this->store),
179            'typeNum' => $this->typeNum,
180            'storeName' => $this->store->getCompanyName(),
181            'storeTimezone' => $this->store->getTimeZone() ?: 'America/Los_Angeles',
182        ]);
183    }
184
185    /**
186     * GET /admin/:typeNum/schedule/timesheets/:employeeId
187     *
188     * Render the timesheet detail page for a specific employee
189     *
190     * @param int $employeeId Employee ID
191     */
192    public function timesheetDetail(int $employeeId): void
193    {
194        if (!$this->checkAccess()) {
195            return;
196        }
197
198        $this->app->render('scheduling/timesheet-detail.html', [
199            'page' => [
200                'title' => 'Timesheet Detail',
201                'author' => $this->app->site->author,
202                'description' => 'Employee timesheet detail with punch editing'
203            ],
204            'store' => \getStoreInfo($this->store),
205            'typeNum' => $this->typeNum,
206            'employeeId' => $employeeId,
207            'storeName' => $this->store->getCompanyName(),
208            'storeTimezone' => $this->store->getTimeZone() ?: 'America/Los_Angeles',
209        ]);
210    }
211
212    /**
213     * GET /admin/:typeNum/schedule/my-schedule
214     *
215     * Render the employee's personal schedule view.
216     * Does NOT require uri_schedule permission - any logged-in user with
217     * store access can view their own schedule.
218     *
219     * @see PRD Feature 15: Employee Schedule View
220     */
221    public function mySchedule(): void
222    {
223        // Basic authentication check - does NOT require uri_schedule permission
224        if (!isset($this->app->user) || !$this->app->user) {
225            $this->app->redirect('/login');
226            return;
227        }
228
229        // Check if BuyerKiosk scheduling is enabled for this store
230        $provider = $this->store->getSchedulingProvider();
231        if ($provider !== 'buyerkiosk') {
232            // Show a message that this feature requires BuyerKiosk scheduling
233            $this->app->render('components/error-page.html', [
234                'page' => [
235                    'title' => 'Feature Not Available',
236                    'author' => $this->app->site->author,
237                    'description' => 'Employee schedule view is not available'
238                ],
239                'store' => \getStoreInfo($this->store),
240                'typeNum' => $this->typeNum,
241                'errorTitle' => 'Schedule View Not Available',
242                'errorMessage' => 'The employee schedule view is only available for stores using BuyerKiosk scheduling. This store uses a different scheduling provider.',
243                'backUrl' => '/admin/' . $this->typeNum,
244                'backLabel' => 'Back to Admin'
245            ]);
246            return;
247        }
248
249        $this->app->render('scheduling/my-schedule.html', [
250            'page' => [
251                'title' => 'My Schedule',
252                'author' => $this->app->site->author,
253                'description' => 'View your upcoming work schedule'
254            ],
255            'store' => \getStoreInfo($this->store),
256            'typeNum' => $this->typeNum,
257            'storeName' => $this->store->getCompanyName(),
258            'storeTimezone' => $this->store->getTimeZone() ?: 'America/Los_Angeles',
259        ]);
260    }
261}