Files
crmPRO/templates/tasks/task_popup.php
2026-03-17 19:14:37 +01:00

1152 lines
44 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<div class="task_details" task_id="<?= $this -> task['id'];?>">
<a href="#" class="close"><i class="fa fa-times"></i></a>
<div class="title">
<a href="/tasks/task_edit/task_id=<?= $this -> task['id'];?>" class="_edit">
<i class="fa fa-pencil"></i>
</a>
<? if ( $this -> user['id'] == 1 ):?>
<a href="#" class="task-delete" task_id="<?= $this -> task['id'];?>">
<i class="fa fa-trash"></i>
</a>
<? endif;?>
<span class="task-title-wrapper">
<span class="task-id">#<?= $this -> task['id'];?></span>
<span class="task-title-view">
<span class="task-title-text"><?= $this -> task['name'];?></span>
<a href="#" class="task-title-edit-btn" title="Edytuj tytuł"><i class="fa fa-pencil"></i></a>
</span>
<span class="task-title-edit-box" style="display: none;">
<input type="text" class="task-title-input form-control" value="<?= htmlspecialchars($this -> task['name']);?>">
<a href="#" class="task-title-save" title="Zapisz"><i class="fa fa-check"></i></a>
<a href="#" class="task-title-cancel" title="Anuluj"><i class="fa fa-times"></i></a>
</span>
</span>
</div>
<?
$checklist_count = is_array( $this -> task['actions'] ) ? count( $this -> task['actions'] ) : 0;
$comments_count = is_array( $this -> task['comments'] ) ? count( $this -> task['comments'] ) : 0;
$attachments_count = is_array( $this -> task_attachments ) ? count( $this -> task_attachments ) : 0;
$image_extensions = [ 'jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'svg' ];
$popup_wiki_data = \factory\Tasks::task_wiki_entries_for_user( (int)$this -> task['id'], (int)$this -> user['id'] );
$task_wiki_entries = isset( $popup_wiki_data['entries'] ) && is_array( $popup_wiki_data['entries'] ) ? $popup_wiki_data['entries'] : [];
$wiki_visible_count = isset( $popup_wiki_data['visible_count'] ) ? (int)$popup_wiki_data['visible_count'] : 0;
$wiki_hidden_count = isset( $popup_wiki_data['hidden_count'] ) ? (int)$popup_wiki_data['hidden_count'] : 0;
?>
<?
$task_description_html = htmlspecialchars_decode( (string)$this -> task['text'] );
if ( $task_description_html !== '' )
{
$task_description_html = preg_replace( '/<(script|style|iframe|object|embed|link|meta|base|form|input|button|textarea|select)\b[^>]*>.*?<\/\1>/is', '', $task_description_html );
$task_description_html = preg_replace( '/<(script|style|iframe|object|embed|link|meta|base|form|input|button|textarea|select)\b[^>]*\/?>/is', '', $task_description_html );
$task_description_html = preg_replace( '/\son[a-z]+\s*=\s*(".*?"|\'.*?\'|[^\s>]+)/i', '', $task_description_html );
$task_description_html = preg_replace( '/<(\/?)div\b[^>]*>/i', '<br>', $task_description_html );
$task_description_html = preg_replace( '/<(\/?)span\b[^>]*>/i', '', $task_description_html );
$task_description_html = preg_replace( '/\s(style|class|id|width|height|align|valign|border|cellpadding|cellspacing)\s*=\s*(".*?"|\'.*?\'|[^\s>]+)/i', '', $task_description_html );
$task_description_html = preg_replace_callback( '/<a\b[^>]*>/i', function( $matches ) {
if ( preg_match( '/href\s*=\s*("|\')([^"\']+)\1/i', $matches[0], $href ) )
{
$url = trim( $href[2] );
if ( preg_match( '/^(https?:|mailto:|\/|#)/i', $url ) )
return '<a href="' . htmlspecialchars( $url, ENT_QUOTES ) . '" target="_blank" rel="noopener noreferrer">';
}
return '<a>';
}, $task_description_html );
$task_description_html = preg_replace_callback( '/<img\b[^>]*>/i', function( $matches ) {
$attrs = [];
if ( preg_match( '/src\s*=\s*("|\')([^"\']+)\1/i', $matches[0], $src ) )
{
$img_src = trim( $src[2] );
if ( preg_match( '/^(https?:|\/|data:image\/)/i', $img_src ) )
$attrs[] = 'src="' . htmlspecialchars( $img_src, ENT_QUOTES ) . '"';
}
if ( preg_match( '/alt\s*=\s*("|\')([^"\']*)\1/i', $matches[0], $alt ) )
$attrs[] = 'alt="' . htmlspecialchars( $alt[2], ENT_QUOTES ) . '"';
if ( empty( $attrs ) )
return '';
return '<img ' . implode( ' ', $attrs ) . '>';
}, $task_description_html );
$task_description_html = strip_tags( $task_description_html, '<p><br><b><strong><i><em><u><s><ul><ol><li><a><img><blockquote><pre><code><h1><h2><h3><h4><h5><h6><hr>' );
$task_description_html = preg_replace( '/(<br>\s*){3,}/i', '<br><br>', $task_description_html );
}
?>
<div class="content">
<div class="left">
<div class="task-tabs-nav" role="tablist" aria-label="Prze&#322;&#261;cz zak&#322;adk&#281; zadania">
<a href="#" class="js-task-tab-btn is-active" data-tab="description" role="tab" aria-selected="true">Opis</a>
<a href="#" class="js-task-tab-btn" data-tab="checklist" role="tab" aria-selected="false">Lista kontrolna (<?= (int)$checklist_count;?>)</a>
<a href="#" class="js-task-tab-btn" data-tab="comments" role="tab" aria-selected="false">Komentarze (<?= (int)$comments_count;?>)</a>
<a href="#" class="js-task-tab-btn" data-tab="attachments" role="tab" aria-selected="false">Za&#322;&#261;czniki (<?= (int)$attachments_count;?>)</a>
<a href="#" class="js-task-tab-btn" data-tab="wiki" role="tab" aria-selected="false">Wiki (<?= (int)$wiki_visible_count;?>)</a>
<? if ( $this -> user['id'] == 1 ):?>
<a href="#" class="js-task-tab-btn" data-tab="users" role="tab" aria-selected="false">Uczestnicy</a>
<? endif;?>
</div>
<div class="users box">
<? if ( is_array( $this -> task['users'] ) ): foreach ( $this -> task['users'] as $user_tmp_id ):?>
<? $user_tmp = \factory\Users::user_details( $user_tmp_id );?>
<div class="user">
<div class="avatar" style="background: <?= $user_tmp['color'];?>;" title="<?= $user_tmp['name'] . $user_tmp['surname'];?>"><?= $user_tmp['name'][0] . $user_tmp['surname'][0];?></div>
<?= $user_tmp['name'] . $user_tmp['surname'];?>
</div>
<? endforeach; endif;?>
</div>
<div class="task-tab-panel is-active" data-tab="description">
<div class="task-description-view">
<? if ( $this -> task['text'] ):?>
<div class="description">
<a href="#" class="fullscreen"><i class="fa fa-expand"></i></a>
<?= $task_description_html;?>
</div>
<? else:?>
<div class="description description-empty">
Brak opisu zadania.
</div>
<? endif;?>
</div>
<div class="task-description-actions">
<a href="#" class="btn btn-primary btn-sm task-popup-compact-btn js-start-edit-task-description">Edytuj</a>
</div>
<div class="task-description-editor box" style="display: none;">
<h3>Tre&#347;&#263; zadania</h3>
<textarea class="form-control task-description-input" id="task-popup-description-<?= (int)$this -> task['id'];?>" rows="5"><?= htmlspecialchars( (string)$this -> task['text'] );?></textarea>
<div class="task-description-editor-actions">
<a href="#" class="btn btn-primary btn-sm task-popup-compact-btn js-save-task-description" task_id="<?= (int)$this -> task['id'];?>">Zapisz tre&#347;&#263;</a>
<a href="#" class="btn btn-danger btn-sm task-popup-compact-btn js-cancel-edit-task-description">Anuluj</a>
</div>
</div>
</div>
<div class="task-tab-panel" data-tab="checklist">
<div class="checklist">
<h3>Lista kontrolna</h3>
<div class="new_element">
<input type="text" class="form-control" placeholder="Dodaj nowy element">
<a href="#" class="add_element" task_id="<?= $this -> task['id'];?>"><i class="fa fa-plus"></i></a>
</div>
<ul>
<? foreach ( $this -> task['actions'] as $action ):?>
<li action_id="<?= $action['id'];?>">
<input type="checkbox" class="g-checkbox" value="<?= $action[ 'id' ];?>" <? if ( $action['status'] ):?>checked="checked"<? endif;?>>
<?= $action['name'];?>
<a href="#" class="point-delete" action_id="<?= $action['id'];?>">
<i class="fa fa-trash"></i>
</a>
</li>
<? endforeach;?>
</ul>
</div>
</div>
<div class="task-tab-panel" data-tab="comments">
<div class="comments">
<h3>Komentarze</h3>
<div class="new_comment">
<textarea class="form-control" placeholder="Dodaj nowy komentarz"></textarea>
<a href="#" class="add_comment" task_id="<?= $this -> task['id'];?>">Dodaj nowy komentarz</a>
</div>
<ul>
<? foreach ( $this -> task['comments'] as $comment ):?>
<li>
<a href="#" class="delete_comment" comment_id="<?= $comment['id'];?>"><i class="fa fa-trash"></i></a>
<div class="author"><?= \factory\Users::user_details( $comment['user_id'] )['name'] . ' ' . \factory\Users::user_details( $comment['user_id'] )['surname'];?></div>
<div class="date"><?= date( 'Y/m/d H:i', strtotime( $comment['date_add'] ) );?></div>
<div class="text"><?= nl2br( $comment['text'] );?></div>
</li>
<? endforeach;?>
</ul>
</div>
</div>
<div class="task-tab-panel" data-tab="attachments">
<div class="attachments box">
<h3>Za&#322;&#261;czniki</h3>
<div class="attachments_upload">
<input type="file" class="form-control attachment_file_input" name="attachments[]" multiple>
<a href="#" class="attachment-upload-btn btn btn-primary btn-sm" task_id="<?= $this -> task['id'];?>">Dodaj za&#322;&#261;czniki</a>
</div>
<ul class="attachments_list">
<? if ( is_array( $this -> task_attachments ) and count( $this -> task_attachments ) ):?>
<? foreach ( $this -> task_attachments as $attachment ):?>
<? $attachment_ext = strtolower( pathinfo( (string)$attachment['title_effective'], PATHINFO_EXTENSION ) );?>
<? $is_image_attachment = in_array( $attachment_ext, $image_extensions, true );?>
<li>
<? if ( $is_image_attachment ):?>
<a href="<?= htmlspecialchars( $attachment['url'] );?>" class="attachment-link attachment-preview-link" data-image-src="<?= htmlspecialchars( $attachment['url'] );?>" data-image-title="<?= htmlspecialchars( $attachment['title_effective'] );?>">
<?= htmlspecialchars( $attachment['title_effective'] );?>
</a>
<? else:?>
<a href="<?= htmlspecialchars( $attachment['url'] );?>" target="_blank" rel="noopener noreferrer" class="attachment-link" download="<?= htmlspecialchars( $attachment['title_effective'] );?>">
<?= htmlspecialchars( $attachment['title_effective'] );?>
</a>
<? endif;?>
<small>(<?= $attachment['size_human'];?>)</small>
<? if ( $is_image_attachment ):?>
<a href="<?= htmlspecialchars( $attachment['url'] );?>" target="_blank" rel="noopener noreferrer" class="attachment-download" download="<?= htmlspecialchars( $attachment['title_effective'] );?>" title="Pobierz">
<i class="fa fa-download"></i>
</a>
<? endif;?>
<a href="#" class="attachment-rename" attachment_id="<?= $attachment['id'];?>" title_current="<?= htmlspecialchars( $attachment['title_effective'] );?>"><i class="fa fa-pencil"></i></a>
<a href="#" class="attachment-delete" attachment_id="<?= $attachment['id'];?>"><i class="fa fa-trash"></i></a>
</li>
<? endforeach;?>
<? else:?>
<li class="attachments-empty">Brak za&#322;&#261;cznik&#243;w.</li>
<? endif;?>
</ul>
</div>
</div>
<div class="task-tab-panel" data-tab="wiki">
<div class="box">
<h3>Powiazane wpisy Wiki</h3>
<? if ( $wiki_hidden_count > 0 ):?>
<div class="alert alert-warning" style="margin-bottom: 12px;">
Nie masz dostepu do <?= (int)$wiki_hidden_count;?> powiazanych wpisow Wiki.
</div>
<? endif;?>
<? if ( is_array( $task_wiki_entries ) and count( $task_wiki_entries ) ):?>
<div class="task-wiki-list">
<? foreach ( $task_wiki_entries as $wiki_entry ):?>
<div class="task-wiki-entry">
<h4><?= htmlspecialchars( (string)$wiki_entry['name'] );?></h4>
<div class="task-wiki-content">
<?= (string)$wiki_entry['text'];?>
<? if ( $this -> user['id'] == 1 or $this -> user['id'] == 3 ):?>
<? if ( isset( $wiki_entry['text_admin'] ) and (string)$wiki_entry['text_admin'] !== '' ):?>
<div class="task-wiki-admin"><?= (string)$wiki_entry['text_admin'];?></div>
<? endif;?>
<? endif;?>
</div>
</div>
<? endforeach;?>
</div>
<? else:?>
<div class="task-wiki-empty">Brak widocznych wpisow Wiki dla tego zadania.</div>
<? endif;?>
</div>
</div>
<? if ( $this -> user['id'] == 1 ):?>
<div class="task-tab-panel" data-tab="users">
<div class="task-users-edit">
<h3>Uczestnicy</h3>
<div class="task-users-checkboxes">
<? if ( is_array( $this -> all_users ) ): foreach ( $this -> all_users as $u ):?>
<label class="task-user-label">
<input type="checkbox" class="task-user-checkbox g-checkbox" value="<?= $u['id'];?>" <? if ( is_array( $this -> task['users'] ) and in_array( $u['id'], $this -> task['users'] ) ):?>checked="checked"<? endif;?>>
<?= $u['name'] . ' ' . $u['surname'];?>
</label>
<? endforeach; endif;?>
</div>
<div class="task-users-options">
<label class="task-user-label task-status-mail-label">
<input type="checkbox" class="task-status-change-mail-checkbox g-checkbox" value="1" <? if ( (int)$this -> task['status_change_mail'] === 1 ):?>checked="checked"<? endif;?>>
Powiadom o zmianie statusu:
</label>
</div>
<a href="#" class="btn btn-primary btn-sm js-save-task-users" task_id="<?= $this -> task['id'];?>" style="margin-top: 10px;">Zapisz</a>
</div>
</div>
<? endif;?>
</div>
<div class="right">
<div class="status box">
<h3>Status</h3>
<div class="current_status">
<select name="task_status" class="form-control" task_id="<?= $this -> task['id'];?>">
<? foreach ( \factory\Tasks::get_statuses() as $key => $status ):?>
<option value="<?= $key;?>" <? if ( $this -> task['status'] == $key ):?>selected="selected"<? endif;?>><?= $status;?></option>
<? endforeach;?>
</select>
</div>
</div>
<div class="priority box">
<h3>Priorytet</h3>
<div class="current_priority">
<select name="task_priority" class="form-control" task_id="<?= $this -> task['id'];?>">
<? foreach ( \factory\Tasks::get_priorities() as $key => $priority ):?>
<option value="<?= $key;?>" <? if ( $this -> task['priority'] == $key ):?>selected="selected"<? endif;?>><?= $priority;?></option>
<? endforeach;?>
</select>
</div>
</div>
<div class="project box">
<h3>Projekt</h3>
<div class="current_project">
<select name="task_project" class="form-control" task_id="<?= $this -> task['id'];?>">
<option value="0">-- brak --</option>
<? foreach ( $this -> projects as $project ):?>
<option value="<?= $project['id'];?>" <? if ( $this -> task['project_id'] == $project['id'] ):?>selected="selected"<? endif;?>><?= $project['name'];?></option>
<? endforeach;?>
</select>
</div>
<div class="current_parent" style="margin-top: 10px;">
<select name="task_parent" class="form-control task_parent_select" task_id="<?= (int)$this -> task['id'];?>">
<option value="0">-- bez zadania nadrzednego --</option>
<? if ( is_array( $this -> parent_tasks ) ): foreach ( $this -> parent_tasks as $parent_task ):?>
<? $parent_task_id = isset( $parent_task['id'] ) ? (int)$parent_task['id'] : 0;?>
<? if ( !$parent_task_id or $parent_task_id === (int)$this -> task['id'] ) continue;?>
<option value="<?= $parent_task_id;?>" <? if ( (int)$this -> task['parent_id'] === $parent_task_id ):?>selected="selected"<? endif;?>>
<?= htmlspecialchars( (string)$parent_task['name'] );?><? if ( !empty( $parent_task['client'] ) ):?> - <?= htmlspecialchars( (string)$parent_task['client'] );?><? endif;?>
</option>
<? endforeach; endif;?>
</select>
</div>
<div class="task-subtask-action">
<a href="/tasks/task_edit/project_id=<?= (int)$this -> task['project_id'];?>&client_id=<?= (int)$this -> task['client_id'];?>&parent_id=<?= (int)$this -> task['id'];?>" class="btn btn-success btn-sm task-popup-compact-btn">Dodaj podzadanie</a>
</div>
</div>
<div class="dates box">
<h3>Termin</h3>
<div class="task-date-edit-grid">
<div class="task-date-field">
<label>Data rozpoczęcia</label>
<input type="date" class="form-control task-date-start-input" value="<?= htmlspecialchars( (string)$this -> task['date_start'] );?>">
</div>
<div class="task-date-field">
<label>Data zakończenia</label>
<input type="date" class="form-control task-date-end-input" value="<?= htmlspecialchars( (string)$this -> task['date_end'] );?>">
</div>
</div>
<div class="task-date-actions">
<a href="#" class="btn btn-primary btn-sm task-popup-compact-btn js-save-task-dates" task_id="<?= (int)$this -> task['id'];?>">Zapisz terminy</a>
</div>
</div>
<div class="client box">
<h3>Klient</h3>
<div class="current_client">
<select name="task_client" class="form-control task_client_select" task_id="<?= $this -> task['id'];?>">
<option value="0">-- brak --</option>
<? if ( is_array( $this -> clients ) ): foreach ( $this -> clients as $client ):?>
<option value="<?= (int)$client['id'];?>" <? if ( (int)$this -> task['client_id'] === (int)$client['id'] ):?>selected="selected"<? endif;?>>
<?= htmlspecialchars( $client['firm'] );?>
</option>
<? endforeach; endif;?>
</select>
</div>
</div>
<div class="time box">
<h3>Przepracowany czas</h3>
<div class="time_worked" data-total-seconds="<?= (int)$this -> task['total_time'];?>">
<a href="#" class="time_worked_toggle js-time-worked-value">
<?= sprintf( "%02d%s%02d%s%02d", floor( $this -> task['total_time'] / 3600 ), ':', ( $this -> task['total_time'] / 60) % 60, ':', $this -> task['total_time'] % 60 );?>
</a>
</div>
<a href="#" class="task_start <? if ( $this -> task['is_open'] ):?> hidden<? endif;?>" task_id="<?= $this -> task['id'];?>">
<i class="fa fa-play"></i> W&#322;&#261;cz timer
</a>
<a href="#" class="task_end <? if ( !$this -> task['is_open'] ):?> hidden<? else:?> animate<? endif;?>" task_id="<?= $this -> task['id'];?>">
<i class="fa fa-stop"></i> Wy&#322;&#261;cz timer
</a>
</div>
</div>
<div class="task_work_details">
<? foreach ( $this -> task_works as $task_work ):?>
<div class="_line">
<div class="_user"><?= \factory\Users::user_details( $task_work['user_id'] )['name'] . ' ' . \factory\Users::user_details( $task_work['user_id'] )['surname'];?></div>
<input type="text" class="form-control task_work_date_start" task_work_id="<?= $task_work['id'];?>" value="<?= $task_work['date_start'];?>"><span>-</span><input type="text" class="form-control task_work_date_end" task_work_id="<?= $task_work['id'];?>" value="<?= $task_work['date_end'];?>">
<a href="#" class="_work_delete" work_id="<?= $task_work['id'];?>" task_id="<?= $this -> task['id'];?>">
<i class="fa fa-trash"></i>
</a>
</div>
<? endforeach;?>
</div>
</div>
</div>
<style type="text/css">
.task_popup .task_details .content .right .client .select2-container,
.task_popup .task_details .content .right .project .select2-container {
width: 100% !important;
}
.task_popup .task_details .content .left .task-tabs-nav {
margin: 0 0 10px 0;
display: flex;
flex-wrap: wrap;
border: 1px solid #d8e2f6;
border-radius: 8px;
overflow: hidden;
background: #f4f8ff;
position: sticky;
top: 0;
z-index: 30;
}
.task_popup .task_details .content .left .task-tabs-nav .js-task-tab-btn {
padding: 8px 12px;
color: #355899;
text-decoration: none;
font-size: 13px;
font-weight: 600;
border-right: 1px solid #d8e2f6;
transition: all 0.2s ease;
}
.task_popup .task_details .content .left .task-tabs-nav .js-task-tab-btn:last-child {
border-right: 0;
}
.task_popup .task_details .content .left .task-tabs-nav .js-task-tab-btn:hover {
background: #e7f0ff;
}
.task_popup .task_details .content .left .task-tabs-nav .js-task-tab-btn.is-active {
background: #6690f4;
color: #fff;
}
.task_popup .task_details .content .left .task-tab-panel {
margin-top: 10px;
display: none;
}
.task_popup .task_details .content .left .task-tab-panel.is-active {
display: block;
}
.task_popup .task_details .content .left .task-tab-panel .attachments {
margin-top: 0;
}
.task_popup .task_details .content .left .task-tab-panel .description.description-empty {
color: #6b7280;
font-style: italic;
}
.task_popup .task_details .content .left .task-tab-panel[data-tab="description"] .description:not(.description-empty) {
min-height: 320px;
max-height: calc(90vh - 300px);
overflow-y: auto;
overflow-wrap: anywhere;
word-break: break-word;
}
.task_popup .task_details .content .right .client .select2-container--bootstrap-5 .select2-selection {
min-height: 35px;
border: 1px solid #cdcdcd;
border-radius: 0;
box-shadow: none;
padding: 5px 30px 5px 5px;
display: flex;
align-items: center;
}
.task_popup .task_details .content .right .client .select2-container--bootstrap-5 .select2-selection--single .select2-selection__rendered {
color: #4e5e6a;
padding-left: 0;
padding-right: 0;
line-height: 1.2;
}
.task_popup .task_details .content .right .client .select2-container--bootstrap-5 .select2-selection--single .select2-selection__arrow {
right: 8px;
top: 50%;
transform: translateY(-50%);
}
.task_popup .task_details .content .right .client .select2-container--bootstrap-5.select2-container--focus .select2-selection,
.task_popup .task_details .content .right .client .select2-container--bootstrap-5.select2-container--open .select2-selection {
border-color: #6690f4;
}
.task_popup .task_details .task-users-checkboxes {
display: flex;
flex-direction: column;
gap: 6px;
}
.task_popup .task_details .task-user-label {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
padding: 4px 0;
font-size: 13px;
}
.task_popup .task_details .task-user-label input[type="checkbox"] {
margin: 0;
}
.task_popup .task_details .task-users-options {
margin-top: 10px;
padding-top: 10px;
border-top: 1px solid #e6e9ed;
}
.task_popup .task_details .task-status-mail-label {
font-weight: 600;
}
.task_popup .task_details .task-wiki-list {
display: grid;
gap: 12px;
}
.task_popup .task_details .task-wiki-entry {
border: 1px solid #e5e7eb;
border-radius: 8px;
padding: 12px;
background: #fff;
}
.task_popup .task_details .task-wiki-entry h4 {
margin: 0 0 8px 0;
font-size: 14px;
font-weight: 700;
color: #1f3d72;
}
.task_popup .task_details .task-wiki-content {
font-size: 13px;
line-height: 1.5;
}
.task_popup .task_details .task-wiki-admin {
margin-top: 10px;
padding-top: 10px;
border-top: 1px dashed #d1d5db;
}
.task_popup .task_details .task-wiki-empty {
color: #6b7280;
font-style: italic;
}
/* Lightbox - powiększanie zdjęć w opisie */
.task_popup .task_details .description img {
cursor: zoom-in;
transition: opacity .2s ease;
}
.task_popup .task_details .attachments_list li .attachment-download {
display: inline-flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
margin-left: 4px;
border-radius: 4px;
color: #1f3d72;
text-decoration: none;
transition: background .2s ease, color .2s ease;
}
.task_popup .task_details .attachments_list li .attachment-download:hover {
background: #eef3fb;
color: #16305b;
}
.task_popup .task_details .description img:hover {
opacity: .85;
}
.task-img-lightbox {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 99999;
background: rgba(0,0,0,.85);
display: flex;
align-items: center;
justify-content: center;
cursor: zoom-out;
animation: taskLightboxIn .2s ease;
}
@keyframes taskLightboxIn {
from { opacity: 0; }
to { opacity: 1; }
}
.task-img-lightbox img {
max-width: 90vw;
max-height: 90vh;
border-radius: 4px;
box-shadow: 0 0 40px rgba(0,0,0,.5);
object-fit: contain;
}
.task-img-lightbox .lightbox-content {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
max-width: 90vw;
max-height: 90vh;
}
.task-img-lightbox .lightbox-title {
color: #fff;
font-size: 13px;
text-align: center;
word-break: break-word;
}
.task-img-lightbox .lightbox-close {
position: absolute;
top: 16px;
right: 24px;
color: #fff;
font-size: 32px;
cursor: pointer;
line-height: 1;
text-shadow: 0 0 8px rgba(0,0,0,.6);
}
.task_popup .task_details .title .task-title-wrapper {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: nowrap;
min-width: 0;
flex: 1;
}
.task_popup .task_details .title .task-title-view {
display: inline-flex;
align-items: center;
gap: 8px;
min-width: 0;
flex: 1;
}
.task_popup .task_details .title .task-title-text {
display: inline-block;
max-width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-weight: 600;
}
.task_popup .task_details .title .task-title-edit-btn,
.task_popup .task_details .title .task-title-save,
.task_popup .task_details .title .task-title-cancel {
border: 1px solid #099885;
width: 30px;
height: 30px;
border-radius: 6px;
display: inline-flex;
align-items: center;
justify-content: center;
color: #099885;
text-decoration: none;
flex-shrink: 0;
transition: all 0.2s ease;
}
.task_popup .task_details .title .task-title-edit-btn:hover,
.task_popup .task_details .title .task-title-save:hover,
.task_popup .task_details .title .task-title-cancel:hover {
background: #099885;
color: #fff;
}
.task_popup .task_details .title .task-title-cancel {
border-color: #cc563d;
color: #cc563d;
}
.task_popup .task_details .title .task-title-cancel:hover {
background: #cc563d;
color: #fff;
}
.task_popup .task_details .title .task-title-edit-box {
align-items: center;
gap: 8px;
flex: 1;
min-width: 0;
}
.task_popup .task_details .title .task-title-input {
height: 36px;
border-radius: 6px;
font-size: 18px;
width: 100%;
min-width: 240px;
}
.task_popup .task_details .content .left .task-description-editor {
margin-bottom: 12px;
}
.task_popup .task_details .content .left .task-description-actions {
margin: 10px 0 12px 0;
}
.task_popup .task_details .content .left .task-description-editor .task-description-editor-actions {
margin-top: 10px;
display: flex;
gap: 8px;
}
.task_popup .task_details .content .left .task-description-editor .task-description-input {
min-height: 120px;
resize: vertical;
}
.task_popup .task_details .content .right .project .task-subtask-action {
margin-top: 10px;
}
.task_popup .task_details .content .right .project .task-subtask-action a {
display: flex;
width: 100%;
height: 30px;
align-items: center;
justify-content: center;
line-height: 1;
}
.task_popup .task_details .content .right .dates .task-date-edit-grid {
display: grid;
gap: 8px;
grid-template-columns: 1fr 1fr;
}
.task_popup .task_details .content .right .dates .task-date-actions {
margin-top: 10px;
}
.task_popup .task_details .content .right .dates .task-date-actions .js-save-task-dates {
display: flex;
width: 100%;
height: 30px;
padding: 0 10px;
align-items: center;
justify-content: center;
line-height: 1;
}
.task_popup .task_details .content .right .dates .task-date-field label {
display: block;
font-size: 12px;
font-weight: 600;
margin-bottom: 4px;
color: #4e5e6a;
}
@media (max-width: 767px) {
.task_popup .task_details .content .right .dates .task-date-edit-grid {
grid-template-columns: 1fr;
}
}
.task_popup .task_details .task-popup-compact-btn {
height: 30px;
padding: 0 10px;
font-size: 12px;
border-radius: 5px;
min-width: 0;
}
</style>
<script type="text/javascript">
( function() {
var popup = $( '.task_details[task_id="<?= $this -> task['id'];?>"]' );
if ( !popup.length )
return;
var tab_panels = popup.find( '.task-tab-panel' );
var tab_buttons = popup.find( '.js-task-tab-btn' );
var description_view = popup.find( '.task-description-view' );
var description_actions = popup.find( '.task-description-actions' );
var description_editor_box = popup.find( '.task-description-editor' );
var description_input = popup.find( '.task-description-input' );
var initial_description_text = description_input.val() || '';
var description_editor_instance = null;
var ckeditor_base_path = '/libraries/ckeditor/';
function loadScriptOnce( src ) {
var deferred = $.Deferred();
var existing_script = document.querySelector( 'script[src="' + src + '"]' );
if ( existing_script )
{
deferred.resolve();
return deferred.promise();
}
$.getScript( src )
.done( function() { deferred.resolve(); } )
.fail( function() { deferred.reject(); } );
return deferred.promise();
}
function ensureDescriptionEditorReady( callback ) {
if ( description_editor_instance || !description_input.length )
{
if ( typeof callback === 'function' )
callback();
return;
}
window.CKEDITOR_BASEPATH = ckeditor_base_path;
if ( window.CKEDITOR )
window.CKEDITOR.basePath = ckeditor_base_path;
function initEditor() {
if ( $.fn.ckeditor )
{
description_input.ckeditor({
toolbar: 'Basic',
language: 'pl',
height: '100'
});
try
{
description_editor_instance = description_input.ckeditorGet();
}
catch ( e )
{
description_editor_instance = null;
}
}
if ( typeof callback === 'function' )
callback();
}
if ( $.fn.ckeditor )
{
initEditor();
return;
}
loadScriptOnce( ckeditor_base_path + 'ckeditor.js' )
.done( function() {
if ( window.CKEDITOR )
window.CKEDITOR.basePath = ckeditor_base_path;
loadScriptOnce( ckeditor_base_path + 'adapters/jquery.js' )
.always( function() {
initEditor();
} );
} )
.fail( function() {
if ( typeof callback === 'function' )
callback();
} );
}
function getDescriptionValue() {
if ( description_editor_instance )
{
description_editor_instance.updateElement();
}
return description_input.val();
}
function setDescriptionValue( value ) {
if ( description_editor_instance )
{
description_editor_instance.setData( value || '' );
}
description_input.val( value || '' );
}
function toggleDescriptionEditMode( is_edit ) {
description_view.toggle( !is_edit );
description_actions.toggle( !is_edit );
description_editor_box.toggle( is_edit );
if ( is_edit )
{
ensureDescriptionEditorReady( function() {
if ( description_editor_instance )
description_editor_instance.focus();
else
description_input.trigger( 'focus' );
} );
}
}
function setActiveTaskTab( tab_name ) {
if ( !tab_panels.length )
return;
var available_tabs = [];
tab_buttons.each( function() {
var tab = $( this ).attr( 'data-tab' );
if ( tab )
available_tabs.push( tab );
});
var selected_tab = available_tabs.indexOf( tab_name ) >= 0 ? tab_name : 'description';
if ( available_tabs.indexOf( selected_tab ) < 0 && available_tabs.length )
selected_tab = available_tabs[0];
tab_panels.each( function() {
var panel = $( this );
panel.toggleClass( 'is-active', panel.attr( 'data-tab' ) === selected_tab );
});
tab_buttons.each( function() {
var button = $( this );
var is_active = button.attr( 'data-tab' ) === selected_tab;
button.toggleClass( 'is-active', is_active ).attr( 'aria-selected', is_active ? 'true' : 'false' );
});
window.crm_task_popup_active_tab = selected_tab;
}
popup.on( 'click', '.js-task-tab-btn', function( e ) {
e.preventDefault();
setActiveTaskTab( $( this ).attr( 'data-tab' ) );
} );
setActiveTaskTab( window.crm_task_popup_active_tab || 'description' );
popup.on( 'click', '.js-start-edit-task-description', function( e ) {
e.preventDefault();
toggleDescriptionEditMode( true );
} );
popup.on( 'click', '.js-cancel-edit-task-description', function( e ) {
e.preventDefault();
setDescriptionValue( initial_description_text );
toggleDescriptionEditMode( false );
} );
if ( $.fn.select2 )
{
var client_select = popup.find( '.task_client_select' );
var parent_select = popup.find( '.task_parent_select' );
client_select.select2({
theme: 'bootstrap-5',
width: '100%',
placeholder: 'Wybierz klienta',
dropdownParent: popup
});
parent_select.select2({
theme: 'bootstrap-5',
width: '100%',
placeholder: 'Wybierz zadanie nadrzedne',
dropdownParent: popup
});
client_select.on( 'select2:open', function() {
setTimeout( function() {
var search_field = document.querySelector( '.select2-container--open .select2-search__field' );
if ( search_field )
search_field.focus();
}, 0 );
} );
}
if ( $.fn.iCheck )
{
popup.find( '.task-user-checkbox, .task-status-change-mail-checkbox' ).iCheck({
checkboxClass: 'icheckbox_square-blue',
radioClass: 'iradio_square-blue'
});
}
var time_worked = popup.find( '.time_worked' );
var time_value = popup.find( '.js-time-worked-value' );
var total_seconds = parseInt( time_worked.attr( 'data-total-seconds' ), 10 ) || 0;
function formatTime( seconds ) {
seconds = parseInt( seconds, 10 ) || 0;
var h = Math.floor( seconds / 3600 );
var m = Math.floor( ( seconds % 3600 ) / 60 );
var s = seconds % 60;
return String( h ).padStart( 2, '0' ) + ':' + String( m ).padStart( 2, '0' ) + ':' + String( s ).padStart( 2, '0' );
}
function renderTime() {
time_value.text( formatTime( total_seconds ) );
time_worked.attr( 'data-total-seconds', total_seconds );
}
renderTime();
// Lightbox kliknięcie na zdjęcie w opisie
popup.on( 'click', '.description img', function( e ) {
e.preventDefault();
e.stopPropagation();
var src = $( this ).attr( 'src' );
if ( !src ) return;
var $overlay = $( '<div class="task-img-lightbox">' +
'<span class="lightbox-close">&times;</span>' +
'<img src="' + src + '">' +
'</div>' );
$( 'body' ).append( $overlay );
$overlay.on( 'click', function() {
$overlay.remove();
});
$( document ).one( 'keydown.taskLightbox', function( ev ) {
if ( ev.key === 'Escape' ) {
$overlay.remove();
}
});
});
popup.on( 'click', '.attachment-preview-link', function( e ) {
e.preventDefault();
e.stopPropagation();
var src = $( this ).attr( 'data-image-src' );
if ( !src ) return;
var $overlay = $( '<div class="task-img-lightbox">' +
'<span class="lightbox-close">&times;</span>' +
'<img src="' + src + '">' +
'</div>' );
$( 'body' ).append( $overlay );
$overlay.on( 'click', function() {
$overlay.remove();
});
$( document ).one( 'keydown.taskLightbox', function( ev ) {
if ( ev.key === 'Escape' ) {
$overlay.remove();
}
});
});
popup.on( 'click', '.js-save-task-users', function( e ) {
e.preventDefault();
var btn = $( this );
var task_id = btn.attr( 'task_id' );
var users = [];
var status_change_mail = popup.find( '.task-status-change-mail-checkbox' ).is( ':checked' ) ? 1 : 0;
popup.find( '.task-user-checkbox:checked' ).each( function() {
users.push( $( this ).val() );
});
btn.text( 'Zapisywanie...' ).addClass( 'disabled' );
$.ajax({
type: 'POST',
url: '/tasks/task_change_users/',
data: { task_id: task_id, users: users, status_change_mail: status_change_mail },
success: function( response ) {
var res = typeof response === 'string' ? JSON.parse( response ) : response;
if ( res.status === 'success' ) {
var names = [];
popup.find( '.task-user-checkbox:checked' ).each( function() {
names.push( $( this ).closest( '.task-user-label' ).text().trim() );
});
var html = '';
for ( var i = 0; i < names.length; i++ ) {
var initials = names[i].split( ' ' ).map( function( w ) { return w[0]; } ).join( '' );
html += '<div class="user"><div class="avatar" title="' + names[i] + '">' + initials + '</div>' + names[i] + '</div>';
}
popup.find( '.users.box' ).html( html );
if ( typeof getSelectedTaskFilters === 'function' && typeof reload_tasks === 'function' )
{
var selected_filters = getSelectedTaskFilters();
reload_tasks( selected_filters.projects, selected_filters.users );
}
btn.text( 'Zapisano!' );
} else {
btn.text( 'Błąd' );
}
setTimeout( function() { btn.text( 'Zapisz' ).removeClass( 'disabled' ); }, 1500 );
}
});
});
popup.on( 'click', '.task-title-edit-btn', function( e ) {
e.preventDefault();
popup.find( '.task-title-view' ).hide();
popup.find( '.task-title-edit-box' ).css( 'display', 'inline-flex' );
popup.find( '.task-title-input' ).focus();
});
popup.on( 'click', '.task-title-cancel', function( e ) {
e.preventDefault();
popup.find( '.task-title-edit-box' ).hide();
popup.find( '.task-title-view' ).show();
// Reset input to current text
popup.find( '.task-title-input' ).val( popup.find( '.task-title-text' ).text() );
});
popup.on( 'click', '.task-title-save', function( e ) {
e.preventDefault();
var btn = $( this );
var new_title = popup.find( '.task-title-input' ).val().trim();
var task_id = popup.attr( 'task_id' );
if ( !new_title ) return;
btn.addClass( 'disabled' );
$.ajax({
type: 'POST',
url: '/tasks/task_change_title/',
data: { task_id: task_id, title: new_title },
success: function( response ) {
var res = typeof response === 'string' ? JSON.parse( response ) : response;
if ( res.status === 'success' ) {
popup.find( '.task-title-text' ).text( new_title );
popup.find( '.task-title-cancel' ).click(); // Close edit mode
// Update main view list if exists
if ( typeof getSelectedTaskFilters === 'function' && typeof reload_tasks === 'function' )
{
var selected_filters = getSelectedTaskFilters();
reload_tasks( selected_filters.projects, selected_filters.users );
}
} else {
alert( 'Wystąpił błąd podczas zmiany tytułu.' );
}
btn.removeClass( 'disabled' );
}
});
});
popup.on( 'keypress', '.task-title-input', function( e ) {
if ( e.which === 13 )
{
e.preventDefault();
popup.find( '.task-title-save' ).trigger( 'click' );
}
});
popup.on( 'click', '.js-save-task-dates', function( e ) {
e.preventDefault();
var btn = $( this );
var task_id = btn.attr( 'task_id' );
var date_start = popup.find( '.task-date-start-input' ).val();
var date_end = popup.find( '.task-date-end-input' ).val();
btn.text( 'Zapisywanie...' ).addClass( 'disabled' );
$.ajax({
type: 'POST',
url: '/tasks/task_change_dates/',
data: { task_id: task_id, date_start: date_start, date_end: date_end },
success: function( response ) {
var res = typeof response === 'string' ? JSON.parse( response ) : response;
if ( res.status === 'success' )
{
btn.text( 'Zapisano!' );
if ( typeof getSelectedTaskFilters === 'function' && typeof reload_tasks === 'function' )
{
var selected_filters = getSelectedTaskFilters();
reload_tasks( selected_filters.projects, selected_filters.users );
}
}
else
btn.text( 'Błąd' );
setTimeout( function() { btn.text( 'Zapisz terminy' ).removeClass( 'disabled' ); }, 1400 );
}
} );
} );
popup.on( 'click', '.js-save-task-description', function( e ) {
e.preventDefault();
var btn = $( this );
var task_id = btn.attr( 'task_id' );
var text = getDescriptionValue();
btn.text( 'Zapisywanie...' ).addClass( 'disabled' );
$.ajax({
type: 'POST',
url: '/tasks/task_change_text/',
data: { task_id: task_id, text: text },
success: function( response ) {
var res = typeof response === 'string' ? JSON.parse( response ) : response;
if ( res.status === 'success' )
{
btn.text( 'Zapisano!' );
initial_description_text = text;
task_popup( task_id, is_task_popup_works_time_open() );
}
else
btn.text( 'Błąd' );
setTimeout( function() { btn.text( 'Zapisz treść' ).removeClass( 'disabled' ); }, 1400 );
}
} );
} );
var interval_id = setInterval( function() {
if ( !document.body.contains( popup.get( 0 ) ) )
{
clearInterval( interval_id );
return;
}
var is_timer_running = !popup.find( '.task_end' ).hasClass( 'hidden' );
if ( is_timer_running )
{
total_seconds++;
renderTime();
}
}, 1000 );
} )();
</script>