From db2bd9dbf57ba3ac9621a4468cbdf30fdf2db070 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20David?= Date: Tue, 7 Jan 2025 13:15:50 +0100 Subject: [PATCH] #44 #43 [ClassicKanban] fix: add classic versions of project opportunity and tickets kanban --- core/modules/modDigiKanban.class.php | 15 ++ core/tpl/kanban_view.tpl.php | 19 ++- css/digikanban.min.css | 2 +- css/scss/modules/_kanban_list.scss | 83 ++++++++++ css/scss/modules/_modules.scss | 1 + js/digikanban.min.js | 2 +- js/modules/kanban.js | 92 ++++++----- lib/functions.lib.php | 14 ++ view/kanban/classic_kanban_card.php | 238 +++++++++++++++++++++++++++ view/kanban/classic_kanban_list.php | 95 +++++++++++ 10 files changed, 507 insertions(+), 54 deletions(-) create mode 100644 css/scss/modules/_kanban_list.scss create mode 100644 lib/functions.lib.php create mode 100644 view/kanban/classic_kanban_card.php create mode 100644 view/kanban/classic_kanban_list.php diff --git a/core/modules/modDigiKanban.class.php b/core/modules/modDigiKanban.class.php index f59d00d..af89b53 100644 --- a/core/modules/modDigiKanban.class.php +++ b/core/modules/modDigiKanban.class.php @@ -367,6 +367,21 @@ function __construct($db) 'target' => '', 'user' => 0 ]; + + $this->menu[$r++] = [ + 'fk_menu' => 'fk_mainmenu=digikanban', + 'type' => 'left', + 'titre' => $langs->trans('ClassicKanbans'), + 'prefix' => ' ', + 'leftmenu' => 'digikanban', + 'url' => '/digikanban/view/kanban/classic_kanban_list.php?action=create', + 'langs' => 'digikanban@digikanban', + 'position' => 1000 + $r, + 'enabled' => 1, + 'perms' => '$user->rights->digikanban->kanban->read', + 'target' => '', + 'user' => 0 + ]; } /** diff --git a/core/tpl/kanban_view.tpl.php b/core/tpl/kanban_view.tpl.php index 248c754..c25d141 100644 --- a/core/tpl/kanban_view.tpl.php +++ b/core/tpl/kanban_view.tpl.php @@ -10,10 +10,12 @@ $actionsButton = ''; foreach ($columns as $column) { - $objectSelector = $form->selectArray($objectLinkedMetadata['post_name'] . $column['category_id'], $objectArray, GETPOST($objectLinkedMetadata['post_name']), $langs->trans('Select') . ' ' . strtolower($langs->trans($objectLinkedMetadata['langs'])), 0, 0, '', 0, 0, dol_strlen(GETPOST('fromtype')) > 0 && GETPOST('fromtype') != $objectLinkedMetadata['link_name'], '', 'maxwidth400 minheight30 widthcentpercentminusxx kanban-select-option'); - $selectorName = $objectLinkedMetadata['post_name'] . $column['category_id']; - $objectsInColumn = $column['objects']; + if (!$disableAddColumn) { + $objectSelector = $form->selectArray($objectLinkedMetadata['post_name'] . $column['category_id'], $objectArray, GETPOST($objectLinkedMetadata['post_name']), $langs->trans('Select') . ' ' . strtolower($langs->trans($objectLinkedMetadata['langs'])), 0, 0, '', 0, 0, dol_strlen(GETPOST('fromtype')) > 0 && GETPOST('fromtype') != $objectLinkedMetadata['link_name'], '', 'maxwidth400 minheight30 widthcentpercentminusxx kanban-select-option'); + $selectorName = $objectLinkedMetadata['post_name'] . $column['category_id']; + } + $objectsInColumn = $column['objects']; print '
'; print '
'; print ''; @@ -28,15 +30,15 @@ } print '
'; - print '' . htmlspecialchars($column['label']) . ''; + print '' . $langs->transnoentities(htmlspecialchars($column['label'])) . ''; print '' . $objectsCounter . ''; print '
'; - print ''; + print ''; print ' diff --git a/js/modules/kanban.js b/js/modules/kanban.js index 715217f..ac84c32 100644 --- a/js/modules/kanban.js +++ b/js/modules/kanban.js @@ -22,56 +22,60 @@ window.digikanban.kanban.init = function() { * @return {void} */ window.digikanban.kanban.event = function() { - $(document).on('change', '.kanban-select-option', window.digikanban.kanban.selectOption); - $(document).on('click', '.validate-button:not(.butActionRefused)', window.digikanban.kanban.addObjectToColumn); - - $('.info-box').attr('draggable', 'true'); - - $('.kanban-column-body').sortable({ - connectWith: '.kanban-column-body', - placeholder: 'kanban-placeholder', - handle: '.info-box', - tolerance: 'pointer', - over: function() { - $(this).css('cursor', 'grabbing'); - }, - stop: function(event, ui) { - window.digikanban.kanban.saveCardOrder(); - }, - }); + const disableActions = $('.disable-kanban-actions').val(); - const kanbanBoard = document.getElementById('kanban-board'); - let isDragging = false; - let startX, scrollLeft; + if (!disableActions) { + $(document).on('change', '.kanban-select-option', window.digikanban.kanban.selectOption); + $(document).on('click', '.validate-button:not(.butActionRefused)', window.digikanban.kanban.addObjectToColumn); - kanbanBoard.addEventListener('mousedown', (e) => { - const isClickInsideKanban = e.target.closest('.kanban-column, .kanban-column-header, .kanban-card, .kanban-select-option'); + $('.info-box').attr('draggable', 'true'); - if (!isClickInsideKanban) { - isDragging = true; - kanbanBoard.classList.add('dragging'); - startX = e.pageX - kanbanBoard.offsetLeft; - scrollLeft = kanbanBoard.scrollLeft; - } - }); + $('.kanban-column-body').sortable({ + connectWith: '.kanban-column-body', + placeholder: 'kanban-placeholder', + handle: '.info-box', + tolerance: 'pointer', + over: function() { + $(this).css('cursor', 'grabbing'); + }, + stop: function(event, ui) { + window.digikanban.kanban.saveCardOrder(); + }, + }); - kanbanBoard.addEventListener('mousemove', (e) => { - if (!isDragging) return; - e.preventDefault(); - const x = e.pageX - kanbanBoard.offsetLeft; - const walk = (x - startX) * 1.5; - kanbanBoard.scrollLeft = scrollLeft - walk; - }); + const kanbanBoard = document.getElementById('kanban-board'); + let isDragging = false; + let startX, scrollLeft; - kanbanBoard.addEventListener('mouseup', () => { - isDragging = false; - kanbanBoard.classList.remove('dragging'); - }); + kanbanBoard.addEventListener('mousedown', (e) => { + const isClickInsideKanban = e.target.closest('.kanban-column, .kanban-column-header, .kanban-card, .kanban-select-option'); - kanbanBoard.addEventListener('mouseleave', () => { - isDragging = false; - kanbanBoard.classList.remove('dragging'); - }); + if (!isClickInsideKanban) { + isDragging = true; + kanbanBoard.classList.add('dragging'); + startX = e.pageX - kanbanBoard.offsetLeft; + scrollLeft = kanbanBoard.scrollLeft; + } + }); + + kanbanBoard.addEventListener('mousemove', (e) => { + if (!isDragging) return; + e.preventDefault(); + const x = e.pageX - kanbanBoard.offsetLeft; + const walk = (x - startX) * 1.5; + kanbanBoard.scrollLeft = scrollLeft - walk; + }); + + kanbanBoard.addEventListener('mouseup', () => { + isDragging = false; + kanbanBoard.classList.remove('dragging'); + }); + + kanbanBoard.addEventListener('mouseleave', () => { + isDragging = false; + kanbanBoard.classList.remove('dragging'); + }); + } }; /** diff --git a/lib/functions.lib.php b/lib/functions.lib.php new file mode 100644 index 0000000..c63d774 --- /dev/null +++ b/lib/functions.lib.php @@ -0,0 +1,14 @@ +query($sql); + if ($resql) { + while ($obj = $db->fetch_object($resql)) { + $dictionary[] = $obj; + } + } + return $dictionary; +} diff --git a/view/kanban/classic_kanban_card.php b/view/kanban/classic_kanban_card.php new file mode 100644 index 0000000..979ee2d --- /dev/null +++ b/view/kanban/classic_kanban_card.php @@ -0,0 +1,238 @@ + + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file view/kanban/kanban_card.php + * \ingroup digikanban + * \brief Page to create/edit/view kanban + */ + +// Load digikanban environment +if (file_exists('../digikanban.main.inc.php')) { + require_once __DIR__ . '/../digikanban.main.inc.php'; +} elseif (file_exists('../../digikanban.main.inc.php')) { + require_once __DIR__ . '/../../digikanban.main.inc.php'; +} else { + die('Include of digikanban main fails'); +} + +// Libraries +require_once DOL_DOCUMENT_ROOT.'/core/class/html.formcompany.class.php'; +require_once DOL_DOCUMENT_ROOT.'/core/class/html.formfile.class.php'; +require_once DOL_DOCUMENT_ROOT.'/core/class/html.formprojet.class.php'; +require_once DOL_DOCUMENT_ROOT . '/core/class/doleditor.class.php'; +require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php'; +require_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php'; +require_once DOL_DOCUMENT_ROOT . '/core/lib/images.lib.php'; +require_once DOL_DOCUMENT_ROOT . '/core/lib/project.lib.php'; + +// Load module libraries +require_once __DIR__ . '/../../class/kanban.class.php'; +require_once __DIR__ . '/../../lib/digikanban_kanban.lib.php'; +require_once __DIR__ . '/../../lib/functions.lib.php'; + + +// Global variables definitions +global $conf, $db, $hookmanager, $langs, $user, $langs; + +// Load translation files required by the page +saturne_load_langs(['projects', 'thirdparty']); + +// Get parameters +$id = GETPOST('id', 'int'); +$ref = GETPOST('ref', 'alpha'); +$action = GETPOST('action', 'aZ09'); +$subaction = GETPOST('subaction', 'aZ09'); +$confirm = GETPOST('confirm', 'alpha'); +$cancel = GETPOST('cancel', 'aZ09'); +$contextpage = GETPOST('contextpage', 'aZ') ?GETPOST('contextpage', 'aZ') : 'kanbancard'; // To manage different context of search +$backtopage = GETPOST('backtopage', 'alpha'); +$backtopageforcancel = GETPOST('backtopageforcancel', 'alpha'); +$type = GETPOST('type', 'alpha'); + +// Initialize objects +// Technical objets +$object = new Kanban($db); +$extrafields = new ExtraFields($db); +$categorie = new Categorie($db); + +// View objects +$form = new Form($db); + +$elementArray = get_kanban_linkable_objects(); +$hookmanager->initHooks(array('kanbancard', 'globalcard')); + +// Fetch optionals attributes and labels +$extrafields->fetch_name_optionals_label($object->table_element); + +$search_array_options = $extrafields->getOptionalsFromPost($object->table_element, '', 'search_'); + +// Initialize array of search criterias +$searchAll = GETPOST("search_all", 'alpha'); +$search = array(); +foreach ($object->fields as $key => $val) { + if (GETPOST('search_'.$key, 'alpha')) $search[$key] = GETPOST('search_'.$key, 'alpha'); +} + +if (empty($action) && empty($id) && empty($ref)) $action = 'view'; + +// Load object +include DOL_DOCUMENT_ROOT.'/core/actions_fetchobject.inc.php'; // Must be include, not include_once. + +$permissiontoread = $user->rights->digikanban->kanban->read; +$permissiontoadd = $user->rights->digikanban->kanban->write; // Used by the include of actions_addupdatedelete.inc.php and actions_lineupdown.inc.php +$permissiontodelete = $user->rights->digikanban->kanban->delete || ($permissiontoadd && isset($object->status) && $object->status == $object::STATUS_DRAFT); + +// Security check - Protection if external user +saturne_check_access($permissiontoread, $object); + +/* + * Actions + */ + +$parameters = array(); +$reshook = $hookmanager->executeHooks('doActions', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks +if ($reshook < 0) setEventMessages($hookmanager->error, $hookmanager->errors, 'errors'); + +if (empty($reshook)) { + $error = 0; + + $backurlforlist = dol_buildpath('/digikanban/view/kanban/kanban_list.php', 1); + + if (empty($backtopage) || ($cancel && empty($id))) { + if (empty($backtopage) || ($cancel && strpos($backtopage, '__ID__'))) { + if (empty($id) && (($action != 'add' && $action != 'create') || $cancel)) $backtopage = $backurlforlist; + else $backtopage = dol_buildpath('/digikanban/view/kanban/kanban_card.php', 1).'?id='.($id > 0 ? $id : '__ID__'); + } + } + + // Actions cancel, add, update, update_extras, confirm_validate, confirm_delete, confirm_deleteline, confirm_clone, confirm_close, confirm_setdraft, confirm_reopen + include DOL_DOCUMENT_ROOT.'/core/actions_addupdatedelete.inc.php'; +} + +/* + * View + */ + +$title = $langs->trans(ucfirst($object->element)); +$help_url = 'FR:Module_digikanban'; + + + +saturne_header(1,'', $title, $help_url, '', 0, 0); + +print '
'; + +$possibleKanbans = [ + 'ticket' => [ + "statusField" => "fk_statut", + "useClassStatus" => 1, + "className" => "Ticket", + ], + 'project' => [ + "statusField" => "fk_statut", + "useClassStatus" => 1, + "className" => "Project", + ], + "opportunity" => [ + "statusField" => "fk_opp_status", + "useClassStatus" => 0, + "statusDictionary" => "c_lead_status", + "className" => "Project", + ], +]; + +$object->object_type = $type; +$kanbanMetadata = $possibleKanbans[$type]; +$kanbanObject = new $kanbanMetadata["className"]($db); + +if ($kanbanMetadata['useClassStatus']) { + $kanbanObject->useClassStatus = $kanbanMetadata['useClassStatus']; + $reflection = new ReflectionClass(get_class($kanbanObject)); + $constants = $reflection->getConstants(); + + $columnList = array_filter($constants, function($key) { + return strpos($key, 'STATUS_') === 0; + }, ARRAY_FILTER_USE_KEY); +} else if (dol_strlen($kanbanMetadata['statusDictionary']) > 0) { + $columnList = getDictionary($kanbanMetadata['statusDictionary']); +} + +if (!empty($elementArray)) { + foreach ($elementArray as $linkableElementType => $linkableElement) { + if ($kanbanObject->element == $linkableElement['category_name']) { + $objectLinkedMetadata = $linkableElement; + $objectLinkedType = $linkableElementType; + } + } +} + +$objectFilter = []; +$columns = []; +if (is_array($columnList) && !empty($columnList)) { + foreach ($columnList as $column) { + +// $objectsInStatus = $kanbanObject->fetchAll('', '', 0,0, ['status' => $column]); + + if ($kanbanMetadata['useClassStatus']) { + $objectsInColumn = saturne_fetch_all_object_type($kanbanObject->element, '', '', 0, 0, ['t.' . $kanbanMetadata['statusField'] => $column]); + $columnLabel = $kanbanObject->labelStatus[$column]; + + } else if ($kanbanMetadata['statusDictionary']) { + $columnLabel = $column->label; + $objectsInColumn = saturne_fetch_all_object_type($kanbanObject->element, '', '', 0, 0, ['t.' . $kanbanMetadata['statusField'] => $column->rowid]); + } + + if (is_array($objectsInColumn) && !empty($objectsInColumn)) { + foreach ($objectsInColumn as $objectInStatus) { + $objectFilter[] = $objectInStatus->id; + } + } + $columns[] = [ + 'label' => $columnLabel, + 'category_id' => $column->rowid, + 'objects' => $objectsInColumn + ]; + } +} + +require_once DOL_DOCUMENT_ROOT . '/' . $objectLinkedMetadata['class_path']; + +print ''; + +$publicView = 1; +$disableAddColumn = 1; +$disableActions = 1; +$object->image_path = ''; +$object->track_id = ''; + +print ''; +include_once __DIR__ . '/../../core/tpl/kanban_view.tpl.php'; + +print dol_get_fiche_end(); + +print '
'; + +$maxEvent = 10; + +$morehtmlcenter = dolGetButtonTitle($langs->trans('SeeAll'), '', 'fa fa-bars imgforviewmode', dol_buildpath('/saturne/view/saturne_agenda.php', 1) . '?id=' . $object->id . '&module_name=digikanban&object_type=' . $object->element); + +print '
'; + +// End of page +llxFooter(); +$db->close(); diff --git a/view/kanban/classic_kanban_list.php b/view/kanban/classic_kanban_list.php new file mode 100644 index 0000000..7de7672 --- /dev/null +++ b/view/kanban/classic_kanban_list.php @@ -0,0 +1,95 @@ + + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file view/kanban/kanban_list.php + * \ingroup digikanban + * \brief List page for kanban + */ + +// Load DigiQuali environment +if (file_exists('../digikanban.main.inc.php')) { + require_once __DIR__ . '/../digikanban.main.inc.php'; +} elseif (file_exists('../../digikanban.main.inc.php')) { + require_once __DIR__ . '/../../digikanban.main.inc.php'; +} else { + die('Include of digikanban main fails'); +} + +// Libraries +require_once DOL_DOCUMENT_ROOT.'/core/class/html.formcompany.class.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/class/html.formcategory.class.php'; +require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php'; + +// Load digikanban libraries +require_once __DIR__ . '/../../class/kanban.class.php'; +require_once __DIR__ . '/../../lib/digikanban_kanban.lib.php'; + +if (isModEnabled('digiquali')) { + require_once DOL_DOCUMENT_ROOT . '/custom/digiquali/lib/digiquali_control.lib.php'; +} + +// Global variables definitions +global $db, $hookmanager, $langs, $user; + +// Load translation files required by the page +saturne_load_langs(["other"]); + +// Define kanbans +$kanbans = [ + [ + 'type' => 'ticket', + 'title' => $langs->trans("KanbanTickets"), + 'picto' => 'object_ticket', + ], + [ + 'type' => 'project', + 'title' => $langs->trans("KanbanProjects"), + 'picto' => 'object_project', + ], + [ + 'type' => 'opportunity', + 'title' => $langs->trans("KanbanOpportunities"), + 'picto' => 'object_project', + ] +]; + +// Header +saturne_header(0, '', $langs->trans("KanbanList"), ''); + +// Title +print load_fiche_titre($langs->trans("KanbanList"), '', 'title_generic.png'); + +// Content +print ''; + +// End of page +llxFooter(); +$db->close();