UNPKG

daniil_shornikov-kanban1

Version:

A basic JavaScript math library for number addition

1,288 lines (1,181 loc) 86 kB
class Kanban { constructor(initialState = null, containerId = 'kanban-board',language) { const columnTranslations = { en: ["Starting Tasks", "Main Tasks", "Side Tasks"], ru: ["Стартовые задачи", "Основные задачи", "Побочные задачи"], }; this.translations = { en: { addTask: "Add New Task", taskText: "Task Text:", taskPlaceholder: "Enter task text", taskStatus: "Status:", taskDate: "Date:", confirmTaskButton: "Add Task", addAuthor: "Add New Author", authorName: "Author Name:", authorPlaceholder: "Enter author name", authorColor: "Author Color:", confirmAuthorButton: "Add Author", deleteAuthor: "Delete Author", selectAuthor: "Select Author:", confirmDeleteButton: "Delete Author", addColumn: "Add New Column", columnName: "Column Name:", columnPlaceholder: "Enter column name", confirmColumnButton: "Add Column", selectMode: "Select Add Mode:", closeButton: "Close", complete: "Complete", cancel: "Cancel", edit: "Edit", delete: "Delete", add:"Add", save:"Save", }, ru: { addTask: "Добавить новую задачу", taskText: "Текст задачи:", taskPlaceholder: "Введите текст задачи", taskStatus: "Статус:", taskDate: "Дата:", confirmTaskButton: "Добавить задачу", addAuthor: "Добавить нового автора", authorName: "Имя исполнителя:", authorPlaceholder: "Введите имя исполнителя", authorColor: "Цвет исполнителя:", confirmAuthorButton: "Добавить автора", deleteAuthor: "Удалить автора", selectAuthor: "Выберите исполнителя:", confirmDeleteButton: "Удалить автора", addColumn: "Добавить новую колонку", columnName: "Имя колонки:", columnPlaceholder: "Введите имя колонки", confirmColumnButton: "Добавить колонку", selectMode: "Выберите режим добавления:", closeButton: "Закрыть", add:"Добавить", complete: "Выполнить", cancel: "Отменить", edit: "Редактировать", delete: "Удалить", save:"Сохранить" } }; this.language = language||initialState?.language||"en"; document.documentElement.setAttribute('lang', this.language); this.columns = initialState?.columns || columnTranslations[this.language]; this.tasks = initialState?.tasks || []; this.authors = initialState?.authors || []; this.containerId = containerId; this.taskIdCounter = this.tasks.length > 0 ? Math.max(...this.tasks.map(task => task.id)) + 1 : 1; this.draggedTask = null; this.draggedColumn = null; this.dragOffsetX = 0; this.dragOffsetY = 0; this.editingTaskId = null; this.selectedMode = 'task'; this.trash_can = initialState?.trash || true; this.recycler_name = "recycler"; this.settingsPanelVisible = false; this.minWidthColumn = 250; this.startminWidthColumn = 250; this.padding = 20; this.paddingColumn = 20; this.minWidthRecycler = 100; this.switchColumn = false; this.sortdate = false; this.max_height_column=350; this.reversedate = false;// Добавлена переменная sortdate this.switchTasks = true this.buttonChanges = false; this.buttonSwitchColumn = false; this.moveCompletedTasksToEnd = true; // Добавлено свойство this.functions = initialState?.functions||{ add_author:this.handleAddAssigneeToTask.bind(this), edit: this.handleEdit.bind(this), delete_author:this.handleDeleteAssigneeToTask.bind(this), } this.icons=initialState?.icons||{ url_add: "", hover_add:"blue", background_add:"white", size_add:"50%", url_delete:"", hover_delete:"#2980b9", background_delete:"white", size_delete:"50%", url_add_author:"", url_del_author:"", hover_add_author:"#2980b9", hover_del_author:"#2980b9", background_add_author:"white", background_del_author:"white", size_add_author:"50%", size_del_author:"50%", url_close:"", hover_close:"#2980b9", background_close:"white", size_close:"100%", menu_color:"white", menu_size:"24px", }; this.taskStyles = initialState?.taskStyles || { // стили задач по умолчанию textColor: '#333333', fontFamily: 'Arial, sans-serif', backgroundColor: '#ffffff', fontSize: '1em', completedTaskColor: '#d4edda' // Зеленый цвет для выполненных задач по умолчанию }; this.columnStyles = initialState?.columnStyles || { // стили колонок по умолчанию headerColor: '#3498db', backgroundColor: '#ffffff', edit_width:"200px", }; this.mainCSSVariables = initialState?.mainCSSVariables || { primaryColor: '#3498db', secondaryColor: '#2ecc71', backgroundColor: '#ecf0f1', cardBackground: '#ffffff', textColor: '#333333', borderColor: '#cccccc', shadowColor: 'rgba(0, 0, 0, 0.1)', fontFamily: 'Arial, sans-serif' }; this.applyStyles(); this.render(); this.render() } appendHTMLToStyle() { const styleElement = document.querySelector('style'); if (styleElement) { let newStyle = styleElement.innerHTML.replace(/--kanban-column-min-width:.*?;/, `--kanban-column-min-width: ${this.minWidthColumn}px;`); if (this.columns.includes(this.recycler_name)) { newStyle = newStyle.replace(/--kanban-recycler-width:.*?;/, `--kanban-recycler-width: 250px;`); } else { newStyle = newStyle.replace(/--kanban-recycler-width:.*?;/, `--kanban-recycler-width: 0px;`); } styleElement.innerHTML = newStyle; } else { console.error("Тег <style> с селектором не найден."); } } seticonsVariables(styles) { this.icons = { ...this.icons, ...styles }; this._updateCSSVariables(); } setfunctions(func) { this.functions = { ...this.functions, ...func }; this.render(); } setMainCSSVariables(variables) { this.mainCSSVariables = { ...this.mainCSSVariables, ...variables }; this._updateCSSVariables(); } setTaskStyles(styles) { this.taskStyles = { ...this.taskStyles, ...styles }; this._updateCSSVariables(); } setColumnStyles(styles) { this.columnStyles = { ...this.columnStyles, ...styles }; this._updateCSSVariables(); } _updateCSSVariables() { const styleElement = document.querySelector('style'); if (!styleElement) { console.error("Тег <style> с селектором не найден."); return; } try { let newStyle = styleElement.innerHTML; // Обновляем основные CSS-переменные for (const [key, value] of Object.entries(this.mainCSSVariables)) { newStyle = newStyle.replace(new RegExp(`(--${this._camelToKebab(key)}):\\s*[^;]+;`, 'g'), `\$1: ${value};`); } // Обновляем стили задач newStyle = newStyle.replace(/--task-text-color:.*?;/, `--task-text-color: ${this.taskStyles.textColor};`); newStyle = newStyle.replace(/--task-font-family:.*?;/, `--task-font-family: ${this.taskStyles.fontFamily};`); newStyle = newStyle.replace(/--task-background-color:.*?;/, `--task-background-color: ${this.taskStyles.backgroundColor};`); newStyle = newStyle.replace(/--task-font-size:.*?;/, `--task-font-size: ${this.taskStyles.fontSize};`); newStyle = newStyle.replace(/--completed-task-color:.*?;/, `--completed-task-color: ${this.taskStyles.completedTaskColor};`); // Обновляем стили колонок newStyle = newStyle.replace(/--edit-width:.*?;/, `--edit-width: ${this.columnStyles.edit_width};`); const match = newStyle.match(/--edit-width:.*?;/) newStyle = newStyle.replace(/--column-header-color:.*?;/, `--column-header-color: ${this.columnStyles.headerColor};`); newStyle = newStyle.replace(/--column-background-color:.*?;/, `--column-background-color: ${this.columnStyles.backgroundColor};`); newStyle = newStyle.replace(/--url-add:.*?;/g, `--url-add: url(${this.icons.url_add});`); newStyle = newStyle.replace(/--url-delete:.*?;/g, `--url-delete: url(${this.icons.url_delete});`); newStyle = newStyle.replace(/--url-add-author:.*?;/g, `--url-add-author: url(${this.icons.url_add_author});`); newStyle = newStyle.replace(/--url-del-author:.*?;/g, `--url-del-author: url(${this.icons.url_del_author});`); newStyle = newStyle.replace(/--url-close:.*?;/g, `--url-close: url(${this.icons.url_close});`); newStyle = newStyle.replace(/--hover-add:.*?;/g, `--hover-add: ${this.icons.hover_add};`); newStyle = newStyle.replace(/--hover-delete:.*?;/g, `--hover-delete: ${this.icons.hover_delete};`); newStyle = newStyle.replace(/--hover-add-author:.*?;/g, `--hover-add-author: ${this.icons.hover_add_author};`); newStyle = newStyle.replace(/--hover-del-author:.*?;/g, `--hover-del-author: ${this.icons.hover_del_author};`); newStyle = newStyle.replace(/--hover-close:.*?;/g, `--hover-close: ${this.icons.hover_close};`); newStyle = newStyle.replace(/--background-add:.*?;/g, `--background-add: ${this.icons.background_add};`); newStyle = newStyle.replace(/--background-delete:.*?;/g, `--background-delete: ${this.icons.background_delete};`); newStyle = newStyle.replace(/--background-add-author:.*?;/g, `--background-add-author: ${this.icons.background_add_author};`); newStyle = newStyle.replace(/--background-del-author:.*?;/g, `--background-del-author: ${this.icons.background_del_author};`); newStyle = newStyle.replace(/--background-close:.*?;/g, `--background-close: ${this.icons.background_close};`); newStyle = newStyle.replace(/--size-add:.*?;/g, `--size-add: ${this.icons.size_add};`); newStyle = newStyle.replace(/--size-delete:.*?;/g, `--size-delete: ${this.icons.size_delete};`); newStyle = newStyle.replace(/--size-add-author:.*?;/g, `--size-add-author: ${this.icons.size_add_author};`); newStyle = newStyle.replace(/--size-del-author:.*?;/g, `--size-del-author: ${this.icons.size_del_author};`); newStyle = newStyle.replace(/--size-close:.*?;/g, `--size-close: ${this.icons.size_close};`); // Обновляем стиль styleElement.innerHTML = newStyle; this.render() } catch (error) { console.error("Произошла ошибка при обновлении CSS-переменных:", error); } } _camelToKebab(camelCaseString) { return camelCaseString.replace(/([A-Z])/g, '-$1').toLowerCase(); } handleModeChange(mode) { this.selectedMode = mode; this.render(); } calculateColumnsPerRow(availableWidth, totalColumns) { const maxstr = Math.ceil(availableWidth / (this.startminWidthColumn + this.padding)); const result = Math.ceil(totalColumns / maxstr); return Math.ceil(totalColumns / result); } applyStyles() { const style = document.createElement('style'); style.innerHTML = ` /* Общие стили */ :root { --primary-color: ${this.mainCSSVariables.primaryColor}; --secondary-color: ${this.mainCSSVariables.secondaryColor}; --background-color: ${this.mainCSSVariables.backgroundColor}; --card-background: ${this.mainCSSVariables.cardBackground}; --text-color: ${this.mainCSSVariables.textColor}; --border-color: ${this.mainCSSVariables.borderColor}; --shadow-color: ${this.mainCSSVariables.shadowColor}; --font-family: ${this.mainCSSVariables.fontFamily}; --kanban-column-min-width: ${this.minWidthColumn}px; --kanban-recycler-color: #777777; /* Серый цвет для recycler */ --kanban-recycler-min-width: ${this.minWidthRecycler}px; /* Изначальная ширина 0 */ --settings-panel-display: none; /* Изначально скрываем панель */ --close-button-color: red; --task-menu-background: var(--card-background); --task-menu-border: 1px solid var(--border-color); --task-menu-shadow: 0 2px 5px var(--shadow-color); /* Стили для задач */ --task-text-color: ${this.taskStyles.textColor}; --task-font-family: ${this.taskStyles.fontFamily}; --task-background-color: ${this.taskStyles.backgroundColor}; --task-font-size: ${this.taskStyles.fontSize}; --completed-task-color: ${this.taskStyles.completedTaskColor}; /* Стили для колонок */ --edit-width: ${this.columnStyles.edit_width}; --column-header-color: ${this.columnStyles.headerColor}; --column-background-color: ${this.columnStyles.backgroundColor}; /* Иконки добавления */ --url-add:url(${this.icons.url_delete}); --url-delete:url(${this.icons.url_delete}); --url-add-author:url(${this.icons.url_add_author}); --url-del-author:url(${this.icons.url_del_author}); --url-close:url(${this.icons.url_close}); --hover-add:${this.icons.hover_add}; --hover-delete:${this.icons.hover_delete}; --hover-add-author:${this.icons.hover_add_author}; --hover-del-author:${this.icons.hover_del_author}; --hover-close:${this.icons.hover_close}; --background-add:${this.icons.background_add}; --background-delete:${this.icons.background_delete}; --background-add-author:${this.icons.background_add_author}; --background-del-author:${this.icons.background_del_author}; --background-close:${this.icons.background_close}; --size-add:${this.icons.size_add}; --size-delete:${this.icons.size_delete}; --size-add-author:${this.icons.size_add_author}; --size-del-author:${this.icons.size_del_author}; --size-close:${this.icons.size_close}; --menu-color:${this.icons.menu_color}; --menu-size:${this.icons.menu_size}; /* Иконки удаления */ } .board-container { margin: 15px auto; /* Уменьшили отступ */ padding: 5px; /* Уменьшили паддинг */ box-sizing: border-box; position: relative; } body{ overflow-wrap: break-word; } #${this.containerId} { color: var(--text-color); font-family: var(--font-family); background-color: var(--background-color); width: 100%; display: flex; flex-direction:row; align-items: flex-start; box-sizing: border-box; border-radius: 8px; /* Уменьшили радиус */ background-color: var(--background-color); box-shadow: 0 4px 10px var(--shadow-color); /* Уменьшили тень */ } .kanban-columns-wrapper { display: flex; width: 100%; } .kanban-columns-container { flex: 1; display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: flex-start; box-sizing: border-box; align-items: stretch; } .kanban-column { max-height: ${this.max_height_column}px;; flex: 1 0 var(--kanban-column-min-width); min-width: var(--kanban-column-min-width); padding: ${this.paddingColumn}px; margin: 10px; background-color: var(--column-background-color); border-radius: 10px; box-shadow: 0 3px 10px var(--shadow-color); display: flex; flex-direction: column; overflow-y: scroll; box-sizing: border-box; transition: background-color 0.3s ease; } .kanban-column:hover { background-color: #f2f2f2; } .kanban-column.recycler { background-color: var(--kanban-recycler-color); } .kanban-column-header { font-weight: bold; text-align: center; margin-bottom: 10px; /* Уменьшили отступ */ color: var(--column-header-color); font-size: 1em; /* Уменьшили размер шрифта */ border-bottom: 1px solid var(--border-color); /* Уменьшили толщину границы */ padding-bottom: 5px; /* Уменьшили паддинг */ } .kanban-task { background-color: var(--task-background-color); padding: 10px; margin-bottom: 5px; border: 1px solid var(--border-color); border-radius: 6px; cursor: pointer; position: relative; transition: transform 0.2s ease, box-shadow 0.2s ease; box-sizing: border-box; box-shadow: 0 1px 3px var(--shadow-color); font-family: var(--task-font-family); font-size: var(--task-font-size); color: var(--task-text-color); } .kanban-task.completed { background-color: var(--completed-task-color); opacity: 0.7; } .kanban-task:hover { transform: translateY(-3px); box-shadow: 0 4px 10px var(--shadow-color); } .widdth { width: 100%; max-width: 400px; box-sizing: border-box; margin-bottom: 20px; } .edit-button, .add-button,del-button { border: none; cursor: pointer; color: var(--primary-color); transition: color 0.3s ease; padding: 5px; border-radius: 5px; } .edit-button:hover, .add-button:hover,del-button:hover { color: var(--secondary-color); background-color: rgba(0, 0, 0, 0.05); } .add-button { top:5px; width:20px; height:20px; background-image: var(--url-add-author); background-color:var(--background-add-author); background-size: var(--size-add-author); background-repeat: no-repeat; background-position: center; color: white; border: none; border-radius: 5px; padding: 8px 12px; transition: background-color 0.3s ease; } .del-button { top:10px; margin:5px; width:20px; height:20px; background-image: var(--url-del-author); background-color:var(--background-del-author); background-size: var(--size-del-author); background-repeat: no-repeat; background-position: center; color: white; border: none; border-radius: 5px; padding: 8px 12px; transition: background-color 0.3s ease; } .add-button:hover { background-color:var(--hover-add-author); } .del-button:hover { background-color:var(--hover-del-author); } .settings-modal { width:100%; background: white; /* другие стили для вашего блока */ } .close-button { width:30px; height:30px; border-radius: 50%; color:var(--close-button-color); background-image: var(--url-close); background-color:var(--background-close); background-size: var(--size-close); background-repeat: no-repeat; background-position: center; position: absolute; top: 10px; right: 10px; cursor: pointer; font-size: 14px; } .edit { margin-top: 20px; padding: 15px; border: 1px solid var(--border-color); background-color: #ffffff; border-radius: 8px; width: var(--edit-width); height:auto; box-sizing: border-box; box-shadow: 0 2px 5px var(--shadow-color); } .edit input[type="text"], .edit input[type="date"], .edit select { width: 100%; padding: 8px; margin-bottom: 10px; border: 1px solid var(--border-color); border-radius: 5px; box-sizing: border-box; font-family: var(--font-family); } .styled-form label{ font-weight: bold; display: block; margin-bottom: 5px; } .styled-form input[type="text"], .styled-form input[type="date"], .styled-form select { width: 100%; padding: 10px; margin-bottom: 15px; border: 1px solid #ddd; border-radius: 5px; box-sizing: border-box; font-family: var(--font-family); transition: border-color 0.3s ease; } .styled-form input[type="text"]:focus, .styled-form input[type="date"]:focus, .styled-form select:focus{ border-color:var(--primary-color) } .styled-form input[type="text"]::placeholder, .styled-form input[type="date"]::placeholder, .styled-form select::placeholder { color: #aaa; } .styled-form{ background-color: var(--card-background); padding: 20px; border: 1px solid var(--border-color); border-radius: 10px; box-shadow: 0 4px 8px var(--shadow-color); transition: box-shadow 0.3s ease; margin-bottom: 20px; box-sizing: border-box; } .button-back{ background-color:black; width:43px; height:43px; border:2px solid; } .styled-button { background-color: var(--primary-color); color: white; border: none; border-radius: 5px; padding: 10px 20px; cursor: pointer; transition: background-color 0.3s ease; font-family: var(--font-family); font-size: 1em; box-shadow: 0 2px 5px var(--shadow-color); margin-top: 10px } .styled-form button:hover{ background-color: #2980b9; } .styled-button:hover { background-color: #2980b9; } #root { --column-count: 4; } [data-column-count="1"] { --column-count: 1; } [data-column-count="2"] { --column-count: 2; } [data-column-count="3"] { --column-count: 3; } [data-column-count="4"] { --column-count: 4; } [data-column-count="5"] { --column-count: 5; } [data-column-count="6"] { --column-count: 6; } .edit button { background-color: var(--primary-color); color: white; border: none; border-radius: 5px; padding: 8px 12px; cursor: pointer; transition: background-color 0.3s ease; font-family: var(--font-family); } .edit button:hover { background-color: #2980b9; } .task-date { font-size: 0.8em; color: #777; text-align: right; margin-top: 5px; } .authors-container { display: flex; align-items: center; flex-wrap: wrap; margin-top: 10px; } .assignee-circle { width: 25px; height: 25px; border-radius: 50%; margin-right: 5px; display: flex; align-items: center; justify-content: center; font-size: 0.8em; color: white; box-shadow: 0 1px 3px var(--shadow-color); } /* Стили для кнопок выбора режима */ .controls { display: flex; justify-content: flex-start; /* Выравнивание по левому краю */ gap: 10px; width: 100%; box-sizing: border-box; margin-bottom: 0px; background-color: var(--card-background); /* Общий фон */ padding: 15px; border-radius: 10px; } .radio-button { flex: 1; padding: 8px; background-color: #dfe6e9; text-align: center; cursor: pointer; border: 1px solid var(--border-color); transition: background-color 0.3s ease, color 0.3s ease; box-sizing: border-box; border-radius: 5px; color: var(--text-color); } .active { background-color: #4CAF50; /* Зеленый цвет для активного состояния */ color: white; } .inactive { background-color: #f44336; /* Красный цвет для неактивного состояния */ color: white; } .radio-button:hover { background-color: #c7ecee; } input[type="radio"] { display: none; } input[type="radio"]:checked + .radio-button { background-color: var(--primary-color); color: white; font-weight: bold; } .content-block { width:100%; border: 1px solid var(--border-color); border-radius: 2px; box-sizing: border-box; padding: 5px; background-color: var(--card-background); box-shadow: 0 2px 2px var(--shadow-color); position: relative; /* Для позиционирования settings-button */ } /* Стили для кнопки настроек */ .settings-button { position: absolute; top: 10px; right: 10px; /* Отодвигаем немного вправо */ background-image: var(--url-add); background-color:var(--hover-add); background-size: var(--size-add); background-repeat: no-repeat; background-position: center; background-color: var(--primary-color); color: white; border: none; border-radius: 50%; width: 25px; height: 25px; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: all 0.3s ease; z-index: 1000; box-shadow: 2px 2px 5px var(--shadow-color); } .delete-button { top: 10px; width: 30px; height: 30px; right: 10px; /* Отодвигаем немного вправо */ background-image: var(--url-delete); background-color:var(--background-delete); background-size: var(--size-delete); background-repeat: no-repeat; background-position: center; color: white; border: none; border-radius: 50%; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: all 0.3s ease; z-index: 1000; box-shadow: 2px 2px 5px var(--shadow-color); } .change-button { position: absolute; top: 10px; left: 10px; /* Отодвигаем немного вправо */ color: white; border: none; width: 30px; height: 30px; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: all 0.3s ease; z-index: 1000; box-shadow: 2px 2px 5px var(--shadow-color); } .sort-button { position: absolute; top: 10px; right: 50px; /* Отодвигаем немного вправо */ color: white; border: none; width: 30px; height: 30px; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: all 0.3s ease; z-index: 1000; box-shadow: 2px 2px 5px var(--shadow-color); } .task-change-button { position: absolute; top: 10px; left: 50px; /* Отодвигаем немного вправо */ color: white; border: none; width: 30px; height: 30px; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: all 0.3s ease; z-index: 1000; box-shadow: 2px 2px 5px var(--shadow-color); } .settings-button:hover { background-color: var(--hover-add); } .delete-button:hover { background-color:var(--hover-delete); } .close-button:hover { background-color:var(--hover-close); } /* Стили для панели настроек */ .settings-panel { position: absolute; top: 0; right: 0; background-color: var(--card-background); box-shadow: -5px 0 15px var(--shadow-color); z-index:10000; padding: 20px; box-sizing: border-box; border-radius: 10px; transform: translateX(100%); transition: transform 0.3s ease-in-out; display: flex; flex-direction: column; align-items: flex-start; opacity: 0; visibility: hidden; } .settings-panel.open { transform: translateX(0); opacity: 1; visibility: visible; } /* Ensure the settings container is always on top */ .settings-container { position: absolute; top: 10px; right: 10px; z-index: 1002; /* Higher than content-block */ } .settings-content-wrapper { width: 100%; height: 100%; display: flex; flex-direction: column; align-items: flex-start; } .kanban-columns-wrapper { display: flex; width: 100%; } .kanban-columns-container { flex: 1; display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: flex-start; box-sizing: border-box; align-items: stretch; } .kanban-recycler-container { width: var(--kanban-recycler-width); } /* Styles for the task menu */ .task-menu-button { position: absolute; top: 5px; right: 5px; background: none; border: none; cursor: pointer; font-size: var(--menu-size); color: var(--menu-color); padding: 0; } .task-menu { position: absolute; background-color: var(--task-menu-background); border: var(--task-menu-border); border-radius: 5px; box-shadow: var(--task-menu-shadow); display: none; padding: 5px 0; z-index:10000; } .task-menu.open { display: block; } .task-menu button { display: block; width: 100%; padding: 8px 15px; border: none; background: none; text-align: left; cursor: pointer; transition: background-color 0.2s ease; font-family: var(--font-family); font-size: 1em; color: var(--text-color); } .task-menu button:hover { background-color: #f2f2f2; } `; document.head.appendChild(style); } toJson() { return JSON.stringify({ columns: this.columns, tasks: this.tasks, authors: this.authors, trash_can: this.trash_can, taskStyles: this.taskStyles, columnStyles: this.columnStyles, mainCSSVariables: this.mainCSSVariables, icons: this.icons, language:this.language }); } static fromJson(jsonString, containerId = 'kanban-board') { try { const parsed = JSON.parse(jsonString); return new Kanban(parsed, containerId); } catch (e) { console.error("Error creating Kanban from JSON", e); return null; } } fillColumnSelect() { const statusSelect = document.getElementById('taskStatus'); if (statusSelect) { statusSelect.innerHTML = ''; const statuses = this.columns; statuses.forEach(status => { const option = document.createElement('option'); option.value = status; option.textContent = status; statusSelect.appendChild(option); }); } } async updateContentBlock(contentBlock) { contentBlock.innerHTML = ''; const settingsForm = document.querySelector('.settings-panel form'); let selectedMode = settingsForm?.querySelector('input[name="addMode"]:checked')?.value || null; if (!selectedMode) { this.selectedMode = "task"; } else { this.selectedMode = selectedMode; } const t = this.translations[this.language]; // Получаем переводы для выбранного языка let form; if (this.selectedMode === 'task') { form = document.createElement('form'); form.innerHTML = ` <h3>${t.addTask}</h3> <label for="taskText">${t.taskText}</label> <input pattern=".*\\S.*" title="Пожалуйста, введите текст, не только пробелы." type="text" id="taskText" placeholder="${t.taskPlaceholder}" required> <label for="taskStatus">${t.taskStatus}</label> <select id="taskStatus" required></select> <label for="taskDate">${t.taskDate}</label> <input type="date" id="taskDate" required lang=${this.language}> <button type="submit" class="styled-button" id="confirmTaskButton">${t.confirmTaskButton}</button> `; contentBlock.appendChild(form); form.className = "styled-form"; this.fillColumnSelect(); form.addEventListener('submit', async (e) => { e.preventDefault(); const text = document.getElementById('taskText').value; const status = document.getElementById('taskStatus').value; const date = document.getElementById('taskDate').value; try { await this.addTask(text, status, date); form.reset(); } catch (e) { console.error(e); alert(e); } }); setTimeout(() => { this.fillColumnSelect(); }, 0); } else if (this.selectedMode === 'author') { form = document.createElement('form'); form.innerHTML = ` <h3>${t.addAuthor}</h3> <label for="authorName">${t.authorName}</label> <input pattern=".*\\S.*" title="Пожалуйста, введите текст, не только пробелы." type="text" id="authorName" placeholder="${t.authorPlaceholder}" required> <label for="authorColor">${t.authorColor}</label> <input type="color" id="authorColor" required><br> <button type="submit" class="styled-button" id="confirmAuthorButton">${t.confirmAuthorButton}</button> `; contentBlock.appendChild(form); form.className = "styled-form"; form.addEventListener('submit', async (e) => { e.preventDefault(); const name = document.getElementById('authorName').value; const color = document.getElementById('authorColor').value; try { await this.addAuthor(name, color); form.reset(); } catch (e) { console.error(e); alert(e); } }); } else if (this.selectedMode === 'authorDelete') { form = document.createElement('form'); form.innerHTML = ` <h3>${t.deleteAuthor}</h3> <label for="authorSelect">${t.selectAuthor}</label> <select id="authorSelect" required> <option value="" disabled selected>${t.selectAuthor}</option> </select> <button type="submit" class="styled-button" id="confirmDeleteButton">${t.confirmDeleteButton}</button> `; contentBlock.appendChild(form); form.className = "styled-form"; // Заполнение списка авторов const selectElement = form.querySelector('#authorSelect'); if (this.authors && this.authors.length > 0) { this.authors.forEach(author => { const option = document.createElement('option'); option.value = author.name; option.innerText = author.name; selectElement.appendChild(option); }); } form.addEventListener('submit', async (e) => { e.preventDefault(); const selectedAuthorName = selectElement.value; try { await this.deleteAuthor(selectedAuthorName); // Удаляем автора из локального списка this.authors = this.authors.filter(author => author.name !== selectedAuthorName); form.reset(); // Сброс формы после удаления this.render(); // Обновление интерфейса, если необходимо } catch (e) { console.error(e); alert(e); } }); } else if (this.selectedMode === 'column') { form = document.createElement('form'); form.innerHTML = ` <h3>${t.addColumn}</h3> <label for="columnName">${t.columnName}</label> <input pattern=".*\\S.*" title="Пожалуйста, введите текст, не только пробелы." type="text" id="columnName" placeholder="${t.columnPlaceholder}" required> <button type="submit" class="styled-button" id="confirmColumnButton">${t.confirmColumnButton}</button> `; contentBlock.appendChild(form); form.className = "styled-form"; form.addEventListener('submit', async (e) => { e.preventDefault(); const name = document.getElementById('columnName').value; try { await this.addColumn(name); form.reset(); } catch (e) { console.error(e); alert(e); } }); } if (form) { form.classList.add('styled-form'); } } async addTask(text, status, date, authors = []) { return new Promise((resolve, reject) => { if (!this.columns.includes(status)) { reject(new Error(`status ${status} doesn't exist`)); return; } try { const id = this.taskIdCounter++; const task = { id, text, status, authors, date, isCompleted: false, comments: [], }; this.tasks.push(task); this.render() resolve(task); } catch (error) { console.error("Error adding task:", error); reject(error); } }); } editTask(id, updateData) { return new Promise((resolve, reject) => { try { const taskIndex = this.tasks.findIndex(task => task.id === id); if (taskIndex === -1) { reject(new Error(`Task with id ${id} not found`)); return; } const updatedTask = { ...this.tasks[taskIndex], ...updateData }; this.tasks[taskIndex] = updatedTask; this.render(); resolve(updatedTask); } catch (error) { console.error("Error editing task:", error); reject(error); } }); } deleteTask(id) { return new Promise((resolve, reject) => { try { const taskIndex = this.tasks.findIndex(task => task.id === id); if (taskIndex === -1) { reject(new Error(`Task with id ${id} not found`)); return; } if (this.tasks[taskIndex].status == this.recycler_name) { this.tasks.splice(taskIndex, 1); const taskIndex2 = this.tasks.findIndex(task => task.status === this.recycler_name); if (taskIndex2 === -1) { this.deleteColumn(this.recycler_name) } } else { this.tasks.splice(taskIndex, 1); } this.render(); resolve(this.tasks); } catch (error) { console.error("Error deleting task:", error); reject(error); } }); } toggleCompleteTask(id) { return new Promise((resolve, reject) => { try { const taskIndex = this.tasks.findIndex(task => task.id === id); if (taskIndex === -1) { reject(new Error(`Task with id ${id} not found`)); return; } this.tasks[taskIndex].isCompleted = !this.tasks[taskIndex].isCompleted; this.render(); resolve(this.tasks[taskIndex]); } catch (error) { console.error("Error toggling task completion:", error); reject(error); } }); } getTasks() { return this } getColumns() { return this.columns; } addAuthor(name, color) { return new Promise((resolve, reject) => { try { const author = { name, color }; this.authors.push(author); this.render(); resolve(author); } catch (error) { console.error("Error adding author:", error); reject(error); } }); } deleteAuthor(name) { return new Promise((resolve, reject) => { try { const authorIndex = this.authors.findIndex(author => author.name === name); if (authorIndex === -1) { throw new Error("Автор не найден"); } // Удаляем автора из массива this.authors.splice(authorIndex, 1); this.render(); // Обновляем интерфейс resolve(name); // Возвращаем имя удаленного автора } catch (error) { console.error("Ошибка при удалении автора:", error); reject(error); } }); } addColumn(newColumn) { return new Promise((resolve, reject) => { try { if (this.columns.includes(newColumn)) { reject(new Error(`column ${newColumn} exists`)); return; } if (this.columns.includes(this.recycler_name)) { const index = this.columns.indexOf(this.recycler_name); this.columns.splice(index, 1) this.columns.push(newColumn); this.columns.push(this.recycler_name); } else { this.columns.push(newColumn); } this.render(); resolve(this.columns); } catch (e) { console.error('Error adding new column', e); reject(e); } }); } removeColumn(columnToRemove) { return new Promise((resolve, reject) => { try { if (!this.columns.includes(columnToRemove)) { reject(new Error(`column ${columnToRemove} doesn't exists`)); return; } this.columns = this.columns.filter(c => c !== columnToRemove);