Commit 1c1f45c2 authored by Samir Sadyhov's avatar Samir Sadyhov 🤔

канбан доска

parent fb9f605e
.kanban-board-container {
display: flex;
width: 100%;
height: calc(100vh - 50px);
}
.kanban-board-content {
display: flex;
flex-direction: row;
justify-content: flex-start;
margin: 0;
overflow: auto;
width: 100%;
height: calc(100vh - 50px);
gap: 5px;
}
.column-container {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
flex: 1;
min-width: 200px;
border-left: 1px dashed var(--gray);
border-right: 1px dashed var(--gray);
}
.column-title {
display: flex;
justify-content: center;
align-items: center;
font-weight: 600;
user-select: none;
background: var(--blue);
color: #fff;
width: 100%;
min-height: 75px;
padding: 0 5px;
text-align: center;
}
.column-sum {
font-size: 20px;
font-weight: 300;
color: #666;
width: 100%;
min-height: 40px;
text-align: center;
line-height: 40px;
user-select: none;
}
.column-data {
display: flex;
flex-flow: wrap;
justify-content: center;
align-items: flex-start;
align-content: flex-start;
width: 100%;
background: #fff;
height: 100%;
overflow: auto;
gap: 15px;
padding: 10px 5px
}
.column-data::-webkit-scrollbar-button {
width: 4px;
height: 0px;
}
.column-data::-webkit-scrollbar-thumb {
-webkit-border-radius: 0px;
border-radius: 0px;
background-color: rgba(145, 194, 246, 0.5);
}
.column-data::-webkit-resizer {
width: 4px;
height: 0px;
}
.column-data::-webkit-scrollbar {
width: 4px;
}
.column-card {
display: flex;
flex-direction: column;
min-height: 100px;
height: fit-content;
width: 100%;
background: #fff;
border: 1px solid var(--gray);
border-radius: 3px;
overflow: hidden;
padding: 5px;
user-select: none;
}
.column-card-block {
color: #333;
font-size: 12px;
transition: 0.3s;
cursor: pointer;
display: flex;
gap: 5px;
overflow: hidden;
}
.column-card-block.title {
color: #333;
font-size: 14px;
transition: 0.3s;
cursor: pointer;
margin-bottom: 10px;
}
.column-card-block.title:hover {
color: #1e87f0;
}
.column-card-block.sum {
color: #666;
font-size: 12px;
margin-bottom: 5px;
}
.column-card-block.reglink {
color: #1e87f0;
font-size: 12px;
display: flex;
gap: 5px;
transition: 0.3s;
cursor: pointer;
}
.column-card-block.reglink:hover {
text-decoration: underline;
}
.column-card-block.user {
color: #999;
font-size: 12px;
display: flex;
gap: 5px;
margin-top: 3px;
}
.column-get-rows-button {
display: block;
margin: 10px;
user-select: none;
cursor: pointer;
transition: 0.3s;
letter-spacing: 3px;
font-size: 12px;
font-family: monospace;
}
.column-get-rows-button:hover {
color: var(--blue);
letter-spacing: 5px;
}
.board-context-menu {
position: absolute;
display: none;
background-color: #fff;
box-shadow: 0px 2px 10px rgba(0, 0, 0, 0.2);
padding: 0px;
min-width: 200px;
width: auto;
}
.board-context-menu ul {
list-style: none;
margin: 0;
padding: 0;
}
.board-context-menu ul li {
margin: 0;
padding: 0;
background-color: #fff;
display: block;
}
.board-context-menu ul li a {
color: #555;
padding: 10px;
}
.board-context-menu ul li a:hover {
background-color: rgb(255, 165, 0, 0.3);
}
.board-context-menu ul .uk-disabled a {
color: #dedede;
}
<div class="kanban-board-container">
<div class="kanban-board-content"></div>
</div>
const compContainer = $(`#${comp.code}`);
const boardContent = compContainer.find('.kanban-board-content');
const getRegistryList = async () => {
const {registryList} = Cons.getAppStore();
return new Promise(async resolve => {
if(registryList) {
resolve(registryList);
} else {
const list = await appAPI.getRegistryList();
list ? resolve(UTILS.parseRegistryList(list)) : resolve(null);
}
});
}
const KanbanBoard = {
registryCode: null,
registryID: null,
registryName: '',
rights: [],
filterCode: null,
formCode: null,
formId: null,
formName: '',
statusDict: null,
fields: null,
fieldDict: null,
countInPart: 5,
columns: [],
searchString: null,
sum: null,
pageCode: null,
stringLength: 50,
getUrlParam: function(column){
const {key, currentPage, countInPart} = column;
let param = `?registryCode=${this.registryCode}`;
if(this.filterCode) param += `&filterCode=${this.filterCode}`;
if(this.fields && this.fields.length > 0) {
this.fields.forEach(x => param += `&fields=${x.code}`);
param+=`&fields=${this.fieldDict}`;
}
if(this.searchString) param += `&searchString=${this.searchString}`;
if(key) param += `&field=${this.fieldDict}&condition=CONTAINS&key=${key}`;
param += `&pageNumber=${currentPage}&countInPart=${countInPart}`;
return param;
},
searchInRegistry: async function(param){
return new Promise(resolve => {
rest.synergyGet(`api/registry/data_ext${param}`, resolve, err => {
console.log(`KanbanBoard ERROR [ searchInRegistry ]: ${JSON.stringify(err)}`);
resolve(null);
});
});
},
getSumRowInRegistry: async function(fieldCode, key){
let param = `?registryCode=${this.registryCode}`;
if(this.filterCode) param += `&filterCode=${this.filterCode}`;
if(this.searchString) param += `&searchString=${this.searchString}`;
if(key) param += `&field=${this.fieldDict}&condition=CONTAINS&key=${key}`;
param += `&fieldCode=${fieldCode}&countType=sum`;
return new Promise(resolve => {
rest.synergyGet(`api/registry/count_data${param}`, resolve, err => {
console.log(`KanbanBoard ERROR [ getSumRowInRegistry ]: ${JSON.stringify(err)}`);
resolve(null);
});
});
},
getDictInfo: async function(){
const {code, title, value, color} = this.statusDict;
const {columns, items, name} = await appAPI.getDictionary(code);
this.columns = [];
for(let key in items) {
const item = items[key];
const parseItem = {
key: item[value]?.value,
countInPart: this.countInPart,
currentPage: 0
};
if(item.hasOwnProperty(title)) {
if(item[title].hasOwnProperty('translations')) {
parseItem.name = item[title]?.translations[AS.OPTIONS.locale];
} else {
parseItem.name = item[title]?.value;
}
}
if(color) parseItem.color = item[color]?.value;
this.columns.push(parseItem);
}
this.columns.sort((a,b) => a.key - b.key);
},
allowDrop: function(event) {
event.preventDefault();
},
drag: function(param) {
const {event, dataUUID, documentID} = param;
const parentColumnID = event.target.dataset.parentcolumnid;
event.originalEvent.dataTransfer.setData("dataUUID", dataUUID);
event.originalEvent.dataTransfer.setData("documentID", documentID);
event.originalEvent.dataTransfer.setData("parentColumnID", parentColumnID);
},
openDocument: function(dataUUID, updateData = false) {
if(this.rights.includes('rr_read')) {
if(updateData) {
openFormPlayer(dataUUID, null, () => {
this.reset();
this.render();
});
} else {
openFormPlayer(dataUUID);
}
} else {
showMessage('У вас нет прав на просмотр', 'warning');
}
},
cutString: function(str) {
return str.length > this.stringLength ? `${str.substr(0, this.stringLength)}...` : str;
},
getTitleBlock: function(text, dataUUID){
const block = $('<div>', {class: 'column-card-block title'});
const blockValue = $('<div>', {class: 'column-card-block-value'});
block.append(blockValue);
blockValue.text(this.cutString(text));
blockValue.attr('uk-tooltip', `title: ${text}; duration: 300;`);
block.on('click', e => {
this.openDocument(dataUUID, true);
});
return block;
},
getSumBlock: function(sum, prefix){
const block = $('<div>', {class: 'column-card-block sum'});
const blockValue = $('<div>', {class: 'column-card-block-value'});
block.append(blockValue);
blockValue.text(`${sum} ${prefix}`);
return block;
},
getReglinkBlock: function(title, code, fieldValue, fieldKey){
if(!fieldKey.hasOwnProperty(code)) return;
const block = $('<div>', {class: 'column-card-block reglink'});
const blockTitle = $('<div>', {class: 'column-card-block-title'});
const blockValue = $('<div>', {class: 'column-card-block-value'});
block.append(blockTitle, blockValue);
blockTitle.text(`${title}:`);
blockValue.text(fieldValue[code]);
block.on('click', async e => {
const dataUUID = await appAPI.getAsfDataUUID(fieldKey[code]);
this.openDocument(dataUUID);
});
return block;
},
getUserBlock: function(title, value){
const block = $('<div>', {class: 'column-card-block user'});
const blockTitle = $('<div>', {class: 'column-card-block-title'});
const blockValue = $('<div>', {class: 'column-card-block-value'});
block.append(blockTitle, blockValue);
blockTitle.text(`${title}:`);
blockValue.text(value);
return block;
},
getDefaultBlock: function(title, value){
const block = $('<div>', {class: 'column-card-block'});
const blockTitle = $('<div>', {class: 'column-card-block-title'});
const blockValue = $('<div>', {class: 'column-card-block-value'});
block.append(blockTitle, blockValue);
blockTitle.text(`${title}:`);
blockValue.text(value);
return block;
},
removeRegistryRow: function(dataUUID, e) {
e.preventDefault();
e.target.blur();
UIkit.modal.confirm('Вы действительно хотите удалить запись?').then(() => {
Cons.showLoader();
try {
rest.synergyGet(`api/registry/delete_doc?dataUUID=${dataUUID}`, res => {
if(res.errorCode != '0') throw new Error(res.errorMessage);
showMessage("Запись удалена", "success");
this.render();
Cons.hideLoader();
});
} catch (err) {
Cons.hideLoader();
showMessage("Произошла ошибка при удалении записи", "error");
console.log(error);
}
}, () => null);
},
contextMenu: function(el, dataRow){
let isDelete = false;
if(this.rights.indexOf('rr_delete') !== -1 && dataRow.status != 'STATE_NOT_FINISHED') isDelete = true;
el.on('contextmenu', event => {
$('.board-context-menu').remove();
$('<div>', {class: 'board-context-menu'})
.css({
"left": event.pageX + 'px',
"top": event.pageY + 'px'
})
.appendTo('body')
.append(
$('<ul class="uk-nav-default uk-nav-parent-icon" uk-nav>')
.append($(`<li ><a href="javascript:void(0);"><span class="uk-margin-small-right" uk-icon="icon: push"></span>Открыть</a></li>`)
.on('click', e => {
this.openDocument(dataRow.dataUUID, true);
})
)
.append('<li class="uk-nav-divider"></li>')
.append($(`<li ${isDelete ? '' : 'class="uk-disabled"'} ><a href="javascript:void(0);"><span class="uk-margin-small-right" uk-icon="icon: trash"></span>Удалить запись</a></li>`)
.on('click', e => {
isDelete ? this.removeRegistryRow(dataRow.dataUUID, e) : false;
})
)
)
.show('fast');
return false;
});
},
getColumnCard: function(item, columnID){
const {dataUUID, documentID, fieldValue, fieldKey} = item;
const cardContainer = $('<div>', {class: 'column-card', id: `card-${dataUUID}`});
cardContainer.attr('data-uuid', dataUUID);
cardContainer.attr('data-documentid', documentID);
cardContainer.attr('data-parentcolumnid', columnID);
cardContainer.attr('draggable', true);
for(let i = 0; i < this.fields.length; i++) {
const {title, code, type, prefix = ''} = this.fields[i];
if(!fieldValue.hasOwnProperty(code)) continue;
switch (type) {
case 'title': cardContainer.append(this.getTitleBlock(fieldValue[code], dataUUID)); break;
case 'sum': cardContainer.append(this.getSumBlock(fieldValue[code], prefix)); break;
case 'reglink': cardContainer.append(this.getReglinkBlock(title, code, fieldValue, fieldKey)); break;
case 'user': cardContainer.append(this.getUserBlock(title, fieldValue[code])); break;
default: cardContainer.append(this.getDefaultBlock(title, fieldValue[code])); break;
}
}
cardContainer.on("dragstart", event => this.drag({event, dataUUID, documentID}));
this.contextMenu(cardContainer, item);
return cardContainer;
},
renderColumn: async function(column, data){
const {name, key, color, recordsCount} = column;
const columnContainer = $('<div>', {class: 'column-container', id: `column-${key}`});
const columnTitle = $('<span>', {class: 'column-title'});
const columnSum = $('<span>', {class: 'column-sum'});
const columData = $('<div>', {class: 'column-data'});
const getRowsButton = $('<span>', {class: 'column-get-rows-button'});
let rowsButtonIsHidden = true;
columnContainer.append(columnTitle, columnSum, columData);
boardContent.append(columnContainer);
if(this.sum) {
const {cmp, prefix = ''} = this.sum;
const sumResult = await this.getSumRowInRegistry(cmp, key);
const sum = new Intl.NumberFormat('ru-RU').format(Number(sumResult[`${cmp}_0`]) || 0) + ` ${prefix}`;
columnSum.text(sum);
} else {
columnSum.hide();
}
if(color) columnTitle.css('background', color);
columnTitle.text(`${name} (${recordsCount})`);
columnTitle.attr('uk-tooltip', `title: ${name} (${recordsCount}); duration: 300;`);
getRowsButton.text('ещё...');
data.forEach(item => {
const card = this.getColumnCard(item, key);
columData.append(card);
});
if(column.countInPart < recordsCount) {
columData.append(getRowsButton);
rowsButtonIsHidden = false;
getRowsButton.on('click', async e => {
Cons.showLoader();
column.currentPage++;
const searchResult = await this.searchInRegistry(this.getUrlParam(column));
if(searchResult && searchResult.recordsCount > 0) {
searchResult.result.forEach(item => {
const card = this.getColumnCard(item, key);
columData.append(card);
if((column.countInPart * (column.currentPage + 1)) < recordsCount) {
getRowsButton.detach().appendTo(columData);
rowsButtonIsHidden = false;
} else {
getRowsButton.remove();
rowsButtonIsHidden = true;
}
});
} else {
getRowsButton.hide();
rowsButtonIsHidden = true;
}
Cons.hideLoader();
});
}
columData.on('drop', async event => {
event.preventDefault();
const dataUUID = event.originalEvent.dataTransfer.getData("dataUUID");
const parentColumnID = event.originalEvent.dataTransfer.getData("parentColumnID");
const dataColumn = event.target.closest('.column-data');
const parentColumn = this.columns.find(x => x.key == parentColumnID);
if(parentColumn.key == column.key) return;
Cons.showLoader();
const mergeResult = await appAPI.mergeFormData({
uuid: dataUUID,
data: [{
id: this.fieldDict,
type: "listbox",
value: column.name,
key: column.key
}]
});
if(mergeResult) {
dataColumn.appendChild(document.getElementById(`card-${dataUUID}`));
parentColumn.recordsCount--;
column.recordsCount++;
columnTitle.text(`${column.name} (${column.recordsCount})`);
$(`#column-${parentColumnID} > .column-title`).text(`${parentColumn.name} (${parentColumn.recordsCount})`);
$(`#card-${dataUUID}`).attr('data-parentcolumnid', column.key);
if(this.sum) {
const {cmp, prefix = ''} = this.sum;
const parentSumResult = await this.getSumRowInRegistry(cmp, parentColumnID);
const currentSumResult = await this.getSumRowInRegistry(cmp, key);
const parentSum = new Intl.NumberFormat('ru-RU').format(Number(parentSumResult[`${cmp}_0`]) || 0) + ` ${prefix}`;
const currentSum = new Intl.NumberFormat('ru-RU').format(Number(currentSumResult[`${cmp}_0`]) || 0) + ` ${prefix}`;
columnSum.text(currentSum);
$(`#column-${parentColumnID} > .column-sum`).text(parentSum);
}
if(!rowsButtonIsHidden) getRowsButton.detach().appendTo(columData);
} else {
showMessage("Произошла ошибка при смене статуса", 'error');
}
Cons.hideLoader();
}).on('dragover', this.allowDrop);
},
render: async function(){
boardContent.empty();
for(let i = 0; i < this.columns.length; i++) {
const column = this.columns[i];
const url = this.getUrlParam(column);
const searchResult = await this.searchInRegistry(url);
column.recordsCount = searchResult.recordsCount;
this.renderColumn(column, searchResult.result);
}
},
reset: function(){
this.columns.forEach(column => column.currentPage = 0);
},
init: async function(params){
Cons.showLoader();
try {
const {
registryCode,
filterCode = null,
statusDict,
fields,
fieldDict,
countInPart = 5,
sum,
searchString = null
} = params;
if(!registryCode) throw new Error(`Не передан параметр registryCode`);
if(!statusDict) throw new Error(`Не передан параметр statusDict`);
if(!fieldDict) throw new Error(`Не передан параметр fieldDict`);
const registryList = await getRegistryList();
const registry = registryList.find(x => x.registryCode == registryCode);
if(registry) {
if(!registry.rights.includes("rr_list")) throw new Error(`Нет прав на просмотр данного реестра`);
const info = await appAPI.getRegistryInfo(registryCode);
this.registryCode = registryCode;
this.filterCode = filterCode;
this.registryID = info.registryID;
this.registryName = registry.registryName;
this.rights = registry.rights;
this.formCode = info.formCode;
this.formId = info.formId;
this.formName = info.name;
this.statusDict = statusDict;
this.fields = fields;
this.fieldDict = fieldDict;
this.countInPart = countInPart;
this.sum = sum;
this.searchString = searchString;
await this.getDictInfo();
this.render();
Cons.hideLoader();
} else {
throw new Error(`Не найдено реестра с кодом: ${registryCode}`);
}
} catch (err) {
Cons.hideLoader();
console.log('ERROR init kanban board', err);
showMessage(err.message, 'error');
}
}
}
compContainer.off()
.on('renderNewBoard', e => {
if(!e.hasOwnProperty('eventParam')) return;
KanbanBoard.init(e.eventParam);
}).on('updateBoard', e => {
KanbanBoard.reset();
KanbanBoard.render();
}).on('searchInRegistry', e => {
if(!e.hasOwnProperty('eventParam')) return;
if(e.eventParam.searchString && e.eventParam.searchString !== "") {
KanbanBoard.searchString = e.eventParam.searchString;
} else {
KanbanBoard.searchString = null;
}
KanbanBoard.reset();
KanbanBoard.render();
});
$(document).off()
.on('contextmenu', () => $('.board-context-menu').remove())
.on('click', () => $('.board-context-menu').remove());
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment