22
33namespace Leantime \Domain \Widgets \Hxcontrollers ;
44
5+ use Illuminate \Support \Facades \Log ;
56use Leantime \Core \Controller \HtmxController ;
67use Leantime \Domain \Auth \Models \Roles ;
78use Leantime \Domain \Auth \Services \Auth as AuthService ;
@@ -85,14 +86,36 @@ public function saveSorting($params)
8586 $ userId = session ('userdata.id ' );
8687 unset($ params ['act ' ]);
8788
89+ // Check if group changes are present (indicating new format)
90+ $ hasGroupChanges = false ;
91+ $ groupChanges = [];
92+ $ groupBy = $ post ['groupBy ' ] ?? '' ;
93+
94+ // Extract group changes from POST data
95+ if (isset ($ post ['groupChanges ' ])) {
96+ foreach ($ post ['groupChanges ' ] as $ key => $ value ) {
97+ $ hasGroupChanges = true ;
98+ $ groupChanges [] = json_decode ($ value , true );
99+ }
100+ }
101+
102+ // Process group changes first if present
103+ if ($ hasGroupChanges && ! empty ($ groupChanges )) {
104+ $ this ->processGroupChanges ($ groupChanges , $ groupBy );
105+ }
106+
107+ // Handle sorting
88108 if (is_array ($ params )) {
89109 $ taskList = array_map (function ($ item ) {
90- $ task = json_decode ($ item , true );
91- if (is_array ($ task ) && isset ($ task ['id ' ])) {
92- // start sorting at 10 so we have room for new tasks at the top
93- $ task ['order ' ] += 10 ;
94-
95- return $ task ;
110+ if (is_string ($ item )) {
111+ $ task = json_decode ($ item , true );
112+ if (is_array ($ task ) && isset ($ task ['id ' ])) {
113+ // start sorting at 10 so we have room for new tasks at the top
114+ $ task ['order ' ] = $ task ['order ' ] ?? 0 ;
115+ $ task ['order ' ] += 10 ;
116+
117+ return $ task ;
118+ }
96119 }
97120 }, $ params );
98121
@@ -105,6 +128,7 @@ public function saveSorting($params)
105128 }
106129
107130 $ this ->tpl ->setNotification ($ this ->language ->__ ('notifications.sorting_error ' ), 'error ' );
131+
108132 }
109133
110134 /**
@@ -128,6 +152,199 @@ public function toggleTaskCollapse($params)
128152 }
129153 }
130154
155+ /**
156+ * Process group changes and update corresponding task fields
157+ *
158+ * @param array $groupChanges Array of group change data
159+ * @param string $groupBy The grouping type (time, project, priority)
160+ */
161+ private function processGroupChanges (array $ groupChanges , string $ groupBy ): void
162+ {
163+ $ successCount = 0 ;
164+ $ errorCount = 0 ;
165+
166+ foreach ($ groupChanges as $ change ) {
167+ $ taskId = $ change ['id ' ] ?? null ;
168+ $ toGroup = $ change ['toGroup ' ] ?? null ;
169+ $ fromGroup = $ change ['fromGroup ' ] ?? null ;
170+
171+ // Skip invalid changes
172+ if (empty ($ taskId ) || empty ($ toGroup )) {
173+ $ errorCount ++;
174+
175+ continue ;
176+ }
177+
178+ // Validate that user has permission to update this task
179+ if (! $ this ->canUserUpdateTask ($ taskId )) {
180+ Log::warning ("User does not have permission to update task {$ taskId }" );
181+ $ errorCount ++;
182+
183+ continue ;
184+ }
185+
186+ $ fieldsToUpdate = $ this ->mapGroupToFields ($ groupBy , $ toGroup );
187+
188+ if (! empty ($ fieldsToUpdate )) {
189+ try {
190+ $ result = $ this ->ticketsService ->patch ($ taskId , $ fieldsToUpdate );
191+
192+ if ($ result ) {
193+ $ successCount ++;
194+
195+ // Log successful group change for debugging
196+ Log::info ("Successfully moved task {$ taskId } from group {$ fromGroup } to {$ toGroup } ( {$ groupBy }) " );
197+ } else {
198+ $ errorCount ++;
199+ Log::error ("Failed to update task {$ taskId } with group change to {$ toGroup }" );
200+ }
201+ } catch (\Exception $ e ) {
202+ $ errorCount ++;
203+ Log::error ("Error updating task {$ taskId }: " .$ e ->getMessage ());
204+ }
205+ } else {
206+ // No valid field mapping found
207+ Log::warning ("No valid field mapping found for group {$ toGroup } in groupBy {$ groupBy }" );
208+ }
209+ }
210+
211+ // Set user notifications based on results
212+ if ($ successCount > 0 && $ errorCount === 0 ) {
213+ $ this ->tpl ->setNotification ($ this ->language ->__ ('notifications.group_changes_applied ' ), 'success ' );
214+ } elseif ($ successCount > 0 && $ errorCount > 0 ) {
215+ $ this ->tpl ->setNotification (
216+ $ this ->language ->__ ('notifications.group_changes_partial ' ),
217+ 'warning '
218+ );
219+ } elseif ($ errorCount > 0 ) {
220+ $ this ->tpl ->setNotification (
221+ $ this ->language ->__ ('notifications.group_changes_failed ' ),
222+ 'error '
223+ );
224+ }
225+ }
226+
227+ /**
228+ * Map group key to field updates based on grouping type
229+ *
230+ * @param string $groupBy The grouping type
231+ * @param string $groupKey The target group key
232+ * @return array Fields to update
233+ */
234+ private function mapGroupToFields (string $ groupBy , string $ groupKey ): array
235+ {
236+ switch ($ groupBy ) {
237+ case 'time ' :
238+ return $ this ->mapTimeGroupToFields ($ groupKey );
239+
240+ case 'project ' :
241+ return $ this ->mapProjectGroupToFields ($ groupKey );
242+
243+ case 'priority ' :
244+ return $ this ->mapPriorityGroupToFields ($ groupKey );
245+
246+ default :
247+ return [];
248+ }
249+ }
250+
251+ /**
252+ * Map time group to date fields
253+ *
254+ * @param string $groupKey Time group key (overdue, thisWeek, later)
255+ * @return array Fields to update
256+ */
257+ private function mapTimeGroupToFields (string $ groupKey ): array
258+ {
259+ switch ($ groupKey ) {
260+ case 'overdue ' :
261+ // Set due date to yesterday to make it overdue
262+ return ['dateToFinish ' => date ('Y-m-d ' , strtotime ('yesterday ' ))];
263+
264+ case 'thisWeek ' :
265+ // Set due date to end of current week (Friday)
266+ return ['dateToFinish ' => date ('Y-m-d ' , strtotime ('next friday ' ))];
267+
268+ case 'later ' :
269+ // Clear due date for "later" group
270+ return ['dateToFinish ' => '' ];
271+
272+ default :
273+ return [];
274+ }
275+ }
276+
277+ /**
278+ * Map project group to project field
279+ *
280+ * @param string $groupKey Project ID
281+ * @return array Fields to update
282+ */
283+ private function mapProjectGroupToFields (string $ groupKey ): array
284+ {
285+ // Validate that the group key is a valid project ID
286+ if (is_numeric ($ groupKey ) && $ groupKey > 0 ) {
287+ $ projectId = (int ) $ groupKey ;
288+
289+ // Additional validation: Check if user has access to the target project
290+ // This could be enhanced with a proper project permission check
291+ // For now, we'll trust that the group was presented to the user, so they have access
292+
293+ return ['projectId ' => $ projectId ];
294+ }
295+
296+ return [];
297+ }
298+
299+ /**
300+ * Map priority group to priority field
301+ *
302+ * @param string $groupKey Priority value
303+ * @return array Fields to update
304+ */
305+ private function mapPriorityGroupToFields (string $ groupKey ): array
306+ {
307+ // Handle priority mapping
308+ if ($ groupKey === '999 ' ) {
309+ // 999 represents "undefined priority" - clear the priority
310+ return ['priority ' => '' ];
311+ }
312+
313+ // Validate priority is within valid range (1-4)
314+ if (is_numeric ($ groupKey ) && $ groupKey >= 1 && $ groupKey <= 4 ) {
315+ return ['priority ' => (int ) $ groupKey ];
316+ }
317+
318+ return [];
319+ }
320+
321+ /**
322+ * Check if the current user can update a specific task
323+ *
324+ * @param int $taskId The task ID to check
325+ * @return bool True if user can update, false otherwise
326+ */
327+ private function canUserUpdateTask (int $ taskId ): bool
328+ {
329+ try {
330+ // Attempt to get the ticket - this will return false if user doesn't have access
331+ $ ticket = $ this ->ticketsService ->getTicket ($ taskId );
332+
333+ if (! $ ticket || empty ($ ticket )) {
334+ return false ;
335+ }
336+
337+ // Additional permission checks can be added here if needed
338+ // For now, if user can view the ticket, they can update it
339+ return true ;
340+
341+ } catch (\Exception $ e ) {
342+ Log::error ("Permission check failed for task {$ taskId }: " .$ e ->getMessage ());
343+
344+ return false ;
345+ }
346+ }
347+
131348 /**
132349 * Update ticket dependencies based on the sorting hierarchy
133350 *
0 commit comments