diff --git a/app/Domain/Tickets/Templates/showKanban.tpl.php b/app/Domain/Tickets/Templates/showKanban.tpl.php index d543e274e2..8c8fded707 100644 --- a/app/Domain/Tickets/Templates/showKanban.tpl.php +++ b/app/Domain/Tickets/Templates/showKanban.tpl.php @@ -265,10 +265,15 @@ class="ticketBox moveable container priority-border-" 0) { ?> escape($row['parentHeadline']) ?> // - __('label.'.strtolower($row['type'])); ?> +
-

e($row['headline']); ?>

+

+ + + + e($row['headline']); ?> +

escapeMinimalRemoveImage($row['description']); ?> diff --git a/app/Domain/Tickets/Templates/submodules/ticketDetails.sub.php b/app/Domain/Tickets/Templates/submodules/ticketDetails.sub.php index a1f1476cc8..135e84d672 100644 --- a/app/Domain/Tickets/Templates/submodules/ticketDetails.sub.php +++ b/app/Domain/Tickets/Templates/submodules/ticketDetails.sub.php @@ -184,6 +184,22 @@ class="complexEditor">description !== null ? htmlentities($t
+ id) {?> +

__('subtitles.discussion'); ?>

+ +
+
+ + assign('formUrl', ''.BASE_URL.'/tickets/showTicket/'.$ticket->id.''); + + $tpl->displaySubmodule('comments-generalComment'); + ?> +
+
+ +
+ displayNotification() ?> + + +
diff --git a/app/Domain/Timesheets/Templates/showMy.tpl.php b/app/Domain/Timesheets/Templates/showMy.tpl.php index e24df082d7..2b8caa331f 100644 --- a/app/Domain/Timesheets/Templates/showMy.tpl.php +++ b/app/Domain/Timesheets/Templates/showMy.tpl.php @@ -229,6 +229,7 @@
@@ -268,6 +269,8 @@ $days[] = array_shift($days); ?> + ID + Tick.ID __('label.client_product')?> __('subtitles.todo')?> __('label.type')?> @@ -304,11 +307,40 @@ $timesheetId = 'new'; ?> - e($timeRow['clientName']); ?> // e($timeRow['name']); ?> - - e($timeRow['headline']); ?> + + #' . implode(', #', $logIds) . ''; + } else { + echo '-'; + } + ?> + + + + + + + + e($timeRow['clientName']); ?> // e($timeRow['name']); ?> + + e($timeRow['headline']); ?> - + __($tpl->get('kind')[$timeRow['kind'] ?? 'GENERAL_BILLABLE'] ?? $tpl->get('kind')['GENERAL_BILLABLE']); ?> @@ -356,13 +388,15 @@ class="hourCell" } } ?> - + - + - + - +
- +
- + @@ -437,7 +471,7 @@ class="hourCell" - __('label.total')?> + __('label.total')?> $col) { diff --git a/app/Domain/Timesheets/Templates/showMyList.tpl.php b/app/Domain/Timesheets/Templates/showMyList.tpl.php index 2c3e340280..92b681746b 100644 --- a/app/Domain/Timesheets/Templates/showMyList.tpl.php +++ b/app/Domain/Timesheets/Templates/showMyList.tpl.php @@ -75,6 +75,7 @@ class="dateTo"
@@ -103,6 +104,7 @@ class="dateTo" __('label.id'); ?> + Tick.ID __('label.date'); ?> __('label.hours'); ?> __('label.plan_hours'); ?> @@ -129,7 +131,19 @@ class="dateTo" $sum = $sum + $row['hours']; ?> - #__('label.edit'); ?> + # + + + + escape($row['projectKey']) . '-' . $tpl->escape($row['ticketId']); + } else { + echo '#' . $tpl->escape($row['ticketId']); + } + ?> + + date(); ?> time(); ?> @@ -195,6 +209,7 @@ class="dateTo" + __('label.total_hours')?> */ ?> diff --git a/app/Domain/Timesheets/Templates/showMyMonthlyTimesheet.tpl.php b/app/Domain/Timesheets/Templates/showMyMonthlyTimesheet.tpl.php new file mode 100644 index 0000000000..27e4837201 --- /dev/null +++ b/app/Domain/Timesheets/Templates/showMyMonthlyTimesheet.tpl.php @@ -0,0 +1,406 @@ + $val) { + $$var = $val; +} + +$dateFrom = $tpl->get('dateFrom'); +$daysInMonth = $dateFrom->daysInMonth(); +$hoursFormat = session('usersettings.hours_format', 'decimal'); + +?> + + + + + +
+
+ displayNotification(); + + ?> + + +
+ + +
+
+ +
+ + + + __('label.until'); ?> + + + +
+ +
+
+ + + + + + + copy()->setDay($d); + echo ''; + } + ?> + + + + + get('allTimesheets') as $timeRow) { + ?> + + + + + + + + + + + + + + + + + + copy()->setDay($d); + ?> + + + + + + + + + + + + + + +
__('label.client_product') ?>__('subtitles.todo') ?>__('label.type') ?>' . $dayDate->format('j.n.Y.') . '__('label.total') ?>
e($timeRow['clientName']); ?> // e($timeRow['name']); ?> + e($timeRow['headline']); ?> + + __($tpl->get('kind')[$timeRow['kind'] ?? 'GENERAL_BILLABLE'] ?? $tpl->get('kind')['GENERAL_BILLABLE']); ?> + + + + + +
+ formatDateForUser() . '|' . $dayData['actualWorkDate']->getTimestamp(); + ?> + + + + + + + +
+ + + +
+
+ +
+
+
+ +
+
+ + + +
__('label.total') ?> + 0) { + echo format_hours($col); + } ?> +
+
+
+ +
+
+ +
+
\ No newline at end of file diff --git a/app/Language/en-GB.ini b/app/Language/en-GB.ini index 51e0d0650d..e57fc25273 100644 --- a/app/Language/en-GB.ini +++ b/app/Language/en-GB.ini @@ -2347,7 +2347,7 @@ label.project_type="Project Type" # Project Key Labels label.project_key="Project Key" label.project_key_format="(2-10 characters, letters and numbers only)" -label.project_key_description="A unique identifier for this project (like Jira). Leave empty to auto-generate." +label.project_key_description="A unique identifier for this project (like Jira). Maximum of 10 characters, no special characters allowed. Press Enter to confirm." input.placeholders.auto_generated="Auto-generated" input.placeholders.enter_project_key="e.g., FL, PROJ" diff --git a/app/Language/en-US.ini b/app/Language/en-US.ini index 33a30093c1..4564b1af95 100644 --- a/app/Language/en-US.ini +++ b/app/Language/en-US.ini @@ -2416,7 +2416,7 @@ label.project_type = "Project Type" # Project Key Labels label.project_key = "Project Key" label.project_key_format = "(2-10 characters, letters and numbers only)" -label.project_key_description = "A unique identifier for this project (like Jira). Leave empty to auto-generate." +label.project_key_description = "A unique identifier for this project (like Jira). Maximum of 10 characters, no special characters allowed. Press Enter to confirm." input.placeholders.auto_generated = "Auto-generated" input.placeholders.enter_project_key = "e.g., FL, PROJ" diff --git a/public/assets/js/app/core/datePickers.js b/public/assets/js/app/core/datePickers.js index 611abb318e..8afea36964 100644 --- a/public/assets/js/app/core/datePickers.js +++ b/public/assets/js/app/core/datePickers.js @@ -83,8 +83,11 @@ leantime.dateController = (function () { ); } var initModernDateRangePicker = function (fromElement, toElement, minDistance) { - var startDate = moment().startOf('month'); - var endDate = moment().endOf('month'); + var existingFromValue = jQuery(fromElement).val(); + var existingToValue = jQuery(toElement).val(); + + var startDate = existingFromValue ? moment(existingFromValue, 'YYYY-MM-DD') : moment().startOf('month'); + var endDate = existingToValue ? moment(existingToValue, 'YYYY-MM-DD') : moment().endOf('month'); jQuery(fromElement).daterangepicker({ autoUpdateInput: false, @@ -111,8 +114,12 @@ leantime.dateController = (function () { } }); - jQuery(fromElement).val(startDate.format('YYYY-MM-DD')); - jQuery(toElement).val(endDate.format('YYYY-MM-DD')); + if (!existingFromValue) { + jQuery(fromElement).val(startDate.format('YYYY-MM-DD')); + } + if (!existingToValue) { + jQuery(toElement).val(endDate.format('YYYY-MM-DD')); + } jQuery(fromElement).on('apply.daterangepicker', function(ev, picker) { jQuery(fromElement).val(picker.startDate.format('YYYY-MM-DD')); diff --git a/public/assets/js/app/timesheets/filterPreferences.js b/public/assets/js/app/timesheets/filterPreferences.js index 2f6a9f7587..d880930d23 100644 --- a/public/assets/js/app/timesheets/filterPreferences.js +++ b/public/assets/js/app/timesheets/filterPreferences.js @@ -5,6 +5,7 @@ let currentPreferences = {}; let dataTableInstance = null; + let selectedRangeName = null; function init(dataTable) { @@ -18,6 +19,7 @@ const dtButtons = jQuery('#tableButtons .dt-buttons'); if (dtButtons.length > 0) { initUI(); + initDateRangeTracking(); } }; @@ -25,6 +27,20 @@ setTimeout(checkAndInit, 100); } + function initDateRangeTracking() { + const dateInput = jQuery('input[name="dateFrom"]'); + + if (dateInput.length && dateInput.data('daterangepicker')) { + dateInput.on('apply.daterangepicker', function(ev, picker) { + if (picker.chosenLabel) { + selectedRangeName = picker.chosenLabel; + } + }); + } else { + setTimeout(initDateRangeTracking, 500); + } + } + async function loadPreference(name) { try { const response = await fetch(leantime.appUrl + PROFILE_ENDPOINT, { @@ -104,6 +120,35 @@ alert('Failed to save profile'); return false; } + function applyDateRange(rangeName) { + const dateInput = jQuery('input[name="dateFrom"]'); + + if (!dateInput.length || !dateInput.data('daterangepicker')) { + return false; + } + + const picker = dateInput.data('daterangepicker'); + const ranges = picker.ranges; + + if (ranges && ranges[rangeName]) { + const range = ranges[rangeName]; + const startDate = range[0]; + const endDate = range[1]; + + picker.setStartDate(startDate); + picker.setEndDate(endDate); + picker.chosenLabel = rangeName; + + jQuery('input[name="dateFrom"]').val(startDate.format('YYYY-MM-DD')); + jQuery('input[name="dateTo"]').val(endDate.format('YYYY-MM-DD')); + + selectedRangeName = rangeName; + + return true; + } + + return false; + } async function applyFilters(filters) { if (!filters) { @@ -119,12 +164,26 @@ jQuery('select[name="kind"]').val(filters.kind); } - if (filters.dateFrom) { - jQuery('input[name="dateFrom"]').val(filters.dateFrom); - } - if (filters.dateTo) { - jQuery('input[name="dateTo"]').val(filters.dateTo); + if (filters.dateRange && filters.dateRange !== 'Custom') { + const applied = applyDateRange(filters.dateRange); + + if (!applied) { + if (filters.dateFrom) { + jQuery('input[name="dateFrom"]').val(filters.dateFrom); + } + if (filters.dateTo) { + jQuery('input[name="dateTo"]').val(filters.dateTo); + } + } + } else { + if (filters.dateFrom) { + jQuery('input[name="dateFrom"]').val(filters.dateFrom); + } + if (filters.dateTo) { + jQuery('input[name="dateTo"]').val(filters.dateTo); + } } + jQuery('input[name="invEmpl"]').prop('checked', filters.invEmpl === '1'); jQuery('input[name="invComp"]').prop('checked', filters.invComp === '1'); jQuery('input[name="paid"]').prop('checked', filters.paid === '1'); @@ -186,17 +245,32 @@ } function getCurrentFilters() { + const dateFrom = jQuery('input[name="dateFrom"]').val() || ''; + const dateTo = jQuery('input[name="dateTo"]').val() || ''; + + let dateRange = 'Custom'; + const dateInput = jQuery('input[name="dateFrom"]'); + + if (dateInput.length && dateInput.data('daterangepicker')) { + const picker = dateInput.data('daterangepicker'); + if (picker.chosenLabel) { + dateRange = picker.chosenLabel; + } else if (selectedRangeName) { + dateRange = selectedRangeName; + } + } const projectFilters = { projects: [], clientId: jQuery('select[name="clientId"]').val() || '-1', userId: jQuery('select[name="userId"]').val() || 'all', kind: jQuery('select[name="kind"]').val() || 'all', - dateFrom: jQuery('input[name="dateFrom"]').val() || '', - dateTo: jQuery('input[name="dateTo"]').val() || '', invEmpl: jQuery('input[name="invEmpl"]').is(':checked') ? '1' : '0', invComp: jQuery('input[name="invComp"]').is(':checked') ? '1' : '0', - paid: jQuery('input[name="paid"]').is(':checked') ? '1' : '0' + paid: jQuery('input[name="paid"]').is(':checked') ? '1' : '0', + dateRange: dateRange, + dateFrom: dateFrom, + dateTo: dateTo }; const selectedProjects = []; @@ -285,13 +359,20 @@ function buildPreferencesDropdown() { keys.forEach(function (key) { const pref = currentPreferences[key]; const checked = pref.autoExport ? 'checked' : ''; - + let dateInfo = ''; + if (pref.filters && pref.filters.dateRange && pref.filters.dateRange !== 'Custom') { + dateInfo = `📅 ${pref.filters.dateRange}`; + } + html += `
-
${key}
-
- - +
+ ${key} + ${dateInfo} +
+
+ +