diff --git a/assets/default/css/theme.css b/assets/default/css/theme.css index 5b98eaee93d60bc9f6732c12cd2e3e39d24ca10d..73a76e4aa4e4089bda6264ea9d4fd07ab65448cc 100644 --- a/assets/default/css/theme.css +++ b/assets/default/css/theme.css @@ -794,6 +794,7 @@ label.required, span.required { border: 1px solid #aaa; border-left: none; border-bottom: none; + table-layout: fixed; } #ticketTable caption { padding: 5px; @@ -811,12 +812,13 @@ label.required, span.required { border: 1px solid #aaa; border-right: none; border-top: none; + padding: 0 5px; } #ticketTable th a { color: #000; } #ticketTable td { - padding: 2px; + padding: 3px 5px; border: 1px solid #aaa; border-right: none; border-top: none; @@ -1037,3 +1039,11 @@ img.sign-in-image { margin: 0 1%; vertical-align: top; } +.truncate { + display: inline-block; + width: auto; + max-width: 100%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} diff --git a/include/ajax.forms.php b/include/ajax.forms.php index fcdc4fbc57b734cb89c955df36cb41dd238fe156..d9e61dbd5312a9d5df4d5ca70e92fd25bc2b6025 100644 --- a/include/ajax.forms.php +++ b/include/ajax.forms.php @@ -98,29 +98,228 @@ class DynamicFormsAjaxAPI extends AjaxController { $ent->delete(); } - function getListItemProperties($list_id, $item_id) { + + function _getListItemEditForm($source=null, $item=false) { + return new Form(array( + 'value' => new TextboxField(array( + 'required' => true, + 'label' => __('Value'), + 'configuration' => array( + 'translatable' => $item ? $item->getTranslateTag('value') : false, + 'size' => 60, + 'length' => 0, + ), + )), + 'extra' => new TextboxField(array( + 'label' => __('Abbreviation'), + 'configuration' => array( + 'size' => 60, + 'length' => 0, + ), + )), + ), $source); + } + + function getListItem($list_id, $item_id) { $list = DynamicList::lookup($list_id); if (!$list || !($item = $list->getItem( (int) $item_id))) Http::response(404, 'No such list item'); + $action = "#list/{$list->getId()}/item/{$item->getId()}/update"; + $item_form = $this->_getListItemEditForm($item->ht, $item); + include(STAFFINC_DIR . 'templates/list-item-properties.tmpl.php'); } - function saveListItemProperties($list_id, $item_id) { + function getListItems($list_id) { + global $thisstaff; + + if (!$thisstaff) + Http::response(403, 'Login required'); + + if (!($list = DynamicList::lookup($list_id))) + Http::response(404, 'No such list'); + + $pjax_container = '#items'; + include(STAFFINC_DIR . 'templates/list-items.tmpl.php'); + } + + function saveListItem($list_id, $item_id) { + global $thisstaff; + + if (!$thisstaff) + Http::response(403, 'Login required'); $list = DynamicList::lookup($list_id); if (!$list || !($item = $list->getItem( (int) $item_id))) Http::response(404, 'No such list item'); - if (!$item->setConfiguration()) { + $item_form = $this->_getListItemEditForm($_POST, $item); + + if ($valid = $item_form->isValid()) { + // Update basic information + $basic = $item_form->getClean(); + $item->extra = $basic['extra']; + $item->value = $basic['value']; + + if ($_item = DynamicListItem::lookup(array('value'=>$item->value))) + if ($_item && $_item->id != $item->id) + $item_form->getField('value')->addError( + __('Value already in use')); + } + + // Context + $action = "#list/{$list->getId()}/item/{$item->getId()}/update"; + $icon = ($list->get('sort_mode') == 'SortCol') + ? '<i class="icon-sort"></i> ' : ''; + + if (!$valid || !$item->setConfiguration()) { include STAFFINC_DIR . 'templates/list-item-properties.tmpl.php'; return; } - else + else { $item->save(); + } + + // Send the whole row back + $prop_fields = array(); + foreach ($list->getConfigurationForm()->getFields() as $f) { + if (in_array($f->get('type'), array('text', 'datetime', 'phone'))) + $prop_fields[] = $f; + if (strpos($f->get('type'), 'list-') === 0) + $prop_fields[] = $f; + + // 4 property columns max + if (count($prop_fields) == 4) + break; + } + ob_start(); + $item->_config = null; + include STAFFINC_DIR . 'templates/list-item-row.tmpl.php'; + $html = ob_get_clean(); + Http::response(201, $this->encode(array( + 'id' => $item->getId(), + 'row' => $html, + 'success' => true, + ))); + } + + function addListItem($list_id) { + global $thisstaff; + + if (!$thisstaff) + Http::response(403, 'Login required'); + elseif (!($list = DynamicList::lookup($list_id))) + Http::response(404, 'No such list'); + + $action = "#list/{$list->getId()}/item/add"; + $item_form = $this->_getListItemEditForm($_POST ?: null); + + if ($_POST && ($valid = $item_form->isValid())) { + $data = $item_form->getClean(); + if ($_item = DynamicListItem::lookup(array('value'=>$data['value']))) + if ($_item && $_item->id) + $item_form->getField('value')->addError( + __('Value already in use')); + $data['list_id'] = $list_id; + $item = DynamicListItem::create($data); + if ($item->save() && $item->setConfiguration()) + Http::response(201, $this->encode(array('success' => true))); + } - Http::response(201, 'Successfully updated record'); + include(STAFFINC_DIR . 'templates/list-item-properties.tmpl.php'); + } + + function importListItems($list_id) { + global $thisstaff; + + if (!$thisstaff) + Http::response(403, 'Login required'); + elseif (!($list = DynamicList::lookup($list_id))) + Http::response(404, 'No such list'); + + $info = array( + 'title' => sprintf('%s — %s', + $list->getName(), __('Import Items')), + 'action' => "#list/{$list_id}/import", + 'upload_url' => "lists.php?id={$list_id}&do=import-users", + ); + + if ($_POST) { + $status = $list->importFromPost($_POST['pasted']); + if (is_string($status)) + $info['error'] = $status; + else + Http::response(201, $this->encode(array('success' => true, 'count' => $status))); + } + + include(STAFFINC_DIR . 'templates/list-import.tmpl.php'); + } + + function disableItems($list_id) { + global $thisstaff; + + if (!$thisstaff) + Http::response(403, 'Login required'); + elseif (!($list = DynamicList::lookup($list_id))) + Http::response(404, 'No such list'); + elseif (!$_POST['ids']) + Http::response(422, 'Send `ids` parameter'); + + foreach ($_POST['ids'] as $id) { + if ($item = $list->getItem( (int) $id)) { + $item->disable(); + $item->save(); + } + else { + Http::response(404, 'No such list item'); + } + } + Http::response(200, $this->encode(array('success' => true))); + } + + function undisableItems($list_id) { + global $thisstaff; + + if (!$thisstaff) + Http::response(403, 'Login required'); + elseif (!($list = DynamicList::lookup($list_id))) + Http::response(404, 'No such list'); + elseif (!$_POST['ids']) + Http::response(422, 'Send `ids` parameter'); + + foreach ($_POST['ids'] as $id) { + if ($item = $list->getItem( (int) $id)) { + $item->enable(); + $item->save(); + } + else { + Http::response(404, 'No such list item'); + } + } + Http::response(200, $this->encode(array('success' => true))); + } + + function deleteItems($list_id) { + global $thisstaff; + + if (!$thisstaff) + Http::response(403, 'Login required'); + elseif (!($list = DynamicList::lookup($list_id))) + Http::response(404, 'No such list'); + elseif (!$_POST['ids']) + Http::response(422, 'Send `ids` parameter'); + + foreach ($_POST['ids'] as $id) { + if ($item = $list->getItem( (int) $id)) { + $item->delete(); + } + else { + Http::response(404, 'No such list item'); + } + } + #Http::response(200, $this->encode(array('success' => true))); } function upload($id) { diff --git a/include/class.dynamic_forms.php b/include/class.dynamic_forms.php index 015c2a42ed461518d9760c876c005ef4190ae469..d51ddc7af107193a6a76ef24e3e9c836bed5e600 100644 --- a/include/class.dynamic_forms.php +++ b/include/class.dynamic_forms.php @@ -601,7 +601,7 @@ class DynamicFormField extends VerySimpleModel { } function isChangeable() { - return $this->hasFlag(self::FLAG_MASK_CHANGE); + return !$this->hasFlag(self::FLAG_MASK_CHANGE); } function isEditable() { @@ -1276,7 +1276,8 @@ class SelectionField extends FormField { } } - return $selection; + // Don't return an empty array + return $selection ?: null; } function to_database($value) { diff --git a/include/class.list.php b/include/class.list.php index be6baec233279867eb46081bf852ff2db180c8a0..5f4ce5aeac6ebb60037dcc1f80807d3ac6a9d336 100644 --- a/include/class.list.php +++ b/include/class.list.php @@ -188,14 +188,14 @@ class DynamicList extends VerySimpleModel implements CustomList { } function getName() { - return $this->get('name'); + return $this->getLocal('name'); } function getPluralName() { - if ($name = $this->get('name_plural')) + if ($name = $this->getLocal('name_plural')) return $name; else - return $this->get('name') . 's'; + return $this->getName . 's'; } function getItemCount() { @@ -447,6 +447,151 @@ class DynamicList extends VerySimpleModel implements CustomList { return $selections; } + function importCsv($stream, $defaults=array()) { + //Read the header (if any) + $headers = array('value' => __('Value'), 'abbrev' => __('Abbreviation')); + $form = $this->getConfigurationForm(); + $named_fields = $fields = array( + 'value' => new TextboxField(array( + 'label' => __('Value'), + 'name' => 'value', + 'configuration' => array( + 'length' => 0, + ), + )), + 'abbrev' => new TextboxField(array( + 'name' => 'abbrev', + 'label' => __('Abbreviation'), + 'configuration' => array( + 'length' => 0, + ), + )), + ); + $all_fields = $form->getFields(); + $has_header = false; + foreach ($all_fields as $f) + if ($f->get('name')) + $named_fields[] = $f; + + if (!($data = fgetcsv($stream, 1000, ","))) + return __('Whoops. Perhaps you meant to send some CSV records'); + + foreach ($data as $D) { + if (strcasecmp($D, 'value') === 0) + $has_header = true; + } + if ($has_header) { + foreach ($data as $h) { + $found = false; + foreach ($all_fields as $f) { + if (in_array(mb_strtolower($h), array( + mb_strtolower($f->get('name')), mb_strtolower($f->get('label'))))) { + $found = true; + if (!$f->get('name')) + return sprintf(__( + '%s: Field must have `variable` set to be imported'), $h); + $headers[$f->get('name')] = $f->get('label'); + break; + } + } + if (!$found) { + $has_header = false; + if (count($data) == count($named_fields)) { + // Number of fields in the user form matches the number + // of fields in the data. Assume things line up + $headers = array(); + foreach ($named_fields as $f) + $headers[$f->get('name')] = $f->get('label'); + break; + } + else { + return sprintf(__('%s: Unable to map header to a property'), $h); + } + } + } + } + + // 'name' and 'email' MUST be in the headers + if (!isset($headers['value'])) + return __('CSV file must include `value` column'); + + if (!$has_header) + fseek($stream, 0); + + $items = array(); + $keys = array('value', 'abbrev'); + foreach ($headers as $h => $label) { + if (!($f = $form->getField($h))) + continue; + + $name = $keys[] = $f->get('name'); + $fields[$name] = $f->getImpl(); + } + + // Add default fields (org_id, etc). + foreach ($defaults as $key => $val) { + // Don't apply defaults which are also being imported + if (isset($header[$key])) + unset($defaults[$key]); + $keys[] = $key; + } + + while (($data = fgetcsv($stream, 1000, ",")) !== false) { + if (count($data) == 1 && $data[0] == null) + // Skip empty rows + continue; + elseif (count($data) != count($headers)) + return sprintf(__('Bad data. Expected: %s'), implode(', ', $headers)); + // Validate according to field configuration + $i = 0; + foreach ($headers as $h => $label) { + $f = $fields[$h]; + $T = $f->parse($data[$i]); + if ($f->validateEntry($T) && $f->errors()) + return sprintf(__( + /* 1 will be a field label, and 2 will be error messages */ + '%1$s: Invalid data: %2$s'), + $label, implode(', ', $f->errors())); + // Convert to database format + $data[$i] = $f->to_database($T); + $i++; + } + // Add default fields + foreach ($defaults as $key => $val) + $data[] = $val; + + $items[] = $data; + } + + $errors = array(); + foreach ($items as $u) { + $vars = array_combine($keys, $u); + $item = $this->addItem($vars); + if (!$item || !$item->setConfiguration($errors, $vars)) + return sprintf(__('Unable to import item: %s'), + print_r($vars, true)); + } + + return count($items); + } + + function importFromPost($stuff, $extra=array()) { + if (is_array($stuff) && !$stuff['error']) { + // Properly detect Macintosh style line endings + ini_set('auto_detect_line_endings', true); + $stream = fopen($stuff['tmp_name'], 'r'); + } + elseif ($stuff) { + $stream = fopen('php://temp', 'w+'); + fwrite($stream, $stuff); + rewind($stream); + } + else { + return __('Unable to parse submitted items'); + } + + return self::importCsv($stream, $extra); + } } FormField::addFieldTypes(/* @trans */ 'Custom Lists', array('DynamicList', 'getSelections')); @@ -551,9 +696,9 @@ class DynamicListItem extends VerySimpleModel implements CustomListItem { return $this->_config; } - function setConfiguration(&$errors=array()) { + function setConfiguration(&$errors=array(), $source=false) { $config = array(); - foreach ($this->getConfigurationForm($_POST)->getFields() as $field) { + foreach ($this->getConfigurationForm($source ?: $_POST)->getFields() as $field) { $config[$field->get('id')] = $field->to_php($field->getClean()); $errors = array_merge($errors, $field->errors()); } diff --git a/include/class.pagenate.php b/include/class.pagenate.php index 2d6e8b8505233b5535432372d4848c145b6e4749..7f4be6a37ec1ff5b0269cccc67b64d3883a7a672 100644 --- a/include/class.pagenate.php +++ b/include/class.pagenate.php @@ -18,6 +18,7 @@ class PageNate { var $start; var $limit; + var $slack = 0; var $total; var $page; var $pages; @@ -54,13 +55,16 @@ class PageNate { } function getStart() { - return $this->start; + return max($this->start - $this->slack, 0); } function getLimit() { return $this->limit; } + function setSlack($count) { + $this->slack = $count; + } function getNumPages(){ return $this->pages; @@ -72,27 +76,28 @@ class PageNate { function showing() { $html = ''; - $from= $this->start+1; - if ($this->start + $this->limit < $this->total) { - $to= $this->start + $this->limit; + $start = $this->getStart() + 1; + $end = min($start + $this->limit + $this->slack - 1, $this->total); + if ($end < $this->total) { + $to= $end; } else { $to= $this->total; } $html=" ".__('Showing')." "; if ($this->total > 0) { $html .= sprintf(__('%1$d - %2$d of %3$d' /* Used in pagination output */), - $from, $to, $this->total); + $start, $end, $this->total); }else{ $html .= " 0 "; } return $html; } - function getPageLinks() { + function getPageLinks($hash=false, $pjax=false) { $html = ''; $file =$this->url; $displayed_span = 5; - $total_pages = ceil( $this->total / $this->limit ); + $total_pages = ceil( ($this->total - $this->slack) / $this->limit ); $this_page = ceil( ($this->start+1) / $this->limit ); $last=$this_page-1; @@ -116,15 +121,24 @@ class PageNate { for ($i=$start_loop; $i <= $stop_loop; $i++) { $page = ($i - 1) * $this->limit; + $href = "{$file}&p={$i}"; + if ($hash) + $href .= '#'.$hash; if ($i == $this_page) { $html .= "\n<b>[$i]</b>"; + } + elseif ($pjax) { + $html .= " <a href=\"{$href}\" data-pjax-container=\"{$pjax}\"><b>$i</b></a>"; } else { - $html .= "\n<a href=\"$file&p=$i\" ><b>$i</b></a>"; + $html .= "\n<a href=\"{$href}\" ><b>$i</b></a>"; } } if($stop_loop<$total_pages){ $nextspan=($stop_loop+$displayed_span>$total_pages)?$total_pages-$displayed_span:$stop_loop+$displayed_span; - $html .= "\n<a href=\"$file&p=$nextspan\" ><strong>»</strong></a>"; + $href = "{$file}&p={$nextspan}"; + if ($hash) + $href .= '#'.$hash; + $html .= "\n<a href=\"{$href}\" ><strong>»</strong></a>"; } @@ -133,7 +147,9 @@ class PageNate { } function paginate(QuerySet $qs) { - return $qs->limit($this->getLimit())->offset($this->getStart()); + $start = $this->getStart(); + $end = min($start + $this->getLimit() + $this->slack + ($start > 0 ? $this->slack : 0), $this->total); + return $qs->limit($end-$start)->offset($start); } } diff --git a/include/client/tickets.inc.php b/include/client/tickets.inc.php index 76412f7e3b49e8921a432cc4f65369fcb46d1007..58d056a1786b624bb3ea0e63e5c10ae62cc24d80 100644 --- a/include/client/tickets.inc.php +++ b/include/client/tickets.inc.php @@ -141,9 +141,9 @@ $tickets->values( $dept = $T['dept__ispublic'] ? Dept::getLocalById($T['dept_id'], 'name', $T['dept__name']) : $defaultDept; - $subject = Format::truncate($subject_field->display( + $subject = $subject_field->display( $subject_field->to_php($T['cdata__subject']) ?: $T['cdata__subject'] - ), 40); + ); $status = TicketStatus::getLocalById($T['status_id'], 'value', $T['status__name']); if (false) // XXX: Reimplement attachment count support $subject.=' <span class="Icon file"></span>'; @@ -162,9 +162,9 @@ $tickets->values( <td> <?php echo Format::date($T['created']); ?></td> <td> <?php echo $status; ?></td> <td> - <a href="tickets.php?id=<?php echo $T['ticket_id']; ?>"><?php echo $subject; ?></a> + <a class="truncate" href="tickets.php?id=<?php echo $T['ticket_id']; ?>"><?php echo $subject; ?></a> </td> - <td> <?php echo Format::truncate($dept,30); ?></td> + <td> <span class="truncate"><?php echo $dept; ?></span></td> </tr> <?php } diff --git a/include/staff/dynamic-list.inc.php b/include/staff/dynamic-list.inc.php index 9fe06217332e4afc6e2d2b8a2bccf717a7faafba..6a09009906e1d54c33a3e3a187e4a9bb35e87a7b 100644 --- a/include/staff/dynamic-list.inc.php +++ b/include/staff/dynamic-list.inc.php @@ -121,6 +121,7 @@ $info=Format::htmlchars(($errors && $_POST) ? array_merge($info,$_POST) : $info) <th nowrap></th> <th nowrap><?php echo __('Label'); ?></th> <th nowrap><?php echo __('Type'); ?></th> + <th nowrap><?php echo __('Visibility'); ?></th> <th nowrap><?php echo __('Variable'); ?></th> <th nowrap><?php echo __('Delete'); ?></th> </tr> @@ -160,6 +161,8 @@ $info=Format::htmlchars(($errors && $_POST) ? array_merge($info,$_POST) : $info) href="#form/field-config/<?php echo $f->get('id'); ?>"><i class="icon-cog"></i> <?php echo __('Config'); ?></a> <?php } ?></td> + <td> + <?php echo $f->getVisibilityDescription(); ?></td> <td> <input type="text" size="20" name="name-<?php echo $id; ?>" value="<?php echo Format::htmlchars($f->get('name')); @@ -200,6 +203,7 @@ $info=Format::htmlchars(($errors && $_POST) ? array_merge($info,$_POST) : $info) </optgroup> <?php } ?> </select></td> + <td></td> <td><input type="text" size="20" name="name-new-<?php echo $i; ?>" value="<?php echo $info["name-new-$i"]; ?>"/> <font class="error"><?php @@ -212,125 +216,9 @@ $info=Format::htmlchars(($errors && $_POST) ? array_merge($info,$_POST) : $info) </table> </div> <div id="items" class="hidden tab_content"> - <table class="form_table" width="940" border="0" cellspacing="0" cellpadding="2"> - <thead> - <?php if ($list) { - $page = ($_GET['p'] && is_numeric($_GET['p'])) ? $_GET['p'] : 1; - $count = $list->getNumItems(); - $pageNav = new Pagenate($count, $page, PAGE_LIMIT); - $pageNav->setURL('list.php', array('id' => $list->getId())); - $showing=$pageNav->showing().' '.__('list items'); - ?> - <?php } - else $showing = __('Add a few initial items to the list'); - ?> - <tr> - <th colspan="5"> - <em><?php echo $showing; ?></em> - </th> - </tr> - <tr> - <th></th> - <th><?php echo __('Value'); ?></th> - <?php - if (!$list || $list->hasAbbrev()) { ?> - <th><?php echo __(/* Short for 'abbreviation' */ 'Abbrev'); ?> <em style="display:inline">— - <?php echo __('abbreviations and such'); ?></em></th> - <?php - } ?> - <th><?php echo __('Disabled'); ?></th> - <th><?php echo __('Delete'); ?></th> - </tr> - </thead> - - <tbody <?php if ($info['sort_mode'] == 'SortCol') { ?> - class="sortable-rows" data-sort="sort-"<?php } ?>> - <?php - if ($list) { - $icon = ($info['sort_mode'] == 'SortCol') - ? '<i class="icon-sort"></i> ' : ''; - foreach ($list->getAllItems() as $i) { - $id = $i->getId(); ?> - <tr class="<?php if (!$i->isEnabled()) echo 'disabled'; ?>"> - <td><?php echo $icon; ?> - <input type="hidden" name="sort-<?php echo $id; ?>" - value="<?php echo $i->getSortOrder(); ?>"/></td> - <td><input type="text" size="40" name="value-<?php echo $id; ?>" - data-translate-tag="<?php echo $i->getTranslateTag('value'); ?>" - value="<?php echo $i->getValue(); ?>"/> - <?php if ($list->hasProperties()) { ?> - <a class="action-button field-config" - style="overflow:inherit" - href="#list/<?php - echo $list->getId(); ?>/item/<?php - echo $id ?>/properties" - id="item-<?php echo $id; ?>" - ><?php - echo sprintf('<i class="icon-edit" %s></i> ', - $i->getConfiguration() - ? '': 'style="color:red; font-weight:bold;"'); - echo __('Properties'); - ?></a> - <?php - } - - if ($errors["value-$id"]) - echo sprintf('<br><span class="error">%s</span>', - $errors["value-$id"]); - ?> - </td> - <?php - if ($list->hasAbbrev()) { ?> - <td><input type="text" size="30" name="abbrev-<?php echo $id; ?>" - value="<?php echo $i->getAbbrev(); ?>"/></td> - <?php - } ?> - <td> - <?php - if (!$i->isDisableable()) - echo '<i class="icon-ban-circle"></i>'; - else - echo sprintf('<input type="checkbox" name="disable-%s" - %s %s />', - $id, - !$i->isEnabled() ? ' checked="checked" ' : '', - (!$i->isEnabled() && !$i->isEnableable()) ? ' disabled="disabled" ' : '' - ); - ?> - </td> - <td> - <?php - if (!$i->isDeletable()) - echo '<i class="icon-ban-circle"></i>'; - else - echo sprintf('<input type="checkbox" name="delete-item-%s">', $id); - - ?> - </td> - </tr> - <?php } - } - - if (!$list || $list->allowAdd()) { - for ($i=0; $i<$newcount; $i++) { ?> - <tr> - <td><?php echo $icon; ?> <em>+</em> - <input type="hidden" name="sort-new-<?php echo $i; ?>"/></td> - <td><input type="text" size="40" name="value-new-<?php echo $i; ?>"/></td> - <?php - if (!$list || $list->hasAbbrev()) { ?> - <td><input type="text" size="30" name="abbrev-new-<?php echo $i; ?>"/></td> - <?php - } ?> - <td> </td> - <td> </td> - </tr> - <?php - } - }?> - </tbody> - </table> -</div> +<?php + $pjax_container = '#items'; + include STAFFINC_DIR . 'templates/list-items.tmpl.php'; ?> </div> <p class="centered"> <input type="submit" name="submit" value="<?php echo $submit_text; ?>"> @@ -342,14 +230,46 @@ $info=Format::htmlchars(($errors && $_POST) ? array_merge($info,$_POST) : $info) <script type="text/javascript"> $(function() { - $('a.field-config').click( function(e) { + $(document).on('click', 'a.field-config', function(e) { e.preventDefault(); var $id = $(this).attr('id'); var url = 'ajax.php/'+$(this).attr('href').substr(1); - $.dialog(url, [201], function (xhr) { - $('a#'+$id+' i').removeAttr('style'); + $.dialog(url, [201], function (xhr, resp) { + var json = $.parseJSON(resp); + if (json && json.success) { + if (json.id && json.row) { + $('#list-item-' + json.id).replaceWith(json.row); + } + else { + $.pjax.reload('#pjax-container'); + } + } }); return false; }); + $(document).on('click', 'a.items-action', function(e) { + e.preventDefault(); + var ids = []; + $('form#save :checkbox.mass:checked').each(function() { + ids.push($(this).val()); + }); + if (ids.length && confirm(__('You sure?'))) { + $.ajax({ + url: 'ajax.php/' + $(this).attr('href').substr(1), + type: 'POST', + data: {count:ids.length, ids:ids}, + dataType: 'json', + success: function(json) { + if (json.success) { + if (window.location.search.indexOf('a=items') != -1) + $.pjax.reload('#items'); + else + $.pjax.reload('#pjax-container'); + } + } + }); + } + return false; + }); }); </script> diff --git a/include/staff/footer.inc.php b/include/staff/footer.inc.php index 5abc0997369e1e00e9e3d79bc98ae92b671438ca..9f2ee556c48cc1ff95de18e93be5c3a61196bfb7 100644 --- a/include/staff/footer.inc.php +++ b/include/staff/footer.inc.php @@ -43,10 +43,11 @@ if(is_object($thisstaff) && $thisstaff->isStaff()) { ?> <script type="text/javascript"> if ($.support.pjax) { $(document).on('click', 'a', function(event) { - if (!$(this).hasClass('no-pjax') - && !$(this).closest('.no-pjax').length - && $(this).attr('href')[0] != '#') - $.pjax.click(event, {container: $('#pjax-container'), timeout: 2000}); + var $this = $(this); + if (!$this.hasClass('no-pjax') + && !$this.closest('.no-pjax').length + && $this.attr('href')[0] != '#') + $.pjax.click(event, {container: $this.data('pjaxContainer') || $('#pjax-container'), timeout: 2000}); }) } </script> diff --git a/include/staff/templates/list-import.tmpl.php b/include/staff/templates/list-import.tmpl.php new file mode 100644 index 0000000000000000000000000000000000000000..b3c38a99d66b83973de5459acc6285769e02f625 --- /dev/null +++ b/include/staff/templates/list-import.tmpl.php @@ -0,0 +1,85 @@ +<h3><?php echo $info['title']; ?></h3> +<b><a class="close" href="#"><i class="icon-remove-circle"></i></a></b> +<hr/> +<?php +if ($info['error']) { + echo sprintf('<p id="msg_error">%s</p>', $info['error']); +} elseif ($info['warn']) { + echo sprintf('<p id="msg_warning">%s</p>', $info['warn']); +} elseif ($info['msg']) { + echo sprintf('<p id="msg_notice">%s</p>', $info['msg']); +} ?> +<ul class="tabs" id="user-import-tabs"> + <li class="active"><a href="#copy-paste" + ><i class="icon-edit"></i> <?php echo __('Copy Paste'); ?></a></li> + <li><a href="#upload" + ><i class="icon-fixed-width icon-cloud-upload"></i> <?php echo __('Upload'); ?></a></li> +</ul> + +<form action="<?php echo $info['action']; ?>" method="post" enctype="multipart/form-data" + onsubmit="javascript: + if ($(this).find('[name=import]').val()) { + $(this).attr('action', '<?php echo $info['upload_url']; ?>'); + $(document).unbind('submit.dialog'); + }"> +<?php echo csrf_token(); +if ($org_id) { ?> + <input type="hidden" name="id" value="<?php echo $org_id; ?>"/> +<?php } ?> +<div id="user-import-tabs_container"> +<div class="tab_content" id="copy-paste" style="margin:5px;"> +<h2 style="margin-bottom:10px"><?php echo __('Value and Abbreviation'); ?></h2> +<p><?php echo __( +'Enter one name and abbreviation per line.'); ?><br/><em><?php echo __( +'To import items with properties, use the Upload tab.'); ?></em> +</p> +<textarea name="pasted" style="display:block;width:100%;height:8em;padding:5px" + placeholder="<?php echo __('e.g. My Location, MY'); ?>"> +<?php echo $info['pasted']; ?> +</textarea> +</div> + +<div class="hidden tab_content" id="upload" style="margin:5px;"> +<h2 style="margin-bottom:10px"><?php echo __('Import a CSV File'); ?></h2> +<p> +<em><?php echo __( +'Use the columns shown in the table below. To add more properties, use the Properties tab. Only properties with `variable` defined can be imported.'); ?> +</p> +<table class="list"><tr> +<?php + $fields = array('Value', 'Abbreviation'); + $data = array( + array('Value' => __('My Location'), 'Abbreviation' => 'MY') + ); + foreach ($list->getConfigurationForm()->getFields() as $f) + if ($f->get('name')) + $fields[] = $f->get('label'); + foreach ($fields as $f) { ?> + <th><?php echo mb_convert_case($f, MB_CASE_TITLE); ?></th> +<?php } ?> +</tr> +<?php + foreach ($data as $d) { + foreach ($fields as $f) { + ?><td><?php + if (isset($d[$f])) echo $d[$f]; + ?></td><?php + } + } ?> +</tr></table> +<br/> +<input type="file" name="import"/> +</div> + + <hr> + <p class="full-width"> + <span class="buttons pull-left"> + <input type="reset" value="<?php echo __('Reset'); ?>"> + <input type="button" name="cancel" class="close" value="<?php + echo __('Cancel'); ?>"> + </span> + <span class="buttons pull-right"> + <input type="submit" value="<?php echo __('Import Items'); ?>"> + </span> + </p> +</form> diff --git a/include/staff/templates/list-item-properties.tmpl.php b/include/staff/templates/list-item-properties.tmpl.php index def77747a2bea226a3e73bece150c380aea696bd..706c9484cb171e959788ce13a5c5cedb0624008e 100644 --- a/include/staff/templates/list-item-properties.tmpl.php +++ b/include/staff/templates/list-item-properties.tmpl.php @@ -1,63 +1,52 @@ - <h3><?php echo __('Item Properties'); ?> — <?php echo $item->getValue() ?></h3> - <a class="close" href=""><i class="icon-remove-circle"></i></a> - <hr/> - <form method="post" action="#list/<?php - echo $list->getId(); ?>/item/<?php - echo $item->getId(); ?>/properties"> - <?php - echo csrf_token(); - $config = $item->getConfiguration(); - $internal = $item->isInternal(); - $form = $item->getConfigurationForm(); - echo $form->getMedia(); - foreach ($form->getFields() as $f) { - ?> - <div class="custom-field" id="field<?php - echo $f->getWidget()->id; ?>" - <?php - if (!$f->isVisible()) echo 'style="display:none;"'; ?>> - <div class="field-label"> - <label for="<?php echo $f->getWidget()->name; ?>" - style="vertical-align:top;padding-top:0.2em"> - <?php echo Format::htmlchars($f->getLocal('label')); ?>:</label> - <?php - if (!$internal && $f->isEditable() && $f->get('hint')) { ?> - <br /><em style="color:gray;display:inline-block"><?php - echo Format::htmlchars($f->get('hint')); ?></em> - <?php - } ?> - </div><div> - <?php - if ($internal && !$f->isEditable()) - $f->render(array('mode'=>'view')); - else { - $f->render(); - if ($f->get('required')) { ?> - <font class="error">*</font> - <?php - } - } - ?> - </div> - <?php - foreach ($f->errors() as $e) { ?> - <div class="error"><?php echo $e; ?></div> - <?php } ?> - </div> - <?php - } - ?> - </table> - <hr> - <p class="full-width"> - <span class="buttons pull-left"> - <input type="reset" value="<?php echo __('Reset'); ?>"> - <input type="button" value="<?php echo __('Cancel'); ?>" class="close"> - </span> - <span class="buttons pull-right"> - <input type="submit" value="<?php echo __('Save'); ?>"> - </span> - </p> - </form> - <div class="clear"></div> +<h3><?php echo $list->getName(); ?> — <?php + echo $item ? $item->getValue() : __('Add New List Item'); ?></h3> +<a class="close" href=""><i class="icon-remove-circle"></i></a> +<hr/> +<ul class="tabs" id="item_tabs"> + <li class="active"> + <a href="#value"><i class="icon-reorder"></i> + <?php echo __('Value'); ?></a> + </li> + <li><a href="#properties"><i class="icon-asterisk"></i> + <?php echo __('Item Properties'); ?></a> + </li> +</ul> + +<form method="post" id="item_tabs_container" action="<?php echo $action; ?>"> + <?php + echo csrf_token(); + $internal = $item ? $item->isInternal() : false; +?> + +<div class="tab_content" id="value"> +<?php + $form = $item_form; + include 'dynamic-form-simple.tmpl.php'; +?> +</div> + +<div class="tab_content hidden" id="properties"> +<?php + $form = $item ? $item->getConfigurationForm($_POST ?: null) + : $list->getConfigurationForm(); + include 'dynamic-form-simple.tmpl.php'; +?> +</div> + +<hr> +<p class="full-width"> + <span class="buttons pull-left"> + <input type="reset" value="<?php echo __('Reset'); ?>"> + <input type="button" value="<?php echo __('Cancel'); ?>" class="close"> + </span> + <span class="buttons pull-right"> + <input type="submit" value="<?php echo __('Save'); ?>"> + </span> + </p> +</form> + +<script type="text/javascript"> + // Make translatable fields translatable + $('input[data-translate-tag], textarea[data-translate-tag]').translatable(); +</script> diff --git a/include/staff/templates/list-item-row.tmpl.php b/include/staff/templates/list-item-row.tmpl.php new file mode 100644 index 0000000000000000000000000000000000000000..267cb1e5e7e0ca7a2d1666bf8c1993ab93b82f76 --- /dev/null +++ b/include/staff/templates/list-item-row.tmpl.php @@ -0,0 +1,39 @@ +<?php + $id = $item->getId(); ?> + <tr id="list-item-<?php echo $id; ?>" class="<?php if (!$item->isEnabled()) echo 'disabled'; ?>"> + <td nowrap><?php echo $icon; ?> + <input type="hidden" name="sort-<?php echo $id; ?>" + value="<?php echo $item->getSortOrder(); ?>"/> + <input type="checkbox" value="<?php echo $id; ?>" class="mass nowarn"/> + </td> + <td> + <a class="field-config" + style="overflow:inherit" + href="#list/<?php + echo $list->getId(); ?>/item/<?php + echo $id ?>/update" + id="item-<?php echo $id; ?>" + ><?php + echo sprintf('<i class="icon-edit" %s></i> ', + $item->getConfiguration() + ? '': 'style="color:red; font-weight:bold;"'); + ?> + <?php echo Format::htmlchars($item->getValue()); ?> + <?php + if ($list->hasAbbrev() && ($A = $item->getAbbrev())) { ?> + ( <?php echo Format::htmlchars($A); ?> ) + <?php + } ?> +<?php if ($errors["value-$id"]) + echo sprintf('<div class="error">%s</div>', + $errors["value-$id"]); + ?> + </a> + </td> +<?php $props = $item->getConfiguration(); + foreach ($prop_fields as $F) { ?> + <td style="max-width: 20%"><span class="truncate"><?php + echo $F->display($props[$F->get('id')]); + ?></span></td> +<?php } ?> + </tr> diff --git a/include/staff/templates/list-items.tmpl.php b/include/staff/templates/list-items.tmpl.php new file mode 100644 index 0000000000000000000000000000000000000000..e57a8bf09fd5265c675447728c2aeb880e45e074 --- /dev/null +++ b/include/staff/templates/list-items.tmpl.php @@ -0,0 +1,127 @@ + <?php if ($list) { + $page = ($_GET['p'] && is_numeric($_GET['p'])) ? $_GET['p'] : 1; + $count = $list->getNumItems(); + $pageNav = new Pagenate($count, $page, PAGE_LIMIT); + if ($list->getSortMode() == 'SortCol') + $pageNav->setSlack(1); + $pageNav->setURL('lists.php?id='.$list->getId().'&a=items'); + $showing=$pageNav->showing().' '.__('list items'); + ?> + <?php } + else $showing = __('Add a few initial items to the list'); + ?> + <div style="margin: 5px 0"> + <div class="pull-left"> + <input type="search" size="25" id="search" value="<?php + echo Format::htmlchars($_POST['search']); ?>"/> + <button type="submit" onclick="javascript: + event.preventDefault(); + $.pjax({type: 'POST', data: { search: $('#search').val() }, container: '#pjax-container'}); + return false; +"><?php echo __('Search'); ?></button> + <?php if ($_POST['search']) { ?> + <a href="#" onclick="javascript: + $.pjax.reload('#pjax-container'); return false; " + ><i class="icon-remove-sign"></i> <?php + echo __('clear'); ?></a> + <?php } ?> + </div> + <?php if ($list) { ?> + <div class="pull-right"> + <em style="display:inline-block; padding-bottom: 3px;"><?php echo $showing; ?></em> + <?php if ($list->allowAdd()) { ?> + <a class="action-button field-config" + href="#list/<?php + echo $list->getId(); ?>/item/add"> + <i class="icon-plus-sign"></i> + <?php echo __('Add New Item'); ?> + </a> + <a class="action-button field-config" + href="#list/<?php + echo $list->getId(); ?>/import"> + <i class="icon-upload"></i> + <?php echo __('Import Items'); ?> + </a> + <?php } ?> + <span class="action-button pull-right" data-dropdown="#action-dropdown-more"> + <i class="icon-caret-down pull-right"></i> + <span ><i class="icon-cog"></i> <?php echo __('More');?></span> + </span> + <div id="action-dropdown-more" class="action-dropdown anchor-right"> + <ul> + <li><a class="items-action" href="#list/<?php echo $list->getId(); ?>/delete"> + <i class="icon-trash icon-fixed-width"></i> + <?php echo __('Delete'); ?></a></li> + <li><a class="items-action" href="#list/<?php echo $list->getId(); ?>/disable"> + <i class="icon-ban-circle icon-fixed-width"></i> + <?php echo __('Disable'); ?></a></li> + <li><a class="items-action" href="#list/<?php echo $list->getId(); ?>/enable"> + <i class="icon-ok-sign icon-fixed-width"></i> + <?php echo __('Enable'); ?></a></li> + </ul> + </div> + </div> + <?php } ?> + + <div class="clear"></div> + </div> + + +<?php +$prop_fields = array(); +if ($list) { + foreach ($list->getConfigurationForm()->getFields() as $f) { + if (in_array($f->get('type'), array('text', 'datetime', 'phone'))) + $prop_fields[] = $f; + if (strpos($f->get('type'), 'list-') === 0) + $prop_fields[] = $f; + + // 4 property columns max + if (count($prop_fields) == 4) + break; + } +} +?> + + <table class="form_table fixed" width="940" border="0" cellspacing="0" cellpadding="2"> + <thead> + <tr> + <th width="24" nowrap></th> + <th><?php echo __('Value'); ?></th> +<?php foreach ($prop_fields as $F) { ?> + <th><?php echo $F->getLocal('label'); ?></th> +<?php } ?> + </tr> + </thead> + + <tbody <?php if (!isset($_POST['search']) && $list && $list->get('sort_mode') == 'SortCol') { ?> + class="sortable-rows" data-sort="sort-"<?php } ?>> + <?php + if ($list) { + $icon = ($list->get('sort_mode') == 'SortCol') + ? '<i class="icon-sort"></i> ' : ''; + $items = $list->getAllItems(); + if ($_POST['search']) { + $items->filter(Q::any(array( + 'value__contains'=>$_POST['search'], + 'extra__contains'=>$_POST['search'], + 'properties__contains'=>$_POST['search'], + ))); + $search = true; + } + $items = $pageNav->paginate($items); + // Emit a marker for the first sort offset ?> + <input type="hidden" id="sort-offset" value="<?php echo + max($items[0]->sort, $pageNav->getStart()); ?>"/> +<?php + foreach ($items as $item) { + include STAFFINC_DIR . 'templates/list-item-row.tmpl.php'; + } + } ?> + </tbody> + </table> +<?php if ($pageNav && $pageNav->getNumPages()) { ?> + <div><?php echo __('Page').':'.$pageNav->getPageLinks('items', $pjax_container); ?></div> +<?php } ?> +</div> + diff --git a/include/staff/templates/simple-form.tmpl.php b/include/staff/templates/simple-form.tmpl.php index 2191e474de62a90d55e15ceb5ac9a3ed691e9272..705592fcf516635c4f9148d12716d638f7710014 100644 --- a/include/staff/templates/simple-form.tmpl.php +++ b/include/staff/templates/simple-form.tmpl.php @@ -8,7 +8,7 @@ <div class="form-field"><?php if (!$field->isBlockLevel()) { ?> <div class="<?php if ($field->isRequired()) echo 'required'; - ?>" style="display:inline-block;width:260px;"> + ?>" style="display:inline-block;width:27%;"> <?php echo Format::htmlchars($field->getLocal('label')); ?>: <?php if ($field->isRequired()) { ?> <span class="error">*</span> @@ -20,7 +20,7 @@ ?></div> <?php } ?> </div> - <div style="display:inline-block;max-width:700px"><?php + <div style="display:inline-block;max-width:73%"><?php } $field->render($options); foreach ($field->errors() as $e) { ?> diff --git a/include/staff/templates/tickets.tmpl.php b/include/staff/templates/tickets.tmpl.php index ce48d471b659a6ae2b0cb2fb95bc9a646bd90066..e70d3e29cd537338d5feb20b6f5f070acdf60de9 100644 --- a/include/staff/templates/tickets.tmpl.php +++ b/include/staff/templates/tickets.tmpl.php @@ -85,7 +85,7 @@ if ($results) { ?> <?php csrf_token(); ?> <input type="hidden" name="a" value="mass_process" > <input type="hidden" name="do" id="action" value="" > - <table class="list" border="0" cellspacing="1" cellpadding="2" width="940"> + <table class="list fixed" border="0" cellspacing="1" cellpadding="2" width="940"> <thead> <tr> <?php @@ -99,11 +99,11 @@ if ($results) { ?> <th width="300"><?php echo __('Subject'); ?></th> <?php if ($user) { ?> - <th width="200"><?php echo __('Department'); ?></th> - <th width="200"><?php echo __('Assignee'); ?></th> + <th width="100"><?php echo __('Department'); ?></th> + <th width="100"><?php echo __('Assignee'); ?></th> <?php } else { ?> - <th width="400"><?php echo __('User'); ?></th> + <th width="200"><?php echo __('User'); ?></th> <?php } ?> </tr> @@ -119,15 +119,15 @@ if ($results) { ?> $assigned=''; if ($row['staff_id']) - $assigned=sprintf('<span class="Icon staffAssigned">%s</span>',Format::truncate($row['staff'],40)); + $assigned=sprintf('<span class="truncate Icon staffAssigned">%s</span>',$row['staff']); elseif ($row['team_id']) - $assigned=sprintf('<span class="Icon teamAssigned">%s</span>',Format::truncate($row['team'],40)); + $assigned=sprintf('<span class="truncate Icon teamAssigned">%s</span>',$row['team']); else $assigned=' '; $status = ucfirst($row['status']); $tid=$row['number']; - $subject = Format::htmlchars(Format::truncate($row['subject'],40)); + $subject = Format::htmlchars($row['subject']); $threadcount=$row['thread_count']; ?> <tr id="<?php echo $row['ticket_id']; ?>"> @@ -147,7 +147,8 @@ if ($results) { ?> data-preview="#tickets/<?php echo $row['ticket_id']; ?>/preview"><?php echo $tid; ?></a></td> <td align="center" nowrap><?php echo Format::datetime($row['effective_date']); ?></td> <td><?php echo $status; ?></td> - <td><a <?php if ($flag) { ?> class="Icon <?php echo $flag; ?>Ticket" title="<?php echo ucfirst($flag); ?> Ticket" <?php } ?> + <td><a class="truncate <?php if ($flag) { ?> Icon <?php echo $flag; ?>Ticket" title="<?php echo ucfirst($flag); ?> Ticket<?php } ?>" + style="max-width: 80%; max-width: calc(100% - 86px);" href="tickets.php?id=<?php echo $row['ticket_id']; ?>"><?php echo $subject; ?></a> <?php if ($threadcount>1) @@ -161,7 +162,7 @@ if ($results) { ?> </td> <?php if ($user) { ?> - <td><?php echo Format::truncate($row['department'], 40); ?></td> + <td><span class="truncate"><?php echo $row['department']; ?></td> <td> <?php echo $assigned; ?></td> <?php } else { ?> diff --git a/include/staff/ticket-view.inc.php b/include/staff/ticket-view.inc.php index 0560eaf00ce70eacee1048f45dd9903a13368843..d16b43523050e3a12e75f73b472954880c42e209 100644 --- a/include/staff/ticket-view.inc.php +++ b/include/staff/ticket-view.inc.php @@ -403,8 +403,8 @@ $tcount = $ticket->getThreadEntries($types)->count(); <span class="pull-left"> <span style="display:inline-block"><?php echo Format::datetime($entry->created);?></span> - <span style="display:inline-block;padding:0 1em" class="faded title"><?php - echo Format::truncate($entry->title, 100); ?></span> + <span style="display:inline-block;padding:0 1em;max-width: 500px" class="faded title truncate"><?php + echo $entry->title; ?></span> </span> <div class="pull-right"> <?php if ($entry->hasActions()) { diff --git a/include/staff/tickets.inc.php b/include/staff/tickets.inc.php index f0d4a6b2d5cecc791158f4af4fd646397442f2b5..f8013df254662376ad4546e6d10d3c0cf115813e 100644 --- a/include/staff/tickets.inc.php +++ b/include/staff/tickets.inc.php @@ -226,15 +226,15 @@ $_SESSION[':Q:tickets'] = $tickets; <input type="hidden" name="do" id="action" value="" > <input type="hidden" name="status" value="<?php echo Format::htmlchars($_REQUEST['status'], true); ?>" > - <table class="list" border="0" cellspacing="1" cellpadding="2" width="940"> + <table class="list fixed" border="0" cellspacing="1" cellpadding="2" width="940"> <thead> <tr> <?php if ($thisstaff->canManageTickets()) { ?> - <th width="8px"> </th> + <th width="12px"> </th> <?php } ?> <th width="70"> <?php echo __('Ticket'); ?></th> - <th width="70"> + <th width="100"> <?php echo $date_header ?: __('Date'); ?></th> <th width="280"> <?php echo __('Subject'); ?></th> @@ -290,17 +290,17 @@ $_SESSION[':Q:tickets'] = $tickets; $dept = Dept::getLocalById($T['dept_id'], 'name', $T['dept__name']); if($showassigned) { if($T['staff_id']) - $lc=sprintf('<span class="Icon staffAssigned">%s</span>',Format::truncate((string) new PersonsName($T['staff__firstname'].' '.$T['staff__lastname']),40)); + $lc=sprintf('<span class="Icon staffAssigned truncate">%s</span>',(string) new PersonsName($T['staff__firstname'].' '.$T['staff__lastname'])); elseif($T['team_id']) $lc=sprintf('<span class="Icon teamAssigned">%s</span>', - Format::truncate(Team::getLocalById($T['team_id'], 'name', $T['team__name']),40)); + Team::getLocalById($T['team_id'], 'name', $T['team__name'])); else $lc=' '; }else{ - $lc=Format::truncate($dept,40); + $lc='<span class="truncate">'.Format::htmlchars($dept).'</span>'; } $tid=$T['number']; - $subject = Format::truncate($subject_field->display($subject_field->to_php($T['cdata__subject'])),40); + $subject = $subject_field->display($subject_field->to_php($T['cdata__subject'])); $threadcount=$T['thread_count']; if(!strcasecmp($T['status__state'],'open') && !$T['isanswered'] && !$T['lock__staff_id']) { $tid=sprintf('<b>%s</b>',$tid); @@ -326,7 +326,9 @@ $_SESSION[':Q:tickets'] = $tickets; ><?php echo $tid; ?></a></td> <td align="center" nowrap><?php echo Format::datetime($T[$date_col ?: 'lastupdate']); ?></td> <td><a <?php if ($flag) { ?> class="Icon <?php echo $flag; ?>Ticket" title="<?php echo ucfirst($flag); ?> Ticket" <?php } ?> - href="tickets.php?id=<?php echo $T['ticket_id']; ?>"><?php echo $subject; ?></a> + style="max-width: 80%; max-width: calc(100% - 86px);" + href="tickets.php?id=<?php echo $T['ticket_id']; ?>"><span + class="truncate"><?php echo $subject; ?></span></a> <?php if ($threadcount>1) echo "<small>($threadcount)</small> ".'<i @@ -337,8 +339,10 @@ $_SESSION[':Q:tickets'] = $tickets; echo '<i class="icon-fixed-width icon-paperclip"></i> '; ?> </td> - <td nowrap> <?php $un = new PersonsName($T['user__name']); echo Format::htmlchars( - Format::truncate($un, 22, strpos($un, '@'))); ?> </td> + <td nowrap><span class="truncate"><?php + $un = new PersonsName($T['user__name']); + echo Format::htmlchars($un); + ?></td> <?php if($search && !$status){ $displaystatus=TicketStatus::getLocalById($T['status_id'], 'value', $T['status__name']); diff --git a/scp/ajax.php b/scp/ajax.php index 140085b3910165086b67e1e9f4d61428777569e2..ed9e37345c16c29766b223c3d5a5da37ec02a1f0 100644 --- a/scp/ajax.php +++ b/scp/ajax.php @@ -64,8 +64,15 @@ $dispatcher = patterns('', url_get('^action/(?P<type>\w+)/config$', 'getFilterActionForm') )), url('^/list/', patterns('ajax.forms.php:DynamicFormsAjaxAPI', - url_get('^(?P<list>\w+)/item/(?P<id>\d+)/properties$', 'getListItemProperties'), - url_post('^(?P<list>\w+)/item/(?P<id>\d+)/properties$', 'saveListItemProperties') + url_get('^(?P<list>\w+)/items$', 'getListItems'), + url_get('^(?P<list>\w+)/item/(?P<id>\d+)/update$', 'getListItem'), + url_post('^(?P<list>\w+)/item/(?P<id>\d+)/update$', 'saveListItem'), + url('^(?P<list>\w+)/item/add$', 'addListItem'), + url('^(?P<list>\w+)/import$', 'importListItems'), + url('^(?P<list>\w+)/manage$', 'massManageListItems'), + url_post('^(?P<list>\w+)/delete$', 'deleteItems'), + url_post('^(?P<list>\w+)/disable$', 'disableItems'), + url_post('^(?P<list>\w+)/enable$', 'undisableItems') )), url('^/report/overview/', patterns('ajax.reports.php:OverviewReportAjaxAPI', # Send diff --git a/scp/css/scp.css b/scp/css/scp.css index e687b82be86bc6700eb385246ce3f4118a182caa..68188bee7044962c36c077f62de59da1250092e1 100644 --- a/scp/css/scp.css +++ b/scp/css/scp.css @@ -641,19 +641,18 @@ a.print { table.fixed { table-layout: fixed; - border-collapse: collapse; width: 100%; } -table.fixed > thead > tr > th, -table.fixed > thead > tr > td, -table.fixed > tbody > tr > td, -table.fixed > tr > td { +table.fixed > thead > tr > th:not([width]), +table.fixed > thead > tr > td:not([width]), +table.fixed > tbody > tr > td:not([width]), +table.fixed > tr > td:not([width]) { width: 180px; } -table.fixed > thead > tr > th + th, -table.fixed > thead > tr > td + td, -table.fixed > tbody > tr > td + td, -table.fixed > tr > td + td { +table.fixed > thead > tr > th + th:not([width]), +table.fixed > thead > tr > td + td:not([width]), +table.fixed > tbody > tr > td + td:not([width]), +table.fixed > tr > td + td:not([width]) { width: auto; } @@ -2084,6 +2083,8 @@ button a:hover { } .truncate { width: auto; + display: inline-block; + max-width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; diff --git a/scp/js/scp.js b/scp/js/scp.js index edb2cf0c0ccf6750c3a9b58584ab3b0a1dbd1687..5e49989761d3b37bcb72fd58291c9a5cf0737e12 100644 --- a/scp/js/scp.js +++ b/scp/js/scp.js @@ -152,8 +152,8 @@ var scp_prep = function() { } }; - $("form#save :input").change(function() { - warnOnLeave($(this)); + $("form#save :input[name]").change(function() { + if (!$(this).is('.nowarn')) warnOnLeave($(this)); }); $("form#save :input[type=reset]").click(function() { @@ -431,10 +431,11 @@ var scp_prep = function() { 'helper': fixHelper, 'cursor': 'move', 'stop': function(e, ui) { - var attr = ui.item.parent('tbody').data('sort'); + var attr = ui.item.parent('tbody').data('sort'), + offset = parseInt($('#sort-offset').val(), 10) || 0; warnOnLeave(ui.item); $('input[name^='+attr+']', ui.item.parent('tbody')).each(function(i, el) { - $(el).val(i+1); + $(el).val(i + 1 + offset); }); } }); @@ -587,7 +588,7 @@ $.dialog = function (url, codes, cb, options) { $.toggleOverlay(false); $popup.hide(); $('div.body', $popup).empty(); - if(cb) cb(xhr); + if(cb) cb(xhr, resp); } else { try { var json = $.parseJSON(resp); diff --git a/scp/lists.php b/scp/lists.php index 5247a0643c36e1aed9c9b8b73c6ff75661aa0dff..628e60b059e73c564c5e23f5e10b7fbf2c0eb1ed 100644 --- a/scp/lists.php +++ b/scp/lists.php @@ -21,7 +21,6 @@ if ($criteria) { } $errors = array(); -$max_isort = 0; if($_POST) { switch(strtolower($_POST['do'])) { @@ -30,36 +29,15 @@ if($_POST) { $errors['err']=sprintf(__('%s: Unknown or invalid ID.'), __('custom list')); elseif ($list->update($_POST, $errors)) { - // Update items - $items = array(); - foreach ($list->getAllItems() as $item) { - $id = $item->getId(); - if ($_POST["delete-item-$id"] == 'on' && $item->isDeletable()) { - $item->delete(); - continue; - } - - $ht = array( - 'value' => $_POST["value-$id"], - 'abbrev' => $_POST["abbrev-$id"], - 'sort' => $_POST["sort-$id"], - ); - $value = mb_strtolower($ht['value']); - if (!$value) - $errors["value-$id"] = __('Value required'); - elseif (in_array($value, $items)) - $errors["value-$id"] = __('Value already in-use'); - elseif ($item->update($ht, $errors)) { - if ($_POST["disable-$id"] == 'on') - $item->disable(); - elseif(!$item->isEnabled() && $item->isEnableable()) - $item->enable(); - - $item->save(); - $items[] = $value; + // Update item sorting + if ($list->getSortMode() == 'SortCol') { + foreach ($list->getAllItems() as $item) { + $id = $item->getId(); + if (isset($_POST["sort-{$id}"])) { + $item->sort = $_POST["sort-$id"]; + $item->save(); + } } - - $max_isort = max($max_isort, $_POST["sort-$id"]); } // Update properties @@ -154,19 +132,21 @@ if($_POST) { } } break; - } - - if ($list && $list->allowAdd()) { - for ($i=0; isset($_POST["sort-new-$i"]); $i++) { - if (!$_POST["value-new-$i"]) - continue; - $list->addItem(array( - 'value' => $_POST["value-new-$i"], - 'abbrev' =>$_POST["abbrev-new-$i"], - 'sort' => $_POST["sort-new-$i"] ?: ++$max_isort, - ), $errors); - } + case 'import-items': + if (!$list) { + $errors['err']=sprintf(__('%s: Unknown or invalid ID.'), + __('custom list')); + } + else { + $status = $list->importFromPost($_FILES['import'] ?: $_POST['pasted']); + if (is_numeric($status)) + $msg = sprintf(__('Successfully imported %1$d %2$s.'), $status, + _N('list item', 'list items', $status)); + else + $errors['err'] = $status; + } + break; } if ($form) { @@ -179,6 +159,9 @@ if($_POST) { 'label' => $_POST["prop-label-new-$i"], 'type' => $_POST["type-new-$i"], 'name' => $_POST["name-new-$i"], + 'flags' => DynamicFormField::FLAG_ENABLED + | DynamicFormField::FLAG_AGENT_VIEW + | DynamicFormField::FLAG_AGENT_EDIT, )); $field->setForm($form); if ($field->isValid()) @@ -193,6 +176,13 @@ if($_POST) { } $page='dynamic-lists.inc.php'; +if($list && !strcasecmp(@$_REQUEST['a'],'items') && isset($_SERVER['HTTP_X_PJAX'])) { + $page='templates/list-items.tmpl.php'; + $pjax_container = @$_SERVER['HTTP_X_PJAX_CONTAINER']; + require(STAFFINC_DIR.$page); + // Don't emit the header + return; +} if($list || ($_REQUEST['a'] && !strcasecmp($_REQUEST['a'],'add'))) { $page='dynamic-list.inc.php'; $ost->addExtraHeader('<meta name="tip-namespace" content="manage.custom_list" />',