diff options
137 files changed, 8404 insertions, 512 deletions
diff --git a/README.markdown b/README.markdown index 7fe2c58d..52667d86 100644 --- a/README.markdown +++ b/README.markdown @@ -33,7 +33,7 @@ Features - Host anywhere (shared hosting, VPS, Raspberry Pi or localhost) - No external dependencies - **Super easy setup**, copy and paste files and you are done! -- Translated in 15 languages (Brazilian, Chinese, Danish, English, Finnish, French, German, Hungarian, Italian, Japanese, Polish, Russian, Spanish, Swedish, Thai) +- Translated in 18 languages (Brazilian, Chinese, Danish, Dutch, English, Finnish, French, German, Hungarian, Italian, Japanese, Polish, Russian, Serbian, Spanish, Swedish, Thai, Turkish) Known bugs and feature requests ------------------------------- @@ -172,12 +172,15 @@ Contributors: - [Cmer](https://github.com/chncsu) - [Colin Williams](https://github.com/crwilliams) - [Crash5](https://github.com/crash5) +- [Creador30](https://github.com/creador30) - [Cynthia Pereira](https://github.com/cynthiapereira) - [David-Norris](https://github.com/David-Norris) +- [Draza (bdpsoft)](https://github.com/bdpsoft) - [Esteban Monge](https://github.com/EstebanMonge) - [Fengchao](https://github.com/fengchao) - [Floaltvater](https://github.com/floaltvater) - [Gavlepeter](https://github.com/gavlepeter) +- [Hendrik Stocker](https://github.com/hendrik-stoker) - [Iterate From 0](https://github.com/freebsd-kanboard) - [Jan Dittrich](https://github.com/jdittrich) - [Janne Mäntyharju](https://github.com/JanneMantyharju) diff --git a/app/Auth/Ldap.php b/app/Auth/Ldap.php index 376d16f6..ed29199f 100644 --- a/app/Auth/Ldap.php +++ b/app/Auth/Ldap.php @@ -261,7 +261,7 @@ class Ldap extends Base private function getQuery($username, $email) { if ($username && $email) { - return '(&('.sprintf(LDAP_USER_PATTERN, $username).')('.sprintf(LDAP_ACCOUNT_EMAIL, $email).')'; + return '(&('.sprintf(LDAP_USER_PATTERN, $username).')('.LDAP_ACCOUNT_EMAIL.'='.$email.'))'; } else if ($username) { return sprintf(LDAP_USER_PATTERN, $username); diff --git a/app/Controller/Base.php b/app/Controller/Base.php index d949048d..6420e0ee 100644 --- a/app/Controller/Base.php +++ b/app/Controller/Base.php @@ -34,6 +34,7 @@ use Symfony\Component\EventDispatcher\Event; * @property \Model\Config $config * @property \Model\DateParser $dateParser * @property \Model\File $file + * @property \Model\HourlyRate $hourlyRate * @property \Model\LastLogin $lastLogin * @property \Model\Notification $notification * @property \Model\Project $project @@ -56,12 +57,16 @@ use Symfony\Component\EventDispatcher\Event; * @property \Model\TaskPosition $taskPosition * @property \Model\TaskPermission $taskPermission * @property \Model\TaskStatus $taskStatus + * @property \Model\Timetable $timetable + * @property \Model\TimetableDay $timetableDay + * @property \Model\TimetableWeek $timetableWeek + * @property \Model\TimetableExtra $timetableExtra + * @property \Model\TimetableOff $timetableOff * @property \Model\TaskValidator $taskValidator * @property \Model\TaskLink $taskLink * @property \Model\CommentHistory $commentHistory * @property \Model\SubtaskHistory $subtaskHistory * @property \Model\SubtaskTimeTracking $subtaskTimeTracking - * @property \Model\TimeTracking $timeTracking * @property \Model\User $user * @property \Model\UserSession $userSession * @property \Model\Webhook $webhook @@ -148,7 +153,7 @@ abstract class Base $this->response->xss(); // Allow the public board iframe inclusion - if ($action !== 'readonly') { + if (ENABLE_XFRAME && $action !== 'readonly') { $this->response->xframe(); } diff --git a/app/Controller/Board.php b/app/Controller/Board.php index 90b7f357..17170317 100644 --- a/app/Controller/Board.php +++ b/app/Controller/Board.php @@ -127,6 +127,7 @@ class Board extends Base 'swimlanes' => $this->board->getBoard($project['id']), 'categories' => $this->category->getList($project['id'], false), 'title' => $project['name'], + 'description' => $project['description'], 'no_layout' => true, 'not_editable' => true, 'board_public_refresh_interval' => $this->config->get('board_public_refresh_interval'), @@ -187,6 +188,7 @@ class Board extends Base 'swimlanes' => $this->board->getBoard($project['id']), 'categories' => $this->category->getList($project['id'], true, true), 'title' => $project['name'], + 'description' => $project['description'], 'board_selector' => $board_selector, 'board_private_refresh_interval' => $this->config->get('board_private_refresh_interval'), 'board_highlight_period' => $this->config->get('board_highlight_period'), @@ -439,7 +441,8 @@ class Board extends Base $task = $this->getTask(); $this->response->html($this->template->render('board/files', array( - 'files' => $this->file->getAll($task['id']), + 'files' => $this->file->getAllDocuments($task['id']), + 'images' => $this->file->getAllImages($task['id']), 'task' => $task, ))); } diff --git a/app/Controller/Budget.php b/app/Controller/Budget.php new file mode 100644 index 00000000..2c56c79d --- /dev/null +++ b/app/Controller/Budget.php @@ -0,0 +1,135 @@ +<?php + +namespace Controller; + +/** + * Budget + * + * @package controller + * @author Frederic Guillot + */ +class Budget extends Base +{ + /** + * Budget index page + * + * @access public + */ + public function index() + { + $project = $this->getProject(); + + $this->response->html($this->projectLayout('budget/index', array( + 'daily_budget' => $this->budget->getDailyBudgetBreakdown($project['id']), + 'project' => $project, + 'title' => t('Budget') + ))); + } + + /** + * Cost breakdown by users/subtasks/tasks + * + * @access public + */ + public function breakdown() + { + $project = $this->getProject(); + + $paginator = $this->paginator + ->setUrl('budget', 'breakdown', array('project_id' => $project['id'])) + ->setMax(30) + ->setOrder('start') + ->setDirection('DESC') + ->setQuery($this->budget->getSubtaskBreakdown($project['id'])) + ->calculate(); + + $this->response->html($this->projectLayout('budget/breakdown', array( + 'paginator' => $paginator, + 'project' => $project, + 'title' => t('Budget') + ))); + } + + /** + * Create budget lines + * + * @access public + */ + public function create(array $values = array(), array $errors = array()) + { + $project = $this->getProject(); + + if (empty($values)) { + $values['date'] = date('Y-m-d'); + } + + $this->response->html($this->projectLayout('budget/create', array( + 'lines' => $this->budget->getAll($project['id']), + 'values' => $values + array('project_id' => $project['id']), + 'errors' => $errors, + 'project' => $project, + 'title' => t('Budget') + ))); + } + + /** + * Validate and save a new budget + * + * @access public + */ + public function save() + { + $project = $this->getProject(); + + $values = $this->request->getValues(); + list($valid, $errors) = $this->budget->validateCreation($values); + + if ($valid) { + + if ($this->budget->create($values['project_id'], $values['amount'], $values['comment'], $values['date'])) { + $this->session->flash(t('The budget line have been created successfully.')); + $this->response->redirect($this->helper->url('budget', 'create', array('project_id' => $project['id']))); + } + else { + $this->session->flashError(t('Unable to create the budget line.')); + } + } + + $this->create($values, $errors); + } + + /** + * Confirmation dialog before removing a budget + * + * @access public + */ + public function confirm() + { + $project = $this->getProject(); + + $this->response->html($this->projectLayout('budget/remove', array( + 'project' => $project, + 'budget_id' => $this->request->getIntegerParam('budget_id'), + 'title' => t('Remove a budget line'), + ))); + } + + /** + * Remove a budget + * + * @access public + */ + public function remove() + { + $this->checkCSRFParam(); + $project = $this->getProject(); + + if ($this->budget->remove($this->request->getIntegerParam('budget_id'))) { + $this->session->flash(t('Budget line removed successfully.')); + } else { + $this->session->flashError(t('Unable to remove this budget line.')); + } + + $this->response->redirect($this->helper->url('budget', 'create', array('project_id' => $project['id']))); + } +} diff --git a/app/Controller/Calendar.php b/app/Controller/Calendar.php index 1c7ac7c0..6cfa2bad 100644 --- a/app/Controller/Calendar.php +++ b/app/Controller/Calendar.php @@ -14,7 +14,7 @@ use Model\Task as TaskModel; class Calendar extends Base { /** - * Show calendar view + * Show calendar view for projects * * @access public */ @@ -59,9 +59,7 @@ class Calendar extends Base ->filterByDueDateRange($start, $end) ->toCalendarEvents(); - $subtask_timeslots = $this->subtaskTimeTracking->getProjectCalendarEvents($project_id, $start, $end); - - $this->response->json(array_merge($due_tasks, $subtask_timeslots)); + $this->response->json($due_tasks); } /** @@ -100,7 +98,7 @@ class Calendar extends Base $this->taskModification->update(array( 'id' => $values['task_id'], - 'date_due' => $values['date_due'], + 'date_due' => substr($values['date_due'], 0, 10), )); } } diff --git a/app/Controller/Config.php b/app/Controller/Config.php index 01c7ad53..bee897be 100644 --- a/app/Controller/Config.php +++ b/app/Controller/Config.php @@ -38,7 +38,11 @@ class Config extends Base { if ($this->request->isPost()) { - $values = $this->request->getValues() + array('subtask_restriction' => 0, 'subtask_time_tracking' => 0); + $values = $this->request->getValues(); + + if ($redirect === 'board') { + $values += array('subtask_restriction' => 0, 'subtask_time_tracking' => 0); + } if ($this->config->save($values)) { $this->config->reload(); diff --git a/app/Controller/File.php b/app/Controller/File.php index 3255fe84..3963e2d7 100644 --- a/app/Controller/File.php +++ b/app/Controller/File.php @@ -102,6 +102,70 @@ class File extends Base } /** + * Return image thumbnails + * + * @access public + */ + public function thumbnail() + { + $task = $this->getTask(); + $file = $this->file->getById($this->request->getIntegerParam('file_id')); + $width_param = $this->request->getIntegerParam('width'); + $height_param = $this->request->getIntegerParam('height'); + $filename = FILES_DIR.$file['path']; + + if ($file['task_id'] == $task['id'] && file_exists($filename)) { + + // Get new sizes + list($width, $height) = getimagesize($filename); + + if ($width_param == 0 && $height_param == 0) { + $newwidth = 100; + $newheight = 100; + } elseif ($width_param > 0 && $height_param == 0) { + $newwidth = $width_param; + $newheight = floor($height * ($width_param / $width)); + } elseif ($width_param == 0 && $height_param > 0) { + $newwidth = floor($width * ($height_param / $height)); + $newheight = $height_param; + } else { + $newwidth = $width_param; + $newheight = $height_param; + } + + // Load + $thumb = imagecreatetruecolor($newwidth, $newheight); + $extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION)); + + switch ($extension) { + case 'jpeg': + case 'jpg': + $source = imagecreatefromjpeg($filename); + break; + case 'png': + $source = imagecreatefrompng($filename); + break; + case 'gif': + $source = imagecreatefromgif($filename); + break; + default: + die('File "' . $filename . '" is not valid jpg, png or gif image.'); + break; + } + + // Resize + imagecopyresampled($thumb, $source, 0, 0, 0, 0, $newwidth, $newheight, $width, $height); + + $metadata = getimagesize($filename); + + if (isset($metadata['mime'])) { + $this->response->contentType($metadata['mime']); + imagejpeg($thumb); + } + } + } + + /** * Remove a file * * @access public diff --git a/app/Controller/Hourlyrate.php b/app/Controller/Hourlyrate.php new file mode 100644 index 00000000..8d96e5ca --- /dev/null +++ b/app/Controller/Hourlyrate.php @@ -0,0 +1,89 @@ +<?php + +namespace Controller; + +/** + * Hourly Rate controller + * + * @package controller + * @author Frederic Guillot + */ +class Hourlyrate extends User +{ + /** + * Display rate and form + * + * @access public + */ + public function index(array $values = array(), array $errors = array()) + { + $user = $this->getUser(); + + $this->response->html($this->layout('hourlyrate/index', array( + 'rates' => $this->hourlyRate->getAllByUser($user['id']), + 'currencies_list' => $this->config->getCurrencies(), + 'values' => $values + array('user_id' => $user['id']), + 'errors' => $errors, + 'user' => $user, + ))); + } + + /** + * Validate and save a new rate + * + * @access public + */ + public function save() + { + $values = $this->request->getValues(); + list($valid, $errors) = $this->hourlyRate->validateCreation($values); + + if ($valid) { + + if ($this->hourlyRate->create($values['user_id'], $values['rate'], $values['currency'], $values['date_effective'])) { + $this->session->flash(t('Hourly rate created successfully.')); + $this->response->redirect($this->helper->url('hourlyrate', 'index', array('user_id' => $values['user_id']))); + } + else { + $this->session->flashError(t('Unable to save the hourly rate.')); + } + } + + $this->index($values, $errors); + } + + /** + * Confirmation dialag box to remove a row + * + * @access public + */ + public function confirm() + { + $user = $this->getUser(); + + $this->response->html($this->layout('hourlyrate/remove', array( + 'rate_id' => $this->request->getIntegerParam('rate_id'), + 'user' => $user, + ))); + } + + /** + * Remove a row + * + * @access public + */ + public function remove() + { + $this->checkCSRFParam(); + $user = $this->getUser(); + + if ($this->hourlyRate->remove($this->request->getIntegerParam('rate_id'))) { + $this->session->flash(t('Rate removed successfully.')); + } + else { + $this->session->flash(t('Unable to remove this rate.')); + } + + $this->response->redirect($this->helper->url('hourlyrate', 'index', array('user_id' => $user['id']))); + } +} diff --git a/app/Controller/Project.php b/app/Controller/Project.php index fb0a8d05..4e01271a 100644 --- a/app/Controller/Project.php +++ b/app/Controller/Project.php @@ -23,7 +23,7 @@ class Project extends Base else { $project_ids = $this->projectPermission->getMemberProjectIds($this->userSession->getId()); } - + $nb_projects = count($project_ids); $paginator = $this->paginator @@ -128,6 +128,11 @@ class Project extends Base { $project = $this->getProject(); $values = $this->request->getValues(); + + if ($project['is_private'] == 1) { + $values += array('is_private' => 0); + } + list($valid, $errors) = $this->project->validateModification($values); if ($valid) { diff --git a/app/Controller/Subtask.php b/app/Controller/Subtask.php index c7ec00d1..385785a1 100644 --- a/app/Controller/Subtask.php +++ b/app/Controller/Subtask.php @@ -185,7 +185,7 @@ class Subtask extends Base if ($redirect === 'board') { $this->session['has_subtask_inprogress'] = $this->subtask->hasSubtaskInProgress($this->userSession->getId()); - + $this->response->html($this->template->render('board/subtasks', array( 'subtasks' => $this->subtask->getAll($task['id']), 'task' => $task, @@ -259,4 +259,22 @@ class Subtask extends Base $this->response->redirect($this->helper->url('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']))); } } + + /** + * Move subtask position + * + * @access public + */ + public function movePosition() + { + $this->checkCSRFParam(); + $project_id = $this->request->getIntegerParam('project_id'); + $task_id = $this->request->getIntegerParam('task_id'); + $subtask_id = $this->request->getIntegerParam('subtask_id'); + $direction = $this->request->getStringParam('direction'); + $method = $direction === 'up' ? 'moveUp' : 'moveDown'; + + $this->subtask->$method($task_id, $subtask_id); + $this->response->redirect($this->helper->url('task', 'show', array('project_id' => $project_id, 'task_id' => $task_id)).'#subtasks'); + } } diff --git a/app/Controller/Task.php b/app/Controller/Task.php index 741db61e..ace40a01 100644 --- a/app/Controller/Task.php +++ b/app/Controller/Task.php @@ -68,12 +68,14 @@ class Task extends Base $this->response->html($this->taskLayout('task/show', array( 'project' => $this->project->getById($task['project_id']), - 'files' => $this->file->getAll($task['id']), + 'files' => $this->file->getAllDocuments($task['id']), + 'images' => $this->file->getAllImages($task['id']), 'comments' => $this->comment->getAll($task['id']), 'subtasks' => $subtasks, 'links' => $this->taskLink->getLinks($task['id']), 'task' => $task, 'values' => $values, + 'link_label_list' => $this->link->getList(0, false), 'columns_list' => $this->board->getColumnsList($task['project_id']), 'colors_list' => $this->color->getList(), 'date_format' => $this->config->get('application_date_format'), @@ -91,11 +93,12 @@ class Task extends Base { $project = $this->getProject(); $method = $this->request->isAjax() ? 'render' : 'layout'; + $swimlanes_list = $this->swimlane->getList($project['id']); if (empty($values)) { $values = array( - 'swimlane_id' => $this->request->getIntegerParam('swimlane_id'), + 'swimlane_id' => $this->request->getIntegerParam('swimlane_id', key($swimlanes_list)), 'column_id' => $this->request->getIntegerParam('column_id'), 'color_id' => $this->request->getStringParam('color_id'), 'owner_id' => $this->request->getIntegerParam('owner_id'), @@ -112,6 +115,7 @@ class Task extends Base 'users_list' => $this->projectPermission->getMemberList($project['id'], true, false, true), 'colors_list' => $this->color->getList(), 'categories_list' => $this->category->getList($project['id']), + 'swimlanes_list' => $swimlanes_list, 'date_format' => $this->config->get('application_date_format'), 'date_formats' => $this->dateParser->getAvailableFormats(), 'title' => $project['name'].' > '.t('New task') diff --git a/app/Controller/Tasklink.php b/app/Controller/Tasklink.php index 61b7fab8..59ce0433 100644 --- a/app/Controller/Tasklink.php +++ b/app/Controller/Tasklink.php @@ -36,6 +36,7 @@ class Tasklink extends Base public function create(array $values = array(), array $errors = array()) { $task = $this->getTask(); + $ajax = $this->request->isAjax() || $this->request->getIntegerParam('ajax'); if (empty($values)) { $values = array( @@ -43,6 +44,17 @@ class Tasklink extends Base ); } + if ($ajax) { + $this->response->html($this->template->render('tasklink/create', array( + 'values' => $values, + 'errors' => $errors, + 'task' => $task, + 'labels' => $this->link->getList(0, false), + 'title' => t('Add a new link'), + 'ajax' => $ajax, + ))); + } + $this->response->html($this->taskLayout('tasklink/create', array( 'values' => $values, 'errors' => $errors, @@ -61,6 +73,7 @@ class Tasklink extends Base { $task = $this->getTask(); $values = $this->request->getValues(); + $ajax = $this->request->isAjax() || $this->request->getIntegerParam('ajax'); list($valid, $errors) = $this->taskLink->validateCreation($values); @@ -68,6 +81,9 @@ class Tasklink extends Base if ($this->taskLink->create($values['task_id'], $values['opposite_task_id'], $values['link_id'])) { $this->session->flash(t('Link added successfully.')); + if ($ajax) { + $this->response->redirect($this->helper->url('board', 'show', array('project_id' => $task['project_id']))); + } $this->response->redirect($this->helper->url('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])).'#links'); } else { diff --git a/app/Controller/Timetable.php b/app/Controller/Timetable.php new file mode 100644 index 00000000..65edb44c --- /dev/null +++ b/app/Controller/Timetable.php @@ -0,0 +1,39 @@ +<?php + +namespace Controller; + +use DateTime; + +/** + * Timetable controller + * + * @package controller + * @author Frederic Guillot + */ +class Timetable extends User +{ + /** + * Display timetable for the user + * + * @access public + */ + public function index() + { + $user = $this->getUser(); + $from = $this->request->getStringParam('from', date('Y-m-d')); + $to = $this->request->getStringParam('to', date('Y-m-d', strtotime('next week'))); + $timetable = $this->timetable->calculate($user['id'], new DateTime($from), new DateTime($to)); + + $this->response->html($this->layout('timetable/index', array( + 'user' => $user, + 'timetable' => $timetable, + 'values' => array( + 'from' => $from, + 'to' => $to, + 'controller' => 'timetable', + 'action' => 'index', + 'user_id' => $user['id'], + ), + ))); + } +} diff --git a/app/Controller/Timetableday.php b/app/Controller/Timetableday.php new file mode 100644 index 00000000..eea44ae1 --- /dev/null +++ b/app/Controller/Timetableday.php @@ -0,0 +1,88 @@ +<?php + +namespace Controller; + +/** + * Day Timetable controller + * + * @package controller + * @author Frederic Guillot + */ +class Timetableday extends User +{ + /** + * Display timetable for the user + * + * @access public + */ + public function index(array $values = array(), array $errors = array()) + { + $user = $this->getUser(); + + $this->response->html($this->layout('timetable_day/index', array( + 'timetable' => $this->timetableDay->getByUser($user['id']), + 'values' => $values + array('user_id' => $user['id']), + 'errors' => $errors, + 'user' => $user, + ))); + } + + /** + * Validate and save + * + * @access public + */ + public function save() + { + $values = $this->request->getValues(); + list($valid, $errors) = $this->timetableDay->validateCreation($values); + + if ($valid) { + + if ($this->timetableDay->create($values['user_id'], $values['start'], $values['end'])) { + $this->session->flash(t('Time slot created successfully.')); + $this->response->redirect($this->helper->url('timetableday', 'index', array('user_id' => $values['user_id']))); + } + else { + $this->session->flashError(t('Unable to save this time slot.')); + } + } + + $this->index($values, $errors); + } + + /** + * Confirmation dialag box to remove a row + * + * @access public + */ + public function confirm() + { + $user = $this->getUser(); + + $this->response->html($this->layout('timetable_day/remove', array( + 'slot_id' => $this->request->getIntegerParam('slot_id'), + 'user' => $user, + ))); + } + + /** + * Remove a row + * + * @access public + */ + public function remove() + { + $this->checkCSRFParam(); + $user = $this->getUser(); + + if ($this->timetableDay->remove($this->request->getIntegerParam('slot_id'))) { + $this->session->flash(t('Time slot removed successfully.')); + } + else { + $this->session->flash(t('Unable to remove this time slot.')); + } + + $this->response->redirect($this->helper->url('timetableday', 'index', array('user_id' => $user['id']))); + } +} diff --git a/app/Controller/Timetableextra.php b/app/Controller/Timetableextra.php new file mode 100644 index 00000000..7c6fe265 --- /dev/null +++ b/app/Controller/Timetableextra.php @@ -0,0 +1,16 @@ +<?php + +namespace Controller; + +/** + * Over-time Timetable controller + * + * @package controller + * @author Frederic Guillot + */ +class Timetableextra extends Timetableoff +{ + protected $model = 'timetableExtra'; + protected $controller_url = 'timetableextra'; + protected $template_dir = 'timetable_extra'; +} diff --git a/app/Controller/Timetableoff.php b/app/Controller/Timetableoff.php new file mode 100644 index 00000000..19a6fab1 --- /dev/null +++ b/app/Controller/Timetableoff.php @@ -0,0 +1,107 @@ +<?php + +namespace Controller; + +/** + * Time-off Timetable controller + * + * @package controller + * @author Frederic Guillot + */ +class Timetableoff extends User +{ + protected $model = 'timetableOff'; + protected $controller_url = 'timetableoff'; + protected $template_dir = 'timetable_off'; + + /** + * Display timetable for the user + * + * @access public + */ + public function index(array $values = array(), array $errors = array()) + { + $user = $this->getUser(); + + $paginator = $this->paginator + ->setUrl($this->controller_url, 'index', array('user_id' => $user['id'])) + ->setMax(10) + ->setOrder('date') + ->setDirection('desc') + ->setQuery($this->{$this->model}->getUserQuery($user['id'])) + ->calculate(); + + $this->response->html($this->layout($this->template_dir.'/index', array( + 'values' => $values + array('user_id' => $user['id']), + 'errors' => $errors, + 'paginator' => $paginator, + 'user' => $user, + ))); + } + + /** + * Validate and save + * + * @access public + */ + public function save() + { + $values = $this->request->getValues(); + list($valid, $errors) = $this->{$this->model}->validateCreation($values); + + if ($valid) { + + if ($this->{$this->model}->create( + $values['user_id'], + $values['date'], + isset($values['all_day']) && $values['all_day'] == 1, + $values['start'], + $values['end'], + $values['comment'])) { + + $this->session->flash(t('Time slot created successfully.')); + $this->response->redirect($this->helper->url($this->controller_url, 'index', array('user_id' => $values['user_id']))); + } + else { + $this->session->flashError(t('Unable to save this time slot.')); + } + } + + $this->index($values, $errors); + } + + /** + * Confirmation dialag box to remove a row + * + * @access public + */ + public function confirm() + { + $user = $this->getUser(); + + $this->response->html($this->layout($this->template_dir.'/remove', array( + 'slot_id' => $this->request->getIntegerParam('slot_id'), + 'user' => $user, + ))); + } + + /** + * Remove a row + * + * @access public + */ + public function remove() + { + $this->checkCSRFParam(); + $user = $this->getUser(); + + if ($this->{$this->model}->remove($this->request->getIntegerParam('slot_id'))) { + $this->session->flash(t('Time slot removed successfully.')); + } + else { + $this->session->flash(t('Unable to remove this time slot.')); + } + + $this->response->redirect($this->helper->url($this->controller_url, 'index', array('user_id' => $user['id']))); + } +} diff --git a/app/Controller/Timetableweek.php b/app/Controller/Timetableweek.php new file mode 100644 index 00000000..829f4402 --- /dev/null +++ b/app/Controller/Timetableweek.php @@ -0,0 +1,99 @@ +<?php + +namespace Controller; + +/** + * Week Timetable controller + * + * @package controller + * @author Frederic Guillot + */ +class Timetableweek extends User +{ + /** + * Display timetable for the user + * + * @access public + */ + public function index(array $values = array(), array $errors = array()) + { + $user = $this->getUser(); + + if (empty($values)) { + + $day = $this->timetableDay->getByUser($user['id']); + + $values = array( + 'user_id' => $user['id'], + 'start' => isset($day[0]['start']) ? $day[0]['start'] : null, + 'end' => isset($day[0]['end']) ? $day[0]['end'] : null, + ); + } + + $this->response->html($this->layout('timetable_week/index', array( + 'timetable' => $this->timetableWeek->getByUser($user['id']), + 'values' => $values, + 'errors' => $errors, + 'user' => $user, + ))); + } + + /** + * Validate and save + * + * @access public + */ + public function save() + { + $values = $this->request->getValues(); + list($valid, $errors) = $this->timetableWeek->validateCreation($values); + + if ($valid) { + + if ($this->timetableWeek->create($values['user_id'], $values['day'], $values['start'], $values['end'])) { + $this->session->flash(t('Time slot created successfully.')); + $this->response->redirect($this->helper->url('timetableweek', 'index', array('user_id' => $values['user_id']))); + } + else { + $this->session->flashError(t('Unable to save this time slot.')); + } + } + + $this->index($values, $errors); + } + + /** + * Confirmation dialag box to remove a row + * + * @access public + */ + public function confirm() + { + $user = $this->getUser(); + + $this->response->html($this->layout('timetable_week/remove', array( + 'slot_id' => $this->request->getIntegerParam('slot_id'), + 'user' => $user, + ))); + } + + /** + * Remove a row + * + * @access public + */ + public function remove() + { + $this->checkCSRFParam(); + $user = $this->getUser(); + + if ($this->timetableWeek->remove($this->request->getIntegerParam('slot_id'))) { + $this->session->flash(t('Time slot removed successfully.')); + } + else { + $this->session->flash(t('Unable to remove this time slot.')); + } + + $this->response->redirect($this->helper->url('timetableweek', 'index', array('user_id' => $user['id']))); + } +} diff --git a/app/Controller/User.php b/app/Controller/User.php index decdb646..46d0214d 100644 --- a/app/Controller/User.php +++ b/app/Controller/User.php @@ -69,12 +69,12 @@ class User extends Base /** * Common layout for user views * - * @access private + * @access protected * @param string $template Template name * @param array $params Template parameters * @return string */ - private function layout($template, array $params) + protected function layout($template, array $params) { $content = $this->template->render($template, $params); $params['user_content_for_layout'] = $content; @@ -90,10 +90,10 @@ class User extends Base /** * Common method to get the user * - * @access private + * @access protected * @return array */ - private function getUser() + protected function getUser() { $user = $this->user->getById($this->request->getIntegerParam('user_id')); diff --git a/app/Core/Helper.php b/app/Core/Helper.php index 01ebb08f..34a5e6ab 100644 --- a/app/Core/Helper.php +++ b/app/Core/Helper.php @@ -677,4 +677,97 @@ class Helper array('task_id' => $subtask['task_id'], 'subtask_id' => $subtask['id'], 'redirect' => $redirect) ); } + + /** + * Get all hours for day + * + * @access public + * @return array + */ + public function getDayHours() + { + $values = array(); + + foreach (range(0, 23) as $hour) { + foreach (array(0, 30) as $minute) { + $time = sprintf('%02d:%02d', $hour, $minute); + $values[$time] = $time; + } + } + + return $values; + } + + /** + * Get all days of a week + * + * @access public + * @return array + */ + public function getWeekDays() + { + $values = array(); + + foreach (range(1, 7) as $day) { + $values[$day] = $this->getWeekDay($day); + } + + return $values; + } + + /** + * Get the localized day name from the day number + * + * @access public + * @param integer $day Day number + * @return string + */ + public function getWeekDay($day) + { + return dt('%A', strtotime('next Monday +'.($day - 1).' days')); + } + + /** + * Get file icon + * + * @access public + * @param string $filename Filename + * @return string Font-Awesome-Icon-Name + */ + public function getFileIcon($filename){ + + $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); + + switch ($extension) { + case 'jpeg': + case 'jpg': + case 'png': + case 'gif': + return 'fa-file-image-o'; + case 'xls': + case 'xlsx': + return 'fa-file-excel-o'; + case 'doc': + case 'docx': + return 'fa-file-word-o'; + case 'ppt': + case 'pptx': + return 'fa-file-powerpoint-o'; + case 'zip': + case 'rar': + return 'fa-archive-o'; + case 'mp3': + return 'fa-audio-o'; + case 'avi': + return 'fa-video-o'; + case 'php': + case 'html': + case 'css': + return 'fa-code-o'; + case 'pdf': + return 'fa-file-pdf-o'; + } + + return 'fa-file-o'; + } } diff --git a/app/Locale/da_DK/translations.php b/app/Locale/da_DK/translations.php index 75f27a7c..1732e7bb 100644 --- a/app/Locale/da_DK/translations.php +++ b/app/Locale/da_DK/translations.php @@ -1,6 +1,8 @@ <?php return array( + // 'number.decimals_separator' => '', + // 'number.thousands_separator' => '', 'None' => 'Ingen', 'edit' => 'rediger', 'Edit' => 'Rediger', @@ -734,4 +736,72 @@ return array( // 'Filter recently updated' => '', // 'since %B %e, %Y at %k:%M %p' => '', // 'More filters' => '', + // 'Compact view' => '', + // 'Horizontal scrolling' => '', + // 'Compact/wide view' => '', + // 'No results match:' => '', + // 'Remove hourly rate' => '', + // 'Do you really want to remove this hourly rate?' => '', + // 'Hourly rates' => '', + // 'Hourly rate' => '', + // 'Currency' => '', + // 'Effective date' => '', + // 'Add new rate' => '', + // 'Rate removed successfully.' => '', + // 'Unable to remove this rate.' => '', + // 'Unable to save the hourly rate.' => '', + // 'Hourly rate created successfully.' => '', + // 'Start time' => '', + // 'End time' => '', + // 'Comment' => '', + // 'All day' => '', + // 'Day' => '', + // 'Manage timetable' => '', + // 'Overtime timetable' => '', + // 'Time off timetable' => '', + // 'Timetable' => '', + // 'Work timetable' => '', + // 'Week timetable' => '', + // 'Day timetable' => '', + // 'From' => '', + // 'To' => '', + // 'Time slot created successfully.' => '', + // 'Unable to save this time slot.' => '', + // 'Time slot removed successfully.' => '', + // 'Unable to remove this time slot.' => '', + // 'Do you really want to remove this time slot?' => '', + // 'Remove time slot' => '', + // 'Add new time slot' => '', + // 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => '', + // 'Files' => '', + // 'Images' => '', + // 'Private project' => '', + // 'Amount' => '', + // 'AUD - Australian Dollar' => '', + // 'Budget' => '', + // 'Budget line' => '', + // 'Budget line removed successfully.' => '', + // 'Budget lines' => '', + // 'CAD - Canadian Dollar' => '', + // 'CHF - Swiss Francs' => '', + // 'Cost' => '', + // 'Cost breakdown' => '', + // 'Custom Stylesheet' => '', + // 'download' => '', + // 'Do you really want to remove this budget line?' => '', + // 'EUR - Euro' => '', + // 'Expenses' => '', + // 'GBP - British Pound' => '', + // 'INR - Indian Rupee' => '', + // 'JPY - Japanese Yen' => '', + // 'New budget line' => '', + // 'NZD - New Zealand Dollar' => '', + // 'Remove a budget line' => '', + // 'Remove budget line' => '', + // 'RSD - Serbian dinar' => '', + // 'The budget line have been created successfully.' => '', + // 'Unable to create the budget line.' => '', + // 'Unable to remove this budget line.' => '', + // 'USD - US Dollar' => '', + // 'Remaining' => '', ); diff --git a/app/Locale/de_DE/translations.php b/app/Locale/de_DE/translations.php index 54503b64..3ab31d97 100644 --- a/app/Locale/de_DE/translations.php +++ b/app/Locale/de_DE/translations.php @@ -1,6 +1,8 @@ <?php return array( + // 'number.decimals_separator' => '', + // 'number.thousands_separator' => '', 'None' => 'Keines', 'edit' => 'Bearbeiten', 'Edit' => 'Bearbeiten', @@ -625,8 +627,8 @@ return array( 'Example: "Bug, Feature Request, Improvement"' => 'Beispiel: "Bug, Funktionswünsche, Verbesserung"', 'Default categories for new projects (Comma-separated)' => 'Standard Kategorien für neue Projekte (Komma-getrennt)', 'Gitlab commit received' => 'Gitlab commit erhalten', - 'Gitlab issue opened' => 'Gitlab Thema eröffnet', - 'Gitlab issue closed' => 'Gitlab Thema geschlossen', + 'Gitlab issue opened' => 'Gitlab Fehler eröffnet', + 'Gitlab issue closed' => 'Gitlab Fehler geschlossen', 'Gitlab webhooks' => 'Gitlab Webhook', 'Help on Gitlab webhooks' => 'Hilfe für Gitlab Webhooks', 'Integrations' => 'Integration', @@ -635,7 +637,7 @@ return array( 'Project manager' => 'Projektmanager', 'Project member' => 'Projektmitglied', 'A project manager can change the settings of the project and have more privileges than a standard user.' => 'Ein Projektmanager kann die Projekteinstellungen ändern und hat mehr Rechte als ein normaler Benutzer.', - 'Gitlab Issue' => 'Gitlab Thema', + 'Gitlab Issue' => 'Gitlab Fehler', 'Subtask Id' => 'Teilaufgaben Id', 'Subtasks' => 'Teilaufgaben', 'Subtasks Export' => 'Teilaufgaben Export', @@ -734,4 +736,72 @@ return array( 'Filter recently updated' => 'Zuletzt geänderte anzeigen', 'since %B %e, %Y at %k:%M %p' => 'seit %B %e, %Y um %k:%M %p', 'More filters' => 'Mehr Filter', + // 'Compact view' => '', + // 'Horizontal scrolling' => '', + // 'Compact/wide view' => '', + // 'No results match:' => '', + // 'Remove hourly rate' => '', + // 'Do you really want to remove this hourly rate?' => '', + // 'Hourly rates' => '', + // 'Hourly rate' => '', + // 'Currency' => '', + // 'Effective date' => '', + // 'Add new rate' => '', + // 'Rate removed successfully.' => '', + // 'Unable to remove this rate.' => '', + // 'Unable to save the hourly rate.' => '', + // 'Hourly rate created successfully.' => '', + // 'Start time' => '', + // 'End time' => '', + // 'Comment' => '', + // 'All day' => '', + // 'Day' => '', + // 'Manage timetable' => '', + // 'Overtime timetable' => '', + // 'Time off timetable' => '', + // 'Timetable' => '', + // 'Work timetable' => '', + // 'Week timetable' => '', + // 'Day timetable' => '', + // 'From' => '', + // 'To' => '', + // 'Time slot created successfully.' => '', + // 'Unable to save this time slot.' => '', + // 'Time slot removed successfully.' => '', + // 'Unable to remove this time slot.' => '', + // 'Do you really want to remove this time slot?' => '', + // 'Remove time slot' => '', + // 'Add new time slot' => '', + // 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => '', + // 'Files' => '', + // 'Images' => '', + // 'Private project' => '', + // 'Amount' => '', + // 'AUD - Australian Dollar' => '', + // 'Budget' => '', + // 'Budget line' => '', + // 'Budget line removed successfully.' => '', + // 'Budget lines' => '', + // 'CAD - Canadian Dollar' => '', + // 'CHF - Swiss Francs' => '', + // 'Cost' => '', + // 'Cost breakdown' => '', + // 'Custom Stylesheet' => '', + // 'download' => '', + // 'Do you really want to remove this budget line?' => '', + // 'EUR - Euro' => '', + // 'Expenses' => '', + // 'GBP - British Pound' => '', + // 'INR - Indian Rupee' => '', + // 'JPY - Japanese Yen' => '', + // 'New budget line' => '', + // 'NZD - New Zealand Dollar' => '', + // 'Remove a budget line' => '', + // 'Remove budget line' => '', + // 'RSD - Serbian dinar' => '', + // 'The budget line have been created successfully.' => '', + // 'Unable to create the budget line.' => '', + // 'Unable to remove this budget line.' => '', + // 'USD - US Dollar' => '', + // 'Remaining' => '', ); diff --git a/app/Locale/es_ES/translations.php b/app/Locale/es_ES/translations.php index 3981f12d..f186dfc8 100644 --- a/app/Locale/es_ES/translations.php +++ b/app/Locale/es_ES/translations.php @@ -1,6 +1,8 @@ <?php return array( + // 'number.decimals_separator' => '', + // 'number.thousands_separator' => '', 'None' => 'Ninguno', 'edit' => 'modificar', 'Edit' => 'Modificar', @@ -734,4 +736,72 @@ return array( // 'Filter recently updated' => '', // 'since %B %e, %Y at %k:%M %p' => '', // 'More filters' => '', + // 'Compact view' => '', + // 'Horizontal scrolling' => '', + // 'Compact/wide view' => '', + // 'No results match:' => '', + // 'Remove hourly rate' => '', + // 'Do you really want to remove this hourly rate?' => '', + // 'Hourly rates' => '', + // 'Hourly rate' => '', + // 'Currency' => '', + // 'Effective date' => '', + // 'Add new rate' => '', + // 'Rate removed successfully.' => '', + // 'Unable to remove this rate.' => '', + // 'Unable to save the hourly rate.' => '', + // 'Hourly rate created successfully.' => '', + // 'Start time' => '', + // 'End time' => '', + // 'Comment' => '', + // 'All day' => '', + // 'Day' => '', + // 'Manage timetable' => '', + // 'Overtime timetable' => '', + // 'Time off timetable' => '', + // 'Timetable' => '', + // 'Work timetable' => '', + // 'Week timetable' => '', + // 'Day timetable' => '', + // 'From' => '', + // 'To' => '', + // 'Time slot created successfully.' => '', + // 'Unable to save this time slot.' => '', + // 'Time slot removed successfully.' => '', + // 'Unable to remove this time slot.' => '', + // 'Do you really want to remove this time slot?' => '', + // 'Remove time slot' => '', + // 'Add new time slot' => '', + // 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => '', + // 'Files' => '', + // 'Images' => '', + // 'Private project' => '', + // 'Amount' => '', + // 'AUD - Australian Dollar' => '', + // 'Budget' => '', + // 'Budget line' => '', + // 'Budget line removed successfully.' => '', + // 'Budget lines' => '', + // 'CAD - Canadian Dollar' => '', + // 'CHF - Swiss Francs' => '', + // 'Cost' => '', + // 'Cost breakdown' => '', + // 'Custom Stylesheet' => '', + // 'download' => '', + // 'Do you really want to remove this budget line?' => '', + // 'EUR - Euro' => '', + // 'Expenses' => '', + // 'GBP - British Pound' => '', + // 'INR - Indian Rupee' => '', + // 'JPY - Japanese Yen' => '', + // 'New budget line' => '', + // 'NZD - New Zealand Dollar' => '', + // 'Remove a budget line' => '', + // 'Remove budget line' => '', + // 'RSD - Serbian dinar' => '', + // 'The budget line have been created successfully.' => '', + // 'Unable to create the budget line.' => '', + // 'Unable to remove this budget line.' => '', + // 'USD - US Dollar' => '', + // 'Remaining' => '', ); diff --git a/app/Locale/fi_FI/translations.php b/app/Locale/fi_FI/translations.php index 06b21953..f235cc12 100644 --- a/app/Locale/fi_FI/translations.php +++ b/app/Locale/fi_FI/translations.php @@ -1,6 +1,8 @@ <?php return array( + // 'number.decimals_separator' => '', + // 'number.thousands_separator' => '', 'None' => 'Ei mikään', 'edit' => 'muokkaa', 'Edit' => 'Muokkaa', @@ -734,4 +736,72 @@ return array( // 'Filter recently updated' => '', // 'since %B %e, %Y at %k:%M %p' => '', // 'More filters' => '', + // 'Compact view' => '', + // 'Horizontal scrolling' => '', + // 'Compact/wide view' => '', + // 'No results match:' => '', + // 'Remove hourly rate' => '', + // 'Do you really want to remove this hourly rate?' => '', + // 'Hourly rates' => '', + // 'Hourly rate' => '', + // 'Currency' => '', + // 'Effective date' => '', + // 'Add new rate' => '', + // 'Rate removed successfully.' => '', + // 'Unable to remove this rate.' => '', + // 'Unable to save the hourly rate.' => '', + // 'Hourly rate created successfully.' => '', + // 'Start time' => '', + // 'End time' => '', + // 'Comment' => '', + // 'All day' => '', + // 'Day' => '', + // 'Manage timetable' => '', + // 'Overtime timetable' => '', + // 'Time off timetable' => '', + // 'Timetable' => '', + // 'Work timetable' => '', + // 'Week timetable' => '', + // 'Day timetable' => '', + // 'From' => '', + // 'To' => '', + // 'Time slot created successfully.' => '', + // 'Unable to save this time slot.' => '', + // 'Time slot removed successfully.' => '', + // 'Unable to remove this time slot.' => '', + // 'Do you really want to remove this time slot?' => '', + // 'Remove time slot' => '', + // 'Add new time slot' => '', + // 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => '', + // 'Files' => '', + // 'Images' => '', + // 'Private project' => '', + // 'Amount' => '', + // 'AUD - Australian Dollar' => '', + // 'Budget' => '', + // 'Budget line' => '', + // 'Budget line removed successfully.' => '', + // 'Budget lines' => '', + // 'CAD - Canadian Dollar' => '', + // 'CHF - Swiss Francs' => '', + // 'Cost' => '', + // 'Cost breakdown' => '', + // 'Custom Stylesheet' => '', + // 'download' => '', + // 'Do you really want to remove this budget line?' => '', + // 'EUR - Euro' => '', + // 'Expenses' => '', + // 'GBP - British Pound' => '', + // 'INR - Indian Rupee' => '', + // 'JPY - Japanese Yen' => '', + // 'New budget line' => '', + // 'NZD - New Zealand Dollar' => '', + // 'Remove a budget line' => '', + // 'Remove budget line' => '', + // 'RSD - Serbian dinar' => '', + // 'The budget line have been created successfully.' => '', + // 'Unable to create the budget line.' => '', + // 'Unable to remove this budget line.' => '', + // 'USD - US Dollar' => '', + // 'Remaining' => '', ); diff --git a/app/Locale/fr_FR/translations.php b/app/Locale/fr_FR/translations.php index b05f7076..1aadb393 100644 --- a/app/Locale/fr_FR/translations.php +++ b/app/Locale/fr_FR/translations.php @@ -1,6 +1,8 @@ <?php return array( + 'number.decimals_separator' => ',', + 'number.thousands_separator' => ' ', 'None' => 'Aucun', 'edit' => 'modifier', 'Edit' => 'Modifier', @@ -736,4 +738,72 @@ return array( 'Filter recently updated' => 'Récemment modifié', 'since %B %e, %Y at %k:%M %p' => 'depuis le %d/%m/%Y à %H:%M', 'More filters' => 'Plus de filtres', + 'Compact view' => 'Vue compacte', + 'Horizontal scrolling' => 'Défilement horizontal', + 'Compact/wide view' => 'Basculer entre la vue compacte et étendue', + 'No results match:' => 'Aucun résultat :', + 'Remove hourly rate' => 'Supprimer un taux horaire', + 'Do you really want to remove this hourly rate?' => 'Voulez-vous vraiment supprimer ce taux horaire ?', + 'Hourly rates' => 'Taux horaires', + 'Hourly rate' => 'Taux horaire', + 'Currency' => 'Devise', + 'Effective date' => 'Date d\'effet', + 'Add new rate' => 'Ajouter un nouveau taux horaire', + 'Rate removed successfully.' => 'Taux horaire supprimé avec succès.', + 'Unable to remove this rate.' => 'Impossible de supprimer ce taux horaire.', + 'Unable to save the hourly rate.' => 'Impossible de sauvegarder ce taux horaire.', + 'Hourly rate created successfully.' => 'Taux horaire créé avec succès.', + 'Start time' => 'Date de début', + 'End time' => 'Date de fin', + 'Comment' => 'Commentaire', + 'All day' => 'Toute la journée', + 'Day' => 'Jour', + 'Manage timetable' => 'Gérer les horaires', + 'Overtime timetable' => 'Heures supplémentaires', + 'Time off timetable' => 'Heures d\'absences', + 'Timetable' => 'Horaires', + 'Work timetable' => 'Horaires travaillés', + 'Week timetable' => 'Horaires de la semaine', + 'Day timetable' => 'Horaire d\'une journée', + 'From' => 'Depuis', + 'To' => 'À', + 'Time slot created successfully.' => 'Créneau horaire créé avec succès.', + 'Unable to save this time slot.' => 'Impossible de sauvegarder ce créneau horaire.', + 'Time slot removed successfully.' => 'Créneau horaire supprimé avec succès.', + 'Unable to remove this time slot.' => 'Impossible de supprimer ce créneau horaire.', + 'Do you really want to remove this time slot?' => 'Voulez-vous vraiment supprimer ce créneau horaire ?', + 'Remove time slot' => 'Supprimer un créneau horaire', + 'Add new time slot' => 'Ajouter un créneau horaire', + 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => 'Ces horaires sont utilisés lorsque la case « Toute la journée » est cochée pour les heures d\'absences ou supplémentaires programmées.', + 'Files' => 'Fichiers', + 'Images' => 'Images', + 'Private project' => 'Projet privé', + 'Amount' => 'Montant', + 'AUD - Australian Dollar' => 'AUD - Dollar australien', + 'Budget' => 'Budget', + 'Budget line' => 'Ligne budgétaire', + 'Budget line removed successfully.' => 'Ligne budgétaire supprimée avec succès.', + 'Budget lines' => 'Lignes budgétaire', + 'CAD - Canadian Dollar' => 'CAD - Dollar canadien', + 'CHF - Swiss Francs' => 'CHF - Franc suisse', + 'Cost' => 'Coût', + 'Cost breakdown' => 'Détail des coûts', + 'Custom Stylesheet' => 'Feuille de style personalisée', + 'download' => 'télécharger', + 'Do you really want to remove this budget line?' => 'Voulez-vous vraiment supprimer cette ligne budgétaire ?', + 'EUR - Euro' => 'EUR - Euro', + 'Expenses' => 'Dépenses', + 'GBP - British Pound' => 'GBP - Livre sterling', + 'INR - Indian Rupee' => 'INR - Roupie indienne', + 'JPY - Japanese Yen' => 'JPY - Yen', + 'New budget line' => 'Nouvelle ligne budgétaire', + 'NZD - New Zealand Dollar' => 'NZD - Dollar néo-zélandais', + 'Remove a budget line' => 'Supprimer une ligne budgétaire', + 'Remove budget line' => 'Supprimer une ligne budgétaire', + 'RSD - Serbian dinar' => 'RSD - Dinar serbe', + 'The budget line have been created successfully.' => 'La ligne de budgétaire a été créee avec succès.', + 'Unable to create the budget line.' => 'Impossible de créer cette ligne budgétaire.', + 'Unable to remove this budget line.' => 'Impossible de supprimer cette ligne budgétaire.', + 'USD - US Dollar' => 'USD - Dollar américain', + 'Remaining' => 'Restant', ); diff --git a/app/Locale/hu_HU/translations.php b/app/Locale/hu_HU/translations.php index 1780505f..75cdbebc 100644 --- a/app/Locale/hu_HU/translations.php +++ b/app/Locale/hu_HU/translations.php @@ -1,6 +1,8 @@ <?php return array( + // 'number.decimals_separator' => '', + // 'number.thousands_separator' => '', 'None' => 'Nincs', 'edit' => 'szerkesztés', 'Edit' => 'Szerkesztés', @@ -104,7 +106,7 @@ return array( 'Open a task' => 'Feladat felnyitás', 'Do you really want to open this task: "%s"?' => 'Tényleg meg akarja nyitni ezt a feladatot: "%s"?', 'Back to the board' => 'Vissza a táblához', - 'Created on %B %e, %Y at %k:%M %p' => 'Létrehozva: %Y.%m.%d %H:%M', + 'Created on %B %e, %Y at %k:%M %p' => 'Létrehozva: %Y. %m. %d. %H:%M', 'There is nobody assigned' => 'Nincs felelős', 'Column on the board:' => 'Tábla oszlopa: ', 'Status is open' => 'Nyitott állapot', @@ -133,12 +135,12 @@ return array( 'The title is required' => 'A címet meg kell adni', 'The language is required' => 'A nyelvet meg kell adni', 'There is no active project, the first step is to create a new project.' => 'Nincs aktív projekt. Először létre kell hozni egy projektet.', - 'Settings saved successfully.' => 'A beállítások mentése sikeres.', + 'Settings saved successfully.' => 'A beállítások sikeresen mentve.', 'Unable to save your settings.' => 'A beállítások mentése sikertelen.', 'Database optimization done.' => 'Adatbázis optimalizálás kész.', 'Your project have been created successfully.' => 'Projekt sikeresen létrehozva', 'Unable to create your project.' => 'Projekt létrehozása sikertelen.', - 'Project updated successfully.' => 'Projekt frissítése sikeres.', + 'Project updated successfully.' => 'Projekt sikeresen frissítve.', 'Unable to update this project.' => 'Projekt frissítése sikertelen.', 'Unable to remove this project.' => 'Projekt törlése sikertelen.', 'Project removed successfully.' => 'Projekt sikeresen törölve.', @@ -146,7 +148,7 @@ return array( 'Unable to activate this project.' => 'Projekt aktiválása sikertelen.', 'Project disabled successfully.' => 'Projekt sikeresen letiltva.', 'Unable to disable this project.' => 'Projekt letiltása sikertelen.', - 'Unable to open this task.' => 'A feladat felnyitása nem sikerült.', + 'Unable to open this task.' => 'A feladat felnyitása sikertelen.', 'Task opened successfully.' => 'Feladat sikeresen megnyitva .', 'Unable to close this task.' => 'A feladat lezárása sikertelen.', 'Task closed successfully.' => 'Feladat sikeresen lezárva.', @@ -166,8 +168,8 @@ return array( 'Work in progress' => 'Folyamatban', 'Done' => 'Kész', 'Application version:' => 'Alkalmazás verzió:', - 'Completed on %B %e, %Y at %k:%M %p' => 'Elkészült %Y.%m.%d %H:%M ..', - '%B %e, %Y at %k:%M %p' => '%Y.%m.%d %H:%M', + 'Completed on %B %e, %Y at %k:%M %p' => 'Elkészült: %Y. %m. %d. %H:%M', + '%B %e, %Y at %k:%M %p' => '%Y. %m. %d. %H:%M', 'Date created' => 'Létrehozás időpontja', 'Date completed' => 'Befejezés időpontja', 'Id' => 'ID', @@ -211,9 +213,9 @@ return array( 'Edit this task' => 'Feladat módosítása', 'Due Date' => 'Határidő', 'Invalid date' => 'Érvénytelen dátum', - 'Must be done before %B %e, %Y' => 'Kész kell lennie %Y.%m.%d előtt', - '%B %e, %Y' => '%Y.%m.%d', - '%b %e, %Y' => '%Y.%m.%d', + 'Must be done before %B %e, %Y' => 'Kész kell lennie %Y. %m. %d. előtt', + '%B %e, %Y' => '%Y. %m. %d.', + '%b %e, %Y' => '%Y. %m. %d.', 'Automatic actions' => 'Automatikus intézkedések', 'Your automatic action have been created successfully.' => 'Az automatikus intézkedés sikeresen elkészült.', 'Unable to create your automatic action.' => 'Automatikus intézkedés létrehozása nem lehetséges.', @@ -254,7 +256,7 @@ return array( 'link' => 'link', 'Update this comment' => 'Hozzászólás frissítése', 'Comment updated successfully.' => 'Megjegyzés sikeresen frissítve.', - 'Unable to update your comment.' => 'Megjegyzés frissítése nem sikerült.', + 'Unable to update your comment.' => 'Megjegyzés frissítése sikertelen.', 'Remove a comment' => 'Megjegyzés törlése', 'Comment removed successfully.' => 'Megjegyzés sikeresen törölve.', 'Unable to remove this comment.' => 'Megjegyzés törölése nem lehetséges.', @@ -294,8 +296,8 @@ return array( 'Your Google Account is not linked anymore to your profile.' => 'Google Fiók már nincs a profilhoz kapcsolva.', 'Unable to unlink your Google Account.' => 'Leválasztás a Google fiókról nem lehetséges.', 'Google authentication failed' => 'Google azonosítás sikertelen', - 'Unable to link your Google Account.' => 'Google profilhoz kapcsolás nem sikerült.', - 'Your Google Account is linked to your profile successfully.' => 'Sikeresen összekapcsolva a Google fiókkal.', + 'Unable to link your Google Account.' => 'A Google profilhoz kapcsolás sikertelen.', + 'Your Google Account is linked to your profile successfully.' => 'Google fiókkal sikeresen összekapcsolva.', 'Email' => 'E-mail', 'Link my Google Account' => 'Kapcsold össze a Google fiókkal', 'Unlink my Google Account' => 'Válaszd le a Google fiókomat', @@ -377,7 +379,7 @@ return array( 'Link my GitHub Account' => 'GitHub fiók csatolása', 'Unlink my GitHub Account' => 'GitHub fiók leválasztása', 'Created by %s' => 'Készítette: %s', - 'Last modified on %B %e, %Y at %k:%M %p' => 'Utolsó módosítás: %Y.%m.%d %H:%M', + 'Last modified on %B %e, %Y at %k:%M %p' => 'Utolsó módosítás: %Y. %m. %d. %H:%M', 'Tasks Export' => 'Feladatok exportálása', 'Tasks exportation for "%s"' => 'Feladatok exportálása: "%s"', 'Start Date' => 'Kezdés dátuma', @@ -391,7 +393,7 @@ return array( 'Webhook URL for task modification' => 'Webhook URL a feladatot módosításakor', 'Clone' => 'Másolat', 'Clone Project' => 'Projekt másolása', - 'Project cloned successfully.' => 'A projekt másolása sikeres', + 'Project cloned successfully.' => 'A projekt sikeresen másolva.', 'Unable to clone this project.' => 'A projekt másolása sikertelen.', 'Email notifications' => 'E-mail értesítések', 'Enable email notifications' => 'E-mail értesítések engedélyezése', @@ -541,7 +543,7 @@ return array( 'Add' => 'Hozzáadás', 'Estimated time: %s hours' => 'Becsült idő: %s óra', 'Time spent: %s hours' => 'Eltöltött idő: %s óra', - 'Started on %B %e, %Y' => 'Elkezdve: %Y.%m.%d', + 'Started on %B %e, %Y' => 'Elkezdve: %Y. %m. %d.', 'Start date' => 'Kezdés dátuma', 'Time estimated' => 'Becsült időtartam', 'There is nothing assigned to you.' => 'Nincs kiosztott feladat.', @@ -670,7 +672,7 @@ return array( 'Time Tracking' => 'Idő követés', 'You already have one subtask in progress' => 'Már van egy folyamatban levő részfeladata', 'Which parts of the project do you want to duplicate?' => 'A projekt mely részeit szeretné duplikálni?', - 'Change dashboard view' => 'Vezérlőpult megjelenítés változtatás', + 'Change dashboard view' => 'Vezérlőpult megjelenés változtatás', 'Show/hide activities' => 'Tevékenységek megjelenítése/elrejtése', 'Show/hide projects' => 'Projektek megjelenítése/elrejtése', 'Show/hide subtasks' => 'Részfeladatok megjelenítése/elrejtése', @@ -730,8 +732,76 @@ return array( 'Board view' => 'Tábla nézet', 'Keyboard shortcuts' => 'Billentyű kombináció', 'Open board switcher' => 'Tábla választó lenyitása', - // 'Application' => '', - // 'Filter recently updated' => '', - // 'since %B %e, %Y at %k:%M %p' => '', - // 'More filters' => '', + 'Application' => 'Alkalmazás', + 'Filter recently updated' => 'Szűrés az utolsó módosítás ideje szerint', + 'since %B %e, %Y at %k:%M %p' => '%Y. %m. %d. %H:%M óta', + 'More filters' => 'További szűrők', + // 'Compact view' => '', + // 'Horizontal scrolling' => '', + // 'Compact/wide view' => '', + // 'No results match:' => '', + // 'Remove hourly rate' => '', + // 'Do you really want to remove this hourly rate?' => '', + // 'Hourly rates' => '', + // 'Hourly rate' => '', + // 'Currency' => '', + // 'Effective date' => '', + // 'Add new rate' => '', + // 'Rate removed successfully.' => '', + // 'Unable to remove this rate.' => '', + // 'Unable to save the hourly rate.' => '', + // 'Hourly rate created successfully.' => '', + // 'Start time' => '', + // 'End time' => '', + // 'Comment' => '', + // 'All day' => '', + // 'Day' => '', + // 'Manage timetable' => '', + // 'Overtime timetable' => '', + // 'Time off timetable' => '', + // 'Timetable' => '', + // 'Work timetable' => '', + // 'Week timetable' => '', + // 'Day timetable' => '', + // 'From' => '', + // 'To' => '', + // 'Time slot created successfully.' => '', + // 'Unable to save this time slot.' => '', + // 'Time slot removed successfully.' => '', + // 'Unable to remove this time slot.' => '', + // 'Do you really want to remove this time slot?' => '', + // 'Remove time slot' => '', + // 'Add new time slot' => '', + // 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => '', + // 'Files' => '', + // 'Images' => '', + // 'Private project' => '', + // 'Amount' => '', + // 'AUD - Australian Dollar' => '', + // 'Budget' => '', + // 'Budget line' => '', + // 'Budget line removed successfully.' => '', + // 'Budget lines' => '', + // 'CAD - Canadian Dollar' => '', + // 'CHF - Swiss Francs' => '', + // 'Cost' => '', + // 'Cost breakdown' => '', + // 'Custom Stylesheet' => '', + // 'download' => '', + // 'Do you really want to remove this budget line?' => '', + // 'EUR - Euro' => '', + // 'Expenses' => '', + // 'GBP - British Pound' => '', + // 'INR - Indian Rupee' => '', + // 'JPY - Japanese Yen' => '', + // 'New budget line' => '', + // 'NZD - New Zealand Dollar' => '', + // 'Remove a budget line' => '', + // 'Remove budget line' => '', + // 'RSD - Serbian dinar' => '', + // 'The budget line have been created successfully.' => '', + // 'Unable to create the budget line.' => '', + // 'Unable to remove this budget line.' => '', + // 'USD - US Dollar' => '', + // 'Remaining' => '', ); diff --git a/app/Locale/it_IT/translations.php b/app/Locale/it_IT/translations.php index 525c828b..d5fd6902 100644 --- a/app/Locale/it_IT/translations.php +++ b/app/Locale/it_IT/translations.php @@ -1,6 +1,8 @@ <?php return array( + // 'number.decimals_separator' => '', + // 'number.thousands_separator' => '', 'None' => 'Nessuno', 'edit' => 'modificare', 'Edit' => 'Modificare', @@ -734,4 +736,72 @@ return array( // 'Filter recently updated' => '', // 'since %B %e, %Y at %k:%M %p' => '', // 'More filters' => '', + // 'Compact view' => '', + // 'Horizontal scrolling' => '', + // 'Compact/wide view' => '', + // 'No results match:' => '', + // 'Remove hourly rate' => '', + // 'Do you really want to remove this hourly rate?' => '', + // 'Hourly rates' => '', + // 'Hourly rate' => '', + // 'Currency' => '', + // 'Effective date' => '', + // 'Add new rate' => '', + // 'Rate removed successfully.' => '', + // 'Unable to remove this rate.' => '', + // 'Unable to save the hourly rate.' => '', + // 'Hourly rate created successfully.' => '', + // 'Start time' => '', + // 'End time' => '', + // 'Comment' => '', + // 'All day' => '', + // 'Day' => '', + // 'Manage timetable' => '', + // 'Overtime timetable' => '', + // 'Time off timetable' => '', + // 'Timetable' => '', + // 'Work timetable' => '', + // 'Week timetable' => '', + // 'Day timetable' => '', + // 'From' => '', + // 'To' => '', + // 'Time slot created successfully.' => '', + // 'Unable to save this time slot.' => '', + // 'Time slot removed successfully.' => '', + // 'Unable to remove this time slot.' => '', + // 'Do you really want to remove this time slot?' => '', + // 'Remove time slot' => '', + // 'Add new time slot' => '', + // 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => '', + // 'Files' => '', + // 'Images' => '', + // 'Private project' => '', + // 'Amount' => '', + // 'AUD - Australian Dollar' => '', + // 'Budget' => '', + // 'Budget line' => '', + // 'Budget line removed successfully.' => '', + // 'Budget lines' => '', + // 'CAD - Canadian Dollar' => '', + // 'CHF - Swiss Francs' => '', + // 'Cost' => '', + // 'Cost breakdown' => '', + // 'Custom Stylesheet' => '', + // 'download' => '', + // 'Do you really want to remove this budget line?' => '', + // 'EUR - Euro' => '', + // 'Expenses' => '', + // 'GBP - British Pound' => '', + // 'INR - Indian Rupee' => '', + // 'JPY - Japanese Yen' => '', + // 'New budget line' => '', + // 'NZD - New Zealand Dollar' => '', + // 'Remove a budget line' => '', + // 'Remove budget line' => '', + // 'RSD - Serbian dinar' => '', + // 'The budget line have been created successfully.' => '', + // 'Unable to create the budget line.' => '', + // 'Unable to remove this budget line.' => '', + // 'USD - US Dollar' => '', + // 'Remaining' => '', ); diff --git a/app/Locale/ja_JP/translations.php b/app/Locale/ja_JP/translations.php index 90af6a02..6e3c14c8 100644 --- a/app/Locale/ja_JP/translations.php +++ b/app/Locale/ja_JP/translations.php @@ -1,6 +1,8 @@ <?php return array( + // 'number.decimals_separator' => '', + // 'number.thousands_separator' => '', 'None' => 'なし', 'edit' => '変更', 'Edit' => '変更', @@ -734,4 +736,72 @@ return array( // 'Filter recently updated' => '', // 'since %B %e, %Y at %k:%M %p' => '', // 'More filters' => '', + // 'Compact view' => '', + // 'Horizontal scrolling' => '', + // 'Compact/wide view' => '', + // 'No results match:' => '', + // 'Remove hourly rate' => '', + // 'Do you really want to remove this hourly rate?' => '', + // 'Hourly rates' => '', + // 'Hourly rate' => '', + // 'Currency' => '', + // 'Effective date' => '', + // 'Add new rate' => '', + // 'Rate removed successfully.' => '', + // 'Unable to remove this rate.' => '', + // 'Unable to save the hourly rate.' => '', + // 'Hourly rate created successfully.' => '', + // 'Start time' => '', + // 'End time' => '', + // 'Comment' => '', + // 'All day' => '', + // 'Day' => '', + // 'Manage timetable' => '', + // 'Overtime timetable' => '', + // 'Time off timetable' => '', + // 'Timetable' => '', + // 'Work timetable' => '', + // 'Week timetable' => '', + // 'Day timetable' => '', + // 'From' => '', + // 'To' => '', + // 'Time slot created successfully.' => '', + // 'Unable to save this time slot.' => '', + // 'Time slot removed successfully.' => '', + // 'Unable to remove this time slot.' => '', + // 'Do you really want to remove this time slot?' => '', + // 'Remove time slot' => '', + // 'Add new time slot' => '', + // 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => '', + // 'Files' => '', + // 'Images' => '', + // 'Private project' => '', + // 'Amount' => '', + // 'AUD - Australian Dollar' => '', + // 'Budget' => '', + // 'Budget line' => '', + // 'Budget line removed successfully.' => '', + // 'Budget lines' => '', + // 'CAD - Canadian Dollar' => '', + // 'CHF - Swiss Francs' => '', + // 'Cost' => '', + // 'Cost breakdown' => '', + // 'Custom Stylesheet' => '', + // 'download' => '', + // 'Do you really want to remove this budget line?' => '', + // 'EUR - Euro' => '', + // 'Expenses' => '', + // 'GBP - British Pound' => '', + // 'INR - Indian Rupee' => '', + // 'JPY - Japanese Yen' => '', + // 'New budget line' => '', + // 'NZD - New Zealand Dollar' => '', + // 'Remove a budget line' => '', + // 'Remove budget line' => '', + // 'RSD - Serbian dinar' => '', + // 'The budget line have been created successfully.' => '', + // 'Unable to create the budget line.' => '', + // 'Unable to remove this budget line.' => '', + // 'USD - US Dollar' => '', + // 'Remaining' => '', ); diff --git a/app/Locale/nl_NL/translations.php b/app/Locale/nl_NL/translations.php new file mode 100644 index 00000000..2b434d53 --- /dev/null +++ b/app/Locale/nl_NL/translations.php @@ -0,0 +1,807 @@ +<?php + +return array( + // 'number.decimals_separator' => '', + // 'number.thousands_separator' => '', + 'None' => 'Geen', + 'edit' => 'bewerken', + 'Edit' => 'Bewerken', + 'remove' => 'verwijderen', + 'Remove' => 'Verwijderen', + 'Update' => 'Update', + 'Yes' => 'Ja', + 'No' => 'Nee', + 'cancel' => 'annuleren', + 'or' => 'of', + 'Yellow' => 'Geel', + 'Blue' => 'Blauw', + 'Green' => 'Groen', + 'Purple' => 'Paars', + 'Red' => 'Rood', + 'Orange' => 'Oranje', + 'Grey' => 'Grijs', + 'Save' => 'Opslaan', + 'Login' => 'Inloggen', + 'Official website:' => 'Officiële website :', + 'Unassigned' => 'Niet toegewezen', + 'View this task' => 'Deze taak bekijken', + 'Remove user' => 'Gebruiker verwijderen', + 'Do you really want to remove this user: "%s"?' => 'Weet u zeker dat u deze gebruiker wil verwijderen : « %s » ?', + 'New user' => 'Nieuwe gebruiker', + 'All users' => 'Alle gebruikers', + 'Username' => 'Gebruikersnaam', + 'Password' => 'Wachtwoord', + 'Default project' => 'Standaard wachtwoord', + 'Administrator' => 'Administrator', + 'Sign in' => 'Inloggen', + 'Users' => 'Gebruikers', + 'No user' => 'Geen gebruiker', + 'Forbidden' => 'Geweigerd', + 'Access Forbidden' => 'Toegang geweigerd', + 'Only administrators can access to this page.' => 'Alleen administrators hebben toegang tot deze pagina.', + 'Edit user' => 'Gebruiker bewerken', + 'Logout' => 'Uitloggen', + 'Bad username or password' => 'Verkeerde gebruikersnaam of wachtwoord', + 'users' => 'gebruikers', + 'projects' => 'projecten', + 'Edit project' => 'Project bewerken', + 'Name' => 'Naam', + 'Activated' => 'Geactiveerd', + 'Projects' => 'Projecten', + 'No project' => 'Geen project', + 'Project' => 'Project', + 'Status' => 'Status', + 'Tasks' => 'Taken', + 'Board' => 'Bord', + 'Actions' => 'Acties', + 'Inactive' => 'Inactief', + 'Active' => 'Actief', + 'Column %d' => 'Kolom %d', + 'Add this column' => 'Deze kolom toevoegen', + '%d tasks on the board' => '%d taken op het bord', + '%d tasks in total' => '%d taken in totaal', + 'Unable to update this board.' => 'Update van dit bord niet mogelijk.', + 'Edit board' => 'Bord bewerken', + 'Disable' => 'Deactiveren', + 'Enable' => 'Activeren', + 'New project' => 'Nieuw project', + 'Do you really want to remove this project: "%s"?' => 'Weet u zeker dat u dit project wil verwijderen : « %s » ?', + 'Remove project' => 'Project verwijderen', + 'Boards' => 'Borden', + 'Edit the board for "%s"' => 'Bord bewerken voor « %s »', + 'All projects' => 'Alle projecten', + 'Change columns' => 'Kolommen veranderen', + 'Add a new column' => 'Kolom toevoegen', + 'Title' => 'Titel', + 'Add Column' => 'Kolom toevoegen', + 'Project "%s"' => 'Project « %s »', + 'Nobody assigned' => 'Niemand toegewezen', + 'Assigned to %s' => 'Toegewezen aan %s', + 'Remove a column' => 'Kolom verwijderen', + 'Remove a column from a board' => 'Kolom verwijderen van het bord', + 'Unable to remove this column.' => 'Verwijderen van deze kolom niet mogelijk.', + 'Do you really want to remove this column: "%s"?' => 'Weet u zeker dat u deze kolom wil verwijderen : « %s » ?', + 'This action will REMOVE ALL TASKS associated to this column!' => 'Deze actie zal ALLE TAKEN VERWIJDEREN die zijn geassocieerd met deze kolom!', + 'Settings' => 'Instellingen', + 'Application settings' => 'Applicatie instellingen', + 'Language' => 'Taal', + 'Webhook token:' => 'Webhook token :', + 'API token:' => 'API token :', + 'More information' => 'Meer informatie', + 'Database size:' => 'Database grootte :', + 'Download the database' => 'Download de database', + 'Optimize the database' => 'Optimaliseer de database', + '(VACUUM command)' => '(VACUUM commando)', + '(Gzip compressed Sqlite file)' => '(Gzip ingepakt Sqlite bestand)', + 'User settings' => 'Gebruikers instellingen', + 'My default project:' => 'Mijn standaard project : ', + 'Close a task' => 'Taak sluiten', + 'Do you really want to close this task: "%s"?' => 'Weet u zeker dat u deze taak wil sluiten : « %s » ?', + 'Edit a task' => 'Taak bewerken', + 'Column' => 'Kolom', + 'Color' => 'Kleur', + 'Assignee' => 'Toegewezene', + 'Create another task' => 'Nog een taak aanmaken', + 'New task' => 'Nieuwe taak', + 'Open a task' => 'Een taak openen', + 'Do you really want to open this task: "%s"?' => 'Weet u zeker dat u deze taak wil openen : « %s » ?', + 'Back to the board' => 'Terug naar het bord', + 'Created on %B %e, %Y at %k:%M %p' => 'Aangemaakt op %d/%m/%Y à %H:%M', + 'There is nobody assigned' => 'Er is niemand toegewezen', + 'Column on the board:' => 'Kolom op het bord : ', + 'Status is open' => 'Status is open', + 'Status is closed' => 'Status is gesloten', + 'Close this task' => 'Deze taak sluiten', + 'Open this task' => 'Deze taak openen', + 'There is no description.' => 'Er is geen omschrijving.', + 'Add a new task' => 'Een nieuwe taak toevoegen', + 'The username is required' => 'De gebruikersnaam is verplicht', + 'The maximum length is %d characters' => 'De maximale lengte is %d karakters', + 'The minimum length is %d characters' => 'De minimale lengte is %d karakters', + 'The password is required' => 'Het wachtwoord is verplicht', + 'This value must be an integer' => 'Deze waarde dient een integer te zijn', + 'The username must be unique' => 'De gebruikersnaam moet uniek zijn', + 'The username must be alphanumeric' => 'De gebruikersnaam moet alfanumeriek zijn', + 'The user id is required' => 'Het gebruikers id is verplicht', + 'Passwords don\'t match' => 'De wachtwoorden komen niet overeen', + 'The confirmation is required' => 'De bevestiging is verplicht', + 'The column is required' => 'De kolom is verplicht', + 'The project is required' => 'Het project is verplicht', + 'The color is required' => 'De kleur is verplicht', + 'The id is required' => 'Het id is verplicht', + 'The project id is required' => 'Het project id is verplicht', + 'The project name is required' => 'De projectnaam is verplicht', + 'This project must be unique' => 'Dit project moet uniek zijn', + 'The title is required' => 'De titel is verplicht', + 'The language is required' => 'De taal is verplicht', + 'There is no active project, the first step is to create a new project.' => 'Er is geen actief project, de eerste stap is een nieuw project aanmaken.', + 'Settings saved successfully.' => 'Instellingen succesvol opgeslagen.', + 'Unable to save your settings.' => 'Instellingen opslaan niet gelukt.', + 'Database optimization done.' => 'Database optimaliseren voltooid.', + 'Your project have been created successfully.' => 'Uw project is succesvol aangemaakt.', + 'Unable to create your project.' => 'Het aanmaken van het project is niet gelukt.', + 'Project updated successfully.' => 'Project succesvol geupdate.', + 'Unable to update this project.' => 'Updaten van project niet gelukt.', + 'Unable to remove this project.' => 'Verwijderen van project niet gelukt.', + 'Project removed successfully.' => 'Project succesvol verwijderd.', + 'Project activated successfully.' => 'Project succesvol geactiveerd.', + 'Unable to activate this project.' => 'Project activeren niet gelukt.', + 'Project disabled successfully.' => 'Project uitschakelen succesvol.', + 'Unable to disable this project.' => 'Project uitschakelen niet gelukt.', + 'Unable to open this task.' => 'Openen van deze taak niet gelukt.', + 'Task opened successfully.' => 'Taak succesvol geopend.', + 'Unable to close this task.' => 'Sluiten van deze taak niet gelukt.', + 'Task closed successfully.' => 'Taak succesvol gesloten.', + 'Unable to update your task.' => 'Updaten van uw taak mislukt.', + 'Task updated successfully.' => 'Taak succesvol geupdate.', + 'Unable to create your task.' => 'Taak aanmaken niet gelukt.', + 'Task created successfully.' => 'Taak succesvol aangemaakt.', + 'User created successfully.' => 'Gebruiker succesvol aangemaakt.', + 'Unable to create your user.' => 'Aanmaken van gebruiker niet gelukt.', + 'User updated successfully.' => 'Gebruiker succesvol geupdate', + 'Unable to update your user.' => 'Updaten van gebruiker niet gelukt.', + 'User removed successfully.' => 'Gebruiker succesvol verwijderd.', + 'Unable to remove this user.' => 'Verwijderen van gebruikers niet gelukt.', + 'Board updated successfully.' => 'Board succesvol geupdate.', + 'Ready' => 'Klaar', + 'Backlog' => 'En attente', + 'Work in progress' => 'In behandeling', + 'Done' => 'Klaar', + 'Application version:' => 'Applicatie versie :', + 'Completed on %B %e, %Y at %k:%M %p' => 'Voltooid op %d/%m/%Y à %H:%M', + '%B %e, %Y at %k:%M %p' => '%d/%m/%Y op %H:%M', + 'Date created' => 'Datum aangemaakt', + 'Date completed' => 'Datum voltooid', + 'Id' => 'Id', + 'No task' => 'Geen taak', + 'Completed tasks' => 'Voltooide taken', + 'List of projects' => 'Lijst van projecten', + 'Completed tasks for "%s"' => 'Vooltooide taken voor « %s »', + '%d closed tasks' => '%d gesloten taken', + 'No task for this project' => 'Geen taken voor dit project', + 'Public link' => 'Publieke link', + 'There is no column in your project!' => 'Er is geen kolom in uw project !', + 'Change assignee' => 'Toegewezene aanpassen', + 'Change assignee for the task "%s"' => 'Toegewezene aanpassen voor taak « %s »', + 'Timezone' => 'Tijdzone', + 'Sorry, I didn\'t find this information in my database!' => 'Sorry deze informatie kon niet worden gevonden in de database !', + 'Page not found' => 'Pagina niet gevonden', + 'Complexity' => 'Complexiteit', + 'limit' => 'Limiet', + 'Task limit' => 'Taak limiet.', + 'Task count' => 'Aantal taken', + 'This value must be greater than %d' => 'Deze waarde moet groter zijn dan %d', + 'Edit project access list' => 'Aanpassen toegangsrechten project', + 'Edit users access' => 'Gebruikerstoegang aanpassen', + 'Allow this user' => 'Deze gebruiker toestaan', + 'Only those users have access to this project:' => 'Alleen deze gebruikers hebben toegang tot dit project :', + 'Don\'t forget that administrators have access to everything.' => 'Vergeet niet dat administrators overal toegang hebben.', + 'Revoke' => 'Intrekken', + 'List of authorized users' => 'Lijst met geautoriseerde gebruikers', + 'User' => 'Gebruiker', + 'Nobody have access to this project.' => 'Niemand heeft toegang tot dit project', + 'You are not allowed to access to this project.' => 'U heeft geen toegang tot dit project.', + 'Comments' => 'Commentaar', + 'Post comment' => 'Commentaar toevoegen', + 'Write your text in Markdown' => 'Schrijf uw tekst in Markdown', + 'Leave a comment' => 'Schrijf een commentaar', + 'Comment is required' => 'Commentaar is verplicht', + 'Leave a description' => 'Schrijf een omschrijving', + 'Comment added successfully.' => 'Commentaar succesvol toegevoegd.', + 'Unable to create your comment.' => 'Commentaar toevoegen niet gelukt.', + 'The description is required' => 'Omschrijving is verplicht', + 'Edit this task' => 'Deze taak aanpassen', + 'Due Date' => 'Vervaldag', + 'Invalid date' => 'Ongeldige datum', + 'Must be done before %B %e, %Y' => 'Moet voltooid zijn voor %d/%m/%Y', + '%B %e, %Y' => '%d %B %Y', + '%b %e, %Y' => '%d/%m/%Y', + 'Automatic actions' => 'Geautomatiseerd acties', + 'Your automatic action have been created successfully.' => 'Geautomatiseerde actie succesvol aangemaakt.', + 'Unable to create your automatic action.' => 'Geautomatiseerde actie aanmaken niet gelukt.', + 'Remove an action' => 'Actie verwijderen', + 'Unable to remove this action.' => 'Actie verwijderen niet gelukt', + 'Action removed successfully.' => 'Actie succesvol verwijder.', + 'Automatic actions for the project "%s"' => 'Automatiseer acties voor project « %s »', + 'Defined actions' => 'Gedefinieerde acties', + 'Add an action' => 'Actie toevoegen', + 'Event name' => 'Naam gebeurtenis', + 'Action name' => 'Actie naam', + 'Action parameters' => 'Actie paramaters', + 'Action' => 'Actie', + 'Event' => 'Evenement', + 'When the selected event occurs execute the corresponding action.' => 'Als de geselecteerde gebeurtenis optreedt de volgende actie uitvoeren', + 'Next step' => 'Volgende stap', + 'Define action parameters' => 'Bepaal actie parameters', + 'Save this action' => 'Actie opslaan', + 'Do you really want to remove this action: "%s"?' => 'Weet u zeker dat u de volgende actie wil verwijderen : « %s » ?', + 'Remove an automatic action' => 'Automatische actie verwijderen', + 'Close the task' => 'Taak sluiten', + 'Assign the task to a specific user' => 'Taak toewijzen aan een gebruiker', + 'Assign the task to the person who does the action' => 'Taak toewijzen aan een gebruiker die de actie uitvoert', + 'Duplicate the task to another project' => 'Taak dupliceren in een ander project', + 'Move a task to another column' => 'Taak verplaatsen naar een andere kolom', + 'Move a task to another position in the same column' => 'Taak verplaatsen naar een andere positie in dezelfde kolom', + 'Task modification' => 'Taak aanpassen', + 'Task creation' => 'Taak aanmaken', + 'Open a closed task' => 'Gesloten taak openen', + 'Closing a task' => 'Taak sluiten', + 'Assign a color to a specific user' => 'Wijs een kleur toe aan een gebruiker', + 'Column title' => 'Kolom titel', + 'Position' => 'Positie', + 'Move Up' => 'Omhoog verplaatsen', + 'Move Down' => 'Omlaag verplaatsen', + 'Duplicate to another project' => 'Dupliceren in een ander project', + 'Duplicate' => 'Dupliceren', + 'link' => 'koppelen', + 'Update this comment' => 'Commentaar aanpassen', + 'Comment updated successfully.' => 'Commentaar succesvol aangepast.', + 'Unable to update your comment.' => 'Commentaar aanpassen niet gelukt.', + 'Remove a comment' => 'Commentaar verwijderen', + 'Comment removed successfully.' => 'Commentaar succesvol verwijder.', + 'Unable to remove this comment.' => 'Commentaar verwijderen niet gelukt.', + 'Do you really want to remove this comment?' => 'Weet u zeker dat u dit commentaar wil verwijderen ?', + 'Only administrators or the creator of the comment can access to this page.' => 'Alleen administrators of de aanmaker van het commentaar hebben toegang tot deze pagina.', + 'Details' => 'Details', + 'Current password for the user "%s"' => 'Huidig wachtwoord voor gebruiker « %s »', + 'The current password is required' => 'Huidig wachtwoord is verplicht', + 'Wrong password' => 'Onjuist wachtwoord', + 'Reset all tokens' => 'Alle tokens resetten', + 'All tokens have been regenerated.' => 'Alle tokens zijn opnieuw gegenereerd.', + 'Unknown' => 'Onbekend', + 'Last logins' => 'Laatste logins', + 'Login date' => 'Login datum', + 'Authentication method' => 'Authenticatie methode', + 'IP address' => 'IP adres', + 'User agent' => 'User agent', + 'Persistent connections' => 'Persistente connectie', + 'No session.' => 'Geen sessie.', + 'Expiration date' => 'Verloopdatum', + 'Remember Me' => 'Onthoud mij', + 'Creation date' => 'Aanmaakdatum', + 'Filter by user' => 'Filter op gebruiker', + 'Filter by due date' => 'Filter op vervaldatum', + 'Everybody' => 'Iedereen', + 'Open' => 'Open', + 'Closed' => 'Gesloten', + 'Search' => 'Zoek', + 'Nothing found.' => 'Niets gevonden.', + 'Search in the project "%s"' => 'Zoek in project « %s »', + 'Due date' => 'Vervaldatum', + 'Others formats accepted: %s and %s' => 'Andere toegestane formaten : %s en %s', + 'Description' => 'Omschrijving', + '%d comments' => '%d commentaren', + '%d comment' => '%d commentaar', + 'Email address invalid' => 'Ongeldig emailadres', + 'Your Google Account is not linked anymore to your profile.' => 'Uw Google Account is niet meer aan uw profiel gelinkt.', + 'Unable to unlink your Google Account.' => 'Verwijderen link met Google Account niet gelukt.', + 'Google authentication failed' => 'Google authenticatie niet gelukt', + 'Unable to link your Google Account.' => 'Linken met Google Account niet gelukt', + 'Your Google Account is linked to your profile successfully.' => 'Linken met Google Account succesvol.', + 'Email' => 'Email', + 'Link my Google Account' => 'Link mijn Google Account', + 'Unlink my Google Account' => 'Link met Google Account verwijderen', + 'Login with my Google Account' => 'Inloggen met mijn Google Account', + 'Project not found.' => 'Project niet gevonden.', + 'Task #%d' => 'Taak %d', + 'Task removed successfully.' => 'Taak succesvol verwijderd.', + 'Unable to remove this task.' => 'Taak verwijderen niet gelukt.', + 'Remove a task' => 'Taak verwijderen', + 'Do you really want to remove this task: "%s"?' => 'Weet u zeker dat u deze taak wil verwijderen « %s » ?', + 'Assign automatically a color based on a category' => 'Automatisch een kleur toewijzen aan de hand van een categorie', + 'Assign automatically a category based on a color' => 'Automatisch een categorie toewijzen aan de hand van een kleur', + 'Task creation or modification' => 'Taak aanmaken of wijzigen', + 'Category' => 'Categorie', + 'Category:' => 'Categorie :', + 'Categories' => 'Categorieën', + 'Category not found.' => 'Categorie niet gevonden', + 'Your category have been created successfully.' => 'Categorie succesvol aangemaakt.', + 'Unable to create your category.' => 'Categorie aanmaken niet gelukt.', + 'Your category have been updated successfully.' => 'Categorie succesvol aangepast.', + 'Unable to update your category.' => 'Aanpassen van categorie niet gelukt.', + 'Remove a category' => 'Categorie verwijderen', + 'Category removed successfully.' => 'Categorie succesvol verwijderd.', + 'Unable to remove this category.' => 'Categorie verwijderen niet gelukt.', + 'Category modification for the project "%s"' => 'Categorie aanpassen voor project « %s »', + 'Category Name' => 'Categorie naam', + 'Categories for the project "%s"' => 'Categorieën voor project « %s »', + 'Add a new category' => 'Categorie toevoegen', + 'Do you really want to remove this category: "%s"?' => 'Weet u zeker dat u deze categorie wil verwijderen: « %s » ?', + 'Filter by category' => 'Filter op categorie', + 'All categories' => 'Alle categorieën', + 'No category' => 'Geen categorie', + 'The name is required' => 'De naam is verplicht', + 'Remove a file' => 'Bestand verwijderen', + 'Unable to remove this file.' => 'Bestand verwijderen niet gelukt.', + 'File removed successfully.' => 'Bestand succesvol verwijdered.', + 'Attach a document' => 'Document toevoegen', + 'Do you really want to remove this file: "%s"?' => 'Weet u zeker dat u dit bestand wil verwijderen: « %s » ?', + 'open' => 'openen', + 'Attachments' => 'Bijlages', + 'Edit the task' => 'Taak aanpassen', + 'Edit the description' => 'Omschrijving aanpassen', + 'Add a comment' => 'Commentaar toevoegen', + 'Edit a comment' => 'Commentaar aanpassen', + 'Summary' => 'Samenvatting', + 'Time tracking' => 'Tijdschrijven', + 'Estimate:' => 'Schatting :', + 'Spent:' => 'Besteed :', + 'Do you really want to remove this sub-task?' => 'Weet u zeker dat u deze subtaak wil verwijderen ?', + 'Remaining:' => 'Restant :', + 'hours' => 'uren', + 'spent' => 'besteed', + 'estimated' => 'geschat', + 'Sub-Tasks' => 'Subtaken', + 'Add a sub-task' => 'Subtaak toevoegen', + 'Original estimate' => 'Orginele schatting', + 'Create another sub-task' => 'Nog een subtaak toevoegen', + 'Time spent' => 'Tijd besteed', + 'Edit a sub-task' => 'Subtaak aanpassen', + 'Remove a sub-task' => 'Subtaak verwijderen', + 'The time must be a numeric value' => 'De tijd moet een numerieke waarde zijn', + 'Todo' => 'Nog te doen', + 'In progress' => 'In behandeling', + 'Sub-task removed successfully.' => 'Subtaak succesvol verwijderd.', + 'Unable to remove this sub-task.' => 'Subtaak verwijderen niet gelukt.', + 'Sub-task updated successfully.' => 'Subtaak succesvol aangepast.', + 'Unable to update your sub-task.' => 'Subtaak aanpassen niet gelukt.', + 'Unable to create your sub-task.' => 'Subtaak aanmaken niet gelukt.', + 'Sub-task added successfully.' => 'Subtaak succesvol aangemaakt.', + 'Maximum size: ' => 'Maximale grootte : ', + 'Unable to upload the file.' => 'Uploaden van bestand niet gelukt.', + 'Display another project' => 'Een ander project weergeven', + 'Your GitHub account was successfully linked to your profile.' => 'Uw Github Account is succesvol gelinkt aan uw profiel.', + 'Unable to link your GitHub Account.' => 'Linken van uw Github Account niet gelukt.', + 'GitHub authentication failed' => 'Github Authenticatie niet gelukt', + 'Your GitHub account is no longer linked to your profile.' => 'Uw Github Account is niet langer gelinkt aan uw profiel.', + 'Unable to unlink your GitHub Account.' => 'Verwijdern van de link met uw Github Account niet gelukt.', + 'Login with my GitHub Account' => 'Login met mijn Github Account', + 'Link my GitHub Account' => 'Link met mijn Github', + 'Unlink my GitHub Account' => 'Link met mijn Github verwijderen', + 'Created by %s' => 'Aangemaakt door %s', + 'Last modified on %B %e, %Y at %k:%M %p' => 'Laatst gewijzigd op %d/%m/%Y à %H:%M', + 'Tasks Export' => 'Taken exporteren', + 'Tasks exportation for "%s"' => 'Taken exporteren voor « %s »', + 'Start Date' => 'Startdatum', + 'End Date' => 'Einddatum', + 'Execute' => 'Uitvoeren', + 'Task Id' => 'Taak Id', + 'Creator' => 'Aangemaakt door', + 'Modification date' => 'Wijzigingsdatum', + 'Completion date' => 'Afgerond op', + 'Webhook URL for task creation' => 'Webhook URL voor aanmaken taak', + 'Webhook URL for task modification' => 'Webhook URL voor wijzigen taak', + 'Clone' => 'Kloon', + 'Clone Project' => 'Project klonen', + 'Project cloned successfully.' => 'Project succesvol gekloond.', + 'Unable to clone this project.' => 'Klonen van project niet gelukt.', + 'Email notifications' => 'Email notificatie', + 'Enable email notifications' => 'Email notificatie aanzetten', + 'Task position:' => 'Taak positie :', + 'The task #%d have been opened.' => 'Taak #%d is geopend.', + 'The task #%d have been closed.' => 'Taak #%d is gesloten.', + 'Sub-task updated' => 'Subtaak aangepast', + 'Title:' => 'Titel :', + 'Status:' => 'Status :', + 'Assignee:' => 'Toegewezene :', + 'Time tracking:' => 'Tijdschrijven :', + 'New sub-task' => 'Nieuwe subtaak', + 'New attachment added "%s"' => 'Nieuwe bijlage toegevoegd « %s »', + 'Comment updated' => 'Commentaar aangepast', + 'New comment posted by %s' => 'Nieuw commentaar geplaatst door « %s »', + 'List of due tasks for the project "%s"' => 'Lijst van taken die binnenkort voltooid moeten worden voor project « %s »', + 'New attachment' => 'Nieuwe bijlage', + 'New comment' => 'Nieuw commentaar', + 'New subtask' => 'Nieuwe subtaak', + 'Subtask updated' => 'Subtaak aangepast', + 'Task updated' => 'Taak aangepast', + 'Task closed' => 'Taak gesloten', + 'Task opened' => 'Taak geopend', + '[%s][Due tasks]' => '[%s][binnekort te voltooien taken]', + '[Kanboard] Notification' => '[Kanboard] Notificatie', + 'I want to receive notifications only for those projects:' => 'Ik wil notificaties ontvangen van de volgende projecten :', + 'view the task on Kanboard' => 'taak bekijken op Kanboard', + 'Public access' => 'Publieke toegang', + 'Category management' => 'Categorie management', + 'User management' => 'Gebruikers management', + 'Active tasks' => 'Actieve taken', + 'Disable public access' => 'Publieke toegang uitschakelen', + 'Enable public access' => 'Publieke toegang inschakelen', + 'Active projects' => 'Actieve projecten', + 'Inactive projects' => 'Inactieve projecten', + 'Public access disabled' => 'Publieke toegang uitgeschakeld', + 'Do you really want to disable this project: "%s"?' => 'Weet u zeker dat u dit project wil uitschakelen : « %s » ?', + 'Do you really want to duplicate this project: "%s"?' => 'Weet u zeker dat u dit project wil dupliceren : « %s » ?', + 'Do you really want to enable this project: "%s"?' => 'Weet u zeker dat u dit project wil activeren : « %s » ?', + 'Project activation' => 'Project activatie', + 'Move the task to another project' => 'Taak verplaatsen naar een ander project', + 'Move to another project' => 'Verplaats naar een ander project', + 'Do you really want to duplicate this task?' => 'Weet u zeker dat u deze taak wil dupliceren ?', + 'Duplicate a task' => 'Taak dupliceren', + 'External accounts' => 'Externe accounts', + 'Account type' => 'Account type', + 'Local' => 'Lokaal', + 'Remote' => 'Remote', + 'Enabled' => 'Actief', + 'Disabled' => 'Inactief', + 'Google account linked' => 'Gelinkt Google Account', + 'Github account linked' => 'Gelinkt Github Account', + 'Username:' => 'Gebruikersnaam :', + 'Name:' => 'Naam :', + 'Email:' => 'Email :', + 'Default project:' => 'Standaard project :', + 'Notifications:' => 'Notificaties :', + 'Notifications' => 'Notificaties', + 'Group:' => 'Groep :', + 'Regular user' => 'Normale gebruiker', + 'Account type:' => 'Account type:', + 'Edit profile' => 'Profiel aanpassen', + 'Change password' => 'Wachtwoord aanpassen', + 'Password modification' => 'Wachtwoord aanpassen', + 'External authentications' => 'Externe authenticatie', + 'Google Account' => 'Google Account', + 'Github Account' => 'Github Account', + 'Never connected.' => 'Nooit verbonden.', + 'No account linked.' => 'Geen account gelinkt.', + 'Account linked.' => 'Account gelinkt.', + 'No external authentication enabled.' => 'Geen externe authenticatie aangezet.', + 'Password modified successfully.' => 'Wachtwoord succesvol aangepast.', + 'Unable to change the password.' => 'Aanpassen van wachtwoord niet gelukt.', + 'Change category for the task "%s"' => 'Pas categorie aan voor taak « %s »', + 'Change category' => 'Categorie aanpassen', + '%s updated the task %s' => '%s heeft taak %s aangepast', + '%s opened the task %s' => '%s heeft taak %s geopend', + '%s moved the task %s to the position #%d in the column "%s"' => '%s heeft taak %s naar positie %d in de kolom « %s » verplaatst', + '%s moved the task %s to the column "%s"' => '%s heeft taak %s verplaatst naar kolom « %s »', + '%s created the task %s' => '%s heeft taak %s aangemaakt', + '%s closed the task %s' => '%s heeft taak %s gesloten', + '%s created a subtask for the task %s' => '%s heeft een subtaak aangemaakt voor taak %s', + '%s updated a subtask for the task %s' => '%s heeft een subtaak aangepast voor taak %s', + 'Assigned to %s with an estimate of %s/%sh' => 'Toegewezen aan %s met een schatting van %s/%sh', + 'Not assigned, estimate of %sh' => 'Niet toegewezen, schatting: %sh', + '%s updated a comment on the task %s' => '%s heeft een commentaar aangepast voor taak %s', + '%s commented the task %s' => '%s heeft een commentaar geplaatst voor taak %s', + '%s\'s activity' => 'Activiteiten van %s', + 'No activity.' => 'Geen activiteiten.', + 'RSS feed' => 'RSS feed', + '%s updated a comment on the task #%d' => '%s heeft een commentaar aangepast voor taak %d', + '%s commented on the task #%d' => '%s heeft commentaar geplaatst voor taak %d', + '%s updated a subtask for the task #%d' => '%s heeft een commentaar aangepast voor subtaak %d', + '%s created a subtask for the task #%d' => '%s heeft een subtaak aangemaakt voor taak %d', + '%s updated the task #%d' => '%s heeft taak %d aangepast', + '%s created the task #%d' => '%s heeft taak %d aangemaakt', + '%s closed the task #%d' => '%s heeft taak %d gesloten', + '%s open the task #%d' => '%s a heeft taak %d geopend', + '%s moved the task #%d to the column "%s"' => '%s heeft taak %d verplaatst naar kolom « %s »', + '%s moved the task #%d to the position %d in the column "%s"' => '%s heeft taak %d verplaatst naar positie %d in kolom « %s »', + 'Activity' => 'Activiteit', + 'Default values are "%s"' => 'Standaardwaarden zijn « %s »', + 'Default columns for new projects (Comma-separated)' => 'Standaard kolommen voor nieuw projecten (komma gescheiden)', + 'Task assignee change' => 'Taak toegewezene verandering', + '%s change the assignee of the task #%d to %s' => '%s heeft de toegewezene voor taak %d veranderd in %s', + '%s changed the assignee of the task %s to %s' => '%s heeft de toegewezene voor taak %d veranderd in %s', + 'Column Change' => 'Kolom verandering', + 'Position Change' => 'Positie verandering', + 'Assignee Change' => 'Toegewezene verandering', + 'New password for the user "%s"' => 'Nieuw wachtwoord voor gebruiker « %s »', + 'Choose an event' => 'Kies een gebeurtenis', + 'Github commit received' => 'Github commentaar ontvangen', + 'Github issue opened' => 'Github issue geopend', + 'Github issue closed' => 'Github issue gesloten', + 'Github issue reopened' => 'Github issue heropend', + 'Github issue assignee change' => 'Github toegewezen veranderd', + 'Github issue label change' => 'Github issue label verander', + 'Create a task from an external provider' => 'Maak een taak aan vanuit een externe provider', + 'Change the assignee based on an external username' => 'Verander de toegewezene aan de hand van de externe gebruikersnaam', + 'Change the category based on an external label' => 'Verander de categorie aan de hand van een extern label', + 'Reference' => 'Referentie', + 'Reference: %s' => 'Referentie : %s', + 'Label' => 'Label', + 'Database' => 'Database', + 'About' => 'Over', + 'Database driver:' => 'Database driver :', + 'Board settings' => 'Bord instellingen', + 'URL and token' => 'URL en token', + 'Webhook settings' => 'Webhook instellingen', + 'URL for task creation:' => 'URL voor aanmaken taken :', + 'Reset token' => 'Token resetten', + 'API endpoint:' => 'API endpoint :', + 'Refresh interval for private board' => 'Verversingsinterval voor private borden', + 'Refresh interval for public board' => 'Verversingsinterval voor publieke borden', + 'Task highlight period' => 'Taak highlight periode', + 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'Periode (in seconden) om aan te geven of een taak recent is aangepast (0 om uit te schakelen, standaard 2 dagen)', + 'Frequency in second (60 seconds by default)' => 'Frequentie in seconden (stadaard 60)', + 'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'Frequentie in seconden (0 om uit te schakelen, standaard 10)', + 'Application URL' => 'Applicatie URL', + 'Example: http://example.kanboard.net/ (used by email notifications)' => 'Voorbeeld: http://example.kanboard.net/ (gebruikt voor email notificaties)', + 'Token regenerated.' => 'Token opnieuw gegenereerd.', + 'Date format' => 'Datum formaat', + 'ISO format is always accepted, example: "%s" and "%s"' => 'ISO formaat is altijd geaccepteerd, bijvoorbeeld : « %s » et « %s »', + 'New private project' => 'Nieuw privé project', + 'This project is private' => 'Dit project is privé', + 'Type here to create a new sub-task' => 'Typ hier om een nieuwe subtaak aan te maken', + 'Add' => 'Toevoegen', + 'Estimated time: %s hours' => 'Geschatte tijd: %s hours', + 'Time spent: %s hours' => 'Tijd besteed : %s heures', + 'Started on %B %e, %Y' => 'Gestart op %d/%m/%Y', + 'Start date' => 'Startdatum', + 'Time estimated' => 'Geschatte tijd', + 'There is nothing assigned to you.' => 'Er is niets aan u toegewezen.', + 'My tasks' => 'Mijn taken', + 'Activity stream' => 'Activiteiten', + 'Dashboard' => 'Dashboard', + // 'Confirmation' => '', + 'Allow everybody to access to this project' => 'Geef iedereen toegang tot dit project', + 'Everybody have access to this project.' => 'Iedereen heeft toegang tot dit project.', + 'Webhooks' => 'Webhooks', + 'API' => 'API', + 'Integration' => 'Integratue', + 'Github webhooks' => 'Github webhooks', + 'Help on Github webhooks' => 'Hulp bij Github webhooks', + 'Create a comment from an external provider' => 'Voeg een commentaar toe van een externe provider', + 'Github issue comment created' => 'Github issue commentaar aangemaakt', + 'Configure' => 'Configureren', + 'Project management' => 'Project management', + 'My projects' => 'Mijn projecten', + 'Columns' => 'Kolommen', + 'Task' => 'Taak', + 'Your are not member of any project.' => 'U bent van geen enkel project lid.', + 'Percentage' => 'Percentage', + 'Number of tasks' => 'Aantal taken', + 'Task distribution' => 'Distributie van taken', + 'Reportings' => 'Rapporten', + 'Task repartition for "%s"' => 'Taakverdeling voor « %s »', + 'Analytics' => 'Analytics', + 'Subtask' => 'Subtaak', + 'My subtasks' => 'Mijn subtaken', + 'User repartition' => 'Gebruikerverdeling', + 'User repartition for "%s"' => 'Gebruikerverdeling voor « %s »', + 'Clone this project' => 'Kloon dit project', + 'Column removed successfully.' => 'Kolom succesvol verwijderd.', + 'Edit Project' => 'Project aanpassen', + 'Github Issue' => 'Github issue', + 'Not enough data to show the graph.' => 'Niet genoeg data om de grafiek te laten zien.', + // 'Previous' => '', + 'The id must be an integer' => 'Het id moet een integer zijn', + 'The project id must be an integer' => 'Het project id moet een integer zijn', + 'The status must be an integer' => 'De status moet een integer zijn', + 'The subtask id is required' => 'Het id van de subtaak is verplicht', + 'The subtask id must be an integer' => 'Het id van de subtaak moet een integer zijn', + 'The task id is required' => 'Het id van de taak is verplicht', + 'The task id must be an integer' => 'Het id van de taak moet een integer zijn', + 'The user id must be an integer' => 'Het id van de gebruiker moet een integer zijn', + 'This value is required' => 'Deze waarde is verplicht', + 'This value must be numeric' => 'Deze waarde moet numeriek zijn', + 'Unable to create this task.' => 'Aanmaken van de taak mislukt', + 'Cumulative flow diagram' => 'Cummulatief stroomdiagram', + 'Cumulative flow diagram for "%s"' => 'Cummulatief stroomdiagram voor « %s »', + 'Daily project summary' => 'Dagelijkse project samenvatting', + 'Daily project summary export' => 'Dagelijkse project samenvatting export', + 'Daily project summary export for "%s"' => 'Dagelijkse project samenvatting voor « %s »', + 'Exports' => 'Exports', + 'This export contains the number of tasks per column grouped per day.' => 'Dit rapport bevat het aantal taken per kolom gegroupeerd per dag.', + 'Nothing to preview...' => 'Niets om te previewen...', + 'Preview' => 'Preview', + 'Write' => 'Schrijf', + 'Active swimlanes' => 'Actieve swinlanes', + 'Add a new swimlane' => 'Nieuwe swimlane toevoegen', + 'Change default swimlane' => 'Standaard swimlane aapassen', + 'Default swimlane' => 'Standaard swinlane', + 'Do you really want to remove this swimlane: "%s"?' => 'Weet u zeker dat u deze swimlane wil verwijderen : « %s » ?', + 'Inactive swimlanes' => 'Inactieve swinlanes', + 'Set project manager' => 'Project manager instellen', + 'Set project member' => 'Project lid instellen', + 'Remove a swimlane' => 'Verwijder swinlane', + 'Rename' => 'Hernoemen', + 'Show default swimlane' => 'Standaard swimlane tonen', + 'Swimlane modification for the project "%s"' => 'Swinlane aanpassing voor project « %s »', + 'Swimlane not found.' => 'Swimlane niet gevonden.', + 'Swimlane removed successfully.' => 'Swimlane succesvol verwijderd.', + 'Swimlanes' => 'Swimlanes', + 'Swimlane updated successfully.' => 'Swimlane succesvol aangepast.', + 'The default swimlane have been updated successfully.' => 'De standaard swimlane is succesvol aangepast.', + 'Unable to create your swimlane.' => 'Swimlane aanmaken niet gelukt.', + 'Unable to remove this swimlane.' => 'Swimlane verwijderen niet gelukt.', + 'Unable to update this swimlane.' => 'Swimlane aanpassen niet gelukt.', + 'Your swimlane have been created successfully.' => 'Swimlane succesvol aangemaakt.', + 'Example: "Bug, Feature Request, Improvement"' => 'Voorbeeld: « Bug, Feature Request, Improvement »', + 'Default categories for new projects (Comma-separated)' => 'Standaard categorieën voor nieuwe projecten (komma gescheiden)', + 'Gitlab commit received' => 'Gitlab commir ontvangen', + 'Gitlab issue opened' => 'Gitlab issue geopend', + 'Gitlab issue closed' => 'Gitlab issue gesloten', + 'Gitlab webhooks' => 'Gitlab webhooks', + 'Help on Gitlab webhooks' => 'Hulp bij Gitlab webhooks', + 'Integrations' => 'Integraties', + 'Integration with third-party services' => 'Integratie met derde-partij-services', + 'Role for this project' => 'Rol voor dit project', + 'Project manager' => 'Project manager', + 'Project member' => 'Project lid', + 'A project manager can change the settings of the project and have more privileges than a standard user.' => 'Een project manager kan de instellingen van het project wijzigen en heeft meer rechten dan een normale gebruiker.', + 'Gitlab Issue' => 'Gitlab issue', + 'Subtask Id' => 'Subtaak id', + 'Subtasks' => 'Subtaken', + 'Subtasks Export' => 'Subtaken exporteren', + 'Subtasks exportation for "%s"' => 'Subtaken exporteren voor project « %s »', + 'Task Title' => 'Taak title', + 'Untitled' => 'Geen titel', + 'Application default' => 'Standaard taal voor applicatie', + 'Language:' => 'Taal :', + 'Timezone:' => 'Tijdzone :', + 'All columns' => 'Alle kolommen', + 'Calendar for "%s"' => 'Agenda voor « %s »', + 'Filter by column' => 'Filter op kolom', + 'Filter by status' => 'Filter op status', + 'Calendar' => 'Agenda', + 'Next' => 'Volgende', + '#%d' => '%d', + 'Filter by color' => 'Filter op kleur', + 'Filter by swimlane' => 'Filter op swimlane', + 'All swimlanes' => 'Alle swimlanes', + 'All colors' => 'Alle kleuren', + 'All status' => 'Alle statussen', + 'Add a comment logging moving the task between columns' => 'Voeg een commentaar toe bij het verplaatsen van een taak tussen kolommen', + 'Moved to column %s' => 'Verplaatst naar kolom', + 'Change description' => 'Verandering omschrijving', + 'User dashboard' => 'Gebruiker dashboard', + 'Allow only one subtask in progress at the same time for a user' => 'Sta maximaal één subtaak in behandeling toe per gebruiker', + 'Edit column "%s"' => 'Kolom « %s » aanpassen', + 'Enable time tracking for subtasks' => 'Activeer tijdschrijven voor subtaken', + 'Select the new status of the subtask: "%s"' => 'Selecteer nieuwe status voor subtaak : « %s »', + 'Subtask timesheet' => 'Subtaak timesheet', + 'There is nothing to show.' => 'Er is niets om te laten zijn.', + 'Time Tracking' => 'Tijdschrijven', + 'You already have one subtask in progress' => 'U heeft al een subtaak in behandeling', + 'Which parts of the project do you want to duplicate?' => 'Welke onderdelen van het project wilt u dupliceren?', + 'Change dashboard view' => 'Pas dashboard aan', + 'Show/hide activities' => 'Toon/verberg activiteiten', + 'Show/hide projects' => 'Toon/verberg projecten', + 'Show/hide subtasks' => 'Toon/verberg subtaken', + 'Show/hide tasks' => 'Toon/verberg taken', + 'Disable login form' => 'Schakel login scherm uit', + 'Show/hide calendar' => 'Toon/verberg agenda', + 'User calendar' => 'Agenda gebruiker', + 'Bitbucket commit received' => 'Bitbucket commit ontvangen', + 'Bitbucket webhooks' => 'Bitbucket webhooks', + 'Help on Bitbucket webhooks' => 'Help bij Bitbucket webhooks', + 'Start' => 'Start', + 'End' => 'Eind', + 'Task age in days' => 'Leeftijd taak in dagen', + 'Days in this column' => 'Dagen in deze kolom', + '%dd' => '%dj', + 'Add a link' => 'Link toevoegen', + 'Add a new link' => 'Nieuwe link toevoegen', + 'Do you really want to remove this link: "%s"?' => 'Weet u zeker dat u deze link wil verwijderen : « %s » ?', + 'Do you really want to remove this link with task #%d?' => 'Weet u zeker dat u deze link met taak %d wil verwijderen?', + 'Field required' => 'Veld verplicht', + 'Link added successfully.' => 'Link succesvol toegevoegd.', + 'Link updated successfully.' => 'Link succesvol aangepast.', + 'Link removed successfully.' => 'Link succesvol verwijderd.', + 'Link labels' => 'Link labels', + 'Link modification' => 'Link aanpassing', + 'Links' => 'Links', + 'Link settings' => 'Link instellingen', + 'Opposite label' => 'Tegenovergesteld label', + 'Remove a link' => 'Link verwijderen', + 'Task\'s links' => 'Links van taak', + 'The labels must be different' => 'De labels moeten verschillend zijn', + 'There is no link.' => 'Er is geen link.', + 'This label must be unique' => 'Dit label moet uniek zijn', + 'Unable to create your link.' => 'Link aanmaken niet gelukt.', + 'Unable to update your link.' => 'Link aanpassen niet gelukt.', + 'Unable to remove this link.' => 'Link verwijderen niet gelukt.', + 'relates to' => 'is gerelateerd aan', + 'blocks' => 'blokkeert', + 'is blocked by' => 'is geblokkeerd door', + 'duplicates' => 'dupliceert', + 'is duplicated by' => 'is gedupliceerd', + 'is a child of' => 'is een kind van', + 'is a parent of' => 'is een ouder van', + 'targets milestone' => 'is nodig voor milestone', + 'is a milestone of' => 'is een milestone voor', + 'fixes' => 'corrigeert', + 'is fixed by' => 'word gecorrigeerd door', + 'This task' => 'Deze taal', + '<1h' => '<1h', + '%dh' => '%dh', + '%b %e' => '%e %b', + 'Expand tasks' => 'Taken uitklappen', + 'Collapse tasks' => 'Taken inklappen', + 'Expand/collapse tasks' => 'Taken in/uiklappen', + 'Close dialog box' => 'Venster sluiten', + 'Submit a form' => 'Formulier insturen', + 'Board view' => 'Bord weergave', + 'Keyboard shortcuts' => 'Keyboard snelkoppelingen', + 'Open board switcher' => 'Open bord switcher', + 'Application' => 'Applicatie', + 'Filter recently updated' => 'Filter recent aangepast', + 'since %B %e, %Y at %k:%M %p' => 'sinds %d/%m/%Y à %H:%M', + 'More filters' => 'Meer filters', + // 'Compact view' => '', + // 'Horizontal scrolling' => '', + // 'Compact/wide view' => '', + // 'No results match:' => '', + // 'Remove hourly rate' => '', + // 'Do you really want to remove this hourly rate?' => '', + // 'Hourly rates' => '', + // 'Hourly rate' => '', + // 'Currency' => '', + // 'Effective date' => '', + // 'Add new rate' => '', + // 'Rate removed successfully.' => '', + // 'Unable to remove this rate.' => '', + // 'Unable to save the hourly rate.' => '', + // 'Hourly rate created successfully.' => '', + // 'Start time' => '', + // 'End time' => '', + // 'Comment' => '', + // 'All day' => '', + // 'Day' => '', + // 'Manage timetable' => '', + // 'Overtime timetable' => '', + // 'Time off timetable' => '', + // 'Timetable' => '', + // 'Work timetable' => '', + // 'Week timetable' => '', + // 'Day timetable' => '', + // 'From' => '', + // 'To' => '', + // 'Time slot created successfully.' => '', + // 'Unable to save this time slot.' => '', + // 'Time slot removed successfully.' => '', + // 'Unable to remove this time slot.' => '', + // 'Do you really want to remove this time slot?' => '', + // 'Remove time slot' => '', + // 'Add new time slot' => '', + // 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => '', + // 'Files' => '', + // 'Images' => '', + // 'Private project' => '', + // 'Amount' => '', + // 'AUD - Australian Dollar' => '', + // 'Budget' => '', + // 'Budget line' => '', + // 'Budget line removed successfully.' => '', + // 'Budget lines' => '', + // 'CAD - Canadian Dollar' => '', + // 'CHF - Swiss Francs' => '', + // 'Cost' => '', + // 'Cost breakdown' => '', + // 'Custom Stylesheet' => '', + // 'download' => '', + // 'Do you really want to remove this budget line?' => '', + // 'EUR - Euro' => '', + // 'Expenses' => '', + // 'GBP - British Pound' => '', + // 'INR - Indian Rupee' => '', + // 'JPY - Japanese Yen' => '', + // 'New budget line' => '', + // 'NZD - New Zealand Dollar' => '', + // 'Remove a budget line' => '', + // 'Remove budget line' => '', + // 'RSD - Serbian dinar' => '', + // 'The budget line have been created successfully.' => '', + // 'Unable to create the budget line.' => '', + // 'Unable to remove this budget line.' => '', + // 'USD - US Dollar' => '', + // 'Remaining' => '', +); diff --git a/app/Locale/pl_PL/translations.php b/app/Locale/pl_PL/translations.php index c6becf4c..4bee7c42 100644 --- a/app/Locale/pl_PL/translations.php +++ b/app/Locale/pl_PL/translations.php @@ -1,6 +1,8 @@ <?php return array( + // 'number.decimals_separator' => '', + // 'number.thousands_separator' => '', 'None' => 'Brak', 'edit' => 'edytuj', 'Edit' => 'Edytuj', @@ -734,4 +736,72 @@ return array( // 'Filter recently updated' => '', // 'since %B %e, %Y at %k:%M %p' => '', // 'More filters' => '', + // 'Compact view' => '', + // 'Horizontal scrolling' => '', + // 'Compact/wide view' => '', + // 'No results match:' => '', + // 'Remove hourly rate' => '', + // 'Do you really want to remove this hourly rate?' => '', + // 'Hourly rates' => '', + // 'Hourly rate' => '', + // 'Currency' => '', + // 'Effective date' => '', + // 'Add new rate' => '', + // 'Rate removed successfully.' => '', + // 'Unable to remove this rate.' => '', + // 'Unable to save the hourly rate.' => '', + // 'Hourly rate created successfully.' => '', + // 'Start time' => '', + // 'End time' => '', + // 'Comment' => '', + // 'All day' => '', + // 'Day' => '', + // 'Manage timetable' => '', + // 'Overtime timetable' => '', + // 'Time off timetable' => '', + // 'Timetable' => '', + // 'Work timetable' => '', + // 'Week timetable' => '', + // 'Day timetable' => '', + // 'From' => '', + // 'To' => '', + // 'Time slot created successfully.' => '', + // 'Unable to save this time slot.' => '', + // 'Time slot removed successfully.' => '', + // 'Unable to remove this time slot.' => '', + // 'Do you really want to remove this time slot?' => '', + // 'Remove time slot' => '', + // 'Add new time slot' => '', + // 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => '', + // 'Files' => '', + // 'Images' => '', + // 'Private project' => '', + // 'Amount' => '', + // 'AUD - Australian Dollar' => '', + // 'Budget' => '', + // 'Budget line' => '', + // 'Budget line removed successfully.' => '', + // 'Budget lines' => '', + // 'CAD - Canadian Dollar' => '', + // 'CHF - Swiss Francs' => '', + // 'Cost' => '', + // 'Cost breakdown' => '', + // 'Custom Stylesheet' => '', + // 'download' => '', + // 'Do you really want to remove this budget line?' => '', + // 'EUR - Euro' => '', + // 'Expenses' => '', + // 'GBP - British Pound' => '', + // 'INR - Indian Rupee' => '', + // 'JPY - Japanese Yen' => '', + // 'New budget line' => '', + // 'NZD - New Zealand Dollar' => '', + // 'Remove a budget line' => '', + // 'Remove budget line' => '', + // 'RSD - Serbian dinar' => '', + // 'The budget line have been created successfully.' => '', + // 'Unable to create the budget line.' => '', + // 'Unable to remove this budget line.' => '', + // 'USD - US Dollar' => '', + // 'Remaining' => '', ); diff --git a/app/Locale/pt_BR/translations.php b/app/Locale/pt_BR/translations.php index 89f7e294..edba2cae 100644 --- a/app/Locale/pt_BR/translations.php +++ b/app/Locale/pt_BR/translations.php @@ -1,6 +1,8 @@ <?php return array( + // 'number.decimals_separator' => '', + // 'number.thousands_separator' => '', 'None' => 'Nenhum', 'edit' => 'editar', 'Edit' => 'Editar', @@ -734,4 +736,72 @@ return array( 'Filter recently updated' => 'Filtro recentemente atualizado', // 'since %B %e, %Y at %k:%M %p' => '', 'More filters' => 'Mais filtros', + // 'Compact view' => '', + // 'Horizontal scrolling' => '', + // 'Compact/wide view' => '', + // 'No results match:' => '', + // 'Remove hourly rate' => '', + // 'Do you really want to remove this hourly rate?' => '', + // 'Hourly rates' => '', + // 'Hourly rate' => '', + // 'Currency' => '', + // 'Effective date' => '', + // 'Add new rate' => '', + // 'Rate removed successfully.' => '', + // 'Unable to remove this rate.' => '', + // 'Unable to save the hourly rate.' => '', + // 'Hourly rate created successfully.' => '', + // 'Start time' => '', + // 'End time' => '', + // 'Comment' => '', + // 'All day' => '', + // 'Day' => '', + // 'Manage timetable' => '', + // 'Overtime timetable' => '', + // 'Time off timetable' => '', + // 'Timetable' => '', + // 'Work timetable' => '', + // 'Week timetable' => '', + // 'Day timetable' => '', + // 'From' => '', + // 'To' => '', + // 'Time slot created successfully.' => '', + // 'Unable to save this time slot.' => '', + // 'Time slot removed successfully.' => '', + // 'Unable to remove this time slot.' => '', + // 'Do you really want to remove this time slot?' => '', + // 'Remove time slot' => '', + // 'Add new time slot' => '', + // 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => '', + // 'Files' => '', + // 'Images' => '', + // 'Private project' => '', + // 'Amount' => '', + // 'AUD - Australian Dollar' => '', + // 'Budget' => '', + // 'Budget line' => '', + // 'Budget line removed successfully.' => '', + // 'Budget lines' => '', + // 'CAD - Canadian Dollar' => '', + // 'CHF - Swiss Francs' => '', + // 'Cost' => '', + // 'Cost breakdown' => '', + // 'Custom Stylesheet' => '', + // 'download' => '', + // 'Do you really want to remove this budget line?' => '', + // 'EUR - Euro' => '', + // 'Expenses' => '', + // 'GBP - British Pound' => '', + // 'INR - Indian Rupee' => '', + // 'JPY - Japanese Yen' => '', + // 'New budget line' => '', + // 'NZD - New Zealand Dollar' => '', + // 'Remove a budget line' => '', + // 'Remove budget line' => '', + // 'RSD - Serbian dinar' => '', + // 'The budget line have been created successfully.' => '', + // 'Unable to create the budget line.' => '', + // 'Unable to remove this budget line.' => '', + // 'USD - US Dollar' => '', + // 'Remaining' => '', ); diff --git a/app/Locale/ru_RU/translations.php b/app/Locale/ru_RU/translations.php index 02a66b56..6a2ed073 100644 --- a/app/Locale/ru_RU/translations.php +++ b/app/Locale/ru_RU/translations.php @@ -1,6 +1,8 @@ <?php return array( + // 'number.decimals_separator' => '', + // 'number.thousands_separator' => '', 'None' => 'Отсутствует', 'edit' => 'изменить', 'Edit' => 'Изменить', @@ -734,4 +736,72 @@ return array( // 'Filter recently updated' => '', // 'since %B %e, %Y at %k:%M %p' => '', // 'More filters' => '', + // 'Compact view' => '', + // 'Horizontal scrolling' => '', + // 'Compact/wide view' => '', + // 'No results match:' => '', + // 'Remove hourly rate' => '', + // 'Do you really want to remove this hourly rate?' => '', + // 'Hourly rates' => '', + // 'Hourly rate' => '', + // 'Currency' => '', + // 'Effective date' => '', + // 'Add new rate' => '', + // 'Rate removed successfully.' => '', + // 'Unable to remove this rate.' => '', + // 'Unable to save the hourly rate.' => '', + // 'Hourly rate created successfully.' => '', + // 'Start time' => '', + // 'End time' => '', + // 'Comment' => '', + // 'All day' => '', + // 'Day' => '', + // 'Manage timetable' => '', + // 'Overtime timetable' => '', + // 'Time off timetable' => '', + // 'Timetable' => '', + // 'Work timetable' => '', + // 'Week timetable' => '', + // 'Day timetable' => '', + // 'From' => '', + // 'To' => '', + // 'Time slot created successfully.' => '', + // 'Unable to save this time slot.' => '', + // 'Time slot removed successfully.' => '', + // 'Unable to remove this time slot.' => '', + // 'Do you really want to remove this time slot?' => '', + // 'Remove time slot' => '', + // 'Add new time slot' => '', + // 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => '', + // 'Files' => '', + // 'Images' => '', + // 'Private project' => '', + // 'Amount' => '', + // 'AUD - Australian Dollar' => '', + // 'Budget' => '', + // 'Budget line' => '', + // 'Budget line removed successfully.' => '', + // 'Budget lines' => '', + // 'CAD - Canadian Dollar' => '', + // 'CHF - Swiss Francs' => '', + // 'Cost' => '', + // 'Cost breakdown' => '', + // 'Custom Stylesheet' => '', + // 'download' => '', + // 'Do you really want to remove this budget line?' => '', + // 'EUR - Euro' => '', + // 'Expenses' => '', + // 'GBP - British Pound' => '', + // 'INR - Indian Rupee' => '', + // 'JPY - Japanese Yen' => '', + // 'New budget line' => '', + // 'NZD - New Zealand Dollar' => '', + // 'Remove a budget line' => '', + // 'Remove budget line' => '', + // 'RSD - Serbian dinar' => '', + // 'The budget line have been created successfully.' => '', + // 'Unable to create the budget line.' => '', + // 'Unable to remove this budget line.' => '', + // 'USD - US Dollar' => '', + // 'Remaining' => '', ); diff --git a/app/Locale/sr_Latn_RS/translations.php b/app/Locale/sr_Latn_RS/translations.php new file mode 100644 index 00000000..83d6eaa3 --- /dev/null +++ b/app/Locale/sr_Latn_RS/translations.php @@ -0,0 +1,807 @@ +<?php + +return array( + // 'number.decimals_separator' => '', + // 'number.thousands_separator' => '', + 'None' => 'None', + 'edit' => 'izmeni', + 'Edit' => 'Izmeni', + 'remove' => 'ukloni', + 'Remove' => 'Ukloni', + 'Update' => 'Ažuriraj', + 'Yes' => 'Da', + 'No' => 'Ne', + 'cancel' => 'odustani', + 'or' => 'ili', + 'Yellow' => 'Žuta', + 'Blue' => 'Plava', + 'Green' => 'Zelena', + 'Purple' => 'Ljubičasta', + 'Red' => 'Crvena', + 'Orange' => 'Narandžasta', + 'Grey' => 'Siva', + 'Save' => 'Snimi', + 'Login' => 'Prijava', + 'Official website:' => 'Zvanična strana:', + 'Unassigned' => 'Nedodeljen', + 'View this task' => 'Pregledaj zadatak', + 'Remove user' => 'Ukloni korisnika', + 'Do you really want to remove this user: "%s"?' => 'Da li zaista želiš da ukloniš korisnika: "%s"?', + 'New user' => 'novi korisnik', + 'All users' => 'Svi korisnici', + 'Username' => 'Korisnik', + 'Password' => 'Lozinka', + 'Default project' => 'Podrazumevani projekat', + 'Administrator' => 'Administrator', + 'Sign in' => 'Odjava', + 'Users' => 'Korisnik', + 'No user' => 'Ne', + 'Forbidden' => 'Zabranjeno', + 'Access Forbidden' => 'Zabranjen prostup', + 'Only administrators can access to this page.' => 'Samo administrator može videti ovu stranu.', + 'Edit user' => 'Izmeni korisnika', + 'Logout' => 'Odjava', + 'Bad username or password' => 'Loše korisničko ime ili lozinka', + 'users' => 'korisnici', + 'projects' => 'projekti', + 'Edit project' => 'Izmeni projekat', + 'Name' => 'Ime', + 'Activated' => 'Aktiviran', + 'Projects' => 'Projekti', + 'No project' => 'Bez projekta', + 'Project' => 'Projekat', + 'Status' => 'Status', + 'Tasks' => 'Zadatak', + 'Board' => 'Tabla', + 'Actions' => 'Akcje', + 'Inactive' => 'Neaktivan', + 'Active' => 'Aktivan', + 'Column %d' => 'Kolona %d', + 'Add this column' => 'Dodaj kolonu', + '%d tasks on the board' => '%d zadataka na tabli', + '%d tasks in total' => '%d zadataka ukupno', + 'Unable to update this board.' => 'Nemogu da ažuriram ovu tablu.', + 'Edit board' => 'Izmeni tablu', + 'Disable' => 'Onemogući', + 'Enable' => 'Omogući', + 'New project' => 'Novi projekat', + 'Do you really want to remove this project: "%s"?' => 'Da li želiš da ukloniš projekat: "%s"?', + 'Remove project' => 'Ukloni projekat', + 'Boards' => 'Table', + 'Edit the board for "%s"' => 'Izmeni tablu za "%s"', + 'All projects' => 'Svi projekti', + 'Change columns' => 'Zameni kolonu', + 'Add a new column' => 'Dodaj novu kolonu', + 'Title' => 'Naslov', + 'Add Column' => 'Dodaj kolunu', + 'Project "%s"' => 'Projekt "%s"', + 'Nobody assigned' => 'Niko nije dodeljen', + 'Assigned to %s' => 'Dodeljen korisniku %s', + 'Remove a column' => 'Ukloni kolonu', + 'Remove a column from a board' => 'Ukloni kolonu sa table', + 'Unable to remove this column.' => 'Nemoguće uklanjanje kolone.', + 'Do you really want to remove this column: "%s"?' => 'Da li zaista želiš da ukoniš ovu kolonu: "%s"?', + 'This action will REMOVE ALL TASKS associated to this column!' => 'Ova akcija BRIŠE SVE ZADATKE vezane za ovu kolonu!', + 'Settings' => 'Podešavanja', + 'Application settings' => 'Podešavanja aplikacije', + 'Language' => 'Jezik', + 'Webhook token:' => 'Token :', + 'API token:' => 'Token za API', + 'More information' => 'Još informacja', + 'Database size:' => 'Veličina baze :', + 'Download the database' => 'Preuzmi bazu', + 'Optimize the database' => 'Optimizuj bazu', + '(VACUUM command)' => '(komanda VACUUM)', + '(Gzip compressed Sqlite file)' => '(Sqlite baza spakovana Gzip-om)', + 'User settings' => 'Korisnička podešavanja', + 'My default project:' => 'Moj podrazumevani projekat:', + 'Close a task' => 'Zatvori zadatak', + 'Do you really want to close this task: "%s"?' => 'Da li zaista želiš da zatvoriš ovaj zadatak: "%s"?', + 'Edit a task' => 'Izmeni zadatak', + 'Column' => 'Kolona', + 'Color' => 'Boja', + 'Assignee' => 'Dodeli', + 'Create another task' => 'Dodaj zadatak', + 'New task' => 'Novi zadatak', + 'Open a task' => 'Otvori zadatak', + 'Do you really want to open this task: "%s"?' => 'Da li zaista želiš da otvoriš zadatak: "%s"?', + 'Back to the board' => 'Nazad na tablu', + 'Created on %B %e, %Y at %k:%M %p' => 'Kreiran %e %B %Y o %k:%M', + 'There is nobody assigned' => 'Niko nije dodeljen!', + 'Column on the board:' => 'Kolona na tabli:', + 'Status is open' => 'Status otvoren', + 'Status is closed' => 'Status zatvoren', + 'Close this task' => 'Zatvori ovaj zadatak', + 'Open this task' => 'Otvori ovaj zadatak', + 'There is no description.' => 'Bez opisa.', + 'Add a new task' => 'Dodaj zadatak', + 'The username is required' => 'Korisničko ime je obavezno', + 'The maximum length is %d characters' => 'Maksimalna dužina je %d znakova', + 'The minimum length is %d characters' => 'Minimalna dužina je %d znakova', + 'The password is required' => 'Lozinka je obavezna', + 'This value must be an integer' => 'Mora biti ceo broj', + 'The username must be unique' => 'Korisničko ime mora biti jedinstveno', + 'The username must be alphanumeric' => 'Korisničko ime sme sadržati samo brojeve i slova', + 'The user id is required' => 'ID korisnika je obavezan', + 'Passwords don\'t match' => 'Lozinke se ne podudaraju', + 'The confirmation is required' => 'Potvrda je obavezna', + 'The column is required' => 'Kolona je obavezna', + 'The project is required' => 'Projekat je obavezan', + 'The color is required' => 'Boja je obavezna', + 'The id is required' => 'ID je obavezan', + 'The project id is required' => 'ID projekta je obavezan', + 'The project name is required' => 'Naziv projekta je obavezan', + 'This project must be unique' => 'Projekat mora biti jedinstven', + 'The title is required' => 'Naslov je obavezan', + 'The language is required' => 'Jezik je obavezan', + 'There is no active project, the first step is to create a new project.' => 'Nema aktivnih projekata. Potrebno je prvo napraviti novi projekat.', + 'Settings saved successfully.' => 'Podešavanja uspešno snimljena.', + 'Unable to save your settings.' => 'Nemoguće snimanje podešavanja.', + 'Database optimization done.' => 'Optimizacija baze je završena.', + 'Your project have been created successfully.' => 'Projekat je uspešno napravljen.', + 'Unable to create your project.' => 'Nemoguće kreiranje projekta.', + 'Project updated successfully.' => 'Projekt je uspešno ažuriran.', + 'Unable to update this project.' => 'Nemoguće ažuriranje projekta.', + 'Unable to remove this project.' => 'Nemoguće uklanjanje projekta.', + 'Project removed successfully.' => 'Projekat uspešno uklonjen.', + 'Project activated successfully.' => 'Projekt uspešno aktiviran.', + 'Unable to activate this project.' => 'Nemoguće aktiviranje projekta.', + 'Project disabled successfully.' => 'Projekat uspešno deaktiviran.', + 'Unable to disable this project.' => 'nemoguće deaktiviranje projekta.', + 'Unable to open this task.' => 'Nemoguće otvaranje zadatka.', + 'Task opened successfully.' => 'Zadatak uspešno otvoren.', + 'Unable to close this task.' => 'Nije moguće zatvaranje ovog zadatka.', + 'Task closed successfully.' => 'Zadatak uspešno zatvoren.', + 'Unable to update your task.' => 'Nije moguće ažuriranje zadatka.', + 'Task updated successfully.' => 'Zadatak uspešno ažuriran.', + 'Unable to create your task.' => 'Nije moguće kreiranje zadatka.', + 'Task created successfully.' => 'Zadatak uspešno kreiran.', + 'User created successfully.' => 'Korisnik uspešno kreiran', + 'Unable to create your user.' => 'Nije uspelo kreiranje korisnika.', + 'User updated successfully.' => 'Korisnik uspešno ažuriran.', + 'Unable to update your user.' => 'Nije moguće ažuriranje korisnika.', + 'User removed successfully.' => 'Korisnik uspešno uklonjen.', + 'Unable to remove this user.' => 'Nije moguće uklanjanje korisnika.', + 'Board updated successfully.' => 'Tabla uspešno ažurirana.', + 'Ready' => 'Spreman', + 'Backlog' => 'Log', + 'Work in progress' => 'U radu', + 'Done' => 'Gotovo', + 'Application version:' => 'Verzija aplikacije:', + 'Completed on %B %e, %Y at %k:%M %p' => 'Završeno u %e %B %Y o %k:%M', + '%B %e, %Y at %k:%M %p' => '%e %B %Y o %k:%M', + 'Date created' => 'Kreiran dana', + 'Date completed' => 'Završen dana', + 'Id' => 'Id', + 'No task' => 'bez zadataka', + 'Completed tasks' => 'Zatvoreni zadaci', + 'List of projects' => 'Spisak projekata', + 'Completed tasks for "%s"' => 'zatvoreni zadaci za "%s"', + '%d closed tasks' => '%d zatvorenih zadataka', + 'No task for this project' => 'Nema dodeljenih zadataka ovom projektu', + 'Public link' => 'Javni link', + 'There is no column in your project!' => 'Nema dodeljenih kolona ovom projektu', + 'Change assignee' => 'Izmeni dodelu', + 'Change assignee for the task "%s"' => 'Izmeni dodelu za ovaj zadatak "%s"', + 'Timezone' => 'Vremenska zona', + 'Sorry, I didn\'t find this information in my database!' => 'Na žalost, nije pronađena informacija u bazi', + 'Page not found' => 'Strana nije pronađena', + 'Complexity' => 'Složenost', + 'limit' => 'ograničenje', + 'Task limit' => 'Ograničenje zadatka', + 'Task count' => 'Broj zadataka', + 'This value must be greater than %d' => 'Vrednost mora biti veća od %d', + 'Edit project access list' => 'Izmeni prava pristupa projektu', + 'Edit users access' => 'Izmeni korisnička prava', + 'Allow this user' => 'Dozvoli ovog korisnika', + 'Only those users have access to this project:' => 'Samo ovi korisnici imaju pristup projektu:', + 'Don\'t forget that administrators have access to everything.' => 'Zapamti: Administrator može pristupiti svemu!', + 'Revoke' => 'Povuci', + 'List of authorized users' => 'Spisak odobrenih korisnika', + 'User' => 'Korisnik', + 'Nobody have access to this project.' => 'Niko nema pristup ovom projektu', + 'You are not allowed to access to this project.' => 'Nije ti dozvoljen pristup ovom projektu.', + 'Comments' => 'Komentari', + 'Post comment' => 'Dodaj komentar', + 'Write your text in Markdown' => 'Pisanje teksta pomoću Markdown', + 'Leave a comment' => 'Ostavi komentar', + 'Comment is required' => 'Komentar je obavezan', + 'Leave a description' => 'Dodaj opis', + 'Comment added successfully.' => 'Komentar uspešno ostavljen', + 'Unable to create your comment.' => 'Nemoguće kreiranje komentara', + 'The description is required' => 'Opis je obavezan', + 'Edit this task' => 'Izmeni ovaj zadatak', + 'Due Date' => 'Termin', + 'Invalid date' => 'Loš datum', + 'Must be done before %B %e, %Y' => 'Termin do %e %B %Y', + '%B %e, %Y' => '%e %B %Y', + // '%b %e, %Y' => '', + 'Automatic actions' => 'Automatske akcije', + 'Your automatic action have been created successfully.' => 'Uspešno kreirana automatska akcija', + 'Unable to create your automatic action.' => 'Nemoguće kreiranje automatske akcije', + 'Remove an action' => 'Obriši akciju', + 'Unable to remove this action.' => 'Nije moguće obrisati akciju', + 'Action removed successfully.' => 'Akcija obrisana', + 'Automatic actions for the project "%s"' => 'Akcje za automatizaciju projekta "%s"', + 'Defined actions' => 'Definisane akcje', + 'Add an action' => 'dodaj akcju', + 'Event name' => 'Naziv događaja', + 'Action name' => 'Naziv akcije', + 'Action parameters' => 'Parametri akcije', + 'Action' => 'Akcija', + 'Event' => 'Događaj', + 'When the selected event occurs execute the corresponding action.' => 'Kad se događaj desi izvrši odgovarajuću akciju', + 'Next step' => 'Sledeći korak', + 'Define action parameters' => 'Definiši parametre akcije', + 'Save this action' => 'Snimi akciju', + 'Do you really want to remove this action: "%s"?' => 'Da li da obrišem akciju "%s"?', + 'Remove an automatic action' => 'Obriši automatsku akciju', + 'Close the task' => 'Zatvori zadatak', + 'Assign the task to a specific user' => 'Dodeli zadatak određenom korisniku', + 'Assign the task to the person who does the action' => 'Dodeli zadatak korisniku koji je izvršio akciju', + 'Duplicate the task to another project' => 'Kopiraj akciju u drugi projekat', + 'Move a task to another column' => 'Premesti zadatak u drugu kolonu', + 'Move a task to another position in the same column' => 'Promeni poziciju zadatka u istoj koloni', + 'Task modification' => 'Izman zadatka', + 'Task creation' => 'Kreiranje zadatka', + 'Open a closed task' => 'Otvori zatvoreni zadatak', + 'Closing a task' => 'Zatvaranja zadatka', + 'Assign a color to a specific user' => 'Dodeli boju korisniku', + 'Column title' => 'Naslov kolone', + 'Position' => 'Pozicija', + 'Move Up' => 'Podigni', + 'Move Down' => 'Spusti', + 'Duplicate to another project' => 'Kopiraj u drugi projekat', + 'Duplicate' => 'Napravi kopiju', + 'link' => 'link', + 'Update this comment' => 'Ažuriraj komentar', + 'Comment updated successfully.' => 'Komentar uspešno ažuriran.', + 'Unable to update your comment.' => 'Neuspešno ažuriranje komentara.', + 'Remove a comment' => 'Obriši komentar', + 'Comment removed successfully.' => 'Komentar je uspešno obrisan.', + 'Unable to remove this comment.' => 'Neuspešno brisanje komentara.', + 'Do you really want to remove this comment?' => 'Da li da obrišem ovaj komentar?', + 'Only administrators or the creator of the comment can access to this page.' => 'Samo administrator i kreator komentara mogu ga obrisati.', + 'Details' => 'Detalji', + 'Current password for the user "%s"' => 'Trenutna lozinka za korisnika "%s"', + 'The current password is required' => 'Trenutna lozinka je obavezna', + 'Wrong password' => 'Pogrešna lozinka', + 'Reset all tokens' => 'Resetuj tokene', + 'All tokens have been regenerated.' => 'Svi tokeni su ponovo generisani.', + 'Unknown' => 'Nepoznat', + 'Last logins' => 'Poslednja prijava', + 'Login date' => 'Datum prijave', + 'Authentication method' => 'Metod autentikacije', + 'IP address' => 'IP adresa', + 'User agent' => 'Browser', + 'Persistent connections' => 'Stalna konekcija', + 'No session.' => 'Bez sesjie', + 'Expiration date' => 'Ističe', + 'Remember Me' => 'Zapamti me', + 'Creation date' => 'Datum kreiranja', + 'Filter by user' => 'Po korisniku', + 'Filter by due date' => 'Po terminu', + 'Everybody' => 'Svi', + 'Open' => 'Otvoreni', + 'Closed' => 'Zatvoreni', + 'Search' => 'Traži', + 'Nothing found.' => 'Ništa nije pronađeno', + 'Search in the project "%s"' => 'Traži u prijektu "%s"', + 'Due date' => 'Termin', + 'Others formats accepted: %s and %s' => 'Ostali formati: %s i %s', + 'Description' => 'Opis', + '%d comments' => '%d Komentara', + '%d comment' => '%d Komentar', + 'Email address invalid' => 'Pogrešan e-mail', + 'Your Google Account is not linked anymore to your profile.' => 'Tvoj google nalog više nije povezan sa profilom', + 'Unable to unlink your Google Account.' => 'Neuspešno ukidanje veze od Google naloga', + 'Google authentication failed' => 'Neuspešna Google autentikacija', + 'Unable to link your Google Account.' => 'Neuspešno povezivanje sa Google nalogom', + 'Your Google Account is linked to your profile successfully.' => 'Vaš Google nalog je uspešno povezan sa vašim profilom', + 'Email' => 'E-mail', + 'Link my Google Account' => 'Poveži sa Google nalogom', + 'Unlink my Google Account' => 'Ukini vezu sa Google nalogom', + 'Login with my Google Account' => 'Prijavi se preko Google naloga', + 'Project not found.' => 'Projekat nije pronađen.', + 'Task #%d' => 'Zadatak #%d', + 'Task removed successfully.' => 'Zadatak uspešno uklonjen.', + 'Unable to remove this task.' => 'Nemoguće uklanjanje zadatka.', + 'Remove a task' => 'Ukloni zadatak', + 'Do you really want to remove this task: "%s"?' => 'Da li da obrišem zadatak "%s"?', + 'Assign automatically a color based on a category' => 'Automatski dodeli boju po kategoriji', + 'Assign automatically a category based on a color' => 'Automatski dodeli kategoriju po boji', + 'Task creation or modification' => 'Kreiranje ili izmena zadatka', + 'Category' => 'Kategorija', + 'Category:' => 'Kategorija:', + 'Categories' => 'Kategorije', + 'Category not found.' => 'Kategorija nije pronađena', + 'Your category have been created successfully.' => 'Uspešno kreirana kategorija.', + 'Unable to create your category.' => 'Nije moguće kreirati kategoriju.', + 'Your category have been updated successfully.' => 'Kategorija je uspešno izmenjena', + 'Unable to update your category.' => 'Nemoguće izmeniti kategoriju', + 'Remove a category' => 'Obriši kategoriju', + 'Category removed successfully.' => 'Kategorija uspešno uklonjena.', + 'Unable to remove this category.' => 'Nije moguće ukloniti kategoriju.', + 'Category modification for the project "%s"' => 'Izmena kategorije za projekat "%s"', + 'Category Name' => 'Naziv kategorije', + 'Categories for the project "%s"' => 'Kategorije u projektu', + 'Add a new category' => 'Dodaj novu kategoriju', + 'Do you really want to remove this category: "%s"?' => 'Da li zaista želiš da ukloniš kategoriju: "%s"?', + 'Filter by category' => 'Po kategoriji', + 'All categories' => 'Sve kategorije', + 'No category' => 'Bez kategorije', + 'The name is required' => 'Naziv je obavezan', + 'Remove a file' => 'Ukloni fajl', + 'Unable to remove this file.' => 'Fajl nije moguće ukloniti.', + 'File removed successfully.' => 'Uspešno uklonjen fajl.', + 'Attach a document' => 'Prikači dokument', + 'Do you really want to remove this file: "%s"?' => 'Da li da uklonim fajl: "%s"?', + 'open' => 'otvori', + 'Attachments' => 'Prilozi', + 'Edit the task' => 'Izmena Zadatka', + 'Edit the description' => 'Izmena opisa', + 'Add a comment' => 'Dodaj komentar', + 'Edit a comment' => 'Izmeni komentar', + 'Summary' => 'Pregled', + 'Time tracking' => 'Praćenje vremena', + 'Estimate:' => 'Procena:', + 'Spent:' => 'Potrošeno:', + 'Do you really want to remove this sub-task?' => 'Da li da uklonim pod-zdadatak?', + 'Remaining:' => 'Preostalo:', + 'hours' => 'sati', + 'spent' => 'potrošeno', + 'estimated' => 'procenjeno', + 'Sub-Tasks' => 'Pod-zadaci', + 'Add a sub-task' => 'Dodaj pod-zadatak', + 'Original estimate' => 'Originalna procena', + 'Create another sub-task' => 'Dodaj novi pod-zadatak', + 'Time spent' => 'Utrošeno vreme', + 'Edit a sub-task' => 'Izmeni pod-zadatak', + 'Remove a sub-task' => 'Ukloni pod-zadatak', + 'The time must be a numeric value' => 'Vreme mora biti broj', + 'Todo' => 'Za rad', + 'In progress' => 'U radu', + 'Sub-task removed successfully.' => 'Pod-zadatak uspešno uklonjen.', + 'Unable to remove this sub-task.' => 'Nie można usunąć tego pod-zadania.', + 'Sub-task updated successfully.' => 'Pod-zadatak zaktualizowane pomyślnie.', + 'Unable to update your sub-task.' => 'Nie można zaktalizować tego pod-zadania.', + 'Unable to create your sub-task.' => 'Nie można utworzyć tego pod-zadania.', + 'Sub-task added successfully.' => 'Pod-zadatak utworzone pomyślnie', + 'Maximum size: ' => 'Maksimalna veličina: ', + 'Unable to upload the file.' => 'Nije moguće snimiti fajl.', + 'Display another project' => 'Prikaži drugi projekat', + 'Your GitHub account was successfully linked to your profile.' => 'Konto Github podłączone pomyślnie.', + 'Unable to link your GitHub Account.' => 'Nie można połączyć z kontem Github.', + 'GitHub authentication failed' => 'Autentykacja Github nieudana', + 'Your GitHub account is no longer linked to your profile.' => 'Konto Github nie jest już podłączone do twojego profilu.', + 'Unable to unlink your GitHub Account.' => 'Nie można odłączyć konta Github.', + 'Login with my GitHub Account' => 'Zaloguj przy użyciu konta Github', + 'Link my GitHub Account' => 'Podłącz konto Github', + 'Unlink my GitHub Account' => 'Odłącz konto Github', + 'Created by %s' => 'Kreirao %s', + 'Last modified on %B %e, %Y at %k:%M %p' => 'Poslednja izmena %e %B %Y o %k:%M', + 'Tasks Export' => 'Izvoz zadataka', + 'Tasks exportation for "%s"' => 'Izvoz zadataka za "%s"', + 'Start Date' => 'Početni datum', + 'End Date' => 'Krajni datum', + 'Execute' => 'Izvrši', + 'Task Id' => 'Identifikator Zadatka', + 'Creator' => 'Autor', + 'Modification date' => 'Datum izmene', + 'Completion date' => 'Datum kompletiranja', + 'Webhook URL for task creation' => 'Webhook URL zadatka za kreiranje', + 'Webhook URL for task modification' => 'Webhook URL zadatka za izmenu', + 'Clone' => 'Iskopiraj', + 'Clone Project' => 'Iskopiraj projekat', + 'Project cloned successfully.' => 'Projekat uspešno iskopiran.', + 'Unable to clone this project.' => 'Nije moguće iskopirati projekat.', + 'Email notifications' => 'Obaveštenje e-mailom', + 'Enable email notifications' => 'Omogući obaveštenja e-mailom', + 'Task position:' => 'Pozicija zadatka:', + 'The task #%d have been opened.' => 'Zadatak #%d je otvoren.', + 'The task #%d have been closed.' => 'Zadatak #$d je zatvoren.', + 'Sub-task updated' => 'Pod-zadatak izmenjen', + 'Title:' => 'Naslov:', + // 'Status:' => '', + 'Assignee:' => 'Dodeli:', + 'Time tracking:' => 'Praćenje vremena: ', + 'New sub-task' => 'Novi Pod-zadatak', + 'New attachment added "%s"' => 'Novi prilog ubačen "%s"', + 'Comment updated' => 'Komentar izmenjen', + 'New comment posted by %s' => 'Novi komentar ostavio %s', + 'List of due tasks for the project "%s"' => 'Spisak dospelih zadataka za projekat "%s"', + // 'New attachment' => '', + // 'New comment' => '', + // 'New subtask' => '', + // 'Subtask updated' => '', + // 'Task updated' => '', + 'Task closed' => 'Zadatak je zatvoren', + 'Task opened' => 'Zadatak je otvoren', + '[%s][Due tasks]' => '[%s][Dospeli zadaci]', + '[Kanboard] Notification' => '[Kanboard] Obaveštenja', + 'I want to receive notifications only for those projects:' => 'Želim obaveštenja samo za ovaj projekat:', + 'view the task on Kanboard' => 'Pregledaj zadatke', + 'Public access' => 'Javni pristup', + 'Category management' => 'Uređivanje kategorija', + 'User management' => 'Uređivanje korisnika', + 'Active tasks' => 'Aktivni zadaci', + 'Disable public access' => 'Zabrani javni pristup', + 'Enable public access' => 'Dozvoli javni pristup', + 'Active projects' => 'Aktivni projekti', + 'Inactive projects' => 'Neaktivni projekti', + 'Public access disabled' => 'Javni pristup onemogućen!', + 'Do you really want to disable this project: "%s"?' => 'Da li zaista želiš da deaktiviraš projekat: "%s"?', + 'Do you really want to duplicate this project: "%s"?' => 'Da li da napravim kopiju ovog projekta: "%s"?', + 'Do you really want to enable this project: "%s"?' => 'Da li zaista želiš da aktiviraš projekat: "%s"?', + 'Project activation' => 'Aktivacija projekta', + 'Move the task to another project' => 'Premesti zadatak u drugi projekat', + 'Move to another project' => 'Premesti u drugi projekat', + 'Do you really want to duplicate this task?' => 'Da li da napravim kopiju ovog projekta: "%s"?', + 'Duplicate a task' => 'Kopiraj zadatak', + 'External accounts' => 'Spoljni nalozi', + 'Account type' => 'Tip naloga', + 'Local' => 'Lokalno', + 'Remote' => 'Udaljno', + 'Enabled' => 'Omogući', + 'Disabled' => 'Onemogući', + 'Google account linked' => 'Połączone konto Google', + 'Github account linked' => 'Połączone konto Github', + 'Username:' => 'Korisničko ime:', + 'Name:' => 'Ime i Prezime', + 'Email:' => 'Email: ', + 'Default project:' => 'Osnovni projekat:', + 'Notifications:' => 'Obaveštenja: ', + 'Notifications' => 'Obaveštenja', + 'Group:' => 'Grupa:', + 'Regular user' => 'Standardni korisnik', + 'Account type:' => 'Vrsta naloga:', + 'Edit profile' => 'Izmeni profil', + 'Change password' => 'Izmeni lozinku', + 'Password modification' => 'Izmena lozinke', + 'External authentications' => 'Spoljne akcije', + 'Google Account' => 'Google nalog', + 'Github Account' => 'Github nalog', + 'Never connected.' => 'Bez konekcija.', + 'No account linked.' => 'Bez povezanih naloga.', + 'Account linked.' => 'Nalog povezan.', + 'No external authentication enabled.' => 'Bez omogućenih spoljnih autentikacija.', + 'Password modified successfully.' => 'Uspešna izmena lozinke.', + 'Unable to change the password.' => 'Nije moguće izmeniti lozinku.', + 'Change category for the task "%s"' => 'Izmeni kategoriju zadatka "%s"', + 'Change category' => 'Izmeni kategoriju', + '%s updated the task %s' => '%s izmeni zadatak %s', + '%s opened the task %s' => '%s aktivni zadaci %s', + '%s moved the task %s to the position #%d in the column "%s"' => '%s premešten zadatak %s na poziciju #%d u koloni "%s"', + '%s moved the task %s to the column "%s"' => '%s premešten zadatak %s u kolonu "%s"', + '%s created the task %s' => '%s kreirao zadatak %s', + '%s closed the task %s' => '%s zatvorio zadatak %s', + '%s created a subtask for the task %s' => '%s kreiran pod-zadatak zadatka %s', + '%s updated a subtask for the task %s' => '%s izmenjen pod-zadatak zadatka %s', + 'Assigned to %s with an estimate of %s/%sh' => 'Dodeljen korisniku %s uz procenu vremena %s/%sh', + 'Not assigned, estimate of %sh' => 'Ne dodeljen, procenjeno vreme %sh', + '%s updated a comment on the task %s' => '%s izmenjen komentar zadatka %s', + '%s commented the task %s' => '%s komentarisao zadatak %s', + '%s\'s activity' => 'Aktivnosti %s', + 'No activity.' => 'Bez aktivnosti.', + 'RSS feed' => 'RSS kanal', + '%s updated a comment on the task #%d' => '%s izmenjen komentar zadatka #%d', + '%s commented on the task #%d' => '%s komentarisao zadatak #%d', + '%s updated a subtask for the task #%d' => '%s izmenjen pod-zadatak zadatka #%d', + '%s created a subtask for the task #%d' => '%s kreirao pod-zadatak zadatka #%d', + '%s updated the task #%d' => '%s izmenjen zadatak #%d', + '%s created the task #%d' => '%s kreirao zadatak #%d', + '%s closed the task #%d' => '%s zatvorio zadatak #%d', + '%s open the task #%d' => '%s otvorio zadatak #%d', + '%s moved the task #%d to the column "%s"' => '%s premestio zadatak #%d u kolonu "%s"', + '%s moved the task #%d to the position %d in the column "%s"' => '%s premestio zadatak #%d na pozycję %d w kolmnie "%s"', + 'Activity' => 'Aktivnosti', + 'Default values are "%s"' => 'Osnovne vrednosti su: "%s"', + 'Default columns for new projects (Comma-separated)' => 'Osnovne kolone za novi projekat (Odvojeni zarezom)', + 'Task assignee change' => 'Zmień osobę odpowiedzialną', + '%s change the assignee of the task #%d to %s' => '%s zamena dodele za zadatak #%d na %s', + '%s changed the assignee of the task %s to %s' => '%s zamena dodele za zadatak %s na %s', + // 'Column Change' => '', + // 'Position Change' => '', + // 'Assignee Change' => '', + 'New password for the user "%s"' => 'Nova lozinka za korisnika "%s"', + 'Choose an event' => 'Izaberi događaj', + // 'Github commit received' => '', + // 'Github issue opened' => '', + // 'Github issue closed' => '', + // 'Github issue reopened' => '', + // 'Github issue assignee change' => '', + // 'Github issue label change' => '', + 'Create a task from an external provider' => 'Kreiraj zadatak preko posrednika', + 'Change the assignee based on an external username' => 'Zmień osobę odpowiedzialną na podstawie zewnętrznej nazwy użytkownika', + 'Change the category based on an external label' => 'Zmień kategorię na podstawie zewnętrzenj etykiety', + // 'Reference' => '', + // 'Reference: %s' => '', + 'Label' => 'Etikieta', + 'Database' => 'Baza', + 'About' => 'Informacje', + 'Database driver:' => 'Database driver:', + 'Board settings' => 'Podešavanje table', + 'URL and token' => 'URL i token', + // 'Webhook settings' => '', + 'URL for task creation:' => 'URL za kreiranje zadataka', + 'Reset token' => 'Resetuj token', + // 'API endpoint:' => '', + 'Refresh interval for private board' => 'Interval osvežavanja privatnih tabli', + 'Refresh interval for public board' => 'Interval osvežavanja javnih tabli', + 'Task highlight period' => 'Task highlight period', + // 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => '', + // 'Frequency in second (60 seconds by default)' => '', + // 'Frequency in second (0 to disable this feature, 10 seconds by default)' => '', + 'Application URL' => 'Adres URL aplikacji', + 'Example: http://example.kanboard.net/ (used by email notifications)' => 'Primer: http://example.kanboard.net/ (koristi se u obaveštenjima putem mail-a)', + 'Token regenerated.' => 'Token wygenerowany ponownie.', + 'Date format' => 'Format daty', + 'ISO format is always accepted, example: "%s" and "%s"' => 'Format ISO je uvek prihvatljiv, primer: "%s", "%s"', + 'New private project' => 'Novi privatni projekat', + 'This project is private' => 'Ovaj projekat je privatan', + 'Type here to create a new sub-task' => 'Kucaj ovde za kreiranje novog pod-zadatka', + 'Add' => 'Dodaj', + 'Estimated time: %s hours' => 'Procenjeno vreme: %s godzin', + 'Time spent: %s hours' => 'Utrošeno vreme: %s godzin', + 'Started on %B %e, %Y' => 'Započeto dana %e %B %Y', + 'Start date' => 'Datum početka', + 'Time estimated' => 'Procenjeno vreme', + 'There is nothing assigned to you.' => 'Ništa vam nije dodeljeno', + 'My tasks' => 'Moji zadaci', + 'Activity stream' => 'Spisak aktinosti', + 'Dashboard' => 'Panel', + 'Confirmation' => 'Potvrda', + 'Allow everybody to access to this project' => 'Dozvoli svima pristup projektu', + 'Everybody have access to this project.' => 'Svima je dozvoljen pristup.', + // 'Webhooks' => '', + // 'API' => '', + 'Integration' => 'Integracja', + // 'Github webhooks' => '', + // 'Help on Github webhooks' => '', + // 'Create a comment from an external provider' => '', + // 'Github issue comment created' => '', + 'Configure' => 'Podesi', + 'Project management' => 'Uređivanje projekata', + 'My projects' => 'Moji projekti', + 'Columns' => 'Kolone', + 'Task' => 'Zadaci', + 'Your are not member of any project.' => 'Nisi član ni jednog projekta', + 'Percentage' => 'Procenat', + 'Number of tasks' => 'Broj zadataka', + 'Task distribution' => 'Podela zadataka', + 'Reportings' => 'Izveštaji', + 'Task repartition for "%s"' => 'Zaduženja zadataka za "%s"', + 'Analytics' => 'Analiza', + 'Subtask' => 'Pod-zadatak', + 'My subtasks' => 'Moji pod-zadaci', + 'User repartition' => 'Zaduženja korisnika', + 'User repartition for "%s"' => 'Zaduženja korisnika za "%s"', + 'Clone this project' => 'Kopiraj projekat', + 'Column removed successfully.' => 'Kolumna usunięta pomyslnie.', + 'Edit Project' => 'Izmeni projekat', + // 'Github Issue' => '', + 'Not enough data to show the graph.' => 'Nedovoljno podataka za grafikon.', + 'Previous' => 'Prethodni', + 'The id must be an integer' => 'ID musi być liczbą całkowitą', + 'The project id must be an integer' => 'ID projektu musi być liczbą całkowitą', + 'The status must be an integer' => 'Status musi być liczbą całkowitą', + 'The subtask id is required' => 'ID pod-zadatak jest wymagane', + 'The subtask id must be an integer' => 'ID pod-zadania musi być liczbą całkowitą', + 'The task id is required' => 'ID zadania jest wymagane', + 'The task id must be an integer' => 'ID zadatka mora biti broj', + 'The user id must be an integer' => 'ID korisnika mora biti broj', + 'This value is required' => 'Vrednost je obavezna', + 'This value must be numeric' => 'Vrednost mora biti broj', + 'Unable to create this task.' => 'Nije moguće kreirati zadatak.', + 'Cumulative flow diagram' => 'Zbirni dijagram toka', + 'Cumulative flow diagram for "%s"' => 'Zbirni dijagram toka za "%s"', + 'Daily project summary' => 'Zbirni pregled po danima', + 'Daily project summary export' => 'Izvoz zbirnog pregleda po danima', + 'Daily project summary export for "%s"' => 'Izvoz zbirnig pregleda po danima za "%s"', + 'Exports' => 'Izvoz', + // 'This export contains the number of tasks per column grouped per day.' => '', + 'Nothing to preview...' => 'Ništa za prikazivanje...', + 'Preview' => 'Pregled', + 'Write' => 'Piši', + 'Active swimlanes' => 'Aktivni razdelnik', + 'Add a new swimlane' => 'Dodaj razdelnik', + 'Change default swimlane' => 'Zameni osnovni razdelnik', + 'Default swimlane' => 'Osnovni razdelnik', + 'Do you really want to remove this swimlane: "%s"?' => 'Da li da uklonim razdelnik: "%s"?', + 'Inactive swimlanes' => 'Neaktivni razdelniki', + 'Set project manager' => 'Podesi menadžera projekta', + 'Set project member' => 'Podesi učesnika projekat', + 'Remove a swimlane' => 'Ukloni razdelnik', + 'Rename' => 'Preimenuj', + 'Show default swimlane' => 'Prikaži osnovni razdelnik', + 'Swimlane modification for the project "%s"' => 'Izmena razdelnika za projekat "%s"', + 'Swimlane not found.' => 'Razdelnik nije pronađen.', + 'Swimlane removed successfully.' => 'Razdelnik uspešno uklonjen.', + 'Swimlanes' => 'Razdelnici', + 'Swimlane updated successfully.' => 'Razdelnik zaktualizowany pomyślnie.', + // 'The default swimlane have been updated successfully.' => '', + // 'Unable to create your swimlane.' => '', + // 'Unable to remove this swimlane.' => '', + // 'Unable to update this swimlane.' => '', + 'Your swimlane have been created successfully.' => 'Razdelnik je uspešno kreiran.', + 'Example: "Bug, Feature Request, Improvement"' => 'Npr: "Greška, Zahtev za izmenama, Poboljšanje"', + 'Default categories for new projects (Comma-separated)' => 'Osnovne kategorije za projekat', + // 'Gitlab commit received' => '', + // 'Gitlab issue opened' => '', + // 'Gitlab issue closed' => '', + // 'Gitlab webhooks' => '', + // 'Help on Gitlab webhooks' => '', + 'Integrations' => 'Integracje', + 'Integration with third-party services' => 'Integracja sa uslugama spoljnih servisa', + 'Role for this project' => 'Uloga u ovom projektu', + 'Project manager' => 'Manadžer projekta', + 'Project member' => 'Učesnik projekta', + // 'A project manager can change the settings of the project and have more privileges than a standard user.' => '', + // 'Gitlab Issue' => '', + 'Subtask Id' => 'ID pod-zadania', + 'Subtasks' => 'Pod-zadataka', + 'Subtasks Export' => 'Eksport pod-zadań', + 'Subtasks exportation for "%s"' => 'Izvoz pod-zadań dla "%s"', + 'Task Title' => 'Naslov zadatka', + 'Untitled' => 'Bez naslova', + 'Application default' => 'Postavke aplikacje', + 'Language:' => 'Jezik:', + 'Timezone:' => 'Vremenska zona:', + 'All columns' => 'Sve kolone', + 'Calendar for "%s"' => 'Kalendar za "%s"', + 'Filter by column' => 'Po koloni', + 'Filter by status' => 'Po statusu', + 'Calendar' => 'Kalendar', + 'Next' => 'Sledeći', + // '#%d' => '', + 'Filter by color' => 'Po boji', + 'Filter by swimlane' => 'Po razdelniku', + 'All swimlanes' => 'Svi razdelniki', + 'All colors' => 'Sve boje', + 'All status' => 'Svi statusi', + 'Add a comment logging moving the task between columns' => 'Dodaj logovanje premeštanja zadataka po kolonama', + 'Moved to column %s' => 'Premešten u kolonu %s', + // 'Change description' => '', + 'User dashboard' => 'Korisnički panel', + // 'Allow only one subtask in progress at the same time for a user' => '', + // 'Edit column "%s"' => '', + // 'Enable time tracking for subtasks' => '', + // 'Select the new status of the subtask: "%s"' => '', + // 'Subtask timesheet' => '', + 'There is nothing to show.' => 'Nema podataka', + 'Time Tracking' => 'Praćenje vremena', + // 'You already have one subtask in progress' => '', + 'Which parts of the project do you want to duplicate?' => 'Koje delove projekta želite da kopirate', + // 'Change dashboard view' => '', + // 'Show/hide activities' => '', + // 'Show/hide projects' => '', + // 'Show/hide subtasks' => '', + // 'Show/hide tasks' => '', + // 'Disable login form' => '', + // 'Show/hide calendar' => '', + // 'User calendar' => '', + // 'Bitbucket commit received' => '', + // 'Bitbucket webhooks' => '', + // 'Help on Bitbucket webhooks' => '', + // 'Start' => '', + // 'End' => '', + // 'Task age in days' => '', + // 'Days in this column' => '', + // '%dd' => '', + 'Add a link' => 'Dodaj link', + // 'Add a new link' => '', + // 'Do you really want to remove this link: "%s"?' => '', + // 'Do you really want to remove this link with task #%d?' => '', + // 'Field required' => '', + // 'Link added successfully.' => '', + // 'Link updated successfully.' => '', + // 'Link removed successfully.' => '', + // 'Link labels' => '', + // 'Link modification' => '', + // 'Links' => '', + // 'Link settings' => '', + // 'Opposite label' => '', + // 'Remove a link' => '', + // 'Task\'s links' => '', + // 'The labels must be different' => '', + // 'There is no link.' => '', + // 'This label must be unique' => '', + // 'Unable to create your link.' => '', + // 'Unable to update your link.' => '', + // 'Unable to remove this link.' => '', + // 'relates to' => '', + // 'blocks' => '', + // 'is blocked by' => '', + // 'duplicates' => '', + // 'is duplicated by' => '', + // 'is a child of' => '', + // 'is a parent of' => '', + // 'targets milestone' => '', + // 'is a milestone of' => '', + // 'fixes' => '', + // 'is fixed by' => '', + // 'This task' => '', + // '<1h' => '', + // '%dh' => '', + // '%b %e' => '', + // 'Expand tasks' => '', + // 'Collapse tasks' => '', + // 'Expand/collapse tasks' => '', + // 'Close dialog box' => '', + // 'Submit a form' => '', + // 'Board view' => '', + // 'Keyboard shortcuts' => '', + // 'Open board switcher' => '', + // 'Application' => '', + // 'Filter recently updated' => '', + // 'since %B %e, %Y at %k:%M %p' => '', + // 'More filters' => '', + // 'Compact view' => '', + // 'Horizontal scrolling' => '', + // 'Compact/wide view' => '', + // 'No results match:' => '', + // 'Remove hourly rate' => '', + // 'Do you really want to remove this hourly rate?' => '', + // 'Hourly rates' => '', + // 'Hourly rate' => '', + // 'Currency' => '', + // 'Effective date' => '', + // 'Add new rate' => '', + // 'Rate removed successfully.' => '', + // 'Unable to remove this rate.' => '', + // 'Unable to save the hourly rate.' => '', + // 'Hourly rate created successfully.' => '', + // 'Start time' => '', + // 'End time' => '', + // 'Comment' => '', + // 'All day' => '', + // 'Day' => '', + // 'Manage timetable' => '', + // 'Overtime timetable' => '', + // 'Time off timetable' => '', + // 'Timetable' => '', + // 'Work timetable' => '', + // 'Week timetable' => '', + // 'Day timetable' => '', + // 'From' => '', + // 'To' => '', + // 'Time slot created successfully.' => '', + // 'Unable to save this time slot.' => '', + // 'Time slot removed successfully.' => '', + // 'Unable to remove this time slot.' => '', + // 'Do you really want to remove this time slot?' => '', + // 'Remove time slot' => '', + // 'Add new time slot' => '', + // 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => '', + // 'Files' => '', + // 'Images' => '', + // 'Private project' => '', + // 'Amount' => '', + // 'AUD - Australian Dollar' => '', + // 'Budget' => '', + // 'Budget line' => '', + // 'Budget line removed successfully.' => '', + // 'Budget lines' => '', + // 'CAD - Canadian Dollar' => '', + // 'CHF - Swiss Francs' => '', + // 'Cost' => '', + // 'Cost breakdown' => '', + // 'Custom Stylesheet' => '', + // 'download' => '', + // 'Do you really want to remove this budget line?' => '', + // 'EUR - Euro' => '', + // 'Expenses' => '', + // 'GBP - British Pound' => '', + // 'INR - Indian Rupee' => '', + // 'JPY - Japanese Yen' => '', + // 'New budget line' => '', + // 'NZD - New Zealand Dollar' => '', + // 'Remove a budget line' => '', + // 'Remove budget line' => '', + // 'RSD - Serbian dinar' => '', + // 'The budget line have been created successfully.' => '', + // 'Unable to create the budget line.' => '', + // 'Unable to remove this budget line.' => '', + // 'USD - US Dollar' => '', + // 'Remaining' => '', +); diff --git a/app/Locale/sv_SE/translations.php b/app/Locale/sv_SE/translations.php index 2fcc3ce5..5b3b31c3 100644 --- a/app/Locale/sv_SE/translations.php +++ b/app/Locale/sv_SE/translations.php @@ -1,6 +1,8 @@ <?php return array( + // 'number.decimals_separator' => '', + // 'number.thousands_separator' => '', 'None' => 'Ingen', 'edit' => 'redigera', 'Edit' => 'Redigera', @@ -408,13 +410,13 @@ return array( 'Comment updated' => 'Kommentaren har uppdaterats', 'New comment posted by %s' => 'Ny kommentar postad av %s', 'List of due tasks for the project "%s"' => 'Lista med uppgifter för projektet "%s"', - // 'New attachment' => '', - // 'New comment' => '', - // 'New subtask' => '', - // 'Subtask updated' => '', - // 'Task updated' => '', - // 'Task closed' => '', - // 'Task opened' => '', + 'New attachment' => 'Ny bifogning', + 'New comment' => 'Ny kommentar', + 'New subtask' => 'Ny deluppgift', + 'Subtask updated' => 'Deluppgiften har uppdaterats', + 'Task updated' => 'Uppgiften har uppdaterats', + 'Task closed' => 'Uppgiften har stängts', + 'Task opened' => 'Uppgiften har öppnats', '[%s][Due tasks]' => '[%s][Förfallen uppgift]', '[Kanboard] Notification' => '[Kanboard] Notis', 'I want to receive notifications only for those projects:' => 'Jag vill endast få notiser för dessa projekt:', @@ -498,9 +500,9 @@ return array( 'Task assignee change' => 'Ändra tilldelning av uppgiften', '%s change the assignee of the task #%d to %s' => '%s byt tilldelning av uppgiften #%d till %s', '%s changed the assignee of the task %s to %s' => '%s byt tilldelning av uppgiften %s till %s', - // 'Column Change' => '', - // 'Position Change' => '', - // 'Assignee Change' => '', + 'Column Change' => 'Ändring av kolumn', + 'Position Change' => 'Ändring av position', + 'Assignee Change' => 'Ändring av tilldelning', 'New password for the user "%s"' => 'Nytt lösenord för användaren "%s"', 'Choose an event' => 'Välj en händelse', 'Github commit received' => 'Github-bidrag mottaget', @@ -645,93 +647,161 @@ return array( 'Application default' => 'Applikationsstandard', 'Language:' => 'Språk', 'Timezone:' => 'Tidszon', - // 'All columns' => '', - // 'Calendar for "%s"' => '', - // 'Filter by column' => '', - // 'Filter by status' => '', - // 'Calendar' => '', + 'All columns' => 'Alla kolumner', + 'Calendar for "%s"' => 'Kalender för "%s"', + 'Filter by column' => 'Filtrera på kolumn', + 'Filter by status' => 'Filtrera på status', + 'Calendar' => 'Kalender', 'Next' => 'Nästa', - // '#%d' => '', - // 'Filter by color' => '', - // 'Filter by swimlane' => '', - // 'All swimlanes' => '', - // 'All colors' => '', - // 'All status' => '', - // 'Add a comment logging moving the task between columns' => '', - // 'Moved to column %s' => '', - // 'Change description' => '', - // 'User dashboard' => '', - // 'Allow only one subtask in progress at the same time for a user' => '', - // 'Edit column "%s"' => '', - // 'Enable time tracking for subtasks' => '', - // 'Select the new status of the subtask: "%s"' => '', - // 'Subtask timesheet' => '', - // 'There is nothing to show.' => '', - // 'Time Tracking' => '', - // 'You already have one subtask in progress' => '', - // 'Which parts of the project do you want to duplicate?' => '', - // 'Change dashboard view' => '', - // 'Show/hide activities' => '', - // 'Show/hide projects' => '', - // 'Show/hide subtasks' => '', - // 'Show/hide tasks' => '', - // 'Disable login form' => '', - // 'Show/hide calendar' => '', - // 'User calendar' => '', - // 'Bitbucket commit received' => '', - // 'Bitbucket webhooks' => '', - // 'Help on Bitbucket webhooks' => '', - // 'Start' => '', - // 'End' => '', - // 'Task age in days' => '', - // 'Days in this column' => '', - // '%dd' => '', - // 'Add a link' => '', - // 'Add a new link' => '', - // 'Do you really want to remove this link: "%s"?' => '', - // 'Do you really want to remove this link with task #%d?' => '', - // 'Field required' => '', - // 'Link added successfully.' => '', - // 'Link updated successfully.' => '', - // 'Link removed successfully.' => '', - // 'Link labels' => '', - // 'Link modification' => '', - // 'Links' => '', - // 'Link settings' => '', - // 'Opposite label' => '', - // 'Remove a link' => '', - // 'Task\'s links' => '', - // 'The labels must be different' => '', - // 'There is no link.' => '', - // 'This label must be unique' => '', - // 'Unable to create your link.' => '', - // 'Unable to update your link.' => '', - // 'Unable to remove this link.' => '', - // 'relates to' => '', - // 'blocks' => '', - // 'is blocked by' => '', - // 'duplicates' => '', - // 'is duplicated by' => '', - // 'is a child of' => '', - // 'is a parent of' => '', - // 'targets milestone' => '', - // 'is a milestone of' => '', - // 'fixes' => '', - // 'is fixed by' => '', - // 'This task' => '', - // '<1h' => '', - // '%dh' => '', - // '%b %e' => '', - // 'Expand tasks' => '', - // 'Collapse tasks' => '', - // 'Expand/collapse tasks' => '', - // 'Close dialog box' => '', - // 'Submit a form' => '', - // 'Board view' => '', - // 'Keyboard shortcuts' => '', - // 'Open board switcher' => '', - // 'Application' => '', - // 'Filter recently updated' => '', - // 'since %B %e, %Y at %k:%M %p' => '', - // 'More filters' => '', + '#%d' => '#%d', + 'Filter by color' => 'Filtrera på färg', + 'Filter by swimlane' => 'Filtrera på swimlane', + 'All swimlanes' => 'Alla swimlanes', + 'All colors' => 'Alla färger', + 'All status' => 'Alla status', + 'Add a comment logging moving the task between columns' => 'Lägg till en kommentar för att logga förflyttning av en uppgift mellan kolumner', + 'Moved to column %s' => 'Flyttad till kolumn %s', + 'Change description' => 'Ändra beskrivning', + 'User dashboard' => 'Användardashboard', + 'Allow only one subtask in progress at the same time for a user' => 'Tillåt endast en deluppgift igång samtidigt för en användare', + 'Edit column "%s"' => 'Ändra kolumn "%s"', + 'Enable time tracking for subtasks' => 'Aktivera tidsbevakning för deluppgifter', + 'Select the new status of the subtask: "%s"' => 'Välj ny status för deluppgiften: "%s"', + 'Subtask timesheet' => 'Tidrapport för deluppgiften', + 'There is nothing to show.' => 'Det finns inget att visa', + 'Time Tracking' => 'Tidsbevakning', + 'You already have one subtask in progress' => 'Du har redan en deluppgift igång', + 'Which parts of the project do you want to duplicate?' => 'Vilka delar av projektet vill du duplicera?', + 'Change dashboard view' => 'Ändra dashboard vy', + 'Show/hide activities' => 'Visa/dölj aktiviteter', + 'Show/hide projects' => 'Visa/dölj projekt', + 'Show/hide subtasks' => 'Visa/dölj deluppgifter', + 'Show/hide tasks' => 'Visa/dölj uppgifter', + 'Disable login form' => 'Inaktivera loginformuläret', + 'Show/hide calendar' => 'Visa/dölj kalender', + 'User calendar' => 'Användarkalender', + 'Bitbucket commit received' => 'Bitbucket bidrag mottaget', + 'Bitbucket webhooks' => 'Bitbucket webhooks', + 'Help on Bitbucket webhooks' => 'Hjälp för Bitbucket webhooks', + 'Start' => 'Start', + 'End' => 'Slut', + 'Task age in days' => 'Uppgiftsålder i dagar', + 'Days in this column' => 'Dagar i denna kolumn', + '%dd' => '%dd', + 'Add a link' => 'Lägg till länk', + 'Add a new link' => 'Lägg till ny länk', + 'Do you really want to remove this link: "%s"?' => 'Vill du verkligen ta bort länken: "%s"?', + 'Do you really want to remove this link with task #%d?' => 'Vill du verkligen ta bort länken till uppgiften #%d?', + 'Field required' => 'Fältet krävs', + 'Link added successfully.' => 'Länken har lagts till', + 'Link updated successfully.' => 'Länken har uppdaterats', + 'Link removed successfully.' => 'Länken har tagits bort', + 'Link labels' => 'Länketiketter', + 'Link modification' => 'Länkändring', + 'Links' => 'Länkar', + 'Link settings' => 'Länkinställningar', + 'Opposite label' => 'Motpartslänk', + 'Remove a link' => 'Ta bort en länk', + 'Task\'s links' => 'Uppgiftslänkar', + 'The labels must be different' => 'Etiketterna måste vara olika', + 'There is no link.' => 'Det finns ingen länk', + 'This label must be unique' => 'Länken måste vara unik', + 'Unable to create your link.' => 'Kunde inte skapa din länk', + 'Unable to update your link.' => 'Kunde inte uppdatera din länk', + 'Unable to remove this link.' => 'Kunde inte ta bort din länk', + 'relates to' => 'relaterar till', + 'blocks' => 'blockerar', + 'is blocked by' => 'blockeras av', + 'duplicates' => 'dupplicerar', + 'is duplicated by' => 'är duplicerad av', + 'is a child of' => 'är underliggande till', + 'is a parent of' => 'är överliggande till', + 'targets milestone' => 'milstolpemål', + 'is a milestone of' => 'är en milstolpe för', + 'fixes' => 'åtgärdar', + 'is fixed by' => 'åtgärdas av', + 'This task' => 'Denna uppgift', + '<1h' => '<1h', + '%dh' => '%dh', + '%b %e' => '%b %e', + 'Expand tasks' => 'Expandera uppgifter', + 'Collapse tasks' => 'Minimera uppgifter', + 'Expand/collapse tasks' => 'Expandera/minimera uppgifter', + 'Close dialog box' => 'Stäng dialogruta', + 'Submit a form' => 'Sänd formulär', + 'Board view' => 'Tavelvy', + 'Keyboard shortcuts' => 'Tangentbordsgenvägar', + 'Open board switcher' => 'Växling av öppen tavla', + 'Application' => 'Applikation', + 'Filter recently updated' => 'Filter som uppdaterats nyligen', + 'since %B %e, %Y at %k:%M %p' => 'sedan %B %e, %Y at %k:%M %p', + 'More filters' => 'Fler filter', + // 'Compact view' => '', + // 'Horizontal scrolling' => '', + // 'Compact/wide view' => '', + // 'No results match:' => '', + // 'Remove hourly rate' => '', + // 'Do you really want to remove this hourly rate?' => '', + // 'Hourly rates' => '', + // 'Hourly rate' => '', + // 'Currency' => '', + // 'Effective date' => '', + // 'Add new rate' => '', + // 'Rate removed successfully.' => '', + // 'Unable to remove this rate.' => '', + // 'Unable to save the hourly rate.' => '', + // 'Hourly rate created successfully.' => '', + // 'Start time' => '', + // 'End time' => '', + // 'Comment' => '', + // 'All day' => '', + // 'Day' => '', + // 'Manage timetable' => '', + // 'Overtime timetable' => '', + // 'Time off timetable' => '', + // 'Timetable' => '', + // 'Work timetable' => '', + // 'Week timetable' => '', + // 'Day timetable' => '', + // 'From' => '', + // 'To' => '', + // 'Time slot created successfully.' => '', + // 'Unable to save this time slot.' => '', + // 'Time slot removed successfully.' => '', + // 'Unable to remove this time slot.' => '', + // 'Do you really want to remove this time slot?' => '', + // 'Remove time slot' => '', + // 'Add new time slot' => '', + // 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => '', + // 'Files' => '', + // 'Images' => '', + // 'Private project' => '', + // 'Amount' => '', + // 'AUD - Australian Dollar' => '', + // 'Budget' => '', + // 'Budget line' => '', + // 'Budget line removed successfully.' => '', + // 'Budget lines' => '', + // 'CAD - Canadian Dollar' => '', + // 'CHF - Swiss Francs' => '', + // 'Cost' => '', + // 'Cost breakdown' => '', + // 'Custom Stylesheet' => '', + // 'download' => '', + // 'Do you really want to remove this budget line?' => '', + // 'EUR - Euro' => '', + // 'Expenses' => '', + // 'GBP - British Pound' => '', + // 'INR - Indian Rupee' => '', + // 'JPY - Japanese Yen' => '', + // 'New budget line' => '', + // 'NZD - New Zealand Dollar' => '', + // 'Remove a budget line' => '', + // 'Remove budget line' => '', + // 'RSD - Serbian dinar' => '', + // 'The budget line have been created successfully.' => '', + // 'Unable to create the budget line.' => '', + // 'Unable to remove this budget line.' => '', + // 'USD - US Dollar' => '', + // 'Remaining' => '', ); diff --git a/app/Locale/th_TH/translations.php b/app/Locale/th_TH/translations.php index 1945f043..4b41e625 100644 --- a/app/Locale/th_TH/translations.php +++ b/app/Locale/th_TH/translations.php @@ -1,6 +1,8 @@ <?php return array( + // 'number.decimals_separator' => '', + // 'number.thousands_separator' => '', 'None' => 'ไม่มี', 'edit' => 'แก้ไข', 'Edit' => 'แก้ไข', @@ -734,4 +736,72 @@ return array( // 'Filter recently updated' => '', // 'since %B %e, %Y at %k:%M %p' => '', // 'More filters' => '', + // 'Compact view' => '', + // 'Horizontal scrolling' => '', + // 'Compact/wide view' => '', + // 'No results match:' => '', + // 'Remove hourly rate' => '', + // 'Do you really want to remove this hourly rate?' => '', + // 'Hourly rates' => '', + // 'Hourly rate' => '', + // 'Currency' => '', + // 'Effective date' => '', + // 'Add new rate' => '', + // 'Rate removed successfully.' => '', + // 'Unable to remove this rate.' => '', + // 'Unable to save the hourly rate.' => '', + // 'Hourly rate created successfully.' => '', + // 'Start time' => '', + // 'End time' => '', + // 'Comment' => '', + // 'All day' => '', + // 'Day' => '', + // 'Manage timetable' => '', + // 'Overtime timetable' => '', + // 'Time off timetable' => '', + // 'Timetable' => '', + // 'Work timetable' => '', + // 'Week timetable' => '', + // 'Day timetable' => '', + // 'From' => '', + // 'To' => '', + // 'Time slot created successfully.' => '', + // 'Unable to save this time slot.' => '', + // 'Time slot removed successfully.' => '', + // 'Unable to remove this time slot.' => '', + // 'Do you really want to remove this time slot?' => '', + // 'Remove time slot' => '', + // 'Add new time slot' => '', + // 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => '', + // 'Files' => '', + // 'Images' => '', + // 'Private project' => '', + // 'Amount' => '', + // 'AUD - Australian Dollar' => '', + // 'Budget' => '', + // 'Budget line' => '', + // 'Budget line removed successfully.' => '', + // 'Budget lines' => '', + // 'CAD - Canadian Dollar' => '', + // 'CHF - Swiss Francs' => '', + // 'Cost' => '', + // 'Cost breakdown' => '', + // 'Custom Stylesheet' => '', + // 'download' => '', + // 'Do you really want to remove this budget line?' => '', + // 'EUR - Euro' => '', + // 'Expenses' => '', + // 'GBP - British Pound' => '', + // 'INR - Indian Rupee' => '', + // 'JPY - Japanese Yen' => '', + // 'New budget line' => '', + // 'NZD - New Zealand Dollar' => '', + // 'Remove a budget line' => '', + // 'Remove budget line' => '', + // 'RSD - Serbian dinar' => '', + // 'The budget line have been created successfully.' => '', + // 'Unable to create the budget line.' => '', + // 'Unable to remove this budget line.' => '', + // 'USD - US Dollar' => '', + // 'Remaining' => '', ); diff --git a/app/Locale/tr_TR/translations.php b/app/Locale/tr_TR/translations.php new file mode 100644 index 00000000..488526f0 --- /dev/null +++ b/app/Locale/tr_TR/translations.php @@ -0,0 +1,807 @@ +<?php + +return array( + // 'number.decimals_separator' => '', + // 'number.thousands_separator' => '', + 'None' => 'Hiçbiri', + 'edit' => 'düzenle', + 'Edit' => 'Düzenle', + 'remove' => 'sil', + 'Remove' => 'Sil', + 'Update' => 'Güncelle', + 'Yes' => 'Evet', + 'No' => 'Hayır', + 'cancel' => 'İptal', + 'or' => 'veya', + 'Yellow' => 'Sarı', + 'Blue' => 'Mavi', + 'Green' => 'Yeşil', + 'Purple' => 'Mor', + 'Red' => 'Kırmızı', + 'Orange' => 'Turuncu', + 'Grey' => 'Gri', + 'Save' => 'Kaydet', + 'Login' => 'Giriş', + 'Official website:' => 'Resmi internet sitesi:', + 'Unassigned' => 'Atanmamış', + 'View this task' => 'Bu görevi görüntüle', + 'Remove user' => 'Kullanıcıyı kaldır', + 'Do you really want to remove this user: "%s"?' => 'Bu kullanıcıyı gerçekten silmek istiyor musunuz: "%s"?', + 'New user' => 'Yeni kullanıcı', + 'All users' => 'Tüm kullanıcılar', + 'Username' => 'Kullanıcı adı', + 'Password' => 'Şifre', + // 'Default project' => '', + 'Administrator' => 'Yönetici', + 'Sign in' => 'Giriş yap', + 'Users' => 'Kullanıcılar', + 'No user' => 'Kullanıcı yok', + 'Forbidden' => 'Yasak', + 'Access Forbidden' => 'Erişim yasak', + 'Only administrators can access to this page.' => 'Bu sayfaya yalnızca yöneticiler erişebilir.', + 'Edit user' => 'Kullanıcıyı düzenle', + 'Logout' => 'Çıkış yap', + 'Bad username or password' => 'Hatalı kullanıcı adı veya şifre', + 'users' => 'kullanıcılar', + 'projects' => 'projeler', + 'Edit project' => 'Projeyi düzenle', + 'Name' => 'İsim', + 'Activated' => 'Aktif', + 'Projects' => 'Projeler', + 'No project' => 'Proje yok', + 'Project' => 'Proje', + 'Status' => 'Durum', + 'Tasks' => 'Görevler', + 'Board' => 'Tablo', + 'Actions' => 'İşlemler', + 'Inactive' => 'Aktif değil', + 'Active' => 'Aktif', + 'Column %d' => 'Sütun %d', + 'Add this column' => 'Bu sütunu ekle', + '%d tasks on the board' => '%d görev bu tabloda', + '%d tasks in total' => '%d görev toplam', + 'Unable to update this board.' => 'Bu tablo güncellenemiyor.', + 'Edit board' => 'Tabloyu düzenle', + 'Disable' => 'Devre dışı bırak', + 'Enable' => 'Etkinleştir', + 'New project' => 'Yeni proje', + 'Do you really want to remove this project: "%s"?' => 'Bu projeyi gerçekten silmek istiyor musunuz: "%s"?', + 'Remove project' => 'Projeyi sil', + 'Boards' => 'Tablolar', + 'Edit the board for "%s"' => 'Tabloyu "%s" için güncelle', + 'All projects' => 'Tüm projeler', + 'Change columns' => 'Sütunları değiştir', + 'Add a new column' => 'Yeni sütun ekle', + 'Title' => 'Başlık', + 'Add Column' => 'Sütun ekle', + 'Project "%s"' => 'Proje "%s"', + 'Nobody assigned' => 'Kullanıcı atanmamış', + 'Assigned to %s' => '%s kullanıcısına atanmış', + 'Remove a column' => 'Bir sütunu sil', + 'Remove a column from a board' => 'Tablodan bir sütunu sil', + 'Unable to remove this column.' => 'Bu sütun silinemiyor.', + 'Do you really want to remove this column: "%s"?' => 'Bu sütunu gerçekten silmek istiyor musunuz: "%s"?', + 'This action will REMOVE ALL TASKS associated to this column!' => 'Bu komut sütun içindeki TÜM GÖREVLERİ silecek!', + 'Settings' => 'Ayarlar', + 'Application settings' => 'Uygulama ayarları', + 'Language' => 'Dil', + // 'Webhook token:' => '', + 'API token:' => 'API Token:', + 'More information' => 'Daha fazla bilgi', + 'Database size:' => 'Veritabanı boyutu :', + 'Download the database' => 'Veritabanını indir', + 'Optimize the database' => 'Veritabanını optimize et', + '(VACUUM command)' => '(VACUUM komutu)', + '(Gzip compressed Sqlite file)' => '(Gzip ile sıkıştırılmış Sqlite dosyası)', + 'User settings' => 'Kullanıcı ayarları', + 'My default project:' => 'Benim varsayılan projem:', + 'Close a task' => 'Bir görevi kapat', + 'Do you really want to close this task: "%s"?' => 'Bu görevi gerçekten kapatmak istiyor musunuz: "%s"?', + 'Edit a task' => 'Bir görevi düzenle', + 'Column' => 'Sütun', + 'Color' => 'Renk', + 'Assignee' => 'Atanan', + 'Create another task' => 'Başka bir görev oluştur', + 'New task' => 'Nouvelle tâche', + 'Open a task' => 'Bir görevi aç', + 'Do you really want to open this task: "%s"?' => 'Bu görevi gerçekten açmak istiyor musunuz: "%s"?', + 'Back to the board' => 'Tabloya dön', + // 'Created on %B %e, %Y at %k:%M %p' => '', + 'There is nobody assigned' => 'Kimse atanmamış', + 'Column on the board:' => 'Tablodaki sütun:', + 'Status is open' => 'Açık durumda', + 'Status is closed' => 'Kapalı durumda', + 'Close this task' => 'Görevi kapat', + 'Open this task' => 'Görevi aç', + 'There is no description.' => 'Açıklama yok.', + 'Add a new task' => 'Yeni görev ekle', + 'The username is required' => 'Kullanıcı adı gerekli', + 'The maximum length is %d characters' => 'Maksimum uzunluk %d karakterdir', + 'The minimum length is %d characters' => 'Minimum uzunluk %d karakterdir', + 'The password is required' => 'Şifre gerekli', + 'This value must be an integer' => 'Bu değer bir rakam olmak zorunda', + 'The username must be unique' => 'Kullanıcı adı daha önceden var', + 'The username must be alphanumeric' => 'Kullanıcı adı alfanumerik olmalı (geçersiz karakter var)', + 'The user id is required' => 'Kullanıcı kodu gerekli', + 'Passwords don\'t match' => 'Şifreler uyuşmuyor', + 'The confirmation is required' => 'Onay gerekli', + 'The column is required' => 'Sütun gerekli', + 'The project is required' => 'Proje gerekli', + 'The color is required' => 'Renk gerekli', + 'The id is required' => 'Kod gerekli', + 'The project id is required' => 'Proje kodu gerekli', + 'The project name is required' => 'Proje adı gerekli', + 'This project must be unique' => 'Bu projenin tekil olması gerekli', + 'The title is required' => 'Başlık gerekli', + 'The language is required' => 'Dil seçimi gerekli', + 'There is no active project, the first step is to create a new project.' => 'Aktif bir proje yok. İlk aşama yeni bir proje oluşturmak olmalı.', + 'Settings saved successfully.' => 'Ayarlar başarıyla kaydedildi.', + 'Unable to save your settings.' => 'Ayarlarınız kaydedilemedi.', + 'Database optimization done.' => 'Veritabanı optimizasyonu tamamlandı.', + 'Your project have been created successfully.' => 'Projeniz başarıyla oluşturuldu.', + 'Unable to create your project.' => 'Proje oluşturulamadı.', + 'Project updated successfully.' => 'Proje başarıyla güncellendi.', + 'Unable to update this project.' => 'Bu proje güncellenemedi.', + 'Unable to remove this project.' => 'Bu proje silinemedi.', + 'Project removed successfully.' => 'Proje başarıyla silindi.', + 'Project activated successfully.' => 'Proje başarıyla aktive edildi.', + 'Unable to activate this project.' => 'Bu proje aktive edilemedi.', + 'Project disabled successfully.' => 'Proje devre dışı bırakıldı.', + 'Unable to disable this project.' => 'Bu proje devre dışı bırakılamadı.', + 'Unable to open this task.' => 'Bu görev açılamıyor.', + 'Task opened successfully.' => 'Görev başarıyla açıldı.', + 'Unable to close this task.' => 'Bu görev kapatılamıyor.', + 'Task closed successfully.' => 'Görev başarıyla kapatıldı.', + 'Unable to update your task.' => 'Görev güncellenemiyor.', + 'Task updated successfully.' => 'Görev başarıyla güncellendi.', + 'Unable to create your task.' => 'Görev oluşturulamadı.', + 'Task created successfully.' => 'Görev başarıyla oluşturuldu.', + 'User created successfully.' => 'Kullanıcı başarıyla oluşturuldu', + 'Unable to create your user.' => 'Kullanıcı oluşturulamıyor.', + 'User updated successfully.' => 'Kullanıcı başarıyla güncellendi.', + 'Unable to update your user.' => 'Kullanıcı güncellenemiyor.', + 'User removed successfully.' => 'Kullanıcı silindi.', + 'Unable to remove this user.' => 'Bu kullanıcı silinemiyor.', + 'Board updated successfully.' => 'Tablo başarıyla güncellendi.', + 'Ready' => 'Hazır', + 'Backlog' => 'Bekleme listesi', + 'Work in progress' => 'İşlemde', + 'Done' => 'Tamamlandı', + 'Application version:' => 'Uygulama versiyonu:', + // 'Completed on %B %e, %Y at %k:%M %p' => '', + // '%B %e, %Y at %k:%M %p' => '', + 'Date created' => 'Oluşturulma tarihi', + 'Date completed' => 'Tamamlanma tarihi', + 'Id' => 'Kod', + 'No task' => 'Görev yok', + 'Completed tasks' => 'Tamamlanan görevler', + 'List of projects' => 'Proje listesi', + 'Completed tasks for "%s"' => '"%s" için tamamlanan görevler', + '%d closed tasks' => '%d kapatılmış görevler', + // 'No task for this project' => '', + 'Public link' => 'Dışa açık link', + 'There is no column in your project!' => 'Projenizde hiç sütun yok', + 'Change assignee' => 'Atanmış Kullanıcıyı değiştir', + 'Change assignee for the task "%s"' => '"%s" görevi için atanmış kullanıcıyı değiştir', + 'Timezone' => 'Saat dilimi', + // 'Sorry, I didn\'t find this information in my database!' => '', + 'Page not found' => 'Sayfa bulunamadı', + 'Complexity' => 'Zorluk seviyesi', + 'limit' => 'limit', + 'Task limit' => 'Görev limiti', + 'Task count' => 'Görev sayısı', + 'This value must be greater than %d' => 'Bu değer %d den büyük olmalı', + 'Edit project access list' => 'Proje erişim listesini düzenle', + 'Edit users access' => 'Kullanıcı erişim haklarını düzenle', + 'Allow this user' => 'Bu kullanıcıya izin ver', + 'Only those users have access to this project:' => 'Bu projeye yalnızca şu kullanıcılar erişebilir:', + 'Don\'t forget that administrators have access to everything.' => 'Dikkat: Yöneticilerin herşeye erişimi olduğunu unutmayın!', + 'Revoke' => 'Iptal et', + 'List of authorized users' => 'Yetkili kullanıcıların listesi', + 'User' => 'Kullanıcı', + 'Nobody have access to this project.' => 'Bu projeye kimsenin erişimi yok.', + 'You are not allowed to access to this project.' => 'Bu projeye giriş yetkiniz yok.', + 'Comments' => 'Yorumlar', + 'Post comment' => 'Yorum ekle', + 'Write your text in Markdown' => 'Yazınızı Markdown ile yazın', + 'Leave a comment' => 'Bir yorum ekle', + 'Comment is required' => 'Yorum gerekli', + 'Leave a description' => 'Açıklama ekleyin', + 'Comment added successfully.' => 'Yorum eklendi', + 'Unable to create your comment.' => 'Yorumunuz oluşturulamadı', + 'The description is required' => 'Açıklama gerekli', + 'Edit this task' => 'Bu görevi değiştir', + 'Due Date' => 'Termin', + 'Invalid date' => 'Geçersiz tarihi', + // 'Must be done before %B %e, %Y' => '', + '%B %e, %Y' => '%d %B %Y', + '%b %e, %Y' => '%d/%m/%Y', + 'Automatic actions' => 'Otomatik işlemler', + 'Your automatic action have been created successfully.' => 'Otomatik işlem başarıyla oluşturuldu', + 'Unable to create your automatic action.' => 'Otomatik işleminiz oluşturulamadı', + 'Remove an action' => 'Bir işlemi sil', + 'Unable to remove this action.' => 'Bu işlem silinemedi', + 'Action removed successfully.' => 'İşlem başarıyla silindi', + 'Automatic actions for the project "%s"' => '"%s" projesi için otomatik işlemler', + 'Defined actions' => 'Tanımlanan işlemler', + 'Add an action' => 'İşlem ekle', + 'Event name' => 'Durum adı', + 'Action name' => 'İşlem adı', + 'Action parameters' => 'İşlem parametreleri', + 'Action' => 'İşlem', + 'Event' => 'Durum', + 'When the selected event occurs execute the corresponding action.' => 'Seçilen durum oluştuğunda ilgili eylemi gerçekleştir.', + 'Next step' => 'Sonraki adım', + 'Define action parameters' => 'İşlem parametrelerini düzenle', + 'Save this action' => 'Bu işlemi kaydet', + 'Do you really want to remove this action: "%s"?' => 'Bu işlemi silmek istediğinize emin misiniz: "%s"?', + 'Remove an automatic action' => 'Bir otomatik işlemi sil', + 'Close the task' => 'Görevi kapat', + 'Assign the task to a specific user' => 'Görevi bir kullanıcıya ata', + 'Assign the task to the person who does the action' => 'Görevi, işlemi gerçekleştiren kullanıcıya ata', + 'Duplicate the task to another project' => 'Görevi bir başka projeye kopyala', + 'Move a task to another column' => 'Bir görevi başka bir sütuna taşı', + 'Move a task to another position in the same column' => 'Bir görevin aynı sütunda yerini değiştir', + 'Task modification' => 'Görev düzenleme', + 'Task creation' => 'Görev oluşturma', + 'Open a closed task' => 'Kapalı bir görevi aç', + 'Closing a task' => 'Bir görev kapatılıyor', + 'Assign a color to a specific user' => 'Bir kullanıcıya renk tanımla', + 'Column title' => 'Sütun başlığı', + 'Position' => 'Pozisyon', + 'Move Up' => 'Yukarı taşı', + 'Move Down' => 'Aşağı taşı', + 'Duplicate to another project' => 'Başka bir projeye kopyala', + 'Duplicate' => 'Kopya oluştur', + 'link' => 'link', + 'Update this comment' => 'Bu yorumu güncelle', + 'Comment updated successfully.' => 'Yorum güncellendi.', + 'Unable to update your comment.' => 'Yorum güncellenemedi.', + 'Remove a comment' => 'Bir yorumu sil', + 'Comment removed successfully.' => 'Yorum silindi.', + 'Unable to remove this comment.' => 'Bu yorum silinemiyor.', + 'Do you really want to remove this comment?' => 'Bu yorumu silmek istediğinize emin misiniz?', + 'Only administrators or the creator of the comment can access to this page.' => 'Bu sayfaya yalnızca yorum sahibi ve yöneticiler erişebilir.', + 'Details' => 'Detaylar', + 'Current password for the user "%s"' => 'Kullanıcı için mevcut şifre "%s"', + 'The current password is required' => 'Mevcut şifre gerekli', + 'Wrong password' => 'Yanlış Şifre', + 'Reset all tokens' => 'Tüm fişleri sıfırla', + 'All tokens have been regenerated.' => 'Tüm fişler yeniden oluşturuldu.', + 'Unknown' => 'Bilinmeyen', + 'Last logins' => 'Son kullanıcı girişleri', + 'Login date' => 'Giriş tarihi', + 'Authentication method' => 'Doğrulama yöntemi', + 'IP address' => 'IP adresi', + 'User agent' => 'Kullanıcı sistemi', + 'Persistent connections' => 'Kalıcı bağlantılar', + // 'No session.' => '', + 'Expiration date' => 'Geçerlilik sonu', + 'Remember Me' => 'Beni hatırla', + 'Creation date' => 'Oluşturulma tarihi', + 'Filter by user' => 'Kullanıcıya göre filtrele', + 'Filter by due date' => 'Termine göre filtrele', + 'Everybody' => 'Herkes', + 'Open' => 'Açık', + 'Closed' => 'Kapalı', + 'Search' => 'Ara', + 'Nothing found.' => 'Hiçbir şey bulunamadı', + 'Search in the project "%s"' => '"%s" Projesinde ara', + 'Due date' => 'Termin', + 'Others formats accepted: %s and %s' => 'Diğer kabul edilen formatlar: %s ve %s', + 'Description' => 'Açıklama', + '%d comments' => '%d yorumlar', + '%d comment' => '%d yorum', + 'Email address invalid' => 'E-Posta adresi geçersiz', + 'Your Google Account is not linked anymore to your profile.' => 'Google hesabınız artık profilinize bağlı değil', + 'Unable to unlink your Google Account.' => 'Google hesabınızla bağ koparılamadı', + 'Google authentication failed' => 'Google hesap doğrulaması başarısız', + 'Unable to link your Google Account.' => 'Google hesabınızla bağ oluşturulamadı', + 'Your Google Account is linked to your profile successfully.' => 'Google hesabınız profilinize başarıyla bağlandı', + 'Email' => 'E-Posta', + 'Link my Google Account' => 'Google hesabımla bağ oluştur', + 'Unlink my Google Account' => 'Google hesabımla bağı kaldır', + 'Login with my Google Account' => 'Google hesabımla giriş yap', + 'Project not found.' => 'Proje bulunamadı', + 'Task #%d' => 'Görev #%d', + 'Task removed successfully.' => 'Görev silindi', + 'Unable to remove this task.' => 'Görev silinemiyor', + 'Remove a task' => 'Bir görevi sil', + 'Do you really want to remove this task: "%s"?' => 'Bu görevi silmek istediğinize emin misiniz: "%s"?', + 'Assign automatically a color based on a category' => 'Kategoriye göre otomatik renk ata', + 'Assign automatically a category based on a color' => 'Rengine göre otomatik kategori ata', + 'Task creation or modification' => 'Görev oluşturma veya değiştirme', + 'Category' => 'Kategori', + 'Category:' => 'Kategori:', + 'Categories' => 'Kategoriler', + 'Category not found.' => 'Kategori bulunamadı', + 'Your category have been created successfully.' => 'Kategori oluşturuldu', + 'Unable to create your category.' => 'Kategori oluşturulamadı', + 'Your category have been updated successfully.' => 'Kategori başarıyla güncellendi', + 'Unable to update your category.' => 'Kategori güncellenemedi', + 'Remove a category' => 'Bir kategoriyi sil', + 'Category removed successfully.' => 'Kategori silindi', + 'Unable to remove this category.' => 'Bu kategori silinemedi', + 'Category modification for the project "%s"' => '"%s" projesi için kategori değiştirme', + 'Category Name' => 'Kategori adı', + 'Categories for the project "%s"' => '"%s" Projesi için kategoriler', + 'Add a new category' => 'Yeni kategori ekle', + 'Do you really want to remove this category: "%s"?' => 'Bu kategoriyi silmek istediğinize emin misiniz: "%s"?', + 'Filter by category' => 'Kategoriye göre filtrele', + 'All categories' => 'Tüm kategoriler', + 'No category' => 'Kategori Yok', + 'The name is required' => 'İsim gerekli', + 'Remove a file' => 'Dosya sil', + 'Unable to remove this file.' => 'Dosya silinemedi', + 'File removed successfully.' => 'Dosya silindi', + 'Attach a document' => 'Dosya ekle', + 'Do you really want to remove this file: "%s"?' => 'Bu dosyayı silmek istediğinize emin misiniz: "%s"?', + 'open' => 'aç', + 'Attachments' => 'Ekler', + 'Edit the task' => 'Görevi değiştir', + 'Edit the description' => 'Açıklamayı değiştir', + 'Add a comment' => 'Yorum ekle', + 'Edit a comment' => 'Yorum değiştir', + 'Summary' => 'Özet', + 'Time tracking' => 'Zaman takibi', + 'Estimate:' => 'Tahmini:', + 'Spent:' => 'Harcanan:', + 'Do you really want to remove this sub-task?' => 'Bu alt görevi silmek istediğinize emin misiniz', + 'Remaining:' => 'Kalan', + 'hours' => 'saat', + 'spent' => 'harcanan', + 'estimated' => 'tahmini', + 'Sub-Tasks' => 'Alt Görev', + 'Add a sub-task' => 'Alt görev ekle', + // 'Original estimate' => '', + 'Create another sub-task' => 'Başka bir alt görev daha oluştur', + // 'Time spent' => '', + 'Edit a sub-task' => 'Alt görev düzenle', + 'Remove a sub-task' => 'Alt görev sil', + 'The time must be a numeric value' => 'Zaman alfanumerik bir değer olmalı', + 'Todo' => 'Yapılacaklar', + 'In progress' => 'İşlemde', + 'Sub-task removed successfully.' => 'Alt görev silindi', + 'Unable to remove this sub-task.' => 'Alt görev silinemedi', + 'Sub-task updated successfully.' => 'Alt görev güncellendi', + 'Unable to update your sub-task.' => 'Alt görev güncellenemiyor', + 'Unable to create your sub-task.' => 'Alt görev oluşturulamadı', + 'Sub-task added successfully.' => 'Alt görev başarıyla eklendii', + 'Maximum size: ' => 'Maksimum boyutu', + 'Unable to upload the file.' => 'Karşıya yükleme başarısız', + 'Display another project' => 'Başka bir proje göster', + 'Your GitHub account was successfully linked to your profile.' => 'GitHub Hesabınız Profilinize bağlandı.', + 'Unable to link your GitHub Account.' => 'GitHub hesabınızla bağ oluşturulamadı.', + // 'GitHub authentication failed' => '', + // 'Your GitHub account is no longer linked to your profile.' => '', + // 'Unable to unlink your GitHub Account.' => '', + // 'Login with my GitHub Account' => '', + // 'Link my GitHub Account' => '', + // 'Unlink my GitHub Account' => '', + 'Created by %s' => '%s tarafından oluşturuldu', + 'Last modified on %B %e, %Y at %k:%M %p' => 'Son değişiklik tarihi %d.%m.%Y, saati %H:%M', + 'Tasks Export' => 'Görevleri dışa aktar', + 'Tasks exportation for "%s"' => '"%s" için görevleri dışa aktar', + 'Start Date' => 'Başlangıç tarihi', + 'End Date' => 'Bitiş tarihi', + 'Execute' => 'Gerçekleştir', + 'Task Id' => 'Görev No', + 'Creator' => 'Oluşturan', + 'Modification date' => 'Değişiklik tarihi', + 'Completion date' => 'Tamamlanma tarihi', + // 'Webhook URL for task creation' => '', + // 'Webhook URL for task modification' => '', + 'Clone' => 'Kopya oluştur', + 'Clone Project' => 'Projenin kopyasını oluştur', + 'Project cloned successfully.' => 'Proje kopyası başarıyla oluşturuldu.', + 'Unable to clone this project.' => 'Proje kopyası oluşturulamadı.', + 'Email notifications' => 'E-Posta bilgilendirmesi', + 'Enable email notifications' => 'E-Posta bilgilendirmesini aç', + 'Task position:' => 'Görev pozisyonu', + 'The task #%d have been opened.' => '#%d numaralı görev açıldı.', + 'The task #%d have been closed.' => '#%d numaralı görev kapatıldı.', + 'Sub-task updated' => 'Alt görev güncellendi', + 'Title:' => 'Başlık', + 'Status:' => 'Durum', + 'Assignee:' => 'Sorumlu:', + 'Time tracking:' => 'Zaman takibi', + 'New sub-task' => 'Yeni alt görev', + 'New attachment added "%s"' => 'Yeni dosya "%s" eklendi.', + 'Comment updated' => 'Yorum güncellendi', + 'New comment posted by %s' => '%s tarafından yeni yorum eklendi', + 'List of due tasks for the project "%s"' => '"%s" projesi için ilgili görevlerin listesi', + 'New attachment' => 'Yeni dosya eki', + 'New comment' => 'Yeni yorum', + 'New subtask' => 'Yeni alt görev', + 'Subtask updated' => 'Alt görev güncellendi', + 'Task updated' => 'Görev güncellendi', + 'Task closed' => 'Görev kapatıldı', + 'Task opened' => 'Görev açıldı', + '[%s][Due tasks]' => '[%s][İlgili görevler]', + '[Kanboard] Notification' => '[Kanboard] Bildirim', + 'I want to receive notifications only for those projects:' => 'Yalnızca bu projelerle ilgili bildirim almak istiyorum:', + 'view the task on Kanboard' => 'bu görevi Kanboard\'da göster', + 'Public access' => 'Dışa açık erişim', + 'Category management' => 'Kategori yönetimi', + 'User management' => 'Kullanıcı yönetimi', + 'Active tasks' => 'Aktif görevler', + 'Disable public access' => 'Dışa açık erişimi kapat', + 'Enable public access' => 'Dışa açık erişimi aç', + 'Active projects' => 'Aktif projeler', + 'Inactive projects' => 'Aktif olmayan projeler', + 'Public access disabled' => 'Dışa açık erişim kapatıldı', + 'Do you really want to disable this project: "%s"?' => 'Bu projeyi devre dışı bırakmak istediğinize emin misiniz?: "%s"', + 'Do you really want to duplicate this project: "%s"?' => 'Bu projenin kopyasını oluşturmak istediğinize emin misiniz?: "%s"', + 'Do you really want to enable this project: "%s"?' => 'Bu projeyi aktive etmek istediğinize emin misiniz?: "%s"', + 'Project activation' => 'Proje aktivasyonu', + 'Move the task to another project' => 'Görevi başka projeye taşı', + 'Move to another project' => 'Başka projeye taşı', + 'Do you really want to duplicate this task?' => 'Bu görevin kopyasını oluşturmak istediğinize emin misiniz?', + 'Duplicate a task' => 'Görevin kopyasını oluştur', + 'External accounts' => 'Dış hesaplar', + 'Account type' => 'Hesap türü', + 'Local' => 'Yerel', + 'Remote' => 'Uzak', + 'Enabled' => 'Etkinleştirildi', + 'Disabled' => 'Devre dışı bırakıldı', + 'Google account linked' => 'Google hesabıyla bağlı', + 'Github account linked' => 'Github hesabıyla bağlı', + 'Username:' => 'Kullanıcı adı', + 'Name:' => 'Ad', + 'Email:' => 'E-Posta', + 'Default project:' => 'Varsayılan Proje:', + 'Notifications:' => 'Bildirimler:', + 'Notifications' => 'Bildirimler', + 'Group:' => 'Grup', + 'Regular user' => 'Varsayılan kullanıcı', + 'Account type:' => 'Hesap türü:', + 'Edit profile' => 'Profili değiştir', + 'Change password' => 'Şifre değiştir', + 'Password modification' => 'Şifre değişimi', + 'External authentications' => 'Dış kimlik doğrulamaları', + 'Google Account' => 'Google hesabı', + 'Github Account' => 'Github hesabı', + 'Never connected.' => 'Hiç bağlanmamış.', + 'No account linked.' => 'Bağlanmış hesap yok.', + 'Account linked.' => 'Hesap bağlandı', + 'No external authentication enabled.' => 'Dış kimlik doğrulama kapalı.', + 'Password modified successfully.' => 'Şifre başarıyla değiştirildi.', + 'Unable to change the password.' => 'Şifre değiştirilemedi.', + 'Change category for the task "%s"' => '"%s" görevi için kategori değiştirme', + 'Change category' => 'Kategori değiştirme', + '%s updated the task %s' => '%s kullanıcısı %s görevini güncelledi', + '%s opened the task %s' => '%s kullanıcısı %s görevini açtı', + '%s moved the task %s to the position #%d in the column "%s"' => '%s kullanıcısı %s görevini #%d pozisyonu "%s" sütununa taşıdı', + '%s moved the task %s to the column "%s"' => '%s kullanıcısı %s görevini "%s" sütununa taşıdı', + '%s created the task %s' => '%s kullanıcısı %s görevini oluşturdu', + '%s closed the task %s' => '%s kullanıcısı %s görevini kapattı', + '%s created a subtask for the task %s' => '%s kullanıcısı %s görevi için bir alt görev oluşturdu', + '%s updated a subtask for the task %s' => '%s kullanıcısı %s görevinin bir alt görevini güncelledi', + 'Assigned to %s with an estimate of %s/%sh' => '%s kullanıcısına tahmini %s/%s saat tamamlanma süresi ile atanmış', + 'Not assigned, estimate of %sh' => 'Kimseye atanmamış, tahmini süre %s saat', + '%s updated a comment on the task %s' => '%s kullanıcısı %s görevinde bir yorumu güncelledi', + '%s commented the task %s' => '%s kullanıcısı %s görevine yorum ekledi', + '%s\'s activity' => '%s\'in aktivitesi', + 'No activity.' => 'Aktivite yok.', + 'RSS feed' => 'RSS kaynağı', + '%s updated a comment on the task #%d' => '%s kullanıcısı #%d nolu görevde bir yorumu güncelledi', + '%s commented on the task #%d' => '%s kullanıcısı #%d nolu göreve yorum ekledi', + '%s updated a subtask for the task #%d' => '%s kullanıcısı #%d nolu görevin bir alt görevini güncelledi', + '%s created a subtask for the task #%d' => '%s kullanıcısı #%d nolu göreve bir alt görev ekledi', + '%s updated the task #%d' => '%s kullanıcısı #%d nolu görevi güncelledi', + '%s created the task #%d' => '%s kullanıcısı #%d nolu görevi oluşturdu', + '%s closed the task #%d' => '%s kullanıcısı #%d nolu görevi kapattı', + '%s open the task #%d' => '%s kullanıcısı #%d nolu görevi açtı', + '%s moved the task #%d to the column "%s"' => '%s kullanıcısı #%d nolu görevi "%s" sütununa taşıdı', + '%s moved the task #%d to the position %d in the column "%s"' => '%s kullanıcısı #%d nolu görevi %d pozisyonu "%s" sütununa taşıdı', + 'Activity' => 'Aktivite', + 'Default values are "%s"' => 'Varsayılan değerler "%s"', + 'Default columns for new projects (Comma-separated)' => 'Yeni projeler için varsayılan sütunlar (virgül ile ayrılmış)', + 'Task assignee change' => 'Göreve atanan kullanıcı değişikliği', + '%s change the assignee of the task #%d to %s' => '%s kullanıcısı #%d nolu görevin sorumlusunu %s olarak değiştirdi', + '%s changed the assignee of the task %s to %s' => '%s kullanıcısı %s görevinin sorumlusunu %s olarak değiştirdi', + 'Column Change' => 'Sütun değişikliği', + 'Position Change' => 'Konum değişikliği', + 'Assignee Change' => 'Sorumlu değişikliği', + 'New password for the user "%s"' => '"%s" kullanıcısı için yeni şifre', + 'Choose an event' => 'Bir durum seçin', + // 'Github commit received' => '', + // 'Github issue opened' => '', + // 'Github issue closed' => '', + // 'Github issue reopened' => '', + // 'Github issue assignee change' => '', + // 'Github issue label change' => '', + 'Create a task from an external provider' => 'Dış sağlayıcı ile bir görev oluştur', + 'Change the assignee based on an external username' => 'Dış kaynaklı kullanıcı adı ile göreve atananı değiştir', + 'Change the category based on an external label' => 'Dış kaynaklı bir etiket ile kategori değiştir', + 'Reference' => 'Referans', + 'Reference: %s' => 'Referans: %s', + 'Label' => 'Etiket', + 'Database' => 'Veri bankası', + 'About' => 'Hakkında', + 'Database driver:' => 'Veri bankası sürücüsü', + 'Board settings' => 'Tablo ayarları', + 'URL and token' => 'URL veya Token', + 'Webhook settings' => 'Webhook ayarları', + 'URL for task creation:' => 'Görev oluşturma için URL', + 'Reset token' => 'Reset Token', + 'API endpoint:' => 'API endpoint', + 'Refresh interval for private board' => 'Özel tablolar için yenileme sıklığı', + 'Refresh interval for public board' => 'Dışa açık tablolar için yenileme sıklığı', + 'Task highlight period' => 'Görevi öne çıkarma süresi', + 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'Bir görevin yeni değiştirilmiş sayılması için süre (saniye olarak) (Bu özelliği iptal etmek için 0, varsayılan değer 2 gün)', + 'Frequency in second (60 seconds by default)' => 'Saniye olarak frekans (varsayılan 60 saniye)', + 'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'Saniye olarak frekans (Bu özelliği iptal etmek için 0, varsayılan değer 10 saniye)', + 'Application URL' => 'Uygulama URL', + 'Example: http://example.kanboard.net/ (used by email notifications)' => 'Örneğin: http://example.kanboard.net/ (E-posta bildirimleri için kullanılıyor)', + 'Token regenerated.' => 'Token yeniden oluşturuldu.', + 'Date format' => 'Tarih formatı', + 'ISO format is always accepted, example: "%s" and "%s"' => 'ISO formatı her zaman kabul edilir, örneğin: "%s" ve "%s"', + 'New private project' => 'Yeni özel proje', + 'This project is private' => 'Bu proje özel', + 'Type here to create a new sub-task' => 'Yeni bir alt görev oluşturmak için buraya yazın', + 'Add' => 'Ekle', + 'Estimated time: %s hours' => 'Tahmini süre: %s Saat', + 'Time spent: %s hours' => 'Kullanılan süre: %s Saat', + 'Started on %B %e, %Y' => '%B %e %Y tarihinde başlatıldı', + 'Start date' => 'Başlangıç tarihi', + 'Time estimated' => 'Tahmini süre', + 'There is nothing assigned to you.' => 'Size atanan hiçbir şey yok.', + 'My tasks' => 'Görevlerim', + 'Activity stream' => 'Güncel olay akışı', + 'Dashboard' => 'Anasayfa', + 'Confirmation' => 'Onay', + 'Allow everybody to access to this project' => 'Bu projeye herkesin erişimine izin ver', + 'Everybody have access to this project.' => 'Bu projeye herkesin erişimi var.', + 'Webhooks' => 'Webhooks', + 'API' => 'API', + 'Integration' => 'Entegrasyon', + 'Github webhooks' => 'Github Webhook', + 'Help on Github webhooks' => 'Github Webhooks hakkında yardım', + 'Create a comment from an external provider' => 'Dış sağlayıcı ile bir yorum oluştur', + 'Github issue comment created' => 'Github hata yorumu oluşturuldu', + 'Configure' => 'Ayarla', + 'Project management' => 'Proje yönetimi', + 'My projects' => 'Projelerim', + 'Columns' => 'Sütunlar', + 'Task' => 'Görev', + 'Your are not member of any project.' => 'Hiç bir projenin üyesi değilsiniz.', + 'Percentage' => 'Yüzde', + 'Number of tasks' => 'Görev sayısı', + 'Task distribution' => 'Görev dağılımı', + 'Reportings' => 'Raporlar', + 'Task repartition for "%s"' => '"%s" için görev dağılımı', + 'Analytics' => 'Analiz', + 'Subtask' => 'Alt görev', + 'My subtasks' => 'Alt görevlerim', + 'User repartition' => 'Kullanıcı dağılımı', + 'User repartition for "%s"' => '"%s" için kullanıcı dağılımı', + 'Clone this project' => 'Projenin kopyasını oluştur', + 'Column removed successfully.' => 'Sütun başarıyla kaldırıldı.', + 'Edit Project' => 'Projeyi düzenle', + 'Github Issue' => 'Github Issue', + 'Not enough data to show the graph.' => 'Grafik gösterimi için yeterli veri yok.', + 'Previous' => 'Önceki', + 'The id must be an integer' => 'ID bir tamsayı olmalı', + 'The project id must be an integer' => 'Proje numarası bir tam sayı olmalı', + 'The status must be an integer' => 'Durum bir tam sayı olmalı', + 'The subtask id is required' => 'Alt görev numarası gerekli', + 'The subtask id must be an integer' => 'Alt görev numarası bir tam sayı olmalı', + 'The task id is required' => 'Görev numarası gerekli', + 'The task id must be an integer' => 'Görev numarası bir tam sayı olmalı', + 'The user id must be an integer' => 'Kullanıcı numarası bir tam sayı olmalı', + 'This value is required' => 'Bu değer gerekli', + 'This value must be numeric' => 'Bu değer sayı olmalı', + 'Unable to create this task.' => 'Bu görev oluşturulamıyor.', + 'Cumulative flow diagram' => 'Kümülatif akış diyagramı', + 'Cumulative flow diagram for "%s"' => '"%s" için kümülatif akış diyagramı', + 'Daily project summary' => 'Günlük proje özeti', + 'Daily project summary export' => 'Günlük proje özetini dışa aktar', + 'Daily project summary export for "%s"' => '"%s" için günlük proje özetinin dışa', + 'Exports' => 'Dışa aktarımlar', + 'This export contains the number of tasks per column grouped per day.' => 'Bu dışa aktarım günlük gruplanmış olarak her sütundaki görev sayısını içerir.', + 'Nothing to preview...' => 'Önizleme yapılacak bir şey yok ...', + 'Preview' => 'Önizleme', + 'Write' => 'Değiştir', + 'Active swimlanes' => 'Aktif Kulvar', + 'Add a new swimlane' => 'Yeni bir Kulvar ekle', + 'Change default swimlane' => 'Varsayılan Kulvarı değiştir', + 'Default swimlane' => 'Varsayılan Kulvar', + 'Do you really want to remove this swimlane: "%s"?' => 'Bu Kulvarı silmek istediğinize emin misiniz?: "%s"?', + 'Inactive swimlanes' => 'Pasif Kulvarlar', + 'Set project manager' => 'Proje yöneticisi olarak ata', + 'Set project member' => 'Proje üyesi olarak ata', + 'Remove a swimlane' => 'Kulvarı sil', + 'Rename' => 'Yeniden adlandır', + 'Show default swimlane' => 'Varsayılan Kulvarı göster', + 'Swimlane modification for the project "%s"' => '"% s" Projesi için Kulvar değişikliği', + 'Swimlane not found.' => 'Kulvar bulunamadı', + 'Swimlane removed successfully.' => 'Kulvar başarıyla kaldırıldı.', + 'Swimlanes' => 'Kulvarlar', + 'Swimlane updated successfully.' => 'Kulvar başarıyla güncellendi.', + 'The default swimlane have been updated successfully.' => 'Varsayılan Kulvarlar başarıyla güncellendi.', + 'Unable to create your swimlane.' => 'Bu Kulvarı oluşturmak mümkün değil.', + 'Unable to remove this swimlane.' => 'Bu Kulvarı silmek mümkün değil.', + 'Unable to update this swimlane.' => 'Bu Kulvarı değiştirmek mümkün değil.', + 'Your swimlane have been created successfully.' => 'Kulvar başarıyla oluşturuldu.', + 'Example: "Bug, Feature Request, Improvement"' => 'Örnek: "Sorun, Özellik talebi, İyileştirme"', + 'Default categories for new projects (Comma-separated)' => 'Yeni projeler için varsayılan kategoriler (Virgül ile ayrılmış)', + // 'Gitlab commit received' => '', + // 'Gitlab issue opened' => '', + // 'Gitlab issue closed' => '', + // 'Gitlab webhooks' => '', + // 'Help on Gitlab webhooks' => '', + 'Integrations' => 'Entegrasyon', + 'Integration with third-party services' => 'Dış servislerle entegrasyon', + 'Role for this project' => 'Bu proje için rol', + 'Project manager' => 'Proje Yöneticisi', + 'Project member' => 'Proje Üyesi', + 'A project manager can change the settings of the project and have more privileges than a standard user.' => 'Bir Proje Yöneticisi proje ayarlarını değiştirebilir ve bir üyeden daha fazla yetkiye sahiptir.', + // 'Gitlab Issue' => '', + 'Subtask Id' => 'Alt görev No:', + 'Subtasks' => 'Alt görevler', + 'Subtasks Export' => 'Alt görevleri dışa aktar', + 'Subtasks exportation for "%s"' => '"%s" için alt görevleri dışa aktarımı', + 'Task Title' => 'Görev Başlığı', + 'Untitled' => 'Başlıksız', + 'Application default' => 'Uygulama varsayılanları', + 'Language:' => 'Dil:', + 'Timezone:' => 'Saat dilimi:', + 'All columns' => 'Tüm sütunlar', + 'Calendar for "%s"' => '"%s" için takvim', + 'Filter by column' => 'Sütuna göre filtrele', + 'Filter by status' => 'Duruma göre filtrele', + 'Calendar' => 'Takvim', + 'Next' => 'Sonraki', + '#%d' => '#%d', + 'Filter by color' => 'Renklere göre filtrele', + 'Filter by swimlane' => 'Kulvara göre filtrele', + 'All swimlanes' => 'Tüm Kulvarlar', + 'All colors' => 'Tüm Renkler', + 'All status' => 'Tüm Durumlar', + 'Add a comment logging moving the task between columns' => 'Sütun değiştiğinde kayıt olarak yorum ekle', + 'Moved to column %s' => '%s Sütununa taşındı', + 'Change description' => 'Açıklamayı değiştir', + 'User dashboard' => 'Kullanıcı Anasayfası', + 'Allow only one subtask in progress at the same time for a user' => 'Bir kullanıcı için aynı anda yalnızca bir alt göreve izin ver', + 'Edit column "%s"' => '"%s" sütununu değiştir', + 'Enable time tracking for subtasks' => 'Alt görevler için zaman takibini etkinleştir', + 'Select the new status of the subtask: "%s"' => '"%s" alt görevi için yeni durum seçin.', + 'Subtask timesheet' => 'Alt görev için zaman takip tablosu', + 'There is nothing to show.' => 'Gösterilecek hiçbir şey yok.', + 'Time Tracking' => 'Zaman takibi', + 'You already have one subtask in progress' => 'Zaten işlemde olan bir alt görev var', + 'Which parts of the project do you want to duplicate?' => 'Projenin hangi kısımlarının kopyasını oluşturmak istiyorsunuz?', + 'Change dashboard view' => 'Anasayfa görünümünü değiştir', + 'Show/hide activities' => 'Aktiviteleri göster/gizle', + 'Show/hide projects' => 'Projeleri göster/gizle', + 'Show/hide subtasks' => 'Alt görevleri göster/gizle', + 'Show/hide tasks' => 'Görevleri göster/gizle', + 'Disable login form' => 'Giriş formunu devre dışı bırak', + 'Show/hide calendar' => 'Takvimi göster/gizle', + 'User calendar' => 'Kullanıcı takvimi', + 'Bitbucket commit received' => 'Bitbucket commit alındı', + 'Bitbucket webhooks' => 'Bitbucket webhooks', + 'Help on Bitbucket webhooks' => 'Bitbucket webhooks için yardım', + 'Start' => 'Başlangıç', + 'End' => 'Son', + 'Task age in days' => 'Görev yaşı gün olarak', + 'Days in this column' => 'Bu sütunda geçirilen gün', + '%dd' => '%dG', + 'Add a link' => 'Link ekle', + 'Add a new link' => 'Yeni link ekle', + 'Do you really want to remove this link: "%s"?' => '"%s" linkini gerçekten silmek istiyor musunuz?', + 'Do you really want to remove this link with task #%d?' => '#%d numaralı görev ile linki gerçekten silmek istiyor musunuz?', + 'Field required' => 'Bu alan gerekli', + 'Link added successfully.' => 'Link başarıyla eklendi.', + 'Link updated successfully.' => 'Link başarıyla güncellendi.', + 'Link removed successfully.' => 'Link başarıyla silindi.', + 'Link labels' => 'Link etiketleri', + 'Link modification' => 'Link değiştirme', + 'Links' => 'Links', + 'Link settings' => 'Link ayarları', + 'Opposite label' => 'Zıt etiket', + 'Remove a link' => 'Bir link silmek', + 'Task\'s links' => 'Görevin linkleri', + 'The labels must be different' => 'Etiketler farklı olmalı', + 'There is no link.' => 'Hiç bir bağ yok', + 'This label must be unique' => 'Bu etiket tek olmalı', + 'Unable to create your link.' => 'Link oluşturulamadı.', + 'Unable to update your link.' => 'Link güncellenemiyor.', + 'Unable to remove this link.' => 'Link kaldırılamıyor', + 'relates to' => 'şununla ilgili', + 'blocks' => 'şunu engelliyor', + 'is blocked by' => 'şunun tarafından engelleniyor', + 'duplicates' => 'şunun kopyasını oluşturuyor', + 'is duplicated by' => 'şunun tarafından kopyası oluşturuluyor', + 'is a child of' => 'şunun astı', + 'is a parent of' => 'şunun üstü', + 'targets milestone' => 'şu kilometre taşını hedefliyor', + 'is a milestone of' => 'şunun için bir kilometre taşı', + 'fixes' => 'düzeltiyor', + 'is fixed by' => 'şunun tarafından düzeltildi', + 'This task' => 'Bu görev', + '<1h' => '<1s', + '%dh' => '%ds', + // '%b %e' => '', + 'Expand tasks' => 'Görevleri genişlet', + 'Collapse tasks' => 'Görevleri daralt', + 'Expand/collapse tasks' => 'Görevleri genişlet/daralt', + 'Close dialog box' => 'İletiyi kapat', + 'Submit a form' => 'Formu gönder', + 'Board view' => 'Tablo görünümü', + 'Keyboard shortcuts' => 'Klavye kısayolları', + 'Open board switcher' => 'Tablo seçim listesini aç', + 'Application' => 'Uygulama', + 'Filter recently updated' => 'Son güncellenenleri göster', + 'since %B %e, %Y at %k:%M %p' => '%B %e, %Y saat %k:%M %p\'den beri', + 'More filters' => 'Daha fazla filtre', + 'Compact view' => 'Ekrana sığdır', + 'Horizontal scrolling' => 'Geniş görünüm', + 'Compact/wide view' => 'Ekrana sığdır / Geniş görünüm', + // 'No results match:' => '', + // 'Remove hourly rate' => '', + // 'Do you really want to remove this hourly rate?' => '', + // 'Hourly rates' => '', + // 'Hourly rate' => '', + // 'Currency' => '', + // 'Effective date' => '', + // 'Add new rate' => '', + // 'Rate removed successfully.' => '', + // 'Unable to remove this rate.' => '', + // 'Unable to save the hourly rate.' => '', + // 'Hourly rate created successfully.' => '', + // 'Start time' => '', + // 'End time' => '', + // 'Comment' => '', + // 'All day' => '', + // 'Day' => '', + // 'Manage timetable' => '', + // 'Overtime timetable' => '', + // 'Time off timetable' => '', + // 'Timetable' => '', + // 'Work timetable' => '', + // 'Week timetable' => '', + // 'Day timetable' => '', + // 'From' => '', + // 'To' => '', + // 'Time slot created successfully.' => '', + // 'Unable to save this time slot.' => '', + // 'Time slot removed successfully.' => '', + // 'Unable to remove this time slot.' => '', + // 'Do you really want to remove this time slot?' => '', + // 'Remove time slot' => '', + // 'Add new time slot' => '', + // 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => '', + // 'Files' => '', + // 'Images' => '', + // 'Private project' => '', + // 'Amount' => '', + // 'AUD - Australian Dollar' => '', + // 'Budget' => '', + // 'Budget line' => '', + // 'Budget line removed successfully.' => '', + // 'Budget lines' => '', + // 'CAD - Canadian Dollar' => '', + // 'CHF - Swiss Francs' => '', + // 'Cost' => '', + // 'Cost breakdown' => '', + // 'Custom Stylesheet' => '', + // 'download' => '', + // 'Do you really want to remove this budget line?' => '', + // 'EUR - Euro' => '', + // 'Expenses' => '', + // 'GBP - British Pound' => '', + // 'INR - Indian Rupee' => '', + // 'JPY - Japanese Yen' => '', + // 'New budget line' => '', + // 'NZD - New Zealand Dollar' => '', + // 'Remove a budget line' => '', + // 'Remove budget line' => '', + // 'RSD - Serbian dinar' => '', + // 'The budget line have been created successfully.' => '', + // 'Unable to create the budget line.' => '', + // 'Unable to remove this budget line.' => '', + // 'USD - US Dollar' => '', + // 'Remaining' => '', +); diff --git a/app/Locale/zh_CN/translations.php b/app/Locale/zh_CN/translations.php index cd1e2ebe..31d63632 100644 --- a/app/Locale/zh_CN/translations.php +++ b/app/Locale/zh_CN/translations.php @@ -1,6 +1,8 @@ <?php return array( + // 'number.decimals_separator' => '', + // 'number.thousands_separator' => '', 'None' => '无', 'edit' => '编辑', 'Edit' => '编辑', @@ -379,7 +381,7 @@ return array( 'Created by %s' => '创建者:%s', 'Last modified on %B %e, %Y at %k:%M %p' => '最后修改:%Y/%m/%d/ %H:%M', 'Tasks Export' => '任务导出', - 'Tasks exportation for "%s"' => '导出任务"%s"', + 'Tasks exportation for "%s"' => '导出"%s"的任务', 'Start Date' => '开始时间', 'End Date' => '结束时间', 'Execute' => '执行', @@ -408,13 +410,13 @@ return array( 'Comment updated' => '更新了评论', 'New comment posted by %s' => '%s 的新评论', 'List of due tasks for the project "%s"' => '项目"%s"的到期任务列表', - // 'New attachment' => '', - // 'New comment' => '', - // 'New subtask' => '', - // 'Subtask updated' => '', - // 'Task updated' => '', - // 'Task closed' => '', - // 'Task opened' => '', + 'New attachment' => '新建附件', + 'New comment' => '新建评论', + 'New subtask' => '新建子任务', + 'Subtask updated' => '子任务更新', + 'Task updated' => '任务更新', + 'Task closed' => '任务关闭', + 'Task opened' => '任务开启', '[%s][Due tasks]' => '[%s][到期任务]', '[Kanboard] Notification' => '[Kanboard] 通知', 'I want to receive notifications only for those projects:' => '我仅需要收到下面项目的通知:', @@ -498,9 +500,9 @@ return array( 'Task assignee change' => '任务分配变更', '%s change the assignee of the task #%d to %s' => '%s 将任务 #%d 分配给了 %s', '%s changed the assignee of the task %s to %s' => '%s 将任务 %s 分配给 %s', - // 'Column Change' => '', - // 'Position Change' => '', - // 'Assignee Change' => '', + 'Column Change' => '栏目变更', + 'Position Change' => '位置变更', + 'Assignee Change' => '负责人变更', 'New password for the user "%s"' => '用户"%s"的新密码', 'Choose an event' => '选择一个事件', 'Github commit received' => '收到了Github提交', @@ -607,8 +609,8 @@ return array( 'Default swimlane' => '默认泳道', 'Do you really want to remove this swimlane: "%s"?' => '确定要删除泳道:"%s"?', 'Inactive swimlanes' => '非活动泳道', - // 'Set project manager' => '', - // 'Set project member' => '', + 'Set project manager' => '设为项目经理', + 'Set project member' => '设为项目成员', 'Remove a swimlane' => '删除泳道', 'Rename' => '重命名', 'Show default swimlane' => '显示默认泳道', @@ -622,92 +624,92 @@ return array( 'Unable to remove this swimlane.' => '无法删除此泳道', 'Unable to update this swimlane.' => '无法更新此泳道', 'Your swimlane have been created successfully.' => '已经成功创建泳道。', - // 'Example: "Bug, Feature Request, Improvement"' => '', - // 'Default categories for new projects (Comma-separated)' => '', - // 'Gitlab commit received' => '', - // 'Gitlab issue opened' => '', - // 'Gitlab issue closed' => '', - // 'Gitlab webhooks' => '', - // 'Help on Gitlab webhooks' => '', - // 'Integrations' => '', - // 'Integration with third-party services' => '', - // 'Role for this project' => '', - // 'Project manager' => '', - // 'Project member' => '', - // 'A project manager can change the settings of the project and have more privileges than a standard user.' => '', - // 'Gitlab Issue' => '', - // 'Subtask Id' => '', - // 'Subtasks' => '', - // 'Subtasks Export' => '', - // 'Subtasks exportation for "%s"' => '', - // 'Task Title' => '', - // 'Untitled' => '', - // 'Application default' => '', - // 'Language:' => '', - // 'Timezone:' => '', - // 'All columns' => '', - // 'Calendar for "%s"' => '', - // 'Filter by column' => '', - // 'Filter by status' => '', - // 'Calendar' => '', - // 'Next' => '', - // '#%d' => '', - // 'Filter by color' => '', - // 'Filter by swimlane' => '', - // 'All swimlanes' => '', - // 'All colors' => '', - // 'All status' => '', - // 'Add a comment logging moving the task between columns' => '', - // 'Moved to column %s' => '', - // 'Change description' => '', - // 'User dashboard' => '', - // 'Allow only one subtask in progress at the same time for a user' => '', - // 'Edit column "%s"' => '', - // 'Enable time tracking for subtasks' => '', - // 'Select the new status of the subtask: "%s"' => '', - // 'Subtask timesheet' => '', - // 'There is nothing to show.' => '', - // 'Time Tracking' => '', - // 'You already have one subtask in progress' => '', - // 'Which parts of the project do you want to duplicate?' => '', - // 'Change dashboard view' => '', - // 'Show/hide activities' => '', - // 'Show/hide projects' => '', - // 'Show/hide subtasks' => '', - // 'Show/hide tasks' => '', - // 'Disable login form' => '', - // 'Show/hide calendar' => '', - // 'User calendar' => '', - // 'Bitbucket commit received' => '', - // 'Bitbucket webhooks' => '', - // 'Help on Bitbucket webhooks' => '', - // 'Start' => '', - // 'End' => '', - // 'Task age in days' => '', - // 'Days in this column' => '', - // '%dd' => '', - // 'Add a link' => '', - // 'Add a new link' => '', - // 'Do you really want to remove this link: "%s"?' => '', - // 'Do you really want to remove this link with task #%d?' => '', - // 'Field required' => '', - // 'Link added successfully.' => '', - // 'Link updated successfully.' => '', - // 'Link removed successfully.' => '', - // 'Link labels' => '', - // 'Link modification' => '', - // 'Links' => '', - // 'Link settings' => '', - // 'Opposite label' => '', - // 'Remove a link' => '', - // 'Task\'s links' => '', - // 'The labels must be different' => '', - // 'There is no link.' => '', - // 'This label must be unique' => '', - // 'Unable to create your link.' => '', - // 'Unable to update your link.' => '', - // 'Unable to remove this link.' => '', - // 'relates to' => '', + 'Example: "Bug, Feature Request, Improvement"' => '示例:“缺陷,功能需求,提升', + 'Default categories for new projects (Comma-separated)' => '新项目的默认分类(用逗号分隔)', + 'Gitlab commit received' => '收到 Gitlab 提交', + 'Gitlab issue opened' => '开启 Gitlab 问题', + 'Gitlab issue closed' => '关闭 Gitlab 问题', + 'Gitlab webhooks' => 'Gitlab 网络钩子', + 'Help on Gitlab webhooks' => 'Gitlab 网络钩子帮助', + 'Integrations' => '整合', + 'Integration with third-party services' => '与第三方服务进行整合', + 'Role for this project' => '项目角色', + 'Project manager' => '项目管理员', + 'Project member' => '项目成员', + 'A project manager can change the settings of the project and have more privileges than a standard user.' => '项目经理可以修改项目的设置,比标准用户多了一些权限', + 'Gitlab Issue' => 'Gitlab 问题', + 'Subtask Id' => '子任务 Id', + 'Subtasks' => '子任务', + 'Subtasks Export' => '子任务导出', + 'Subtasks exportation for "%s"' => '导出"%s"的子任务', + 'Task Title' => '任务标题', + 'Untitled' => '无标题', + 'Application default' => '程序默认', + 'Language:' => '语言:', + 'Timezone:' => '时区:', + 'All columns' => '全部栏目', + 'Calendar for "%s"' => '"%s"的日程表', + 'Filter by column' => '按栏目过滤', + 'Filter by status' => '按状态过滤', + 'Calendar' => '日程表', + 'Next' => '前进', + '#%d' => '#%d', + 'Filter by color' => '按颜色过滤', + 'Filter by swimlane' => '按泳道过滤', + 'All swimlanes' => '全部泳道', + 'All colors' => '全部颜色', + 'All status' => '全部状态', + 'Add a comment logging moving the task between columns' => '在不同栏目间移动任务时添加一个评论', + 'Moved to column %s' => '移动到栏目 %s', + 'Change description' => '修改描述', + 'User dashboard' => '用户仪表板', + 'Allow only one subtask in progress at the same time for a user' => '每用户同时仅有一个活动子任务', + 'Edit column "%s"' => '编辑栏目"%s"', + 'Enable time tracking for subtasks' => '启用子任务的时间记录', + 'Select the new status of the subtask: "%s"' => '选择子任务的新状态:"%s"', + 'Subtask timesheet' => '子任务时间', + 'There is nothing to show.' => '无内容。', + 'Time Tracking' => '时间记录', + 'You already have one subtask in progress' => '你已经有了一个进行中的子任务', + 'Which parts of the project do you want to duplicate?' => '要复制项目的哪些内容?', + 'Change dashboard view' => '修改仪表板视图', + 'Show/hide activities' => '显示/隐藏活动', + 'Show/hide projects' => '显示/隐藏项目', + 'Show/hide subtasks' => '显示/隐藏子任务', + 'Show/hide tasks' => '显示/隐藏任务', + 'Disable login form' => '禁用登录界面', + 'Show/hide calendar' => '显示/隐藏日程表', + 'User calendar' => '用户日程表', + 'Bitbucket commit received' => '收到Bitbucket提交', + 'Bitbucket webhooks' => 'Bitbucket网络钩子', + 'Help on Bitbucket webhooks' => 'Bitbucket网络钩子帮助', + 'Start' => '开始', + 'End' => '结束', + 'Task age in days' => '任务存在天数', + 'Days in this column' => '在此栏目的天数', + '%dd' => '%d天', + 'Add a link' => '添加一个关联', + 'Add a new link' => '添加一个新关联', + 'Do you really want to remove this link: "%s"?' => '确认要删除此关联吗:"%s"?', + 'Do you really want to remove this link with task #%d?' => '确认要删除到任务 #%d 的关联吗?', + 'Field required' => '必须的字段', + 'Link added successfully.' => '成功添加关联。', + 'Link updated successfully.' => '成功更新关联。', + 'Link removed successfully.' => '成功删除关联。', + 'Link labels' => '关联标签', + 'Link modification' => '关联修改', + 'Links' => '关联', + 'Link settings' => '关联设置', + 'Opposite label' => '反向标签', + 'Remove a link' => '删除关联', + 'Task\'s links' => '任务的关联', + 'The labels must be different' => '标签不能一样', + 'There is no link.' => '没有关联', + 'This label must be unique' => '关联必须唯一', + 'Unable to create your link.' => '无法创建关联。', + 'Unable to update your link.' => '无法更新关联。', + 'Unable to remove this link.' => '无法删除关联。', + 'relates to' => '关联到', // 'blocks' => '', // 'is blocked by' => '', // 'duplicates' => '', @@ -718,20 +720,88 @@ return array( // 'is a milestone of' => '', // 'fixes' => '', // 'is fixed by' => '', - // 'This task' => '', + 'This task' => '此任务', // '<1h' => '', // '%dh' => '', // '%b %e' => '', - // 'Expand tasks' => '', - // 'Collapse tasks' => '', - // 'Expand/collapse tasks' => '', - // 'Close dialog box' => '', - // 'Submit a form' => '', - // 'Board view' => '', - // 'Keyboard shortcuts' => '', - // 'Open board switcher' => '', - // 'Application' => '', - // 'Filter recently updated' => '', + 'Expand tasks' => '展开任务', + 'Collapse tasks' => '收缩任务', + 'Expand/collapse tasks' => '展开/收缩任务', + 'Close dialog box' => '关闭对话框', + 'Submit a form' => '提交表单', + 'Board view' => '面板视图', + 'Keyboard shortcuts' => '键盘快捷方式', + 'Open board switcher' => '打开面板切换器', + 'Application' => '应用程序', + 'Filter recently updated' => '过滤最近的更新', // 'since %B %e, %Y at %k:%M %p' => '', - // 'More filters' => '', + 'More filters' => '更多过滤', + // 'Compact view' => '', + // 'Horizontal scrolling' => '', + // 'Compact/wide view' => '', + // 'No results match:' => '', + // 'Remove hourly rate' => '', + // 'Do you really want to remove this hourly rate?' => '', + // 'Hourly rates' => '', + // 'Hourly rate' => '', + // 'Currency' => '', + // 'Effective date' => '', + // 'Add new rate' => '', + // 'Rate removed successfully.' => '', + // 'Unable to remove this rate.' => '', + // 'Unable to save the hourly rate.' => '', + // 'Hourly rate created successfully.' => '', + // 'Start time' => '', + // 'End time' => '', + // 'Comment' => '', + // 'All day' => '', + // 'Day' => '', + // 'Manage timetable' => '', + // 'Overtime timetable' => '', + // 'Time off timetable' => '', + // 'Timetable' => '', + // 'Work timetable' => '', + // 'Week timetable' => '', + // 'Day timetable' => '', + // 'From' => '', + // 'To' => '', + // 'Time slot created successfully.' => '', + // 'Unable to save this time slot.' => '', + // 'Time slot removed successfully.' => '', + // 'Unable to remove this time slot.' => '', + // 'Do you really want to remove this time slot?' => '', + // 'Remove time slot' => '', + // 'Add new time slot' => '', + // 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => '', + // 'Files' => '', + // 'Images' => '', + // 'Private project' => '', + // 'Amount' => '', + // 'AUD - Australian Dollar' => '', + // 'Budget' => '', + // 'Budget line' => '', + // 'Budget line removed successfully.' => '', + // 'Budget lines' => '', + // 'CAD - Canadian Dollar' => '', + // 'CHF - Swiss Francs' => '', + // 'Cost' => '', + // 'Cost breakdown' => '', + // 'Custom Stylesheet' => '', + // 'download' => '', + // 'Do you really want to remove this budget line?' => '', + // 'EUR - Euro' => '', + // 'Expenses' => '', + // 'GBP - British Pound' => '', + // 'INR - Indian Rupee' => '', + // 'JPY - Japanese Yen' => '', + // 'New budget line' => '', + // 'NZD - New Zealand Dollar' => '', + // 'Remove a budget line' => '', + // 'Remove budget line' => '', + // 'RSD - Serbian dinar' => '', + // 'The budget line have been created successfully.' => '', + // 'Unable to create the budget line.' => '', + // 'Unable to remove this budget line.' => '', + // 'USD - US Dollar' => '', + // 'Remaining' => '', ); diff --git a/app/Model/Acl.php b/app/Model/Acl.php index 9fc81747..b52a7864 100644 --- a/app/Model/Acl.php +++ b/app/Model/Acl.php @@ -56,6 +56,7 @@ class Acl extends Base 'export' => array('tasks', 'subtasks', 'summary'), 'project' => array('edit', 'update', 'share', 'integration', 'users', 'alloweverybody', 'allow', 'setowner', 'revoke', 'duplicate', 'disable', 'enable'), 'swimlane' => '*', + 'budget' => '*', ); /** @@ -70,6 +71,7 @@ class Acl extends Base 'config' => '*', 'link' => '*', 'project' => array('remove'), + 'hourlyrate' => '*', ); /** diff --git a/app/Model/Action.php b/app/Model/Action.php index c1662fad..0e132d73 100644 --- a/app/Model/Action.php +++ b/app/Model/Action.php @@ -213,7 +213,7 @@ class Action extends Base * * @access public * @param array $values Required parameters to save an action - * @return bool Success or not + * @return integer */ public function create(array $values) { @@ -250,7 +250,7 @@ class Action extends Base // $this->container['fileCache']->remove('proxy_action_getAll'); - return true; + return $action_id; } /** diff --git a/app/Model/Base.php b/app/Model/Base.php index f836231c..0217aae3 100644 --- a/app/Model/Base.php +++ b/app/Model/Base.php @@ -16,6 +16,7 @@ use Pimple\Container; * @property \Model\Action $action * @property \Model\Authentication $authentication * @property \Model\Board $board + * @property \Model\Budget $budget * @property \Model\Category $category * @property \Model\Comment $comment * @property \Model\CommentHistory $commentHistory @@ -42,7 +43,7 @@ use Pimple\Container; * @property \Model\TaskLink $taskLink * @property \Model\TaskPosition $taskPosition * @property \Model\TaskValidator $taskValidator - * @property \Model\TimeTracking $timeTracking + * @property \Model\Timetable $timetable * @property \Model\SubtaskTimeTracking $subtaskTimeTracking * @property \Model\User $user * @property \Model\UserSession $userSession diff --git a/app/Model/Budget.php b/app/Model/Budget.php new file mode 100644 index 00000000..84cadf6e --- /dev/null +++ b/app/Model/Budget.php @@ -0,0 +1,210 @@ +<?php + +namespace Model; + +use DateInterval; +use DateTime; +use SimpleValidator\Validator; +use SimpleValidator\Validators; + +/** + * Budget + * + * @package model + * @author Frederic Guillot + */ +class Budget extends Base +{ + /** + * SQL table name + * + * @var string + */ + const TABLE = 'budget_lines'; + + /** + * Get all budget lines for a project + * + * @access public + * @param integer $project_id + * @return array + */ + public function getAll($project_id) + { + return $this->db->table(self::TABLE)->eq('project_id', $project_id)->desc('date')->findAll(); + } + + /** + * Get the current total of the budget + * + * @access public + * @param integer $project_id + * @return float + */ + public function getTotal($project_id) + { + $result = $this->db->table(self::TABLE)->columns('SUM(amount) as total')->eq('project_id', $project_id)->findOne(); + return isset($result['total']) ? (float) $result['total'] : 0; + } + + /** + * Get breakdown by tasks/subtasks/users + * + * @access public + * @param integer $project_id + * @return \PicoDb\Table + */ + public function getSubtaskBreakdown($project_id) + { + return $this->db + ->table(SubtaskTimeTracking::TABLE) + ->columns( + SubtaskTimeTracking::TABLE.'.id', + SubtaskTimeTracking::TABLE.'.user_id', + SubtaskTimeTracking::TABLE.'.subtask_id', + SubtaskTimeTracking::TABLE.'.start', + SubtaskTimeTracking::TABLE.'.time_spent', + Subtask::TABLE.'.task_id', + Subtask::TABLE.'.title AS subtask_title', + Task::TABLE.'.title AS task_title', + Task::TABLE.'.project_id', + User::TABLE.'.username', + User::TABLE.'.name' + ) + ->join(Subtask::TABLE, 'id', 'subtask_id') + ->join(Task::TABLE, 'id', 'task_id', Subtask::TABLE) + ->join(User::TABLE, 'id', 'user_id') + ->eq(Task::TABLE.'.project_id', $project_id) + ->filter(array($this, 'applyUserRate')); + } + + /** + * Gather necessary information to display the budget graph + * + * @access public + * @param integer $project_id + * @return array + */ + public function getDailyBudgetBreakdown($project_id) + { + $out = array(); + $in = $this->db->hashtable(self::TABLE)->eq('project_id', $project_id)->gt('amount', 0)->asc('date')->getAll('date', 'amount'); + $time_slots = $this->getSubtaskBreakdown($project_id)->findAll(); + + foreach ($time_slots as $slot) { + $date = date('Y-m-d', $slot['start']); + + if (! isset($out[$date])) { + $out[$date] = 0; + } + + $out[$date] += $slot['cost']; + } + + $start = key($in) ?: key($out); + $end = new DateTime; + $left = 0; + $serie = array(); + + for ($today = new DateTime($start); $today <= $end; $today->add(new DateInterval('P1D'))) { + + $date = $today->format('Y-m-d'); + $today_in = isset($in[$date]) ? (int) $in[$date] : 0; + $today_out = isset($out[$date]) ? (int) $out[$date] : 0; + $left += $today_in; + $left -= $today_out; + + $serie[] = array( + 'date' => $date, + 'in' => $today_in, + 'out' => -$today_out, + 'left' => $left, + ); + } + + return $serie; + } + + /** + * Filter callback to apply the rate according to the effective date + * + * @access public + * @param array $records + * @return array + */ + public function applyUserRate(array $records) + { + $rates = $this->hourlyRate->getAllByProject($records[0]['project_id']); + + foreach ($records as &$record) { + + $hourly_price = 0; + + foreach ($rates as $rate) { + + if ($rate['user_id'] == $record['user_id'] && date('Y-m-d', $rate['date_effective']) <= date('Y-m-d', $record['start'])) { + $hourly_price = $rate['rate']; + break; + } + } + + $record['cost'] = $hourly_price * $record['time_spent']; + } + + return $records; + } + + /** + * Add a new budget line in the database + * + * @access public + * @param integer $project_id + * @param float $amount + * @param string $comment + * @param string $date + * @return boolean|integer + */ + public function create($project_id, $amount, $comment, $date = '') + { + $values = array( + 'project_id' => $project_id, + 'amount' => $amount, + 'comment' => $comment, + 'date' => $date ?: date('Y-m-d'), + ); + + return $this->persist(self::TABLE, $values); + } + + /** + * Remove a specific budget line + * + * @access public + * @param integer $budget_id + * @return boolean + */ + public function remove($budget_id) + { + return $this->db->table(self::TABLE)->eq('id', $budget_id)->remove(); + } + + /** + * Validate creation + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateCreation(array $values) + { + $v = new Validator($values, array( + new Validators\Required('project_id', t('Field required')), + new Validators\Required('amount', t('Field required')), + )); + + return array( + $v->execute(), + $v->getErrors() + ); + } +}
\ No newline at end of file diff --git a/app/Model/Config.php b/app/Model/Config.php index 48640f4e..736ae08d 100644 --- a/app/Model/Config.php +++ b/app/Model/Config.php @@ -22,6 +22,28 @@ class Config extends Base const TABLE = 'settings'; /** + * Get available currencies + * + * @access public + * @return array + */ + public function getCurrencies() + { + return array( + 'USD' => t('USD - US Dollar'), + 'EUR' => t('EUR - Euro'), + 'GBP' => t('GBP - British Pound'), + 'CHF' => t('CHF - Swiss Francs'), + 'CAD' => t('CAD - Canadian Dollar'), + 'AUD' => t('AUD - Australian Dollar'), + 'NZD' => t('NZD - New Zealand Dollar'), + 'INR' => t('INR - Indian Rupee'), + 'JPY' => t('JPY - Japanese Yen'), + 'RSD' => t('RSD - Serbian dinar'), + ); + } + + /** * Get available timezones * * @access public @@ -58,11 +80,14 @@ class Config extends Base 'fr_FR' => 'Français', 'it_IT' => 'Italiano', 'hu_HU' => 'Magyar', + 'nl_NL' => 'Nederlands', 'pl_PL' => 'Polski', 'pt_BR' => 'Português (Brasil)', 'ru_RU' => 'Русский', + 'sr_Latn_RS' => 'Srpski', 'fi_FI' => 'Suomi', 'sv_SE' => 'Svenska', + 'tr_TR' => 'Türkçe', 'zh_CN' => '中文(简体)', 'ja_JP' => '日本語', 'th_TH' => 'ไทย', @@ -99,6 +124,7 @@ class Config extends Base 'zh_CN' => 'zh-cn', 'ja_JP' => 'ja', 'th_TH' => 'th', + 'tr_TR' => 'tr', ); $lang = $this->getCurrentLanguage(); diff --git a/app/Model/DateParser.php b/app/Model/DateParser.php index 8a4d3edd..a0d10a36 100644 --- a/app/Model/DateParser.php +++ b/app/Model/DateParser.php @@ -13,6 +13,47 @@ use DateTime; class DateParser extends Base { /** + * Return true if the date is within the date range + * + * @access public + * @param DateTime $date + * @param DateTime $start + * @param DateTime $end + * @return boolean + */ + public function withinDateRange(DateTime $date, DateTime $start, DateTime $end) + { + return $date >= $start && $date <= $end; + } + + /** + * Get the total number of hours between 2 datetime objects + * Minutes are rounded to the nearest quarter + * + * @access public + * @param DateTime $d1 + * @param DateTime $d2 + * @return float + */ + public function getHours(DateTime $d1, DateTime $d2) + { + $seconds = $this->getRoundedSeconds(abs($d1->getTimestamp() - $d2->getTimestamp())); + return round($seconds / 3600, 2); + } + + /** + * Round the timestamp to the nearest quarter + * + * @access public + * @param integer $seconds Timestamp + * @return integer + */ + public function getRoundedSeconds($seconds) + { + return (int) round($seconds / (15 * 60)) * (15 * 60); + } + + /** * Return a timestamp if the given date format is correct otherwise return 0 * * @access public diff --git a/app/Model/File.php b/app/Model/File.php index 1b9351db..a8cce9f4 100644 --- a/app/Model/File.php +++ b/app/Model/File.php @@ -17,7 +17,7 @@ class File extends Base * * @var string */ - const TABLE = 'task_has_files'; + const TABLE = 'files'; /** * Events @@ -113,6 +113,38 @@ class File extends Base } /** + * Get all images for a given task + * + * @access public + * @param integer $task_id Task id + * @return array + */ + public function getAllImages($task_id) + { + return $this->db->table(self::TABLE) + ->eq('task_id', $task_id) + ->eq('is_image', 1) + ->asc('name') + ->findAll(); + } + + /** + * Get all files without images for a given task + * + * @access public + * @param integer $task_id Task id + * @return array + */ + public function getAllDocuments($task_id) + { + return $this->db->table(self::TABLE) + ->eq('task_id', $task_id) + ->eq('is_image', 0) + ->asc('name') + ->findAll(); + } + + /** * Check if a filename is an image * * @access public @@ -121,7 +153,17 @@ class File extends Base */ public function isImage($filename) { - return getimagesize($filename) !== false; + $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); + + switch ($extension) { + case 'jpeg': + case 'jpg': + case 'png': + case 'gif': + return true; + } + + return false; } /** @@ -188,7 +230,7 @@ class File extends Base $task_id, $original_filename, $destination_filename, - $this->isImage(FILES_DIR.$destination_filename) + $this->isImage($original_filename) ); } } diff --git a/app/Model/HourlyRate.php b/app/Model/HourlyRate.php new file mode 100644 index 00000000..1550bdae --- /dev/null +++ b/app/Model/HourlyRate.php @@ -0,0 +1,121 @@ +<?php + +namespace Model; + +use SimpleValidator\Validator; +use SimpleValidator\Validators; + +/** + * Hourly Rate + * + * @package model + * @author Frederic Guillot + */ +class HourlyRate extends Base +{ + /** + * SQL table name + * + * @var string + */ + const TABLE = 'hourly_rates'; + + /** + * Get all user rates for a given project + * + * @access public + * @param integer $project_id + * @return array + */ + public function getAllByProject($project_id) + { + $members = $this->projectPermission->getMembers($project_id); + + if (empty($members)) { + return array(); + } + + return $this->db->table(self::TABLE)->in('user_id', array_keys($members))->desc('date_effective')->findAll(); + } + + /** + * Get all rates for a given user + * + * @access public + * @param integer $user_id User id + * @return array + */ + public function getAllByUser($user_id) + { + return $this->db->table(self::TABLE)->eq('user_id', $user_id)->desc('date_effective')->findAll(); + } + + /** + * Get current rate for a given user + * + * @access public + * @param integer $user_id User id + * @return float + */ + public function getCurrentRate($user_id) + { + return $this->db->table(self::TABLE)->eq('user_id', $user_id)->desc('date_effective')->findOneColumn('rate') ?: 0; + } + + /** + * Add a new rate in the database + * + * @access public + * @param integer $user_id User id + * @param float $rate Hourly rate + * @param string $currency Currency code + * @param string $date ISO8601 date format + * @return boolean|integer + */ + public function create($user_id, $rate, $currency, $date) + { + $values = array( + 'user_id' => $user_id, + 'rate' => $rate, + 'currency' => $currency, + 'date_effective' => $this->dateParser->removeTimeFromTimestamp($this->dateParser->getTimestamp($date)), + ); + + return $this->persist(self::TABLE, $values); + } + + /** + * Remove a specific rate + * + * @access public + * @param integer $rate_id + * @return boolean + */ + public function remove($rate_id) + { + return $this->db->table(self::TABLE)->eq('id', $rate_id)->remove(); + } + + /** + * Validate creation + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateCreation(array $values) + { + $v = new Validator($values, array( + new Validators\Required('user_id', t('Field required')), + new Validators\Required('rate', t('Field required')), + new Validators\Numeric('rate', t('This value must be numeric')), + new Validators\Required('date_effective', t('Field required')), + new Validators\Required('currency', t('Field required')), + )); + + return array( + $v->execute(), + $v->getErrors() + ); + } +} diff --git a/app/Model/ProjectPermission.php b/app/Model/ProjectPermission.php index 12bd9309..d4f44f66 100644 --- a/app/Model/ProjectPermission.php +++ b/app/Model/ProjectPermission.php @@ -316,7 +316,10 @@ class ProjectPermission extends Base { return $this->db ->hashtable(Project::TABLE) - ->eq('user_id', $user_id) + ->beginOr() + ->eq(self::TABLE.'.user_id', $user_id) + ->eq(Project::TABLE.'.is_everybody_allowed', 1) + ->closeOr() ->join(self::TABLE, 'project_id', 'id') ->getAll('projects.id', 'name'); } @@ -332,7 +335,10 @@ class ProjectPermission extends Base { return $this->db ->table(Project::TABLE) - ->eq('user_id', $user_id) + ->beginOr() + ->eq(self::TABLE.'.user_id', $user_id) + ->eq(Project::TABLE.'.is_everybody_allowed', 1) + ->closeOr() ->join(self::TABLE, 'project_id', 'id') ->findAllByColumn('projects.id'); } @@ -348,7 +354,10 @@ class ProjectPermission extends Base { return $this->db ->table(Project::TABLE) - ->eq('user_id', $user_id) + ->beginOr() + ->eq(self::TABLE.'.user_id', $user_id) + ->eq(Project::TABLE.'.is_everybody_allowed', 1) + ->closeOr() ->eq(Project::TABLE.'.is_active', Project::ACTIVE) ->join(self::TABLE, 'project_id', 'id') ->findAllByColumn('projects.id'); @@ -365,7 +374,10 @@ class ProjectPermission extends Base { return $this->db ->hashtable(Project::TABLE) - ->eq('user_id', $user_id) + ->beginOr() + ->eq(self::TABLE.'.user_id', $user_id) + ->eq(Project::TABLE.'.is_everybody_allowed', 1) + ->closeOr() ->eq(Project::TABLE.'.is_active', Project::ACTIVE) ->join(self::TABLE, 'project_id', 'id') ->getAll('projects.id', 'name'); diff --git a/app/Model/Subtask.php b/app/Model/Subtask.php index 048594bd..e33373e0 100644 --- a/app/Model/Subtask.php +++ b/app/Model/Subtask.php @@ -19,7 +19,7 @@ class Subtask extends Base * * @var string */ - const TABLE = 'task_has_subtasks'; + const TABLE = 'subtasks'; /** * Task "done" status @@ -122,7 +122,7 @@ class Subtask extends Base ->eq('task_id', $task_id) ->columns(self::TABLE.'.*', User::TABLE.'.username', User::TABLE.'.name') ->join(User::TABLE, 'id', 'user_id') - ->asc(self::TABLE.'.id') + ->asc(self::TABLE.'.position') ->filter(array($this, 'addStatusName')) ->findAll(); } @@ -164,6 +164,22 @@ class Subtask extends Base } /** + * Get the position of the last column for a given project + * + * @access public + * @param integer $task_id Task id + * @return integer + */ + public function getLastPosition($task_id) + { + return (int) $this->db + ->table(self::TABLE) + ->eq('task_id', $task_id) + ->desc('position') + ->findOneColumn('position'); + } + + /** * Create a new subtask * * @access public @@ -173,6 +189,8 @@ class Subtask extends Base public function create(array $values) { $this->prepare($values); + $values['position'] = $this->getLastPosition($values['task_id']) + 1; + $subtask_id = $this->persist(self::TABLE, $values); if ($subtask_id) { @@ -209,6 +227,64 @@ class Subtask extends Base } /** + * Move a subtask down, increment the position value + * + * @access public + * @param integer $task_id + * @param integer $subtask_id + * @return boolean + */ + public function moveDown($task_id, $subtask_id) + { + $subtasks = $this->db->hashtable(self::TABLE)->eq('task_id', $task_id)->asc('position')->getAll('id', 'position'); + $positions = array_flip($subtasks); + + if (isset($subtasks[$subtask_id]) && $subtasks[$subtask_id] < count($subtasks)) { + + $position = ++$subtasks[$subtask_id]; + $subtasks[$positions[$position]]--; + + $this->db->startTransaction(); + $this->db->table(self::TABLE)->eq('id', $subtask_id)->update(array('position' => $position)); + $this->db->table(self::TABLE)->eq('id', $positions[$position])->update(array('position' => $subtasks[$positions[$position]])); + $this->db->closeTransaction(); + + return true; + } + + return false; + } + + /** + * Move a subtask up, decrement the position value + * + * @access public + * @param integer $task_id + * @param integer $subtask_id + * @return boolean + */ + public function moveUp($task_id, $subtask_id) + { + $subtasks = $this->db->hashtable(self::TABLE)->eq('task_id', $task_id)->asc('position')->getAll('id', 'position'); + $positions = array_flip($subtasks); + + if (isset($subtasks[$subtask_id]) && $subtasks[$subtask_id] > 1) { + + $position = --$subtasks[$subtask_id]; + $subtasks[$positions[$position]]++; + + $this->db->startTransaction(); + $this->db->table(self::TABLE)->eq('id', $subtask_id)->update(array('position' => $position)); + $this->db->table(self::TABLE)->eq('id', $positions[$position])->update(array('position' => $subtasks[$positions[$position]])); + $this->db->closeTransaction(); + + return true; + } + + return false; + } + + /** * Change the status of subtask * * Todo -> In progress -> Done -> Todo -> etc... @@ -286,9 +362,9 @@ class Subtask extends Base return $this->db->transaction(function ($db) use ($src_task_id, $dst_task_id) { $subtasks = $db->table(Subtask::TABLE) - ->columns('title', 'time_estimated') + ->columns('title', 'time_estimated', 'position') ->eq('task_id', $src_task_id) - ->asc('id') // Explicit sorting for postgresql + ->asc('position') ->findAll(); foreach ($subtasks as &$subtask) { @@ -380,7 +456,7 @@ class Subtask extends Base return array( new Validators\Integer('id', t('The subtask id must be an integer')), new Validators\Integer('task_id', t('The task id must be an integer')), - new Validators\MaxLength('title', t('The maximum length is %d characters', 100), 100), + new Validators\MaxLength('title', t('The maximum length is %d characters', 255), 255), new Validators\Integer('user_id', t('The user id must be an integer')), new Validators\Integer('status', t('The status must be an integer')), new Validators\Numeric('time_estimated', t('The time must be a numeric value')), diff --git a/app/Model/SubtaskTimeTracking.php b/app/Model/SubtaskTimeTracking.php index 8b197c46..a984533f 100644 --- a/app/Model/SubtaskTimeTracking.php +++ b/app/Model/SubtaskTimeTracking.php @@ -2,6 +2,8 @@ namespace Model; +use DateTime; + /** * Subtask timesheet * @@ -33,6 +35,7 @@ class SubtaskTimeTracking extends Base self::TABLE.'.subtask_id', self::TABLE.'.end', self::TABLE.'.start', + self::TABLE.'.time_spent', Subtask::TABLE.'.task_id', Subtask::TABLE.'.title AS subtask_title', Task::TABLE.'.title AS task_title', @@ -60,6 +63,7 @@ class SubtaskTimeTracking extends Base self::TABLE.'.subtask_id', self::TABLE.'.end', self::TABLE.'.start', + self::TABLE.'.time_spent', self::TABLE.'.user_id', Subtask::TABLE.'.task_id', Subtask::TABLE.'.title AS subtask_title', @@ -89,6 +93,7 @@ class SubtaskTimeTracking extends Base self::TABLE.'.subtask_id', self::TABLE.'.end', self::TABLE.'.start', + self::TABLE.'.time_spent', self::TABLE.'.user_id', Subtask::TABLE.'.task_id', Subtask::TABLE.'.title AS subtask_title', @@ -133,6 +138,8 @@ class SubtaskTimeTracking extends Base ->addCondition($this->getCalendarCondition($start, $end)) ->findAll(); + $result = $this->timetable->calculateEventsIntersect($user_id, $result, $start, $end); + return $this->toCalendarEvents($result); } @@ -235,7 +242,11 @@ class SubtaskTimeTracking extends Base */ public function logEndTime($subtask_id, $user_id) { - $this->updateSubtaskTimeSpent($subtask_id, $user_id); + $time_spent = $this->getTimeSpent($subtask_id, $user_id); + + if ($time_spent > 0) { + $this->updateSubtaskTimeSpent($subtask_id, $time_spent); + } return $this->db ->table(self::TABLE) @@ -243,11 +254,60 @@ class SubtaskTimeTracking extends Base ->eq('user_id', $user_id) ->eq('end', 0) ->update(array( - 'end' => time() + 'end' => time(), + 'time_spent' => $time_spent, )); } /** + * Calculate the time spent when the clock is stopped + * + * @access public + * @param integer $subtask_id + * @param integer $user_id + * @return float + */ + public function getTimeSpent($subtask_id, $user_id) + { + $start_time = $this->db + ->table(self::TABLE) + ->eq('subtask_id', $subtask_id) + ->eq('user_id', $user_id) + ->eq('end', 0) + ->findOneColumn('start'); + + if ($start_time) { + + $start = new DateTime; + $start->setTimestamp($start_time); + + return $this->timetable->calculateEffectiveDuration($user_id, $start, new DateTime); + } + + return 0; + } + + /** + * Update subtask time spent + * + * @access public + * @param integer $subtask_id + * @param float $time_spent + * @return bool + */ + public function updateSubtaskTimeSpent($subtask_id, $time_spent) + { + $subtask = $this->subtask->getById($subtask_id); + + // Fire the event subtask.update + return $this->subtask->update(array( + 'id' => $subtask['id'], + 'time_spent' => $subtask['time_spent'] + $time_spent, + 'task_id' => $subtask['task_id'], + )); + } + + /** * Update task time tracking based on subtasks time tracking * * @access public @@ -289,31 +349,4 @@ class SubtaskTimeTracking extends Base ) ->findOne(); } - - /** - * Update subtask time spent based on the punch clock table - * - * @access public - * @param integer $subtask_id - * @param integer $user_id - * @return bool - */ - public function updateSubtaskTimeSpent($subtask_id, $user_id) - { - $start_time = $this->db - ->table(self::TABLE) - ->eq('subtask_id', $subtask_id) - ->eq('user_id', $user_id) - ->eq('end', 0) - ->findOneColumn('start'); - - $subtask = $this->subtask->getById($subtask_id); - - return $start_time && - $this->subtask->update(array( // Fire the event subtask.update - 'id' => $subtask['id'], - 'time_spent' => $subtask['time_spent'] + round((time() - $start_time) / 3600, 1), - 'task_id' => $subtask['task_id'], - )); - } } diff --git a/app/Model/Swimlane.php b/app/Model/Swimlane.php index c9bc43e1..cf2103c2 100644 --- a/app/Model/Swimlane.php +++ b/app/Model/Swimlane.php @@ -75,6 +75,22 @@ class Swimlane extends Base } /** + * Get a swimlane by the project and the name + * + * @access public + * @param integer $project_id Project id + * @param string $name Swimlane name + * @return array + */ + public function getByName($project_id, $name) + { + return $this->db->table(self::TABLE) + ->eq('project_id', $project_id) + ->eq('name', $name) + ->findAll(); + } + + /** * Get default swimlane properties * * @access public @@ -83,10 +99,16 @@ class Swimlane extends Base */ public function getDefault($project_id) { - return $this->db->table(Project::TABLE) - ->eq('id', $project_id) - ->columns('id', 'default_swimlane', 'show_default_swimlane') - ->findOne(); + $result = $this->db->table(Project::TABLE) + ->eq('id', $project_id) + ->columns('id', 'default_swimlane', 'show_default_swimlane') + ->findOne(); + + if ($result['default_swimlane'] === 'Default swimlane') { + $result['default_swimlane'] = t($result['default_swimlane']); + } + + return $result; } /** @@ -150,6 +172,11 @@ class Swimlane extends Base ->findOneColumn('default_swimlane'); if ($default_swimlane) { + + if ($default_swimlane === 'Default swimlane') { + $default_swimlane = t($default_swimlane); + } + array_unshift($swimlanes, array('id' => 0, 'name' => $default_swimlane)); } @@ -167,14 +194,17 @@ class Swimlane extends Base public function getList($project_id, $prepend = false) { $swimlanes = array(); - $swimlanes[] = $this->db->table(Project::TABLE)->eq('id', $project_id)->findOneColumn('default_swimlane'); + $default = $this->db->table(Project::TABLE)->eq('id', $project_id)->eq('show_default_swimlane', 1)->findOneColumn('default_swimlane'); - $swimlanes = array_merge( - $swimlanes, - $this->db->hashtable(self::TABLE)->eq('project_id', $project_id)->orderBy('name', 'asc')->getAll('id', 'name') - ); + if ($prepend) { + $swimlanes[-1] = t('All swimlanes'); + } + + if (! empty($default)) { + $swimlanes[0] = $default === 'Default swimlane' ? t($default) : $default; + } - return $prepend ? array(-1 => t('All swimlanes')) + $swimlanes : $swimlanes; + return $swimlanes + $this->db->hashtable(self::TABLE)->eq('project_id', $project_id)->orderBy('position', 'asc')->getAll('id', 'name'); } /** diff --git a/app/Model/TaskFinder.php b/app/Model/TaskFinder.php index 98ece4e1..7216e92a 100644 --- a/app/Model/TaskFinder.php +++ b/app/Model/TaskFinder.php @@ -80,11 +80,11 @@ class TaskFinder extends Base return $this->db ->table(Task::TABLE) ->columns( - '(SELECT count(*) FROM comments WHERE task_id=tasks.id) AS nb_comments', - '(SELECT count(*) FROM task_has_files WHERE task_id=tasks.id) AS nb_files', - '(SELECT count(*) FROM task_has_subtasks WHERE task_id=tasks.id) AS nb_subtasks', - '(SELECT count(*) FROM task_has_subtasks WHERE task_id=tasks.id AND status=2) AS nb_completed_subtasks', - '(SELECT count(*) FROM ' . TaskLink::TABLE . ' WHERE ' . TaskLink::TABLE . '.task_id = tasks.id) AS nb_links', + '(SELECT count(*) FROM '.Comment::TABLE.' WHERE task_id=tasks.id) AS nb_comments', + '(SELECT count(*) FROM '.File::TABLE.' WHERE task_id=tasks.id) AS nb_files', + '(SELECT count(*) FROM '.Subtask::TABLE.' WHERE '.Subtask::TABLE.'.task_id=tasks.id) AS nb_subtasks', + '(SELECT count(*) FROM '.Subtask::TABLE.' WHERE '.Subtask::TABLE.'.task_id=tasks.id AND status=2) AS nb_completed_subtasks', + '(SELECT count(*) FROM '.TaskLink::TABLE.' WHERE '.TaskLink::TABLE.'.task_id = tasks.id) AS nb_links', 'tasks.id', 'tasks.reference', 'tasks.title', diff --git a/app/Model/TaskLink.php b/app/Model/TaskLink.php index f8e9f99e..62391371 100644 --- a/app/Model/TaskLink.php +++ b/app/Model/TaskLink.php @@ -4,6 +4,7 @@ namespace Model; use SimpleValidator\Validator; use SimpleValidator\Validators; +use PicoDb\Table; /** * TaskLink model @@ -57,6 +58,7 @@ class TaskLink extends Base ->join(Link::TABLE, 'id', 'link_id') ->join(Task::TABLE, 'id', 'opposite_task_id') ->join(Board::TABLE, 'id', 'column_id', Task::TABLE) + ->orderBy(Link::TABLE.'.id ASC, '.Board::TABLE.'.position ASC, '.Task::TABLE.'.is_active DESC, '.Task::TABLE.'.id', Table::SORT_ASC) ->findAll(); } diff --git a/app/Model/Timetable.php b/app/Model/Timetable.php new file mode 100644 index 00000000..da2ec10c --- /dev/null +++ b/app/Model/Timetable.php @@ -0,0 +1,358 @@ +<?php + +namespace Model; + +use DateTime; +use DateInterval; + +/** + * Timetable + * + * @package model + * @author Frederic Guillot + */ +class Timetable extends Base +{ + /** + * User time slots + * + * @access private + * @var array + */ + private $day; + private $week; + private $overtime; + private $timeoff; + + /** + * Get a set of events by using the intersection between the timetable and the time tracking data + * + * @access public + * @param integer $user_id + * @param array $events Time tracking data + * @param string $start ISO8601 date + * @param string $end ISO8601 date + * @return array + */ + public function calculateEventsIntersect($user_id, array $events, $start, $end) + { + $start_dt = new DateTime($start); + $start_dt->setTime(0, 0); + + $end_dt = new DateTime($end); + $end_dt->setTime(23, 59); + + $timetable = $this->calculate($user_id, $start_dt, $end_dt); + + // The user has no timetable + if (empty($this->week)) { + return $events; + } + + $results = array(); + + foreach ($events as $event) { + $results = array_merge($results, $this->calculateEventIntersect($event, $timetable)); + } + + return $results; + } + + /** + * Get a serie of events based on the timetable and the provided event + * + * @access public + * @param integer $user_id + * @param array $events Time tracking data + * @param string $start ISO8601 date + * @param string $end ISO8601 date + * @return array + */ + public function calculateEventIntersect(array $event, array $timetable) + { + $events = array(); + + foreach ($timetable as $slot) { + + $start_ts = $slot[0]->getTimestamp(); + $end_ts = $slot[1]->getTimestamp(); + + if ($start_ts > $event['end']) { + break; + } + + if ($event['start'] <= $start_ts) { + $event['start'] = $start_ts; + } + + if ($event['start'] >= $start_ts && $event['start'] <= $end_ts) { + + if ($event['end'] >= $end_ts) { + $events[] = array_merge($event, array('end' => $end_ts)); + } + else { + $events[] = $event; + break; + } + } + } + + return $events; + } + + /** + * Calculate effective worked hours by taking into consideration the timetable + * + * @access public + * @param integer $user_id + * @param \DateTime $start + * @param \DateTime $end + * @return float + */ + public function calculateEffectiveDuration($user_id, DateTime $start, DateTime $end) + { + $end_timetable = clone($end); + $end_timetable->setTime(23, 59); + + $timetable = $this->calculate($user_id, $start, $end_timetable); + $found_start = false; + $hours = 0; + + // The user has no timetable + if (empty($this->week)) { + return $this->dateParser->getHours($start, $end); + } + + foreach ($timetable as $slot) { + + $isStartSlot = $this->dateParser->withinDateRange($start, $slot[0], $slot[1]); + $isEndSlot = $this->dateParser->withinDateRange($end, $slot[0], $slot[1]); + + // Start and end are within the same time slot + if ($isStartSlot && $isEndSlot) { + return $this->dateParser->getHours($start, $end); + } + + // We found the start slot + if (! $found_start && $isStartSlot) { + $found_start = true; + $hours = $this->dateParser->getHours($start, $slot[1]); + } + else if ($found_start) { + + // We found the end slot + if ($isEndSlot) { + $hours += $this->dateParser->getHours($slot[0], $end); + break; + } + else { + + // Sum hours of the intermediate time slots + $hours += $this->dateParser->getHours($slot[0], $slot[1]); + } + } + } + + // The start date was not found in regular hours so we get the nearest time slot + if (! empty($timetable) && ! $found_start) { + $slot = $this->findClosestTimeSlot($start, $timetable); + + if ($start < $slot[0]) { + return $this->calculateEffectiveDuration($user_id, $slot[0], $end); + } + } + + return $hours; + } + + /** + * Find the nearest time slot + * + * @access public + * @param DateTime $date + * @param array $timetable + * @return array + */ + public function findClosestTimeSlot(DateTime $date, array $timetable) + { + $values = array(); + + foreach ($timetable as $slot) { + $t1 = abs($slot[0]->getTimestamp() - $date->getTimestamp()); + $t2 = abs($slot[1]->getTimestamp() - $date->getTimestamp()); + + $values[] = min($t1, $t2); + } + + asort($values); + return $timetable[key($values)]; + } + + /** + * Get the timetable for a user for a given date range + * + * @access public + * @param integer $user_id + * @param \DateTime $start + * @param \DateTime $end + * @return array + */ + public function calculate($user_id, DateTime $start, DateTime $end) + { + $timetable = array(); + + $this->day = $this->timetableDay->getByUser($user_id); + $this->week = $this->timetableWeek->getByUser($user_id); + $this->overtime = $this->timetableExtra->getByUserAndDate($user_id, $start->format('Y-m-d'), $end->format('Y-m-d')); + $this->timeoff = $this->timetableOff->getByUserAndDate($user_id, $start->format('Y-m-d'), $end->format('Y-m-d')); + + for ($today = clone($start); $today <= $end; $today->add(new DateInterval('P1D'))) { + $week_day = $today->format('N'); + $timetable = array_merge($timetable, $this->getWeekSlots($today, $week_day)); + $timetable = array_merge($timetable, $this->getOvertimeSlots($today, $week_day)); + } + + return $timetable; + } + + /** + * Return worked time slots for the given day + * + * @access public + * @param \DateTime $today + * @param string $week_day + * @return array + */ + public function getWeekSlots(DateTime $today, $week_day) + { + $slots = array(); + $dayoff = $this->getDayOff($today); + + if (! empty($dayoff) && $dayoff['all_day'] == 1) { + return array(); + } + + foreach ($this->week as $slot) { + if ($week_day == $slot['day']) { + $slots = array_merge($slots, $this->getDayWorkSlots($slot, $dayoff, $today)); + } + } + + return $slots; + } + + /** + * Get the overtime time slots for the given day + * + * @access public + * @param \DateTime $today + * @param string $week_day + * @return array + */ + public function getOvertimeSlots(DateTime $today, $week_day) + { + $slots = array(); + + foreach ($this->overtime as $slot) { + + $day = new DateTime($slot['date']); + + if ($week_day == $day->format('N')) { + + if ($slot['all_day'] == 1) { + $slots = array_merge($slots, $this->getDaySlots($today)); + } + else { + $slots[] = $this->getTimeSlot($slot, $day); + } + } + } + + return $slots; + } + + /** + * Get worked time slots and remove time off + * + * @access public + * @param array $slot + * @param array $dayoff + * @param \DateTime $today + * @return array + */ + public function getDayWorkSlots(array $slot, array $dayoff, DateTime $today) + { + $slots = array(); + + if (! empty($dayoff) && $dayoff['start'] < $slot['end']) { + + if ($dayoff['start'] > $slot['start']) { + $slots[] = $this->getTimeSlot(array('end' => $dayoff['start']) + $slot, $today); + } + + if ($dayoff['end'] < $slot['end']) { + $slots[] = $this->getTimeSlot(array('start' => $dayoff['end']) + $slot, $today); + } + } + else { + $slots[] = $this->getTimeSlot($slot, $today); + } + + return $slots; + } + + /** + * Get regular day work time slots + * + * @access public + * @param \DateTime $today + * @return array + */ + public function getDaySlots(DateTime $today) + { + $slots = array(); + + foreach ($this->day as $day) { + $slots[] = $this->getTimeSlot($day, $today); + } + + return $slots; + } + + /** + * Get the start and end time slot for a given day + * + * @access public + * @param array $slot + * @param \DateTime $today + * @return array + */ + public function getTimeSlot(array $slot, DateTime $today) + { + $date = $today->format('Y-m-d'); + + return array( + new DateTime($date.' '.$slot['start']), + new DateTime($date.' '.$slot['end']), + ); + } + + /** + * Return day off time slot + * + * @access public + * @param \DateTime $today + * @return array + */ + public function getDayOff(DateTime $today) + { + foreach ($this->timeoff as $day) { + + if ($day['date'] === $today->format('Y-m-d')) { + return $day; + } + } + + return array(); + } +} diff --git a/app/Model/TimetableDay.php b/app/Model/TimetableDay.php new file mode 100644 index 00000000..0c7bf20b --- /dev/null +++ b/app/Model/TimetableDay.php @@ -0,0 +1,87 @@ +<?php + +namespace Model; + +use SimpleValidator\Validator; +use SimpleValidator\Validators; + +/** + * Timetable Workweek + * + * @package model + * @author Frederic Guillot + */ +class TimetableDay extends Base +{ + /** + * SQL table name + * + * @var string + */ + const TABLE = 'timetable_day'; + + /** + * Get the timetable for a given user + * + * @access public + * @param integer $user_id User id + * @return array + */ + public function getByUser($user_id) + { + return $this->db->table(self::TABLE)->eq('user_id', $user_id)->asc('start')->findAll(); + } + + /** + * Add a new time slot in the database + * + * @access public + * @param integer $user_id User id + * @param string $start Start hour (24h format) + * @param string $end End hour (24h format) + * @return boolean|integer + */ + public function create($user_id, $start, $end) + { + $values = array( + 'user_id' => $user_id, + 'start' => $start, + 'end' => $end, + ); + + return $this->persist(self::TABLE, $values); + } + + /** + * Remove a specific time slot + * + * @access public + * @param integer $slot_id + * @return boolean + */ + public function remove($slot_id) + { + return $this->db->table(self::TABLE)->eq('id', $slot_id)->remove(); + } + + /** + * Validate creation + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateCreation(array $values) + { + $v = new Validator($values, array( + new Validators\Required('user_id', t('Field required')), + new Validators\Required('start', t('Field required')), + new Validators\Required('end', t('Field required')), + )); + + return array( + $v->execute(), + $v->getErrors() + ); + } +} diff --git a/app/Model/TimetableExtra.php b/app/Model/TimetableExtra.php new file mode 100644 index 00000000..48db662d --- /dev/null +++ b/app/Model/TimetableExtra.php @@ -0,0 +1,22 @@ +<?php + +namespace Model; + +use SimpleValidator\Validator; +use SimpleValidator\Validators; + +/** + * Timetable over-time + * + * @package model + * @author Frederic Guillot + */ +class TimetableExtra extends TimetableOff +{ + /** + * SQL table name + * + * @var string + */ + const TABLE = 'timetable_extra'; +} diff --git a/app/Model/TimetableOff.php b/app/Model/TimetableOff.php new file mode 100644 index 00000000..aa064f05 --- /dev/null +++ b/app/Model/TimetableOff.php @@ -0,0 +1,125 @@ +<?php + +namespace Model; + +use SimpleValidator\Validator; +use SimpleValidator\Validators; + +/** + * Timetable time off + * + * @package model + * @author Frederic Guillot + */ +class TimetableOff extends Base +{ + /** + * SQL table name + * + * @var string + */ + const TABLE = 'timetable_off'; + + /** + * Get query to fetch everything (pagination) + * + * @access public + * @param integer $user_id User id + * @return \PicoDb\Table + */ + public function getUserQuery($user_id) + { + return $this->db->table(static::TABLE)->eq('user_id', $user_id); + } + + /** + * Get the timetable for a given user + * + * @access public + * @param integer $user_id User id + * @return array + */ + public function getByUser($user_id) + { + return $this->db->table(static::TABLE)->eq('user_id', $user_id)->desc('date')->asc('start')->findAll(); + } + + /** + * Get the timetable for a given user + * + * @access public + * @param integer $user_id User id + * @param string $start_date + * @param string $end_date + * @return array + */ + public function getByUserAndDate($user_id, $start_date, $end_date) + { + return $this->db->table(static::TABLE) + ->eq('user_id', $user_id) + ->gte('date', $start_date) + ->lte('date', $end_date) + ->desc('date') + ->asc('start') + ->findAll(); + } + + /** + * Add a new time slot in the database + * + * @access public + * @param integer $user_id User id + * @param string $date Day (ISO8601 format) + * @param boolean $all_day All day flag + * @param float $start Start hour (24h format) + * @param float $end End hour (24h format) + * @param string $comment + * @return boolean|integer + */ + public function create($user_id, $date, $all_day, $start = '', $end = '', $comment = '') + { + $values = array( + 'user_id' => $user_id, + 'date' => $date, + 'all_day' => $all_day, + 'start' => $all_day ? '' : $start, + 'end' => $all_day ? '' : $end, + 'comment' => $comment, + ); + + return $this->persist(static::TABLE, $values); + } + + /** + * Remove a specific time slot + * + * @access public + * @param integer $slot_id + * @return boolean + */ + public function remove($slot_id) + { + return $this->db->table(static::TABLE)->eq('id', $slot_id)->remove(); + } + + /** + * Validate creation + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateCreation(array $values) + { + $v = new Validator($values, array( + new Validators\Required('user_id', t('Field required')), + new Validators\Required('date', t('Field required')), + new Validators\Numeric('all_day', t('This value must be numeric')), + )); + + return array( + $v->execute(), + $v->getErrors() + ); + } +} diff --git a/app/Model/TimetableWeek.php b/app/Model/TimetableWeek.php new file mode 100644 index 00000000..b22b3b7e --- /dev/null +++ b/app/Model/TimetableWeek.php @@ -0,0 +1,91 @@ +<?php + +namespace Model; + +use SimpleValidator\Validator; +use SimpleValidator\Validators; + +/** + * Timetable Workweek + * + * @package model + * @author Frederic Guillot + */ +class TimetableWeek extends Base +{ + /** + * SQL table name + * + * @var string + */ + const TABLE = 'timetable_week'; + + /** + * Get the timetable for a given user + * + * @access public + * @param integer $user_id User id + * @return array + */ + public function getByUser($user_id) + { + return $this->db->table(self::TABLE)->eq('user_id', $user_id)->asc('day')->asc('start')->findAll(); + } + + /** + * Add a new time slot in the database + * + * @access public + * @param integer $user_id User id + * @param string $day Day of the week (ISO-8601) + * @param string $start Start hour (24h format) + * @param string $end End hour (24h format) + * @return boolean|integer + */ + public function create($user_id, $day, $start, $end) + { + $values = array( + 'user_id' => $user_id, + 'day' => $day, + 'start' => $start, + 'end' => $end, + ); + + return $this->persist(self::TABLE, $values); + } + + /** + * Remove a specific time slot + * + * @access public + * @param integer $slot_id + * @return boolean + */ + public function remove($slot_id) + { + return $this->db->table(self::TABLE)->eq('id', $slot_id)->remove(); + } + + /** + * Validate creation + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateCreation(array $values) + { + $v = new Validator($values, array( + new Validators\Required('user_id', t('Field required')), + new Validators\Required('day', t('Field required')), + new Validators\Numeric('day', t('This value must be numeric')), + new Validators\Required('start', t('Field required')), + new Validators\Required('end', t('Field required')), + )); + + return array( + $v->execute(), + $v->getErrors() + ); + } +} diff --git a/app/Schema/Mysql.php b/app/Schema/Mysql.php index 947a62b3..a78ffacf 100644 --- a/app/Schema/Mysql.php +++ b/app/Schema/Mysql.php @@ -6,7 +6,123 @@ use PDO; use Core\Security; use Model\Link; -const VERSION = 46; +const VERSION = 54; + +function version_54($pdo) +{ + $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)'); + $rq->execute(array('application_stylesheet', '')); +} + +function version_53($pdo) +{ + $pdo->exec("ALTER TABLE subtask_time_tracking ADD COLUMN time_spent FLOAT DEFAULT 0"); +} + +function version_52($pdo) +{ + $pdo->exec('CREATE TABLE budget_lines ( + `id` INT NOT NULL AUTO_INCREMENT, + `project_id` INT NOT NULL, + `amount` FLOAT NOT NULL, + `date` VARCHAR(10) NOT NULL, + `comment` TEXT, + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + PRIMARY KEY(id) + ) ENGINE=InnoDB CHARSET=utf8'); +} + +function version_51($pdo) +{ + $pdo->exec('CREATE TABLE timetable_day ( + id INT NOT NULL AUTO_INCREMENT, + user_id INT NOT NULL, + start VARCHAR(5) NOT NULL, + end VARCHAR(5) NOT NULL, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, + PRIMARY KEY(id) + ) ENGINE=InnoDB CHARSET=utf8'); + + $pdo->exec('CREATE TABLE timetable_week ( + id INT NOT NULL AUTO_INCREMENT, + user_id INTEGER NOT NULL, + day INT NOT NULL, + start VARCHAR(5) NOT NULL, + end VARCHAR(5) NOT NULL, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, + PRIMARY KEY(id) + ) ENGINE=InnoDB CHARSET=utf8'); + + $pdo->exec('CREATE TABLE timetable_off ( + id INT NOT NULL AUTO_INCREMENT, + user_id INT NOT NULL, + date VARCHAR(10) NOT NULL, + all_day TINYINT(1) DEFAULT 0, + start VARCHAR(5) DEFAULT 0, + end VARCHAR(5) DEFAULT 0, + comment TEXT, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, + PRIMARY KEY(id) + ) ENGINE=InnoDB CHARSET=utf8'); + + $pdo->exec('CREATE TABLE timetable_extra ( + id INT NOT NULL AUTO_INCREMENT, + user_id INT NOT NULL, + date VARCHAR(10) NOT NULL, + all_day TINYINT(1) DEFAULT 0, + start VARCHAR(5) DEFAULT 0, + end VARCHAR(5) DEFAULT 0, + comment TEXT, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, + PRIMARY KEY(id) + ) ENGINE=InnoDB CHARSET=utf8'); +} + +function version_50($pdo) +{ + $pdo->exec("CREATE TABLE hourly_rates ( + id INT NOT NULL AUTO_INCREMENT, + user_id INT NOT NULL, + rate FLOAT DEFAULT 0, + date_effective INTEGER NOT NULL, + currency TEXT NOT NULL, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, + PRIMARY KEY(id) + ) ENGINE=InnoDB CHARSET=utf8"); +} + +function version_49($pdo) +{ + $pdo->exec('ALTER TABLE subtasks ADD COLUMN position INTEGER DEFAULT 1'); + + $task_id = 0; + $urq = $pdo->prepare('UPDATE subtasks SET position=? WHERE id=?'); + + $rq = $pdo->prepare('SELECT * FROM subtasks ORDER BY task_id, id ASC'); + $rq->execute(); + + foreach ($rq->fetchAll(PDO::FETCH_ASSOC) as $subtask) { + + if ($task_id != $subtask['task_id']) { + $position = 1; + $task_id = $subtask['task_id']; + } + + $urq->execute(array($position, $subtask['id'])); + $position++; + } +} + +function version_48($pdo) +{ + $pdo->exec('RENAME TABLE task_has_files TO files'); + $pdo->exec('RENAME TABLE task_has_subtasks TO subtasks'); +} + +function version_47($pdo) +{ + $pdo->exec('ALTER TABLE projects ADD COLUMN description TEXT'); +} function version_46($pdo) { diff --git a/app/Schema/Postgres.php b/app/Schema/Postgres.php index 027401ff..2396000f 100644 --- a/app/Schema/Postgres.php +++ b/app/Schema/Postgres.php @@ -6,7 +6,117 @@ use PDO; use Core\Security; use Model\Link; -const VERSION = 27; +const VERSION = 35; + +function version_35($pdo) +{ + $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)'); + $rq->execute(array('application_stylesheet', '')); +} + +function version_34($pdo) +{ + $pdo->exec("ALTER TABLE subtask_time_tracking ADD COLUMN time_spent REAL DEFAULT 0"); +} + +function version_33($pdo) +{ + $pdo->exec('CREATE TABLE budget_lines ( + "id" SERIAL PRIMARY KEY, + "project_id" INTEGER NOT NULL, + "amount" REAL NOT NULL, + "date" VARCHAR(10) NOT NULL, + "comment" TEXT, + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE + )'); +} + +function version_32($pdo) +{ + $pdo->exec('CREATE TABLE timetable_day ( + "id" SERIAL PRIMARY KEY, + "user_id" INTEGER NOT NULL, + "start" VARCHAR(5) NOT NULL, + "end" VARCHAR(5) NOT NULL, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE + )'); + + $pdo->exec('CREATE TABLE timetable_week ( + "id" SERIAL PRIMARY KEY, + "user_id" INTEGER NOT NULL, + "day" INTEGER NOT NULL, + "start" VARCHAR(5) NOT NULL, + "end" VARCHAR(5) NOT NULL, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE + )'); + + $pdo->exec('CREATE TABLE timetable_off ( + "id" SERIAL PRIMARY KEY, + "user_id" INTEGER NOT NULL, + "date" VARCHAR(10) NOT NULL, + "all_day" BOOLEAN DEFAULT \'0\', + "start" VARCHAR(5) DEFAULT 0, + "end" VARCHAR(5) DEFAULT 0, + "comment" TEXT, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE + )'); + + $pdo->exec('CREATE TABLE timetable_extra ( + "id" SERIAL PRIMARY KEY, + "user_id" INTEGER NOT NULL, + "date" VARCHAR(10) NOT NULL, + "all_day" BOOLEAN DEFAULT \'0\', + "start" VARCHAR(5) DEFAULT 0, + "end" VARCHAR(5) DEFAULT 0, + "comment" TEXT, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE + )'); +} + +function version_31($pdo) +{ + $pdo->exec("CREATE TABLE hourly_rates ( + id SERIAL PRIMARY KEY, + user_id INTEGER NOT NULL, + rate REAL DEFAULT 0, + date_effective INTEGER NOT NULL, + currency TEXT NOT NULL, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE + )"); +} + +function version_30($pdo) +{ + $pdo->exec('ALTER TABLE subtasks ADD COLUMN position INTEGER DEFAULT 1'); + + $task_id = 0; + $urq = $pdo->prepare('UPDATE subtasks SET position=? WHERE id=?'); + + $rq = $pdo->prepare('SELECT * FROM subtasks ORDER BY task_id, id ASC'); + $rq->execute(); + + foreach ($rq->fetchAll(PDO::FETCH_ASSOC) as $subtask) { + + if ($task_id != $subtask['task_id']) { + $position = 1; + $task_id = $subtask['task_id']; + } + + $urq->execute(array($position, $subtask['id'])); + $position++; + } +} + +function version_29($pdo) +{ + $pdo->exec('ALTER TABLE task_has_files RENAME TO files'); + $pdo->exec('ALTER TABLE task_has_subtasks RENAME TO subtasks'); +} + +function version_28($pdo) +{ + $pdo->exec('ALTER TABLE projects ADD COLUMN description TEXT'); +} function version_27($pdo) { diff --git a/app/Schema/Sqlite.php b/app/Schema/Sqlite.php index c6dec33f..0e0512d0 100644 --- a/app/Schema/Sqlite.php +++ b/app/Schema/Sqlite.php @@ -6,7 +6,119 @@ use Core\Security; use PDO; use Model\Link; -const VERSION = 45; +const VERSION = 53; + +function version_53($pdo) +{ + $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)'); + $rq->execute(array('application_stylesheet', '')); +} + +function version_52($pdo) +{ + $pdo->exec("ALTER TABLE subtask_time_tracking ADD COLUMN time_spent REAL DEFAULT 0"); +} + +function version_51($pdo) +{ + $pdo->exec('CREATE TABLE budget_lines ( + "id" INTEGER PRIMARY KEY, + "project_id" INTEGER NOT NULL, + "amount" REAL NOT NULL, + "date" TEXT NOT NULL, + "comment" TEXT, + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE + )'); +} + +function version_50($pdo) +{ + $pdo->exec('CREATE TABLE timetable_day ( + "id" INTEGER PRIMARY KEY, + "user_id" INTEGER NOT NULL, + "start" TEXT NOT NULL, + "end" TEXT NOT NULL, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE + )'); + + $pdo->exec('CREATE TABLE timetable_week ( + "id" INTEGER PRIMARY KEY, + "user_id" INTEGER NOT NULL, + "day" INTEGER NOT NULL, + "start" TEXT NOT NULL, + "end" TEXT NOT NULL, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE + )'); + + $pdo->exec('CREATE TABLE timetable_off ( + "id" INTEGER PRIMARY KEY, + "user_id" INTEGER NOT NULL, + "date" TEXT NOT NULL, + "all_day" INTEGER DEFAULT 0, + "start" TEXT DEFAULT 0, + "end" TEXT DEFAULT 0, + "comment" TEXT, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE + )'); + + $pdo->exec('CREATE TABLE timetable_extra ( + "id" INTEGER PRIMARY KEY, + "user_id" INTEGER NOT NULL, + "date" TEXT NOT NULL, + "all_day" INTEGER DEFAULT 0, + "start" TEXT DEFAULT 0, + "end" TEXT DEFAULT 0, + "comment" TEXT, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE + )'); +} + +function version_49($pdo) +{ + $pdo->exec("CREATE TABLE hourly_rates ( + id INTEGER PRIMARY KEY, + user_id INTEGER NOT NULL, + rate REAL DEFAULT 0, + date_effective INTEGER NOT NULL, + currency TEXT NOT NULL, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE + )"); +} + +function version_48($pdo) +{ + $pdo->exec('ALTER TABLE subtasks ADD COLUMN position INTEGER DEFAULT 1'); + + // Migrate all subtasks position + + $task_id = 0; + $urq = $pdo->prepare('UPDATE subtasks SET position=? WHERE id=?'); + + $rq = $pdo->prepare('SELECT * FROM subtasks ORDER BY task_id, id ASC'); + $rq->execute(); + + foreach ($rq->fetchAll(PDO::FETCH_ASSOC) as $subtask) { + + if ($task_id != $subtask['task_id']) { + $position = 1; + $task_id = $subtask['task_id']; + } + + $urq->execute(array($position, $subtask['id'])); + $position++; + } +} + +function version_47($pdo) +{ + $pdo->exec('ALTER TABLE task_has_files RENAME TO files'); + $pdo->exec('ALTER TABLE task_has_subtasks RENAME TO subtasks'); +} + +function version_46($pdo) +{ + $pdo->exec('ALTER TABLE projects ADD COLUMN description TEXT'); +} function version_45($pdo) { diff --git a/app/ServiceProvider/ClassProvider.php b/app/ServiceProvider/ClassProvider.php index 213972ed..fc71ebf9 100644 --- a/app/ServiceProvider/ClassProvider.php +++ b/app/ServiceProvider/ClassProvider.php @@ -17,12 +17,14 @@ class ClassProvider implements ServiceProviderInterface 'Action', 'Authentication', 'Board', + 'Budget', 'Category', 'Color', 'Comment', 'Config', 'DateParser', 'File', + 'HourlyRate', 'LastLogin', 'Link', 'Notification', @@ -48,7 +50,11 @@ class ClassProvider implements ServiceProviderInterface 'TaskPosition', 'TaskStatus', 'TaskValidator', - 'TimeTracking', + 'Timetable', + 'TimetableDay', + 'TimetableWeek', + 'TimetableOff', + 'TimetableExtra', 'User', 'UserSession', 'Webhook', diff --git a/app/ServiceProvider/DatabaseProvider.php b/app/ServiceProvider/DatabaseProvider.php index 4218f5ff..7ee35d5f 100644 --- a/app/ServiceProvider/DatabaseProvider.php +++ b/app/ServiceProvider/DatabaseProvider.php @@ -79,6 +79,7 @@ class DatabaseProvider implements ServiceProviderInterface 'password' => DB_PASSWORD, 'database' => DB_NAME, 'charset' => 'utf8', + 'port' => DB_PORT, )); } @@ -97,6 +98,7 @@ class DatabaseProvider implements ServiceProviderInterface 'username' => DB_USERNAME, 'password' => DB_PASSWORD, 'database' => DB_NAME, + 'port' => DB_PORT, )); } } diff --git a/app/Template/analytic/layout.php b/app/Template/analytic/layout.php index 8c946699..c6e3a962 100644 --- a/app/Template/analytic/layout.php +++ b/app/Template/analytic/layout.php @@ -1,5 +1,5 @@ <?= $this->js('assets/js/vendor/d3.v3.4.8.min.js') ?> -<?= $this->js('assets/js/vendor/dimple.v2.1.0.min.js') ?> +<?= $this->js('assets/js/vendor/dimple.v2.1.2.min.js') ?> <section id="main"> <div class="page-header"> diff --git a/app/Template/app/dashboard.php b/app/Template/app/dashboard.php index e6f124e1..5b83540c 100644 --- a/app/Template/app/dashboard.php +++ b/app/Template/app/dashboard.php @@ -11,8 +11,8 @@ <li><i class="fa fa-cog fa-fw"></i><?= $this->a(t('Settings'), 'config', 'index') ?></li> <?php endif ?> <li> - <ul class="dropdown"> - <li> + <span class="dropdown"> + <span> <i class="fa fa-caret-down"></i> <a href="#" class="dropdown-menu"><?= t('Change dashboard view') ?></a> <ul> <li> @@ -31,8 +31,8 @@ <a href="#" class="dashboard-toggle" data-toggle="activities"><?= t('Show/hide activities') ?></a> </li> </ul> - </li> - </ul> + </span> + </span> </li> </ul> </div> diff --git a/app/Template/app/projects.php b/app/Template/app/projects.php index 4740c4b8..b2744644 100644 --- a/app/Template/app/projects.php +++ b/app/Template/app/projects.php @@ -17,9 +17,15 @@ <?php if ($this->isManager($project['id'])): ?> <?= $this->a('<i class="fa fa-cog"></i>', 'project', 'show', array('project_id' => $project['id']), false, 'dashboard-table-link', t('Settings')) ?> <?php endif ?> - + <?= $this->a('<i class="fa fa-calendar"></i>', 'calendar', 'show', array('project_id' => $project['id']), false, 'dashboard-table-link', t('Calendar')) ?> + <?= $this->a($this->e($project['name']), 'board', 'show', array('project_id' => $project['id'])) ?> + <?php if (! empty($project['description'])): ?> + <span class="column-tooltip" title='<?= $this->e($this->markdown($project['description'])) ?>'> + <i class="fa fa-info-circle"></i> + </span> + <?php endif ?> </td> <td class="dashboard-project-stats"> <?php foreach ($project['columns'] as $column): ?> @@ -32,4 +38,4 @@ </table> <?= $paginator ?> -<?php endif ?>
\ No newline at end of file +<?php endif ?> diff --git a/app/Template/board/edit.php b/app/Template/board/edit.php index b9b1788a..a6df1000 100644 --- a/app/Template/board/edit.php +++ b/app/Template/board/edit.php @@ -13,7 +13,7 @@ <tr> <td class="column-60"><?= $this->e($column['title']) ?> <?php if (! empty($column['description'])): ?> - <span class="column-tooltip" title="<?= $this->markdown($column['description']) ?>"> + <span class="column-tooltip" title='<?= $this->e($this->markdown($column['description'])) ?>'> <i class="fa fa-info-circle"></i> </span> <?php endif ?> @@ -52,12 +52,12 @@ <?= $this->formLabel(t('Title'), 'title') ?> <?= $this->formText('title', $values, $errors, array('required', 'maxlength="50"')) ?> - + <?= $this->formLabel(t('Task limit'), 'task_limit') ?> <?= $this->formNumber('task_limit', $values, $errors) ?> - + <?= $this->formLabel(t('Description'), 'description') ?> - + <div class="form-tabs"> <div class="write-area"> <?= $this->formTextarea('description', $values, $errors) ?> diff --git a/app/Template/board/files.php b/app/Template/board/files.php index 278b906b..851a118d 100644 --- a/app/Template/board/files.php +++ b/app/Template/board/files.php @@ -1,14 +1,31 @@ <section> - <?php foreach ($files as $file): ?> - <i class="fa fa-file-o fa-fw"></i> - - <?= $this->a( - $this->e($file['name']), - 'file', - 'download', - array('file_id' => $file['id'], 'task_id' => $file['task_id'], 'project_id' => $task['project_id']) - ) ?> - - <br/> - <?php endforeach ?> + <table> + <?php if (! empty($images)): ?> + <?php foreach ($images as $file): ?> + <tr> + <td class="column-70"> + <i class="fa fa-file-image-o fa-fw"></i> + <?= $this->e($file['name']) ?> + </td> + <td> + <i class="fa fa-download"></i> <?= $this->a(t('download'), 'file', 'download', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'file_id' => $file['id'])) ?> + <i class="fa fa-eye"></i> <?= $this->a(t('open'), 'file', 'open', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'file_id' => $file['id']), false, 'popover') ?> + </td> + </tr> + <?php endforeach ?> + <?php endif ?> + <?php if (! empty($files)): ?> + <?php foreach ($files as $file): ?> + <tr> + <td> + <i class="fa <?= $this->getFileIcon($file['name']) ?> fa-fw"></i> + <?= $this->e($file['name']) ?> + </td> + <td> + <i class="fa fa-download"></i> <?= $this->a(t('download'), 'file', 'download', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'file_id' => $file['id'])) ?> + </td> + </tr> + <?php endforeach ?> + <?php endif ?> + </table> </section> diff --git a/app/Template/board/filters.php b/app/Template/board/filters.php index a0de5fd9..47304d7d 100644 --- a/app/Template/board/filters.php +++ b/app/Template/board/filters.php @@ -1,8 +1,8 @@ <div class="page-header"> <ul class="board-filters"> <li> - <ul class="dropdown"> - <li> + <span class="dropdown"> + <span> <i class="fa fa-caret-down"></i> <a href="#" class="dropdown-menu"><?= t('Actions') ?></a> <ul> <li> @@ -14,6 +14,14 @@ </span> </li> <li> + <span class="filter-compact"> + <i class="fa fa-th fa-fw"></i> <a href="#" class="filter-toggle-scrolling"><?= t('Compact view') ?></a> + </span> + <span class="filter-wide" style="display: none"> + <i class="fa fa-arrows-h fa-fw"></i> <a href="#" class="filter-toggle-scrolling"><?= t('Horizontal scrolling') ?></a> + </span> + </li> + <li> <i class="fa fa-search fa-fw"></i> <?= $this->a(t('Search'), 'project', 'search', array('project_id' => $project['id'])) ?> </li> @@ -40,22 +48,26 @@ <?= $this->a(t('Analytics'), 'analytic', 'tasks', array('project_id' => $project['id'])) ?> </li> <li> + <i class="fa fa-pie-chart fa-fw"></i> + <?= $this->a(t('Budget'), 'budget', 'index', array('project_id' => $project['id'])) ?> + </li> + <li> <i class="fa fa-cog fa-fw"></i> <?= $this->a(t('Configure'), 'project', 'show', array('project_id' => $project['id'])) ?> </li> <?php endif ?> </ul> - </li> - </ul> + </span> + </span> </li> <li> - <?= $this->formSelect('user_id', $users, array(), array(), array('data-placeholder="'.t('Filter by user').'"'), 'apply-filters chosen-select') ?> + <?= $this->formSelect('user_id', $users, array(), array(), array('data-placeholder="'.t('Filter by user').'"', 'data-notfound="'.t('No results match:').'"'), 'apply-filters chosen-select') ?> </li> <li> - <?= $this->formSelect('category_id', $categories, array(), array(), array('data-placeholder="'.t('Filter by category').'"'), 'apply-filters chosen-select') ?> + <?= $this->formSelect('category_id', $categories, array(), array(), array('data-placeholder="'.t('Filter by category').'"', 'data-notfound="'.t('No results match:').'"'), 'apply-filters chosen-select') ?> </li> <li> - <select id="more-filters" multiple data-placeholder="<?= t('More filters') ?>" class="apply-filters chosen-select hide-mobile"> + <select id="more-filters" multiple data-placeholder="<?= t('More filters') ?>" data-notfound="<?= t('No results match:') ?>" class="apply-filters chosen-select hide-mobile"> <option value=""></option> <option value="filter-due-date"><?= t('Filter by due date') ?></option> <option value="filter-recent"><?= t('Filter recently updated') ?></option> diff --git a/app/Template/board/show.php b/app/Template/board/show.php index f1607d26..6f81fe2e 100644 --- a/app/Template/board/show.php +++ b/app/Template/board/show.php @@ -1,8 +1,9 @@ <div id="board-container"> <?php if (isset($not_editable)): ?> - <table id="board"> + <table id="board" class="board-project-<?= $project['id'] ?>"> <?php else: ?> <table id="board" + class="board-project-<?= $project['id'] ?>" data-project-id="<?= $project['id'] ?>" data-check-interval="<?= $board_private_refresh_interval ?>" data-save-url="<?= $this->u('board', 'save', array('project_id' => $project['id'])) ?>" diff --git a/app/Template/board/swimlane.php b/app/Template/board/swimlane.php index ec298e24..744610ab 100644 --- a/app/Template/board/swimlane.php +++ b/app/Template/board/swimlane.php @@ -28,7 +28,7 @@ <?= $this->e($column['title']) ?> <?php if (! empty($column['description'])): ?> - <span class="column-tooltip pull-right" title="<?= $this->markdown($column['description']) ?>"> + <span class="column-tooltip pull-right" title='<?= $this->e($this->markdown($column['description'])) ?>'> <i class="fa fa-info-circle"></i> </span> <?php endif ?> diff --git a/app/Template/board/task_footer.php b/app/Template/board/task_footer.php index d413692c..635ed31f 100644 --- a/app/Template/board/task_footer.php +++ b/app/Template/board/task_footer.php @@ -22,19 +22,19 @@ <?php endif ?> <?php if (! empty($task['nb_links'])): ?> - <span title="<?= t('Links') ?>" class="task-board-tooltip" data-href="<?= $this->u('board', 'tasklinks', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>"><?= $task['nb_links'] ?> <i class="fa fa-code-fork"></i></span> + <span title="<?= t('Links') ?>" class="task-board-tooltip" data-href="<?= $this->u('board', 'tasklinks', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>"><i class="fa fa-code-fork"></i> <?= $task['nb_links'] ?></span> <?php endif ?> <?php if (! empty($task['nb_subtasks'])): ?> - <span title="<?= t('Sub-Tasks') ?>" class="task-board-tooltip" data-href="<?= $this->u('board', 'subtasks', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>"><?= round($task['nb_completed_subtasks']/$task['nb_subtasks']*100, 0).'%' ?> <i class="fa fa-bars"></i></span> + <span title="<?= t('Sub-Tasks') ?>" class="task-board-tooltip" data-href="<?= $this->u('board', 'subtasks', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>"><i class="fa fa-bars"></i> <?= round($task['nb_completed_subtasks']/$task['nb_subtasks']*100, 0).'%' ?></span> <?php endif ?> <?php if (! empty($task['nb_files'])): ?> - <span title="<?= t('Attachments') ?>" class="task-board-tooltip" data-href="<?= $this->u('board', 'attachments', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>"><?= $task['nb_files'] ?> <i class="fa fa-paperclip"></i></span> + <span title="<?= t('Attachments') ?>" class="task-board-tooltip" data-href="<?= $this->u('board', 'attachments', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>"><i class="fa fa-paperclip"></i> <?= $task['nb_files'] ?></span> <?php endif ?> <?php if (! empty($task['nb_comments'])): ?> - <span title="<?= p($task['nb_comments'], t('%d comment', $task['nb_comments']), t('%d comments', $task['nb_comments'])) ?>" class="task-board-tooltip" data-href="<?= $this->u('board', 'comments', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>"><?= $task['nb_comments'] ?> <i class="fa fa-comment-o"></i></span> + <span title="<?= p($task['nb_comments'], t('%d comment', $task['nb_comments']), t('%d comments', $task['nb_comments'])) ?>" class="task-board-tooltip" data-href="<?= $this->u('board', 'comments', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>"><i class="fa fa-comment-o"></i> <?= $task['nb_comments'] ?></span> <?php endif ?> <?php if (! empty($task['description'])): ?> @@ -42,4 +42,8 @@ <i class="fa fa-file-text-o"></i> </span> <?php endif ?> -</div>
\ No newline at end of file + + <?php if ($task['score']): ?> + <span class="task-score"><?= $this->e($task['score']) ?></span> + <?php endif ?> +</div> diff --git a/app/Template/board/task_menu.php b/app/Template/board/task_menu.php index e7e0f419..35104b66 100644 --- a/app/Template/board/task_menu.php +++ b/app/Template/board/task_menu.php @@ -1,13 +1,14 @@ -<ul class="dropdown"> - <li> +<span class="dropdown"> + <span> <a href="#" class="dropdown-menu"><?= '#'.$task['id'] ?></a> <ul> <li><i class="fa fa-user"></i> <?= $this->a(t('Change assignee'), 'board', 'changeAssignee', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'task-board-popover') ?></li> <li><i class="fa fa-tag"></i> <?= $this->a(t('Change category'), 'board', 'changeCategory', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'task-board-popover') ?></li> <li><i class="fa fa-align-left"></i> <?= $this->a(t('Change description'), 'task', 'description', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'task-board-popover') ?></li> <li><i class="fa fa-comment-o"></i> <?= $this->a(t('Add a comment'), 'comment', 'create', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'task-board-popover') ?></li> + <li><i class="fa fa-code-fork"></i> <?= $this->a(t('Add a link'), 'tasklink', 'create', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'task-board-popover') ?></li> <li><i class="fa fa-pencil-square-o"></i> <?= $this->a(t('Edit this task'), 'task', 'edit', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'task-board-popover') ?></li> <li><i class="fa fa-close"></i> <?= $this->a(t('Close this task'), 'task', 'close', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'redirect' => 'board'), false, 'task-board-popover') ?></li> </ul> - </li> -</ul>
\ No newline at end of file + </span> +</span>
\ No newline at end of file diff --git a/app/Template/board/task_private.php b/app/Template/board/task_private.php index 71d94fbf..9e7358f0 100644 --- a/app/Template/board/task_private.php +++ b/app/Template/board/task_private.php @@ -31,10 +31,6 @@ ) ?> </span> - <?php if ($task['score']): ?> - <span class="task-score"><?= $this->e($task['score']) ?></span> - <?php endif ?> - <div class="task-board-days"> <span title="<?= t('Task age in days')?>" class="task-days-age"><?= $this->getTaskAge($task['date_creation']) ?></span> <span title="<?= t('Days in this column')?>" class="task-days-incolumn"><?= $this->getTaskAge($task['date_moved']) ?></span> diff --git a/app/Template/board/task_public.php b/app/Template/board/task_public.php index 650b956d..4e3ad18c 100644 --- a/app/Template/board/task_public.php +++ b/app/Template/board/task_public.php @@ -18,10 +18,6 @@ <?php endif ?> </span> - <?php if ($task['score']): ?> - <span class="task-score"><?= $this->e($task['score']) ?></span> - <?php endif ?> - <div class="task-board-title"> <?= $this->a($this->e($task['title']), 'task', 'readonly', array('task_id' => $task['id'], 'token' => $project['token'])) ?> </div> diff --git a/app/Template/budget/breakdown.php b/app/Template/budget/breakdown.php new file mode 100644 index 00000000..d4168406 --- /dev/null +++ b/app/Template/budget/breakdown.php @@ -0,0 +1,34 @@ +<div class="page-header"> + <h2><?= t('Budget') ?></h2> + <ul> + <li><?= $this->a(t('Budget lines'), 'budget', 'create', array('project_id' => $project['id'])) ?></li> + <li><?= $this->a(t('Cost breakdown'), 'budget', 'breakdown', array('project_id' => $project['id'])) ?></li> + </ul> +</div> + +<?php if ($paginator->isEmpty()): ?> + <p class="alert"><?= t('There is nothing to show.') ?></p> +<?php else: ?> + <table class="table-fixed"> + <tr> + <th class="column-20"><?= $paginator->order(t('Task'), 'task_title') ?></th> + <th class="column-25"><?= $paginator->order(t('Subtask'), 'subtask_title') ?></th> + <th class="column-20"><?= $paginator->order(t('User'), 'username') ?></th> + <th class="column-10"><?= t('Cost') ?></th> + <th class="column-10"><?= $paginator->order(t('Time spent'), 'time_spent') ?></th> + <th class="column-15"><?= $paginator->order(t('Date'), 'start') ?></th> + </tr> + <?php foreach ($paginator->getCollection() as $record): ?> + <tr> + <td><?= $this->a($this->e($record['task_title']), 'task', 'show', array('project_id' => $project['id'], 'task_id' => $record['task_id'])) ?></td> + <td><?= $this->a($this->e($record['subtask_title']), 'task', 'show', array('project_id' => $project['id'], 'task_id' => $record['task_id'])) ?></td> + <td><?= $this->e($record['name'] ?: $record['username']) ?></td> + <td><?= n($record['cost']) ?></td> + <td><?= n($record['time_spent']).' '.t('hours') ?></td> + <td><?= dt('%B %e, %Y', $record['start']) ?></td> + </tr> + <?php endforeach ?> + </table> + + <?= $paginator ?> +<?php endif ?>
\ No newline at end of file diff --git a/app/Template/budget/create.php b/app/Template/budget/create.php new file mode 100644 index 00000000..5a919ce6 --- /dev/null +++ b/app/Template/budget/create.php @@ -0,0 +1,51 @@ +<div class="page-header"> + <h2><?= t('Budget') ?></h2> + <ul> + <li><?= $this->a(t('Budget lines'), 'budget', 'create', array('project_id' => $project['id'])) ?></li> + <li><?= $this->a(t('Cost breakdown'), 'budget', 'breakdown', array('project_id' => $project['id'])) ?></li> + </ul> +</div> + +<?php if (! empty($lines)): ?> +<table class="table-fixed table-stripped"> + <tr> + <th class="column-20"><?= t('Budget line') ?></th> + <th class="column-20"><?= t('Date') ?></th> + <th><?= t('Comment') ?></th> + <th><?= t('Action') ?></th> + </tr> + <?php foreach ($lines as $line): ?> + <tr> + <td><?= n($line['amount']) ?></td> + <td><?= $this->e($line['date']) ?></td> + <td><?= $this->e($line['comment']) ?></td> + <td> + <?= $this->a(t('Remove'), 'budget', 'confirm', array('project_id' => $project['id'], 'budget_id' => $line['id'])) ?> + </td> + </tr> + <?php endforeach ?> +</table> + +<h3><?= t('New budget line') ?></h3> +<?php endif ?> + +<form method="post" action="<?= $this->u('budget', 'save', array('project_id' => $project['id'])) ?>" autocomplete="off"> + + <?= $this->formCsrf() ?> + + <?= $this->formHidden('id', $values) ?> + <?= $this->formHidden('project_id', $values) ?> + + <?= $this->formLabel(t('Amount'), 'amount') ?> + <?= $this->formText('amount', $values, $errors, array('required'), 'form-numeric') ?> + + <?= $this->formLabel(t('Date'), 'date') ?> + <?= $this->formText('date', $values, $errors, array('required'), 'form-date') ?> + + <?= $this->formLabel(t('Comment'), 'comment') ?> + <?= $this->formText('comment', $values, $errors) ?> + + <div class="form-actions"> + <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/> + </div> +</form>
\ No newline at end of file diff --git a/app/Template/budget/index.php b/app/Template/budget/index.php new file mode 100644 index 00000000..442a6b64 --- /dev/null +++ b/app/Template/budget/index.php @@ -0,0 +1,35 @@ +<?= $this->js('assets/js/vendor/d3.v3.4.8.min.js') ?> +<?= $this->js('assets/js/vendor/dimple.v2.1.2.min.js') ?> + +<div class="page-header"> + <h2><?= t('Budget') ?></h2> + <ul> + <li><?= $this->a(t('Budget lines'), 'budget', 'create', array('project_id' => $project['id'])) ?></li> + <li><?= $this->a(t('Cost breakdown'), 'budget', 'breakdown', array('project_id' => $project['id'])) ?></li> + </ul> +</div> + +<?php if (! empty($daily_budget)): ?> +<div id="budget-chart"> + <div id="chart" + data-serie='<?= json_encode($daily_budget) ?>' + data-labels='<?= json_encode(array('in' => t('Budget line'), 'out' => t('Expenses'), 'left' => t('Remaining'), 'value' => t('Amount'), 'date' => t('Date'), 'type' => t('Type'))) ?>'></div> +</div> +<hr/> +<table class="table-fixed table-stripped"> + <tr> + <th><?= t('Date') ?></td> + <th><?= t('Budget line') ?></td> + <th><?= t('Expenses') ?></td> + <th><?= t('Remaining') ?></td> + </tr> + <?php foreach ($daily_budget as $line): ?> + <tr> + <td><?= $this->e($line['date']) ?></td> + <td><?= n($line['in']) ?></td> + <td><?= n($line['out']) ?></td> + <td><?= n($line['left']) ?></td> + </tr> + <?php endforeach ?> +</table> +<?php endif ?> diff --git a/app/Template/budget/remove.php b/app/Template/budget/remove.php new file mode 100644 index 00000000..97f9c3dc --- /dev/null +++ b/app/Template/budget/remove.php @@ -0,0 +1,13 @@ +<div class="page-header"> + <h2><?= t('Remove budget line') ?></h2> +</div> + +<div class="confirm"> + <p class="alert alert-info"><?= t('Do you really want to remove this budget line?') ?></p> + + <div class="form-actions"> + <?= $this->a(t('Yes'), 'budget', 'remove', array('project_id' => $project['id'], 'budget_id' => $budget_id), true, 'btn btn-red') ?> + <?= t('or') ?> + <?= $this->a(t('cancel'), 'budget', 'create', array('project_id' => $project['id'])) ?> + </div> +</div>
\ No newline at end of file diff --git a/app/Template/config/about.php b/app/Template/config/about.php index f6474e21..b6ed43e5 100644 --- a/app/Template/config/about.php +++ b/app/Template/config/about.php @@ -46,6 +46,7 @@ <ul> <li><?= t('New task') ?> = <strong>n</strong></li> <li><?= t('Expand/collapse tasks') ?> = <strong>s</strong></li> + <li><?= t('Compact/wide view') ?> = <strong>c</strong></li> </ul> <h3><?= t('Application') ?></h3> <ul> diff --git a/app/Template/config/application.php b/app/Template/config/application.php index 26f3743e..f6c9db94 100644 --- a/app/Template/config/application.php +++ b/app/Template/config/application.php @@ -20,6 +20,9 @@ <?= $this->formSelect('application_date_format', $date_formats, $values, $errors) ?><br/> <p class="form-help"><?= t('ISO format is always accepted, example: "%s" and "%s"', date('Y-m-d'), date('Y_m_d')) ?></p> + <?= $this->formLabel(t('Custom Stylesheet'), 'application_stylesheet') ?> + <?= $this->formTextarea('application_stylesheet', $values, $errors) ?><br/> + <div class="form-actions"> <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/> </div> diff --git a/app/Template/file/show.php b/app/Template/file/show.php index 298976f6..b181ab54 100644 --- a/app/Template/file/show.php +++ b/app/Template/file/show.php @@ -1,23 +1,48 @@ -<?php if (! empty($files)): ?> +<?php if (! empty($files) || ! empty($images)): ?> <div id="attachments" class="task-show-section"> <div class="page-header"> <h2><?= t('Attachments') ?></h2> </div> + <?php if (!empty($images)): ?> + <h3><?= t('Images') ?></h3> + <ul class="task-show-images"> + <?php foreach ($images as $file): ?> + <li> + <div class="img_container"> + <img src="<?= $this->u('file', 'thumbnail', array('width' => 250, 'file_id' => $file['id'], 'project_id' => $task['project_id'], 'task_id' => $file['task_id'])) ?>" alt="<?= $this->e($file['name']) ?>"/> + </div> + <p> + <?= $this->e($file['name']) ?> + </p> + <span class="task-show-file-actions task-show-image-actions"> + <i class="fa fa-eye"></i> <?= $this->a(t('open'), 'file', 'open', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'file_id' => $file['id']), false, 'popover') ?> + <i class="fa fa-trash"></i> <?= $this->a(t('remove'), 'file', 'confirm', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'file_id' => $file['id'])) ?> + <i class="fa fa-download"></i> <?= $this->a(t('download'), 'file', 'download', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'file_id' => $file['id'])) ?> + </span> + </li> + <?php endforeach ?> + </ul> + <?php endif ?> - <ul class="task-show-files"> - <?php foreach ($files as $file): ?> - <li> - <?= $this->a($this->e($file['name']), 'file', 'download', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'file_id' => $file['id'])) ?> - <span class="task-show-file-actions"> - <?php if ($file['is_image']): ?> - <?= $this->a(t('open'), 'file', 'open', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'file_id' => $file['id']), false, 'popover') ?>, - <?php endif ?> - <?= $this->a(t('remove'), 'file', 'confirm', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'file_id' => $file['id'])) ?> - </span> - </li> - <?php endforeach ?> - </ul> - + <?php if (! empty($files)): ?> + <h3><?= t('Files') ?></h3> + <table class="task-show-file-table"> + <?php foreach ($files as $file): ?> + <tr> + <td><i class="fa <?= $this->getFileIcon($file['name']) ?> fa-fw"></i></td> + <td> + <?= $this->e($file['name']) ?> + </td> + <td> + <span class="task-show-file-actions"> + <i class="fa fa-trash"></i> <?= $this->a(t('remove'), 'file', 'confirm', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'file_id' => $file['id'])) ?> + <i class="fa fa-download"></i> <?= $this->a(t('download'), 'file', 'download', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'file_id' => $file['id'])) ?> + </span> + </td> + </tr> + <?php endforeach ?> + </table> + <?php endif ?> </div> <?php endif ?>
\ No newline at end of file diff --git a/app/Template/hourlyrate/index.php b/app/Template/hourlyrate/index.php new file mode 100644 index 00000000..9d0b77c8 --- /dev/null +++ b/app/Template/hourlyrate/index.php @@ -0,0 +1,46 @@ +<div class="page-header"> + <h2><?= t('Hourly rates') ?></h2> +</div> + +<?php if (! empty($rates)): ?> + +<table> + <tr> + <th><?= t('Hourly rate') ?></th> + <th><?= t('Currency') ?></th> + <th><?= t('Effective date') ?></th> + <th><?= t('Action') ?></th> + </tr> + <?php foreach ($rates as $rate): ?> + <tr> + <td><?= n($rate['rate']) ?></td> + <td><?= $rate['currency'] ?></td> + <td><?= dt('%b %e, %Y', $rate['date_effective']) ?></td> + <td> + <?= $this->a(t('Remove'), 'hourlyrate', 'confirm', array('user_id' => $user['id'], 'rate_id' => $rate['id'])) ?> + </td> + </tr> + <?php endforeach ?> +</table> + +<h3><?= t('Add new rate') ?></h3> +<?php endif ?> + +<form method="post" action="<?= $this->u('hourlyrate', 'save', array('user_id' => $user['id'])) ?>" autocomplete="off"> + + <?= $this->formHidden('user_id', $values) ?> + <?= $this->formCsrf() ?> + + <?= $this->formLabel(t('Hourly rate'), 'rate') ?> + <?= $this->formText('rate', $values, $errors, array('required'), 'form-numeric') ?> + + <?= $this->formLabel(t('Currency'), 'currency') ?> + <?= $this->formSelect('currency', $currencies_list, $values, $errors, array('required')) ?> + + <?= $this->formLabel(t('Effective date'), 'date_effective') ?> + <?= $this->formText('date_effective', $values, $errors, array('required'), 'form-date') ?> + + <div class="form-actions"> + <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/> + </div> +</form> diff --git a/app/Template/hourlyrate/remove.php b/app/Template/hourlyrate/remove.php new file mode 100644 index 00000000..7f22728e --- /dev/null +++ b/app/Template/hourlyrate/remove.php @@ -0,0 +1,13 @@ +<div class="page-header"> + <h2><?= t('Remove hourly rate') ?></h2> +</div> + +<div class="confirm"> + <p class="alert alert-info"><?= t('Do you really want to remove this hourly rate?') ?></p> + + <div class="form-actions"> + <?= $this->a(t('Yes'), 'hourlyrate', 'remove', array('user_id' => $user['id'], 'rate_id' => $rate_id), true, 'btn btn-red') ?> + <?= t('or') ?> + <?= $this->a(t('cancel'), 'hourlyrate', 'index', array('user_id' => $user['id'])) ?> + </div> +</div>
\ No newline at end of file diff --git a/app/Template/layout.php b/app/Template/layout.php index ad4c4084..ec9f2288 100644 --- a/app/Template/layout.php +++ b/app/Template/layout.php @@ -17,6 +17,10 @@ <?= $this->css($this->u('app', 'colors'), false) ?> <?= $this->css('assets/css/app.css') ?> + <?php if ($this->config->get('application_stylesheet')): ?> + <style><?= $this->config->get('application_stylesheet') ?></style> + <?php endif ?> + <link rel="icon" type="image/png" href="assets/img/favicon.png"> <link rel="apple-touch-icon" href="assets/img/touch-icon-iphone.png"> <link rel="apple-touch-icon" sizes="72x72" href="assets/img/touch-icon-ipad.png"> @@ -35,11 +39,17 @@ <?php else: ?> <header> <nav> - <h1><?= $this->a('K<span>B</span>', 'app', 'index', array(), false, 'logo', t('Dashboard')).' '.$this->summary($this->e($title)) ?></h1> + <h1><?= $this->a('K<span>B</span>', 'app', 'index', array(), false, 'logo', t('Dashboard')).' '.$this->summary($this->e($title)) ?> + <?php if (! empty($description)): ?> + <span class="column-tooltip" title='<?= $this->e($this->markdown($description)) ?>'> + <i class="fa fa-info-circle"></i> + </span> + <?php endif ?> + </h1> <ul> <?php if (isset($board_selector) && ! empty($board_selector)): ?> <li> - <select id="board-selector" data-placeholder="<?= t('Display another project') ?>" data-board-url="<?= $this->u('board', 'show', array('project_id' => 'PROJECT_ID')) ?>"> + <select id="board-selector" data-notfound="<?= t('No results match:') ?>" data-placeholder="<?= t('Display another project') ?>" data-board-url="<?= $this->u('board', 'show', array('project_id' => 'PROJECT_ID')) ?>"> <option value=""></option> <?php foreach($board_selector as $board_id => $board_name): ?> <option value="<?= $board_id ?>"><?= $this->e($board_name) ?></option> diff --git a/app/Template/project/edit.php b/app/Template/project/edit.php index a1b945cd..c1f98315 100644 --- a/app/Template/project/edit.php +++ b/app/Template/project/edit.php @@ -1,7 +1,7 @@ <div class="page-header"> <h2><?= t('Edit project') ?></h2> </div> -<form method="post" action="<?= $this->u('project', 'update', array('project_id' => $values['id'])) ?>" autocomplete="off"> +<form method="post" action="<?= $this->u('project', 'update', array('project_id' => $project['id'])) ?>" autocomplete="off"> <?= $this->formCsrf() ?> <?= $this->formHidden('id', $values) ?> @@ -9,7 +9,32 @@ <?= $this->formLabel(t('Name'), 'name') ?> <?= $this->formText('name', $values, $errors, array('required', 'maxlength="50"')) ?> + <?= $this->formLabel(t('Description'), 'description') ?> + + <div class="form-tabs"> + + <div class="write-area"> + <?= $this->formTextarea('description', $values, $errors) ?> + </div> + <div class="preview-area"> + <div class="markdown"></div> + </div> + <ul class="form-tabs-nav"> + <li class="form-tab form-tab-selected"> + <i class="fa fa-pencil-square-o fa-fw"></i><a id="markdown-write" href="#"><?= t('Write') ?></a> + </li> + <li class="form-tab"> + <a id="markdown-preview" href="#"><i class="fa fa-eye fa-fw"></i><?= t('Preview') ?></a> + </li> + </ul> + </div> + <div class="form-help"><a href="http://kanboard.net/documentation/syntax-guide" target="_blank" rel="noreferrer"><?= t('Write your text in Markdown') ?></a></div> + + <?php if ($project['is_private'] == 1 && $this->userSession->isAdmin()): ?> + <?= $this->formCheckbox('is_private', t('Private project'), 1, $project['is_private'] == 1) ?> + <?php endif ?> + <div class="form-actions"> <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/> </div> -</form>
\ No newline at end of file +</form> diff --git a/app/Template/project/index.php b/app/Template/project/index.php index a36a9ce1..05a7d955 100644 --- a/app/Template/project/index.php +++ b/app/Template/project/index.php @@ -39,7 +39,13 @@ <?php if ($project['is_private']): ?> <i class="fa fa-lock fa-fw"></i> <?php endif ?> + <?= $this->a($this->e($project['name']), 'project', 'show', array('project_id' => $project['id'])) ?> + <?php if (! empty($project['description'])): ?> + <span class="column-tooltip" title='<?= $this->e($this->markdown($project['description'])) ?>'> + <i class="fa fa-info-circle"></i> + </span> + <?php endif ?> </td> <td class="dashboard-project-stats"> <?php foreach ($project['columns'] as $column): ?> @@ -54,4 +60,4 @@ <?= $paginator ?> <?php endif ?> </section> -</section>
\ No newline at end of file +</section> diff --git a/app/Template/project/show.php b/app/Template/project/show.php index b8bfd510..9c6cd1a8 100644 --- a/app/Template/project/show.php +++ b/app/Template/project/show.php @@ -50,7 +50,7 @@ <td> <?= $this->e($column['title']) ?> <?php if (! empty($column['description'])): ?> - <span class="column-tooltip" title="<?= $this->markdown($column['description']) ?>"> + <span class="column-tooltip" title='<?= $this->e($this->markdown($column['description'])) ?>'> <i class="fa fa-info-circle"></i> </span> <?php endif ?> @@ -60,3 +60,13 @@ </tr> <?php endforeach ?> </table> + +<?php if (! empty($project['description'])): ?> + <div class="page-header"> + <h2><?= t('Description') ?></h2> + </div> + + <article class="markdown"> + <?= $this->markdown($project['description']) ?> + </article> +<?php endif ?> diff --git a/app/Template/project/sidebar.php b/app/Template/project/sidebar.php index f4809fde..4afc8ba9 100644 --- a/app/Template/project/sidebar.php +++ b/app/Template/project/sidebar.php @@ -33,7 +33,10 @@ <?= $this->a(t('Automatic actions'), 'action', 'index', array('project_id' => $project['id'])) ?> </li> <li> - <?= $this->a(t('Duplicate'), 'project', 'duplicate', array('project_id' => $project['id']), true) ?> + <?= $this->a(t('Duplicate'), 'project', 'duplicate', array('project_id' => $project['id'])) ?> + </li> + <li> + <?= $this->a(t('Budget'), 'budget', 'index', array('project_id' => $project['id'])) ?> </li> <li> <?php if ($project['is_active']): ?> diff --git a/app/Template/subtask/create.php b/app/Template/subtask/create.php index be7fc311..8c5aae11 100644 --- a/app/Template/subtask/create.php +++ b/app/Template/subtask/create.php @@ -9,7 +9,7 @@ <?= $this->formHidden('task_id', $values) ?> <?= $this->formLabel(t('Title'), 'title') ?> - <?= $this->formText('title', $values, $errors, array('required', 'autofocus', 'maxlength="50"')) ?><br/> + <?= $this->formText('title', $values, $errors, array('required', 'autofocus', 'maxlength="255"')) ?><br/> <?= $this->formLabel(t('Assignee'), 'user_id') ?> <?= $this->formSelect('user_id', $users_list, $values, $errors) ?><br/> diff --git a/app/Template/subtask/edit.php b/app/Template/subtask/edit.php index f34d9532..3058ff44 100644 --- a/app/Template/subtask/edit.php +++ b/app/Template/subtask/edit.php @@ -10,7 +10,7 @@ <?= $this->formHidden('task_id', $values) ?> <?= $this->formLabel(t('Title'), 'title') ?> - <?= $this->formText('title', $values, $errors, array('required', 'autofocus', 'maxlength="50"')) ?><br/> + <?= $this->formText('title', $values, $errors, array('required', 'autofocus', 'maxlength="255"')) ?><br/> <?= $this->formLabel(t('Assignee'), 'user_id') ?> <?= $this->formSelect('user_id', $users_list, $values, $errors) ?><br/> diff --git a/app/Template/subtask/show.php b/app/Template/subtask/show.php index 1d55d1ee..c7ac652a 100644 --- a/app/Template/subtask/show.php +++ b/app/Template/subtask/show.php @@ -20,7 +20,7 @@ <?php if (! isset($not_editable)): ?> <?= $this->toggleSubtaskStatus($subtask, 'task') ?> <?php else: ?> - <?= $this->render('subtask/icons', array('subtask' => $subtask)) . $this->e($subtask['status_name']) ?> + <?= $this->render('subtask/icons', array('subtask' => $subtask)) . $this->e($subtask['title']) ?> <?php endif ?> </td> <td> @@ -40,6 +40,16 @@ <?php if (! isset($not_editable)): ?> <td> <ul> + <?php if ($subtask['position'] > 1): ?> + <li> + <?= $this->a(t('Move Up'), 'subtask', 'movePosition', array('project_id' => $project['id'], 'task_id' => $subtask['task_id'], 'subtask_id' => $subtask['id'], 'direction' => 'up'), true) ?> + </li> + <?php endif ?> + <?php if ($subtask['position'] != 0 && $subtask['position'] != count($subtasks)): ?> + <li> + <?= $this->a(t('Move Down'), 'subtask', 'movePosition', array('project_id' => $project['id'], 'task_id' => $subtask['task_id'], 'subtask_id' => $subtask['id'], 'direction' => 'down'), true) ?> + </li> + <?php endif ?> <li> <?= $this->a(t('Edit'), 'subtask', 'edit', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'subtask_id' => $subtask['id'])) ?> </li> diff --git a/app/Template/task/new.php b/app/Template/task/new.php index e64effde..4bfb8064 100644 --- a/app/Template/task/new.php +++ b/app/Template/task/new.php @@ -47,7 +47,6 @@ <div class="form-column"> <?= $this->formHidden('project_id', $values) ?> - <?= $this->formHidden('swimlane_id', $values) ?> <?= $this->formLabel(t('Assignee'), 'owner_id') ?> <?= $this->formSelect('owner_id', $users_list, $values, $errors) ?><br/> @@ -55,6 +54,9 @@ <?= $this->formLabel(t('Category'), 'category_id') ?> <?= $this->formSelect('category_id', $categories_list, $values, $errors) ?><br/> + <?= $this->formLabel(t('Swimlane'), 'swimlane_id') ?> + <?= $this->formSelect('swimlane_id', $swimlanes_list, $values, $errors) ?><br/> + <?= $this->formLabel(t('Column'), 'column_id') ?> <?= $this->formSelect('column_id', $columns_list, $values, $errors) ?><br/> diff --git a/app/Template/task/show.php b/app/Template/task/show.php index 1ff2ef43..50316c9f 100644 --- a/app/Template/task/show.php +++ b/app/Template/task/show.php @@ -1,8 +1,8 @@ <?= $this->render('task/details', array('task' => $task, 'project' => $project)) ?> <?= $this->render('task/time', array('task' => $task, 'values' => $values, 'date_format' => $date_format, 'date_formats' => $date_formats)) ?> <?= $this->render('task/show_description', array('task' => $task)) ?> -<?= $this->render('tasklink/show', array('task' => $task, 'links' => $links)) ?> -<?= $this->render('subtask/show', array('task' => $task, 'subtasks' => $subtasks)) ?> +<?= $this->render('tasklink/show', array('task' => $task, 'links' => $links, 'link_label_list' => $link_label_list)) ?> +<?= $this->render('subtask/show', array('task' => $task, 'subtasks' => $subtasks, 'project' => $project)) ?> <?= $this->render('task/timesheet', array('task' => $task)) ?> -<?= $this->render('file/show', array('task' => $task, 'files' => $files)) ?> +<?= $this->render('file/show', array('task' => $task, 'files' => $files, 'images' => $images)) ?> <?= $this->render('task/comments', array('task' => $task, 'comments' => $comments, 'project' => $project)) ?> diff --git a/app/Template/task/time_tracking.php b/app/Template/task/time_tracking.php index 1dea0f0b..55d33e5e 100644 --- a/app/Template/task/time_tracking.php +++ b/app/Template/task/time_tracking.php @@ -6,10 +6,11 @@ <?php else: ?> <table class="table-fixed"> <tr> - <th class="column-20"><?= $subtask_paginator->order(t('User'), 'username') ?></th> - <th class="column-30"><?= $subtask_paginator->order(t('Subtask'), 'subtask_title') ?></th> - <th><?= $subtask_paginator->order(t('Start'), 'start') ?></th> - <th><?= $subtask_paginator->order(t('End'), 'end') ?></th> + <th class="column-15"><?= $subtask_paginator->order(t('User'), 'username') ?></th> + <th><?= $subtask_paginator->order(t('Subtask'), 'subtask_title') ?></th> + <th class="column-20"><?= $subtask_paginator->order(t('Start'), 'start') ?></th> + <th class="column-20"><?= $subtask_paginator->order(t('End'), 'end') ?></th> + <th class="column-10"><?= $subtask_paginator->order(t('Time spent'), 'time_spent') ?></th> </tr> <?php foreach ($subtask_paginator->getCollection() as $record): ?> <tr> @@ -17,6 +18,7 @@ <td><?= t($record['subtask_title']) ?></td> <td><?= dt('%B %e, %Y at %k:%M %p', $record['start']) ?></td> <td><?= dt('%B %e, %Y at %k:%M %p', $record['end']) ?></td> + <td><?= n($record['time_spent']).' '.t('hours') ?></td> </tr> <?php endforeach ?> </table> diff --git a/app/Template/tasklink/create.php b/app/Template/tasklink/create.php index fb438cd8..acf9d6d1 100644 --- a/app/Template/tasklink/create.php +++ b/app/Template/tasklink/create.php @@ -2,7 +2,7 @@ <h2><?= t('Add a new link') ?></h2> </div> -<form action="<?= $this->u('tasklink', 'save', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>" method="post" autocomplete="off"> +<form action="<?= $this->u('tasklink', 'save', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'ajax' => isset($ajax))) ?>" method="post" autocomplete="off"> <?= $this->formCsrf() ?> <?= $this->formHidden('task_id', $values) ?> @@ -22,6 +22,10 @@ <div class="form-actions"> <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/> <?= t('or') ?> - <?= $this->a(t('cancel'), 'task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?> + <?php if (isset($ajax)): ?> + <?= $this->a(t('cancel'), 'board', 'show', array('project_id' => $task['project_id']), false, 'close-popover') ?> + <?php else: ?> + <?= $this->a(t('cancel'), 'task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?> + <?php endif ?> </div> </form>
\ No newline at end of file diff --git a/app/Template/tasklink/show.php b/app/Template/tasklink/show.php index a36f89dd..75e3c376 100644 --- a/app/Template/tasklink/show.php +++ b/app/Template/tasklink/show.php @@ -41,4 +41,24 @@ </tr> <?php endforeach ?> </table> -<?php endif ?>
\ No newline at end of file + +<?php if (! isset($not_editable) && isset($link_label_list)): ?> + <form action="<?= $this->u('tasklink', 'save', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>" method="post" autocomplete="off"> + + <?= $this->formCsrf() ?> + <?= $this->formHidden('task_id', array('task_id' => $task['id'])) ?> + <?= $this->formHidden('opposite_task_id', array()) ?> + + <?= $this->formSelect('link_id', $link_label_list, array(), array()) ?> + + <?= $this->formText( + 'title', + array(), + array(), + array('required', 'data-dst-field="opposite_task_id"', 'data-search-url="'.$this->u('app', 'autocomplete', array('exclude_task_id' => $task['id'])).'"'), + 'task-autocomplete') ?> + + <input type="submit" value="<?= t('Add') ?>" class="btn btn-blue"/> + </form> +<?php endif ?> +<?php endif ?> diff --git a/app/Template/timetable/index.php b/app/Template/timetable/index.php new file mode 100644 index 00000000..27cbe39c --- /dev/null +++ b/app/Template/timetable/index.php @@ -0,0 +1,44 @@ +<div class="page-header"> + <h2><?= t('Timetable') ?></h2> + <ul> + <li><?= $this->a(t('Day timetable'), 'timetableday', 'index', array('user_id' => $user['id'])) ?></li> + <li><?= $this->a(t('Week timetable'), 'timetableweek', 'index', array('user_id' => $user['id'])) ?></li> + <li><?= $this->a(t('Time off timetable'), 'timetableoff', 'index', array('user_id' => $user['id'])) ?></li> + <li><?= $this->a(t('Overtime timetable'), 'timetableextra', 'index', array('user_id' => $user['id'])) ?></li> + </ul> +</div> + +<form method="get" action="?" autocomplete="off" class="form-inline"> + + <?= $this->formHidden('controller', $values) ?> + <?= $this->formHidden('action', $values) ?> + <?= $this->formHidden('user_id', $values) ?> + + <?= $this->formLabel(t('From'), 'from') ?> + <?= $this->formText('from', $values, array(), array(), 'form-date') ?> + + <?= $this->formLabel(t('To'), 'to') ?> + <?= $this->formText('to', $values, array(), array(), 'form-date') ?> + + <input type="submit" value="<?= t('Execute') ?>" class="btn btn-blue"/> +</form> + +<?php if (! empty($timetable)): ?> +<hr/> +<h3><?= t('Work timetable') ?></h3> +<table class="table-fixed table-stripped"> + <tr> + <th><?= t('Day') ?></th> + <th><?= t('Start') ?></th> + <th><?= t('End') ?></th> + </tr> + <?php foreach ($timetable as $slot): ?> + <tr> + <td><?= dt('%B %e, %Y', $slot[0]->getTimestamp()) ?></td> + <td><?= dt('%k:%M %p', $slot[0]->getTimestamp()) ?></td> + <td><?= dt('%k:%M %p', $slot[1]->getTimestamp()) ?></td> + </tr> + <?php endforeach ?> +</table> + +<?php endif ?>
\ No newline at end of file diff --git a/app/Template/timetable_day/index.php b/app/Template/timetable_day/index.php new file mode 100644 index 00000000..50aca602 --- /dev/null +++ b/app/Template/timetable_day/index.php @@ -0,0 +1,45 @@ +<div class="page-header"> + <h2><?= t('Day timetable') ?></h2> +</div> + +<?php if (! empty($timetable)): ?> + +<table class="table-fixed table-stripped"> + <tr> + <th><?= t('Start time') ?></th> + <th><?= t('End time') ?></th> + <th><?= t('Action') ?></th> + </tr> + <?php foreach ($timetable as $slot): ?> + <tr> + <td><?= $slot['start'] ?></td> + <td><?= $slot['end'] ?></td> + <td> + <?= $this->a(t('Remove'), 'timetableday', 'confirm', array('user_id' => $user['id'], 'slot_id' => $slot['id'])) ?> + </td> + </tr> + <?php endforeach ?> +</table> + +<h3><?= t('Add new time slot') ?></h3> +<?php endif ?> + +<form method="post" action="<?= $this->u('timetableday', 'save', array('user_id' => $user['id'])) ?>" autocomplete="off"> + + <?= $this->formHidden('user_id', $values) ?> + <?= $this->formCsrf() ?> + + <?= $this->formLabel(t('Start time'), 'start') ?> + <?= $this->formSelect('start', $this->getDayHours(), $values, $errors) ?> + + <?= $this->formLabel(t('End time'), 'end') ?> + <?= $this->formSelect('end', $this->getDayHours(), $values, $errors) ?> + + <div class="form-actions"> + <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/> + </div> +</form> + +<p class="alert alert-info"> + <?= t('This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.') ?> +</p>
\ No newline at end of file diff --git a/app/Template/timetable_day/remove.php b/app/Template/timetable_day/remove.php new file mode 100644 index 00000000..b3ee8775 --- /dev/null +++ b/app/Template/timetable_day/remove.php @@ -0,0 +1,13 @@ +<div class="page-header"> + <h2><?= t('Remove time slot') ?></h2> +</div> + +<div class="confirm"> + <p class="alert alert-info"><?= t('Do you really want to remove this time slot?') ?></p> + + <div class="form-actions"> + <?= $this->a(t('Yes'), 'timetableday', 'remove', array('user_id' => $user['id'], 'slot_id' => $slot_id), true, 'btn btn-red') ?> + <?= t('or') ?> + <?= $this->a(t('cancel'), 'timetableday', 'index', array('user_id' => $user['id'])) ?> + </div> +</div>
\ No newline at end of file diff --git a/app/Template/timetable_extra/index.php b/app/Template/timetable_extra/index.php new file mode 100644 index 00000000..a0a55bec --- /dev/null +++ b/app/Template/timetable_extra/index.php @@ -0,0 +1,56 @@ +<div class="page-header"> + <h2><?= t('Overtime timetable') ?></h2> +</div> + +<?php if (! $paginator->isEmpty()): ?> + +<table class="table-fixed table-stripped"> + <tr> + <th><?= $paginator->order(t('Day'), 'Day') ?></th> + <th><?= $paginator->order(t('All day'), 'all_day') ?></th> + <th><?= $paginator->order(t('Start time'), 'start') ?></th> + <th><?= $paginator->order(t('End time'), 'end') ?></th> + <th class="column-40"><?= t('Comment') ?></th> + <th><?= t('Action') ?></th> + </tr> + <?php foreach ($paginator->getCollection() as $slot): ?> + <tr> + <td><?= $slot['date'] ?></td> + <td><?= $slot['all_day'] == 1 ? t('Yes') : t('No') ?></td> + <td><?= $slot['start'] ?></td> + <td><?= $slot['end'] ?></td> + <td><?= $this->e($slot['comment']) ?></td> + <td> + <?= $this->a(t('Remove'), 'timetableextra', 'confirm', array('user_id' => $user['id'], 'slot_id' => $slot['id'])) ?> + </td> + </tr> + <?php endforeach ?> +</table> + +<?= $paginator ?> + +<?php endif ?> + +<form method="post" action="<?= $this->u('timetableextra', 'save', array('user_id' => $user['id'])) ?>" autocomplete="off"> + + <?= $this->formHidden('user_id', $values) ?> + <?= $this->formCsrf() ?> + + <?= $this->formLabel(t('Day'), 'date') ?> + <?= $this->formText('date', $values, $errors, array('required'), 'form-date') ?> + + <?= $this->formCheckbox('all_day', t('All day'), 1) ?> + + <?= $this->formLabel(t('Start time'), 'start') ?> + <?= $this->formSelect('start', $this->getDayHours(), $values, $errors) ?> + + <?= $this->formLabel(t('End time'), 'end') ?> + <?= $this->formSelect('end', $this->getDayHours(), $values, $errors) ?> + + <?= $this->formLabel(t('Comment'), 'comment') ?> + <?= $this->formText('comment', $values, $errors) ?> + + <div class="form-actions"> + <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/> + </div> +</form>
\ No newline at end of file diff --git a/app/Template/timetable_extra/remove.php b/app/Template/timetable_extra/remove.php new file mode 100644 index 00000000..d8dc5b3b --- /dev/null +++ b/app/Template/timetable_extra/remove.php @@ -0,0 +1,13 @@ +<div class="page-header"> + <h2><?= t('Remove time slot') ?></h2> +</div> + +<div class="confirm"> + <p class="alert alert-info"><?= t('Do you really want to remove this time slot?') ?></p> + + <div class="form-actions"> + <?= $this->a(t('Yes'), 'timetableextra', 'remove', array('user_id' => $user['id'], 'slot_id' => $slot_id), true, 'btn btn-red') ?> + <?= t('or') ?> + <?= $this->a(t('cancel'), 'timetableextra', 'index', array('user_id' => $user['id'])) ?> + </div> +</div>
\ No newline at end of file diff --git a/app/Template/timetable_off/index.php b/app/Template/timetable_off/index.php new file mode 100644 index 00000000..f35d331e --- /dev/null +++ b/app/Template/timetable_off/index.php @@ -0,0 +1,56 @@ +<div class="page-header"> + <h2><?= t('Time off timetable') ?></h2> +</div> + +<?php if (! $paginator->isEmpty()): ?> + +<table class="table-fixed table-stripped"> + <tr> + <th><?= $paginator->order(t('Day'), 'Day') ?></th> + <th><?= $paginator->order(t('All day'), 'all_day') ?></th> + <th><?= $paginator->order(t('Start time'), 'start') ?></th> + <th><?= $paginator->order(t('End time'), 'end') ?></th> + <th class="column-40"><?= t('Comment') ?></th> + <th><?= t('Action') ?></th> + </tr> + <?php foreach ($paginator->getCollection() as $slot): ?> + <tr> + <td><?= $slot['date'] ?></td> + <td><?= $slot['all_day'] == 1 ? t('Yes') : t('No') ?></td> + <td><?= $slot['start'] ?></td> + <td><?= $slot['end'] ?></td> + <td><?= $this->e($slot['comment']) ?></td> + <td> + <?= $this->a(t('Remove'), 'timetableoff', 'confirm', array('user_id' => $user['id'], 'slot_id' => $slot['id'])) ?> + </td> + </tr> + <?php endforeach ?> +</table> + +<?= $paginator ?> + +<?php endif ?> + +<form method="post" action="<?= $this->u('timetableoff', 'save', array('user_id' => $user['id'])) ?>" autocomplete="off"> + + <?= $this->formHidden('user_id', $values) ?> + <?= $this->formCsrf() ?> + + <?= $this->formLabel(t('Day'), 'date') ?> + <?= $this->formText('date', $values, $errors, array('required'), 'form-date') ?> + + <?= $this->formCheckbox('all_day', t('All day'), 1) ?> + + <?= $this->formLabel(t('Start time'), 'start') ?> + <?= $this->formSelect('start', $this->getDayHours(), $values, $errors) ?> + + <?= $this->formLabel(t('End time'), 'end') ?> + <?= $this->formSelect('end', $this->getDayHours(), $values, $errors) ?> + + <?= $this->formLabel(t('Comment'), 'comment') ?> + <?= $this->formText('comment', $values, $errors) ?> + + <div class="form-actions"> + <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/> + </div> +</form>
\ No newline at end of file diff --git a/app/Template/timetable_off/remove.php b/app/Template/timetable_off/remove.php new file mode 100644 index 00000000..64863781 --- /dev/null +++ b/app/Template/timetable_off/remove.php @@ -0,0 +1,13 @@ +<div class="page-header"> + <h2><?= t('Remove time slot') ?></h2> +</div> + +<div class="confirm"> + <p class="alert alert-info"><?= t('Do you really want to remove this time slot?') ?></p> + + <div class="form-actions"> + <?= $this->a(t('Yes'), 'timetableoff', 'remove', array('user_id' => $user['id'], 'slot_id' => $slot_id), true, 'btn btn-red') ?> + <?= t('or') ?> + <?= $this->a(t('cancel'), 'timetableoff', 'index', array('user_id' => $user['id'])) ?> + </div> +</div>
\ No newline at end of file diff --git a/app/Template/timetable_week/index.php b/app/Template/timetable_week/index.php new file mode 100644 index 00000000..8fb51909 --- /dev/null +++ b/app/Template/timetable_week/index.php @@ -0,0 +1,46 @@ +<div class="page-header"> + <h2><?= t('Week timetable') ?></h2> +</div> + +<?php if (! empty($timetable)): ?> + +<table class="table-fixed table-stripped"> + <tr> + <th><?= t('Day') ?></th> + <th><?= t('Start time') ?></th> + <th><?= t('End time') ?></th> + <th><?= t('Action') ?></th> + </tr> + <?php foreach ($timetable as $slot): ?> + <tr> + <td><?= $this->getWeekDay($slot['day']) ?></td> + <td><?= $slot['start'] ?></td> + <td><?= $slot['end'] ?></td> + <td> + <?= $this->a(t('Remove'), 'timetableweek', 'confirm', array('user_id' => $user['id'], 'slot_id' => $slot['id'])) ?> + </td> + </tr> + <?php endforeach ?> +</table> + +<h3><?= t('Add new time slot') ?></h3> +<?php endif ?> + +<form method="post" action="<?= $this->u('timetableweek', 'save', array('user_id' => $user['id'])) ?>" autocomplete="off"> + + <?= $this->formHidden('user_id', $values) ?> + <?= $this->formCsrf() ?> + + <?= $this->formLabel(t('Day'), 'day') ?> + <?= $this->formSelect('day', $this->getWeekDays(), $values, $errors) ?> + + <?= $this->formLabel(t('Start time'), 'start') ?> + <?= $this->formSelect('start', $this->getDayHours(), $values, $errors) ?> + + <?= $this->formLabel(t('End time'), 'end') ?> + <?= $this->formSelect('end', $this->getDayHours(), $values, $errors) ?> + + <div class="form-actions"> + <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/> + </div> +</form>
\ No newline at end of file diff --git a/app/Template/timetable_week/remove.php b/app/Template/timetable_week/remove.php new file mode 100644 index 00000000..f8eb2bbe --- /dev/null +++ b/app/Template/timetable_week/remove.php @@ -0,0 +1,13 @@ +<div class="page-header"> + <h2><?= t('Remove time slot') ?></h2> +</div> + +<div class="confirm"> + <p class="alert alert-info"><?= t('Do you really want to remove this time slot?') ?></p> + + <div class="form-actions"> + <?= $this->a(t('Yes'), 'timetableweek', 'remove', array('user_id' => $user['id'], 'slot_id' => $slot_id), true, 'btn btn-red') ?> + <?= t('or') ?> + <?= $this->a(t('cancel'), 'timetableweek', 'index', array('user_id' => $user['id'])) ?> + </div> +</div>
\ No newline at end of file diff --git a/app/Template/user/sidebar.php b/app/Template/user/sidebar.php index e41851a9..1af10c1d 100644 --- a/app/Template/user/sidebar.php +++ b/app/Template/user/sidebar.php @@ -32,7 +32,7 @@ <?= $this->a(t('Time tracking'), 'user', 'timesheet', array('user_id' => $user['id'])) ?> </li> <?php endif ?> - + <?php if ($this->userSession->isAdmin()): ?> <li> <?= $this->a(t('User dashboard'), 'app', 'dashboard', array('user_id' => $user['id'])) ?> @@ -40,6 +40,12 @@ <li> <?= $this->a(t('User calendar'), 'user', 'calendar', array('user_id' => $user['id'])) ?> </li> + <li> + <?= $this->a(t('Hourly rates'), 'hourlyrate', 'index', array('user_id' => $user['id'])) ?> + </li> + <li> + <?= $this->a(t('Manage timetable'), 'timetable', 'index', array('user_id' => $user['id'])) ?> + </li> <?php endif ?> <?php if ($this->userSession->isAdmin() && ! $this->userSession->isCurrentUser($user['id'])): ?> diff --git a/app/Template/user/timesheet.php b/app/Template/user/timesheet.php index 4f052006..3ae84df0 100644 --- a/app/Template/user/timesheet.php +++ b/app/Template/user/timesheet.php @@ -8,10 +8,11 @@ <?php else: ?> <table class="table-fixed"> <tr> - <th class="column-20"><?= $subtask_paginator->order(t('Task'), 'task_title') ?></th> - <th class="column-20"><?= $subtask_paginator->order(t('Subtask'), 'subtask_title') ?></th> - <th><?= $subtask_paginator->order(t('Start'), 'start') ?></th> - <th><?= $subtask_paginator->order(t('End'), 'end') ?></th> + <th class="column-25"><?= $subtask_paginator->order(t('Task'), 'task_title') ?></th> + <th class="column-25"><?= $subtask_paginator->order(t('Subtask'), 'subtask_title') ?></th> + <th class="column-20"><?= $subtask_paginator->order(t('Start'), 'start') ?></th> + <th class="column-20"><?= $subtask_paginator->order(t('End'), 'end') ?></th> + <th class="column-10"><?= $subtask_paginator->order(t('Time spent'), 'time_spent') ?></th> </tr> <?php foreach ($subtask_paginator->getCollection() as $record): ?> <tr> @@ -19,6 +20,7 @@ <td><?= $this->a($this->e($record['subtask_title']), 'task', 'show', array('project_id' => $record['project_id'], 'task_id' => $record['task_id'])) ?></td> <td><?= dt('%B %e, %Y at %k:%M %p', $record['start']) ?></td> <td><?= dt('%B %e, %Y at %k:%M %p', $record['end']) ?></td> + <td><?= n($record['time_spent']).' '.t('hours') ?></td> </tr> <?php endforeach ?> </table> diff --git a/app/constants.php b/app/constants.php index 39ab5470..82d26f2c 100644 --- a/app/constants.php +++ b/app/constants.php @@ -21,6 +21,7 @@ defined('DB_USERNAME') or define('DB_USERNAME', 'root'); defined('DB_PASSWORD') or define('DB_PASSWORD', ''); defined('DB_HOSTNAME') or define('DB_HOSTNAME', 'localhost'); defined('DB_NAME') or define('DB_NAME', 'kanboard'); +defined('DB_PORT') or define('DB_PORT', null); // LDAP configuration defined('LDAP_AUTH') or define('LDAP_AUTH', false); @@ -67,6 +68,9 @@ defined('MAIL_SENDMAIL_COMMAND') or define('MAIL_SENDMAIL_COMMAND', '/usr/sbin/s // Enable or disable "Strict-Transport-Security" HTTP header defined('ENABLE_HSTS') or define('ENABLE_HSTS', true); +// Enable or disable "X-Frame-Options: DENY" HTTP header +defined('ENABLE_XFRAME') or define('ENABLE_XFRAME', true); + // Default files directory defined('FILES_DIR') or define('FILES_DIR', 'data/files/'); diff --git a/assets/css/app.css b/assets/css/app.css index 7969bca5..79d61c6b 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -57,6 +57,11 @@ hr { border-top: 1px solid rgba(0, 0, 0, 0.1); border-bottom: 1px solid rgba(255, 255, 255, 0.3); } + +#board-selector, +.chosen-select { + min-height: 27px; /* Reserve some space to avoid re-layout due to chosen */ +} /* links */ a { color: #3366CC; @@ -147,6 +152,10 @@ th a:hover { text-overflow: ellipsis; } +.table-stripped tr:nth-child(odd) td { + background: #fefefe; +} + .column-5 { width: 5%; } @@ -619,6 +628,10 @@ div.ui-tooltip { width: 550px; } +.ui-tooltip-content .markdown p { + margin-bottom: 0px; +} + .tooltip-tasklinks li { list-style-type: none; } @@ -735,6 +748,10 @@ nav .active a { font-size: 0.95em; } +#more-filters { + display: none; /* Hide this filter initially, to avoid re-layout */ +} + /* public board */ .public-board { margin-top: 5px; @@ -748,14 +765,28 @@ nav .active a { /* board table */ #board-container { - overflow-x: scroll; padding-bottom: 180px; /* Space to avoid dropdown menu truncated */ + overflow-x: scroll; +} + +.board-container-compact { + overflow-x: initial; +} + +#board { + table-layout: fixed; } -#board td, #board th { - min-width: 240px; - max-width: 240px; + width: 120px; /* Width of swimlane column */ +} + +#board th.board-column { + width: 240px; /* Width of other columns, in default [horizontal scrolling] view mode */ +} + +#board th.board-column.board-column-compact { + width: initial; /* Do not force the width of the columns in compact view mode */ } #board th a { @@ -923,16 +954,14 @@ span.task-board-date-overdue { /* task score */ .task-score { font-weight: bold; - position: absolute; } .task-board .task-score { - font-size: 1.6em; - left: 2px; - bottom: 0; + font-size: 1.1em; } .task-show-details .task-score { + position: absolute; bottom: 5px; right: 5px; font-size: 2em; @@ -1050,6 +1079,41 @@ span.task-board-date-overdue { .task-link-closed { text-decoration: line-through; } +.task-show-images { + list-style-type: none; +} +.task-show-images li img { + width: 100%; +} +.task-show-images li .img_container { + width: 250px; + height: 100px; + overflow: hidden; +} +.task-show-images li { + padding: 10px; + overflow: auto; + width: 250px; + min-height: 120px; + display: inline-block; + vertical-align: top; +} +.task-show-images li p{ + padding: 5px; + font-weight: bold; +} +.task-show-images li:hover { + background: #eee; +} +.task-show-image-actions { + margin-left: 5px; +} +.task-show-images li .img_container:hover { + height: 100%; +} +.task-show-file-table { + width: auto; +} /* comments */ .comment { margin-bottom: 20px; diff --git a/assets/css/src/base.css b/assets/css/src/base.css index a8c7d73e..d92df612 100644 --- a/assets/css/src/base.css +++ b/assets/css/src/base.css @@ -41,3 +41,8 @@ hr { border-top: 1px solid rgba(0, 0, 0, 0.1); border-bottom: 1px solid rgba(255, 255, 255, 0.3); } + +#board-selector, +.chosen-select { + min-height: 27px; /* Reserve some space to avoid re-layout due to chosen */ +} diff --git a/assets/css/src/board.css b/assets/css/src/board.css index 6d7d8dd4..4309b178 100644 --- a/assets/css/src/board.css +++ b/assets/css/src/board.css @@ -3,6 +3,10 @@ font-size: 0.95em; } +#more-filters { + display: none; /* Hide this filter initially, to avoid re-layout */ +} + /* public board */ .public-board { margin-top: 5px; @@ -16,14 +20,28 @@ /* board table */ #board-container { - overflow-x: scroll; padding-bottom: 180px; /* Space to avoid dropdown menu truncated */ + overflow-x: scroll; +} + +.board-container-compact { + overflow-x: initial; +} + +#board { + table-layout: fixed; } -#board td, #board th { - min-width: 240px; - max-width: 240px; + width: 120px; /* Width of swimlane column */ +} + +#board th.board-column { + width: 240px; /* Width of other columns, in default [horizontal scrolling] view mode */ +} + +#board th.board-column.board-column-compact { + width: initial; /* Do not force the width of the columns in compact view mode */ } #board th a { diff --git a/assets/css/src/table.css b/assets/css/src/table.css index 9bc0c712..ea788623 100644 --- a/assets/css/src/table.css +++ b/assets/css/src/table.css @@ -58,6 +58,10 @@ th a:hover { text-overflow: ellipsis; } +.table-stripped tr:nth-child(odd) td { + background: #fefefe; +} + .column-5 { width: 5%; } diff --git a/assets/css/src/task.css b/assets/css/src/task.css index b7a5cb9c..8d94945f 100644 --- a/assets/css/src/task.css +++ b/assets/css/src/task.css @@ -118,16 +118,14 @@ span.task-board-date-overdue { /* task score */ .task-score { font-weight: bold; - position: absolute; } .task-board .task-score { - font-size: 1.6em; - left: 2px; - bottom: 0; + font-size: 1.1em; } .task-show-details .task-score { + position: absolute; bottom: 5px; right: 5px; font-size: 2em; @@ -245,3 +243,38 @@ span.task-board-date-overdue { .task-link-closed { text-decoration: line-through; } +.task-show-images { + list-style-type: none; +} +.task-show-images li img { + width: 100%; +} +.task-show-images li .img_container { + width: 250px; + height: 100px; + overflow: hidden; +} +.task-show-images li { + padding: 10px; + overflow: auto; + width: 250px; + min-height: 120px; + display: inline-block; + vertical-align: top; +} +.task-show-images li p{ + padding: 5px; + font-weight: bold; +} +.task-show-images li:hover { + background: #eee; +} +.task-show-image-actions { + margin-left: 5px; +} +.task-show-images li .img_container:hover { + height: 100%; +} +.task-show-file-table { + width: auto; +} diff --git a/assets/css/src/tooltip.css b/assets/css/src/tooltip.css index 834f416b..fb1d5312 100644 --- a/assets/css/src/tooltip.css +++ b/assets/css/src/tooltip.css @@ -64,6 +64,10 @@ div.ui-tooltip { width: 550px; } +.ui-tooltip-content .markdown p { + margin-bottom: 0px; +} + .tooltip-tasklinks li { list-style-type: none; } diff --git a/assets/js/app.js b/assets/js/app.js index 64ea371d..a1fc5d56 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -140,19 +140,22 @@ var Kanboard=function(){jQuery(document).ready(function(){Kanboard.Init()});retu $("#popover-content").click(function(a){a.stopPropagation()});$(".close-popover").click(function(a){a.preventDefault();$("#popover-container").remove()});Mousetrap.bind("esc",function(){$("#popover-container").remove()});c&&c()})},IsVisible:function(){var a="";"undefined"!==typeof document.hidden?a="visibilityState":"undefined"!==typeof document.mozHidden?a="mozVisibilityState":"undefined"!==typeof document.msHidden?a="msVisibilityState":"undefined"!==typeof document.webkitHidden&&(a="webkitVisibilityState"); return""!=a?"visible"==document[a]:!0},SetStorageItem:function(a,c){"undefined"!==typeof Storage&&localStorage.setItem(a,c)},GetStorageItem:function(a){return"undefined"!==typeof Storage?localStorage.getItem(a):""},MarkdownPreview:function(a){a.preventDefault();var c=$(this),b=$(this).closest("ul"),d=$(".write-area"),e=$(".preview-area"),f=$("textarea");$.ajax({url:"?controller=app&action=preview",contentType:"application/json",type:"POST",processData:!1,dataType:"html",data:JSON.stringify({text:f.val()})}).done(function(a){b.find("li").removeClass("form-tab-selected"); c.parent().addClass("form-tab-selected");e.find(".markdown").html(a);e.css("height",f.css("height"));e.css("width",f.css("width"));d.hide();e.show()})},MarkdownWriter:function(a){a.preventDefault();$(this).closest("ul").find("li").removeClass("form-tab-selected");$(this).parent().addClass("form-tab-selected");$(".write-area").show();$(".preview-area").hide()},CheckSession:function(){$(".form-login").length||$.ajax({cache:!1,url:$("body").data("status-url"),statusCode:{401:function(){window.location= -$("body").data("login-url")}}})},Init:function(){$("#board-selector").chosen({width:180});$("#board-selector").change(function(){window.location=$(this).attr("data-board-url").replace(/PROJECT_ID/g,$(this).val())});window.setInterval(Kanboard.CheckSession,6E4);Mousetrap.bindGlobal("mod+enter",function(){$("form").submit()});Mousetrap.bind("b",function(a){a.preventDefault();$("#board-selector").trigger("chosen:open")});$(".column-tooltip").tooltip({content:function(){return'<div class="markdown">'+ -$(this).attr("title")+"</div>"}});$.datepicker.setDefaults($.datepicker.regional[$("body").data("js-lang")]);Kanboard.InitAfterAjax()},InitAfterAjax:function(){$(document).on("click",".popover",Kanboard.Popover);$(".form-date").datepicker({showOtherMonths:!0,selectOtherMonths:!0,dateFormat:"yy-mm-dd",constrainInput:!1});$("#markdown-preview").click(Kanboard.MarkdownPreview);$("#markdown-write").click(Kanboard.MarkdownWriter);$(".auto-select").focus(function(){$(this).select()});$(".dropit-submenu").hide(); -$(".dropdown").not(".dropit").dropit()}}}(); -Kanboard.Board=function(){function a(a){a.preventDefault();a.stopPropagation();Kanboard.Popover(a,Kanboard.InitAfterAjax)}function c(){Mousetrap.bind("n",function(){Kanboard.OpenPopover($("#board").data("task-creation-url"),Kanboard.InitAfterAjax)});Mousetrap.bind("s",function(){"expanded"===(Kanboard.GetStorageItem(d())||"expanded")?(e(),Kanboard.SetStorageItem(d(),"collapsed")):(f(),Kanboard.SetStorageItem(d(),"expanded"))})}function b(){$(".filter-expand-link").click(function(a){a.preventDefault();f(); -Kanboard.SetStorageItem(d(),"expanded")});$(".filter-collapse-link").click(function(a){a.preventDefault();e();Kanboard.SetStorageItem(d(),"collapsed")});g()}function d(){return"board_stacking_"+$("#board").data("project-id")}function e(){$(".filter-collapse").hide();$(".task-board-collapsed").show();$(".filter-expand").show();$(".task-board-expanded").hide()}function f(){$(".filter-collapse").show();$(".task-board-collapsed").hide();$(".filter-expand").hide();$(".task-board-expanded").show()}function g(){"expanded"=== -(Kanboard.GetStorageItem(d())||"expanded")?f():e()}function k(){$(".column").sortable({delay:300,distance:5,connectWith:".column",placeholder:"draggable-placeholder",stop:function(a,b){n(b.item.attr("data-task-id"),b.item.parent().attr("data-column-id"),b.item.index()+1,b.item.parent().attr("data-swimlane-id"))}});$("#board").on("click",".task-board-popover",a);$("#board").on("click",".task-board",function(){window.location=$(this).data("task-url")});$(".task-board-tooltip").tooltip({track:!1,position:{my:"left-20 top", -at:"center bottom+9",using:function(a,b){$(this).css(a);var c=b.target.left+b.target.width/2-b.element.left-20;$("<div>").addClass("tooltip-arrow").addClass(b.vertical).addClass(0==c?"align-left":"align-right").appendTo(this)}},content:function(a){if(a=$(this).attr("data-href")){var b=this;$.get(a,function p(a){$(".ui-tooltip-content:visible").html(a);a=$(".ui-tooltip:visible");a.css({top:"",left:""});a.children(".tooltip-arrow").remove();var c=$(b).tooltip("option","position");c.of=$(b);a.position(c); -$("#tooltip-subtasks a").not(".popover").click(function(a){a.preventDefault();a.stopPropagation();$(this).hasClass("popover-subtask-restriction")?(Kanboard.OpenPopover($(this).attr("href")),$(b).tooltip("close")):$.get($(this).attr("href"),p)})});return'<i class="fa fa-refresh fa-spin fa-2x"></i>'}}}).on("mouseenter",function(){var a=this;$(this).tooltip("open");$(".ui-tooltip").on("mouseleave",function(){$(a).tooltip("close")})}).on("mouseleave focusout",function(a){a.stopImmediatePropagation(); -var b=this;setTimeout(function(){$(".ui-tooltip:hover").length||$(b).tooltip("close")},100)});var b=parseInt($("#board").attr("data-check-interval"));0<b&&(l=window.setInterval(m,1E3*b))}function n(a,b,c,d){clearInterval(l);$.ajax({cache:!1,url:$("#board").attr("data-save-url"),contentType:"application/json",type:"POST",processData:!1,data:JSON.stringify({task_id:a,column_id:b,swimlane_id:d,position:c}),success:function(a){$("#board-container").remove();$("#main").append(a);Kanboard.InitAfterAjax(); -k();h();g()}})}function m(){Kanboard.IsVisible()&&$.ajax({cache:!1,url:$("#board").attr("data-check-url"),statusCode:{200:function(a){$("#board-container").remove();$("#main").append(a);Kanboard.InitAfterAjax();clearInterval(l);k();h();g()}}})}function h(){var a=$("#form-user_id").val(),b=$("#form-category_id").val(),c=$("#more-filters option[value=filter-due-date]").is(":selected"),d=$("#more-filters option[value=filter-recent]").is(":selected"),e=$("#board").data("project-id");$("[data-task-id]").each(function(e, -g){var k=g.getAttribute("data-owner-id"),f=g.getAttribute("data-due-date"),m=g.getAttribute("data-category-id"),h=$(g).hasClass("task-board-recent");g.style.display=k!=a&&-1!=a?"none":"block";!c||""!=f&&"0"!=f||(g.style.display="none");m!=b&&-1!=b&&(g.style.display="none");d&&!h&&(g.style.display="none")});Kanboard.SetStorageItem("board_filter_"+e+"_form-user_id",a);Kanboard.SetStorageItem("board_filter_"+e+"_form-category_id",b);Kanboard.SetStorageItem("board_filter_"+e+"_filter-due-date",~~c);Kanboard.SetStorageItem("board_filter_"+ -e+"_filter-recent",~~d)}function q(){var a=$("#board").data("project-id");$("#form-user_id").chosen({width:"180px"});$("#form-category_id").chosen({width:"200px"});$("#more-filters").chosen({width:"30%"});$(".apply-filters").change(function(a){h()});$("#form-user_id").val(Kanboard.GetStorageItem("board_filter_"+a+"_form-user_id")||-1);$("#form-user_id").trigger("chosen:updated");$("#form-category_id").val(Kanboard.GetStorageItem("board_filter_"+a+"_form-category_id")||-1);$("#form-category_id").trigger("chosen:updated"); -+Kanboard.GetStorageItem("board_filter_"+a+"_filter-due-date")&&$("#more-filters option[value=filter-due-date]").attr("selected",!0);+Kanboard.GetStorageItem("board_filter_"+a+"_filter-recent")&&$("#more-filters option[value=filter-recent]").attr("selected",!0);$("#more-filters").trigger("chosen:updated");h()}var l=null;jQuery(document).ready(function(){Kanboard.Exists("board")&&(k(),q(),b(),c())})}(); +$("body").data("login-url")}}})},Init:function(){$("#board-selector").chosen({width:180,no_results_text:$("#board-selector").data("notfound")});$("#board-selector").change(function(){window.location=$(this).attr("data-board-url").replace(/PROJECT_ID/g,$(this).val())});window.setInterval(Kanboard.CheckSession,6E4);Mousetrap.bindGlobal("mod+enter",function(){$("form").submit()});Mousetrap.bind("b",function(a){a.preventDefault();$("#board-selector").trigger("chosen:open")});$(".column-tooltip").tooltip({content:function(){return'<div class="markdown">'+ +$(this).attr("title")+"</div>"},position:{my:"left-20 top",at:"center bottom+9",using:function(a,c){$(this).css(a);var b=c.target.left+c.target.width/2-c.element.left-20;$("<div>").addClass("tooltip-arrow").addClass(c.vertical).addClass(0==b?"align-left":"align-right").appendTo(this)}}});$.datepicker.setDefaults($.datepicker.regional[$("body").data("js-lang")]);Kanboard.InitAfterAjax()},InitAfterAjax:function(){$(document).on("click",".popover",Kanboard.Popover);$(".form-date").datepicker({showOtherMonths:!0, +selectOtherMonths:!0,dateFormat:"yy-mm-dd",constrainInput:!1});$("#markdown-preview").click(Kanboard.MarkdownPreview);$("#markdown-write").click(Kanboard.MarkdownWriter);$(".auto-select").focus(function(){$(this).select()});$(".dropit-submenu").hide();$(".dropdown").not(".dropit").dropit({triggerParentEl:"span"});$(".task-autocomplete").length&&($(".task-autocomplete").parent().find("input[type=submit]").attr("disabled","disabled"),$(".task-autocomplete").autocomplete({source:$(".task-autocomplete").data("search-url"), +minLength:2,select:function(a,c){var b=$(".task-autocomplete").data("dst-field");$("input[name="+b+"]").val(c.item.id);$(".task-autocomplete").parent().find("input[type=submit]").removeAttr("disabled")}}))}}}(); +Kanboard.Board=function(){function a(a){a.preventDefault();a.stopPropagation();Kanboard.Popover(a,Kanboard.InitAfterAjax)}function c(){Mousetrap.bind("n",function(){Kanboard.OpenPopover($("#board").data("task-creation-url"),Kanboard.InitAfterAjax)});Mousetrap.bind("s",function(){"expanded"===(Kanboard.GetStorageItem(d())||"expanded")?(e(),Kanboard.SetStorageItem(d(),"collapsed")):(f(),Kanboard.SetStorageItem(d(),"expanded"))});Mousetrap.bind("c",function(){p()})}function b(){$(".filter-expand-link").click(function(a){a.preventDefault(); +f();Kanboard.SetStorageItem(d(),"expanded")});$(".filter-collapse-link").click(function(a){a.preventDefault();e();Kanboard.SetStorageItem(d(),"collapsed")});g()}function d(){return"board_stacking_"+$("#board").data("project-id")}function e(){$(".filter-collapse").hide();$(".task-board-collapsed").show();$(".filter-expand").show();$(".task-board-expanded").hide()}function f(){$(".filter-collapse").show();$(".task-board-collapsed").hide();$(".filter-expand").hide();$(".task-board-expanded").show()} +function g(){"expanded"===(Kanboard.GetStorageItem(d())||"expanded")?f():e()}function h(){$(".column").sortable({delay:300,distance:5,connectWith:".column",placeholder:"draggable-placeholder",stop:function(a,b){q(b.item.attr("data-task-id"),b.item.parent().attr("data-column-id"),b.item.index()+1,b.item.parent().attr("data-swimlane-id"))}});$("#board").on("click",".task-board-popover",a);$("#board").on("click",".task-board",function(){window.location=$(this).data("task-url")});$(".task-board-tooltip").tooltip({track:!1, +position:{my:"left-20 top",at:"center bottom+9",using:function(a,b){$(this).css(a);var c=b.target.left+b.target.width/2-b.element.left-20;$("<div>").addClass("tooltip-arrow").addClass(b.vertical).addClass(0==c?"align-left":"align-right").appendTo(this)}},content:function(a){if(a=$(this).attr("data-href")){var b=this;$.get(a,function r(a){$(".ui-tooltip-content:visible").html(a);a=$(".ui-tooltip:visible");a.css({top:"",left:""});a.children(".tooltip-arrow").remove();var c=$(b).tooltip("option","position"); +c.of=$(b);a.position(c);$("#tooltip-subtasks a").not(".popover").click(function(a){a.preventDefault();a.stopPropagation();$(this).hasClass("popover-subtask-restriction")?(Kanboard.OpenPopover($(this).attr("href")),$(b).tooltip("close")):$.get($(this).attr("href"),r)})});return'<i class="fa fa-refresh fa-spin fa-2x"></i>'}}}).on("mouseenter",function(){var a=this;$(this).tooltip("open");$(".ui-tooltip").on("mouseleave",function(){$(a).tooltip("close")})}).on("mouseleave focusout",function(a){a.stopImmediatePropagation(); +var b=this;setTimeout(function(){$(".ui-tooltip:hover").length||$(b).tooltip("close")},100)});var b=parseInt($("#board").attr("data-check-interval"));0<b&&(m=window.setInterval(n,1E3*b))}function q(a,b,c,d){clearInterval(m);$.ajax({cache:!1,url:$("#board").attr("data-save-url"),contentType:"application/json",type:"POST",processData:!1,data:JSON.stringify({task_id:a,column_id:b,swimlane_id:d,position:c}),success:function(a){$("#board-container").remove();$("#main").append(a);Kanboard.InitAfterAjax(); +h();k();g();l()}})}function n(){Kanboard.IsVisible()&&$.ajax({cache:!1,url:$("#board").attr("data-check-url"),statusCode:{200:function(a){$("#board-container").remove();$("#main").append(a);Kanboard.InitAfterAjax();clearInterval(m);h();k();g();l()}}})}function k(){var a=$("#form-user_id").val(),b=$("#form-category_id").val(),c=$("#more-filters option[value=filter-due-date]").is(":selected"),d=$("#more-filters option[value=filter-recent]").is(":selected"),e=$("#board").data("project-id");$("[data-task-id]").each(function(e, +g){var f=g.getAttribute("data-owner-id"),h=g.getAttribute("data-due-date"),n=g.getAttribute("data-category-id"),k=$(g).hasClass("task-board-recent");g.style.display=f!=a&&-1!=a?"none":"block";!c||""!=h&&"0"!=h||(g.style.display="none");n!=b&&-1!=b&&(g.style.display="none");d&&!k&&(g.style.display="none")});Kanboard.SetStorageItem("board_filter_"+e+"_form-user_id",a);Kanboard.SetStorageItem("board_filter_"+e+"_form-category_id",b);Kanboard.SetStorageItem("board_filter_"+e+"_filter-due-date",~~c);Kanboard.SetStorageItem("board_filter_"+ +e+"_filter-recent",~~d)}function t(){var a=$("#board").data("project-id");$("#form-user_id").chosen({width:"180px",no_results_text:$("#form-user_id").data("notfound")});$("#form-category_id").chosen({width:"200px",no_results_text:$("#form-category_id").data("notfound")});$("#more-filters").chosen({width:"30%",no_results_text:$("#more-filters").data("notfound")});$(".apply-filters").change(function(a){k()});$("#form-user_id").val(Kanboard.GetStorageItem("board_filter_"+a+"_form-user_id")||-1);$("#form-user_id").trigger("chosen:updated"); +$("#form-category_id").val(Kanboard.GetStorageItem("board_filter_"+a+"_form-category_id")||-1);$("#form-category_id").trigger("chosen:updated");+Kanboard.GetStorageItem("board_filter_"+a+"_filter-due-date")&&$("#more-filters option[value=filter-due-date]").attr("selected",!0);+Kanboard.GetStorageItem("board_filter_"+a+"_filter-recent")&&$("#more-filters option[value=filter-recent]").attr("selected",!0);$("#more-filters").trigger("chosen:updated");k()}function u(){jQuery(document).on("click",".filter-toggle-scrolling", +function(a){a.preventDefault();p()});l()}function p(){var a=Kanboard.GetStorageItem("horizontal_scroll")||1;Kanboard.SetStorageItem("horizontal_scroll",0==a?1:0);l()}function l(){0==Kanboard.GetStorageItem("horizontal_scroll")?($(".filter-wide").show(),$(".filter-compact").hide(),$("#board-container").addClass("board-container-compact"),$("#board th").addClass("board-column-compact")):($(".filter-wide").hide(),$(".filter-compact").show(),$("#board-container").removeClass("board-container-compact"), +$("#board th").removeClass("board-column-compact"))}var m=null;jQuery(document).ready(function(){Kanboard.Exists("board")&&(h(),t(),b(),u(),c())})}(); Kanboard.Calendar=function(){function a(a){var b=$("#calendar").data("save-url")||$("#user-calendar").data("save-url");$.ajax({cache:!1,url:b,contentType:"application/json",type:"POST",processData:!1,data:JSON.stringify({task_id:a.id,date_due:a.start.format()})})}function c(){var a=$("#user-calendar"),b=a.data("check-url"),c={start:a.fullCalendar("getView").start.format(),end:a.fullCalendar("getView").end.format(),user_id:a.data("user-id")},d;for(d in c)b+="&"+d+"="+c[d];$.getJSON(b,function(b){a.fullCalendar("removeEvents"); a.fullCalendar("addEventSource",b);a.fullCalendar("rerenderEvents")})}function b(a){var b=$("#calendar"),c=b.data("check-url"),d={start:b.fullCalendar("getView").start.format(),end:b.fullCalendar("getView").end.format()};jQuery.extend(d,a);for(var e in d)c+="&"+e+"="+d[e];$.getJSON(c,function(a){b.fullCalendar("removeEvents");b.fullCalendar("addEventSource",a);b.fullCalendar("rerenderEvents")})}function d(){var a=Kanboard.GetStorageItem(f);if(""!==a){var a=JSON.parse(a),c;for(c in a)$("select[name="+ c+"]").val(a[c])}b(a||{});$(".calendar-filter").change(e)}function e(){var a={};$(".calendar-filter").each(function(){a[$(this).attr("name")]=$(this).val()});Kanboard.SetStorageItem(f,JSON.stringify(a));b(a)}var f="";jQuery(document).ready(function(){Kanboard.Exists("calendar")?(f="calendar_filters_"+$("#calendar").data("project-id"),$("#calendar").fullCalendar({lang:$("body").data("js-lang"),editable:!0,eventLimit:!0,defaultView:"month",header:{left:"prev,next today",center:"title",right:"month,agendaWeek,agendaDay"}, @@ -161,10 +164,11 @@ Kanboard.Analytic=function(){jQuery(document).ready(function(){Kanboard.Exists(" Kanboard.Analytic.CFD=function(){return{Init:function(){jQuery.getJSON($("#chart").attr("data-url"),function(a){var c=a.labels,b=a.columns,d=a.metrics;a=[];for(var e=0;e<d.length;e++){var f={};f[c.column]=d[e].column_title;f[c.day]=d[e].day;f[c.total]=d[e].total;a.push(f)}d=dimple.newSvg("#chart","100%",380);a=new dimple.chart(d,a);a.addCategoryAxis("x",c.day).addOrderRule("Date");a.addMeasureAxis("y",c.total);a.addSeries(c.column,dimple.plot.area).addOrderRule(b.reverse());a.addLegend(10,10,500, 30,"left");a.draw()})}}}();Kanboard.Analytic.TaskRepartition=function(){return{Init:function(){jQuery.getJSON($("#chart").attr("data-url"),function(a){var c=a.labels,b=a.metrics;a=[];for(var d=0;d<b.length;d++){var e={};e[c.nb_tasks]=b[d].nb_tasks;e[c.column_title]=b[d].column_title;a.push(e)}b=dimple.newSvg("#chart","100%",350);a=new dimple.chart(b,a);a.addMeasureAxis("p",c.nb_tasks);a.addSeries(c.column_title,dimple.plot.pie).innerRadius="50%";a.addLegend(0,0,100,"100%","left");a.draw()})}}}(); Kanboard.Analytic.UserRepartition=function(){return{Init:function(){jQuery.getJSON($("#chart").attr("data-url"),function(a){var c=a.labels,b=a.metrics;a=[];for(var d=0;d<b.length;d++){var e={};e[c.nb_tasks]=b[d].nb_tasks;e[c.user]=b[d].user;a.push(e)}b=dimple.newSvg("#chart","100%",350);a=new dimple.chart(b,a);a.addMeasureAxis("p",c.nb_tasks);a.addSeries(c.user,dimple.plot.pie).innerRadius="50%";a.addLegend(0,0,100,"100%","left");a.draw()})}}}(); -Kanboard.Task=function(){jQuery(document).ready(function(){$(".task-autocomplete").length&&($("input[type=submit]").attr("disabled","disabled"),$(".task-autocomplete").autocomplete({source:$(".task-autocomplete").data("search-url"),minLength:2,select:function(a,c){var b=$(".task-autocomplete").data("dst-field");$("input[name="+b+"]").val(c.item.id);$("input[type=submit]").removeAttr("disabled")}}))})}(); Kanboard.Swimlane=function(){function a(a){$(".swimlane-row-"+a).css("display","none");$(".show-icon-swimlane-"+a).css("display","inline");$(".hide-icon-swimlane-"+a).css("display","none")}function c(){var a="hidden_swimlanes_"+$("#board").data("project-id");return JSON.parse(Kanboard.GetStorageItem(a))||[]}jQuery(document).ajaxComplete(function(){c().map(function(b){a(b)})});jQuery(document).ready(function(){c().map(function(b){a(b)})});jQuery(document).on("click",".board-swimlane-toggle",function(b){b.preventDefault(); b=$(this).data("swimlane-id");if(-1<c().indexOf(b)){var d="hidden_swimlanes_"+$("#board").data("project-id"),e=JSON.parse(Kanboard.GetStorageItem(d))||[],f=e.indexOf(b);-1<f&&e.splice(f,1);Kanboard.SetStorageItem(d,JSON.stringify(e));$(".swimlane-row-"+b).css("display","table-row");$(".show-icon-swimlane-"+b).css("display","none");$(".hide-icon-swimlane-"+b).css("display","inline")}else d="hidden_swimlanes_"+$("#board").data("project-id"),e=JSON.parse(Kanboard.GetStorageItem(d))||[],e.push(b),Kanboard.SetStorageItem(d, JSON.stringify(e)),a(b)})}(); Kanboard.Dashboard=function(){function a(){0<$(".dashboard-right-column > div:visible").size()?$(".dashboard-left-column").removeClass("dashboard-single-column"):$(".dashboard-left-column").addClass("dashboard-single-column");0<$(".dashboard-left-column > div:visible").size()?$(".dashboard-right-column").removeClass("dashboard-single-column"):$(".dashboard-right-column").addClass("dashboard-single-column")}jQuery(document).ready(function(){var c=Kanboard.GetStorageItem("dashboard_view");if(c){var c= JSON.parse(c),b;for(b in c)$("#dashboard-"+b).toggle(c[b]);a()}});jQuery(document).on("click",".dashboard-toggle",function(c){c.preventDefault();$("#dashboard-"+$(this).data("toggle")).toggle();a();c=["projects","tasks","subtasks","activities","calendar"];for(var b={},d=0;d<c.length;d++)b[c[d]]=$("#dashboard-"+c[d]).is(":visible");Kanboard.SetStorageItem("dashboard_view",JSON.stringify(b))})}(); -(function(t){"function"==typeof define&&define.amd?define(["jquery","moment"],t):t(jQuery,moment)})(function(t,e){(e.defineLocale||e.lang).call(e,"da",{months:"januar_februar_marts_april_maj_juni_juli_august_september_oktober_november_december".split("_"),monthsShort:"jan_feb_mar_apr_maj_jun_jul_aug_sep_okt_nov_dec".split("_"),weekdays:"søndag_mandag_tirsdag_onsdag_torsdag_fredag_lørdag".split("_"),weekdaysShort:"søn_man_tir_ons_tor_fre_lør".split("_"),weekdaysMin:"sø_ma_ti_on_to_fr_lø".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY LT",LLLL:"dddd [d.] D. MMMM YYYY LT"},calendar:{sameDay:"[I dag kl.] LT",nextDay:"[I morgen kl.] LT",nextWeek:"dddd [kl.] LT",lastDay:"[I går kl.] LT",lastWeek:"[sidste] dddd [kl] LT",sameElse:"L"},relativeTime:{future:"om %s",past:"%s siden",s:"få sekunder",m:"et minut",mm:"%d minutter",h:"en time",hh:"%d timer",d:"en dag",dd:"%d dage",M:"en måned",MM:"%d måneder",y:"et år",yy:"%d år"},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),t.fullCalendar.datepickerLang("da","da",{closeText:"Luk",prevText:"<Forrige",nextText:"Næste>",currentText:"Idag",monthNames:["Januar","Februar","Marts","April","Maj","Juni","Juli","August","September","Oktober","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","Maj","Jun","Jul","Aug","Sep","Okt","Nov","Dec"],dayNames:["Søndag","Mandag","Tirsdag","Onsdag","Torsdag","Fredag","Lørdag"],dayNamesShort:["Søn","Man","Tir","Ons","Tor","Fre","Lør"],dayNamesMin:["Sø","Ma","Ti","On","To","Fr","Lø"],weekHeader:"Uge",dateFormat:"dd-mm-yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),t.fullCalendar.lang("da",{defaultButtonText:{month:"Måned",week:"Uge",day:"Dag",list:"Agenda"},allDayText:"Hele dagen",eventLimitText:"flere"})});(function(t){"function"==typeof define&&define.amd?define(["jquery","moment"],t):t(jQuery,moment)})(function(t,e){function n(t,e,n){var i={m:["eine Minute","einer Minute"],h:["eine Stunde","einer Stunde"],d:["ein Tag","einem Tag"],dd:[t+" Tage",t+" Tagen"],M:["ein Monat","einem Monat"],MM:[t+" Monate",t+" Monaten"],y:["ein Jahr","einem Jahr"],yy:[t+" Jahre",t+" Jahren"]};return e?i[n][0]:i[n][1]}(e.defineLocale||e.lang).call(e,"de",{months:"Januar_Februar_März_April_Mai_Juni_Juli_August_September_Oktober_November_Dezember".split("_"),monthsShort:"Jan._Febr._Mrz._Apr._Mai_Jun._Jul._Aug._Sept._Okt._Nov._Dez.".split("_"),weekdays:"Sonntag_Montag_Dienstag_Mittwoch_Donnerstag_Freitag_Samstag".split("_"),weekdaysShort:"So._Mo._Di._Mi._Do._Fr._Sa.".split("_"),weekdaysMin:"So_Mo_Di_Mi_Do_Fr_Sa".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY LT",LLLL:"dddd, D. MMMM YYYY LT"},calendar:{sameDay:"[Heute um] LT [Uhr]",sameElse:"L",nextDay:"[Morgen um] LT [Uhr]",nextWeek:"dddd [um] LT [Uhr]",lastDay:"[Gestern um] LT [Uhr]",lastWeek:"[letzten] dddd [um] LT [Uhr]"},relativeTime:{future:"in %s",past:"vor %s",s:"ein paar Sekunden",m:n,mm:"%d Minuten",h:n,hh:"%d Stunden",d:n,dd:n,M:n,MM:n,y:n,yy:n},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),t.fullCalendar.datepickerLang("de","de",{closeText:"Schließen",prevText:"<Zurück",nextText:"Vor>",currentText:"Heute",monthNames:["Januar","Februar","März","April","Mai","Juni","Juli","August","September","Oktober","November","Dezember"],monthNamesShort:["Jan","Feb","Mär","Apr","Mai","Jun","Jul","Aug","Sep","Okt","Nov","Dez"],dayNames:["Sonntag","Montag","Dienstag","Mittwoch","Donnerstag","Freitag","Samstag"],dayNamesShort:["So","Mo","Di","Mi","Do","Fr","Sa"],dayNamesMin:["So","Mo","Di","Mi","Do","Fr","Sa"],weekHeader:"KW",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),t.fullCalendar.lang("de",{defaultButtonText:{month:"Monat",week:"Woche",day:"Tag",list:"Terminübersicht"},allDayText:"Ganztägig",eventLimitText:function(t){return"+ weitere "+t}})});(function(e){"function"==typeof define&&define.amd?define(["jquery","moment"],e):e(jQuery,moment)})(function(e,t){var n="ene._feb._mar._abr._may._jun._jul._ago._sep._oct._nov._dic.".split("_"),i="ene_feb_mar_abr_may_jun_jul_ago_sep_oct_nov_dic".split("_");(t.defineLocale||t.lang).call(t,"es",{months:"enero_febrero_marzo_abril_mayo_junio_julio_agosto_septiembre_octubre_noviembre_diciembre".split("_"),monthsShort:function(e,t){return/-MMM-/.test(t)?i[e.month()]:n[e.month()]},weekdays:"domingo_lunes_martes_miércoles_jueves_viernes_sábado".split("_"),weekdaysShort:"dom._lun._mar._mié._jue._vie._sáb.".split("_"),weekdaysMin:"Do_Lu_Ma_Mi_Ju_Vi_Sá".split("_"),longDateFormat:{LT:"H:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D [de] MMMM [de] YYYY",LLL:"D [de] MMMM [de] YYYY LT",LLLL:"dddd, D [de] MMMM [de] YYYY LT"},calendar:{sameDay:function(){return"[hoy a la"+(1!==this.hours()?"s":"")+"] LT"},nextDay:function(){return"[mañana a la"+(1!==this.hours()?"s":"")+"] LT"},nextWeek:function(){return"dddd [a la"+(1!==this.hours()?"s":"")+"] LT"},lastDay:function(){return"[ayer a la"+(1!==this.hours()?"s":"")+"] LT"},lastWeek:function(){return"[el] dddd [pasado a la"+(1!==this.hours()?"s":"")+"] LT"},sameElse:"L"},relativeTime:{future:"en %s",past:"hace %s",s:"unos segundos",m:"un minuto",mm:"%d minutos",h:"una hora",hh:"%d horas",d:"un día",dd:"%d días",M:"un mes",MM:"%d meses",y:"un año",yy:"%d años"},ordinalParse:/\d{1,2}º/,ordinal:"%dº",week:{dow:1,doy:4}}),e.fullCalendar.datepickerLang("es","es",{closeText:"Cerrar",prevText:"<Ant",nextText:"Sig>",currentText:"Hoy",monthNames:["enero","febrero","marzo","abril","mayo","junio","julio","agosto","septiembre","octubre","noviembre","diciembre"],monthNamesShort:["ene","feb","mar","abr","may","jun","jul","ago","sep","oct","nov","dic"],dayNames:["domingo","lunes","martes","miércoles","jueves","viernes","sábado"],dayNamesShort:["dom","lun","mar","mié","jue","vie","sáb"],dayNamesMin:["D","L","M","X","J","V","S"],weekHeader:"Sm",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("es",{defaultButtonText:{month:"Mes",week:"Semana",day:"Día",list:"Agenda"},allDayHtml:"Todo<br/>el día",eventLimitText:"más"})});(function(e){"function"==typeof define&&define.amd?define(["jquery","moment"],e):e(jQuery,moment)})(function(e,t){function n(e,t,n,r){var s="";switch(n){case"s":return r?"muutaman sekunnin":"muutama sekunti";case"m":return r?"minuutin":"minuutti";case"mm":s=r?"minuutin":"minuuttia";break;case"h":return r?"tunnin":"tunti";case"hh":s=r?"tunnin":"tuntia";break;case"d":return r?"päivän":"päivä";case"dd":s=r?"päivän":"päivää";break;case"M":return r?"kuukauden":"kuukausi";case"MM":s=r?"kuukauden":"kuukautta";break;case"y":return r?"vuoden":"vuosi";case"yy":s=r?"vuoden":"vuotta"}return s=i(e,r)+" "+s}function i(e,t){return 10>e?t?s[e]:r[e]:e}var r="nolla yksi kaksi kolme neljä viisi kuusi seitsemän kahdeksan yhdeksän".split(" "),s=["nolla","yhden","kahden","kolmen","neljän","viiden","kuuden",r[7],r[8],r[9]];(t.defineLocale||t.lang).call(t,"fi",{months:"tammikuu_helmikuu_maaliskuu_huhtikuu_toukokuu_kesäkuu_heinäkuu_elokuu_syyskuu_lokakuu_marraskuu_joulukuu".split("_"),monthsShort:"tammi_helmi_maalis_huhti_touko_kesä_heinä_elo_syys_loka_marras_joulu".split("_"),weekdays:"sunnuntai_maanantai_tiistai_keskiviikko_torstai_perjantai_lauantai".split("_"),weekdaysShort:"su_ma_ti_ke_to_pe_la".split("_"),weekdaysMin:"su_ma_ti_ke_to_pe_la".split("_"),longDateFormat:{LT:"HH.mm",LTS:"HH.mm.ss",L:"DD.MM.YYYY",LL:"Do MMMM[ta] YYYY",LLL:"Do MMMM[ta] YYYY, [klo] LT",LLLL:"dddd, Do MMMM[ta] YYYY, [klo] LT",l:"D.M.YYYY",ll:"Do MMM YYYY",lll:"Do MMM YYYY, [klo] LT",llll:"ddd, Do MMM YYYY, [klo] LT"},calendar:{sameDay:"[tänään] [klo] LT",nextDay:"[huomenna] [klo] LT",nextWeek:"dddd [klo] LT",lastDay:"[eilen] [klo] LT",lastWeek:"[viime] dddd[na] [klo] LT",sameElse:"L"},relativeTime:{future:"%s päästä",past:"%s sitten",s:n,m:n,mm:n,h:n,hh:n,d:n,dd:n,M:n,MM:n,y:n,yy:n},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),e.fullCalendar.datepickerLang("fi","fi",{closeText:"Sulje",prevText:"«Edellinen",nextText:"Seuraava»",currentText:"Tänään",monthNames:["Tammikuu","Helmikuu","Maaliskuu","Huhtikuu","Toukokuu","Kesäkuu","Heinäkuu","Elokuu","Syyskuu","Lokakuu","Marraskuu","Joulukuu"],monthNamesShort:["Tammi","Helmi","Maalis","Huhti","Touko","Kesä","Heinä","Elo","Syys","Loka","Marras","Joulu"],dayNamesShort:["Su","Ma","Ti","Ke","To","Pe","La"],dayNames:["Sunnuntai","Maanantai","Tiistai","Keskiviikko","Torstai","Perjantai","Lauantai"],dayNamesMin:["Su","Ma","Ti","Ke","To","Pe","La"],weekHeader:"Vk",dateFormat:"d.m.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("fi",{defaultButtonText:{month:"Kuukausi",week:"Viikko",day:"Päivä",list:"Tapahtumat"},allDayText:"Koko päivä",eventLimitText:"lisää"})});(function(e){"function"==typeof define&&define.amd?define(["jquery","moment"],e):e(jQuery,moment)})(function(e,t){(t.defineLocale||t.lang).call(t,"fr",{months:"janvier_février_mars_avril_mai_juin_juillet_août_septembre_octobre_novembre_décembre".split("_"),monthsShort:"janv._févr._mars_avr._mai_juin_juil._août_sept._oct._nov._déc.".split("_"),weekdays:"dimanche_lundi_mardi_mercredi_jeudi_vendredi_samedi".split("_"),weekdaysShort:"dim._lun._mar._mer._jeu._ven._sam.".split("_"),weekdaysMin:"Di_Lu_Ma_Me_Je_Ve_Sa".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd D MMMM YYYY LT"},calendar:{sameDay:"[Aujourd'hui à] LT",nextDay:"[Demain à] LT",nextWeek:"dddd [à] LT",lastDay:"[Hier à] LT",lastWeek:"dddd [dernier à] LT",sameElse:"L"},relativeTime:{future:"dans %s",past:"il y a %s",s:"quelques secondes",m:"une minute",mm:"%d minutes",h:"une heure",hh:"%d heures",d:"un jour",dd:"%d jours",M:"un mois",MM:"%d mois",y:"un an",yy:"%d ans"},ordinalParse:/\d{1,2}(er|)/,ordinal:function(e){return e+(1===e?"er":"")},week:{dow:1,doy:4}}),e.fullCalendar.datepickerLang("fr","fr",{closeText:"Fermer",prevText:"Précédent",nextText:"Suivant",currentText:"Aujourd'hui",monthNames:["janvier","février","mars","avril","mai","juin","juillet","août","septembre","octobre","novembre","décembre"],monthNamesShort:["janv.","févr.","mars","avr.","mai","juin","juil.","août","sept.","oct.","nov.","déc."],dayNames:["dimanche","lundi","mardi","mercredi","jeudi","vendredi","samedi"],dayNamesShort:["dim.","lun.","mar.","mer.","jeu.","ven.","sam."],dayNamesMin:["D","L","M","M","J","V","S"],weekHeader:"Sem.",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("fr",{defaultButtonText:{month:"Mois",week:"Semaine",day:"Jour",list:"Mon planning"},allDayHtml:"Toute la<br/>journée",eventLimitText:"en plus"})});(function(e){"function"==typeof define&&define.amd?define(["jquery","moment"],e):e(jQuery,moment)})(function(e,t){function n(e,t,n,r){var i=e;switch(n){case"s":return r||t?"néhány másodperc":"néhány másodperce";case"m":return"egy"+(r||t?" perc":" perce");case"mm":return i+(r||t?" perc":" perce");case"h":return"egy"+(r||t?" óra":" órája");case"hh":return i+(r||t?" óra":" órája");case"d":return"egy"+(r||t?" nap":" napja");case"dd":return i+(r||t?" nap":" napja");case"M":return"egy"+(r||t?" hónap":" hónapja");case"MM":return i+(r||t?" hónap":" hónapja");case"y":return"egy"+(r||t?" év":" éve");case"yy":return i+(r||t?" év":" éve")}return""}function r(e){return(e?"":"[múlt] ")+"["+i[this.day()]+"] LT[-kor]"}var i="vasárnap hétfőn kedden szerdán csütörtökön pénteken szombaton".split(" ");(t.defineLocale||t.lang).call(t,"hu",{months:"január_február_március_április_május_június_július_augusztus_szeptember_október_november_december".split("_"),monthsShort:"jan_feb_márc_ápr_máj_jún_júl_aug_szept_okt_nov_dec".split("_"),weekdays:"vasárnap_hétfő_kedd_szerda_csütörtök_péntek_szombat".split("_"),weekdaysShort:"vas_hét_kedd_sze_csüt_pén_szo".split("_"),weekdaysMin:"v_h_k_sze_cs_p_szo".split("_"),longDateFormat:{LT:"H:mm",LTS:"LT:ss",L:"YYYY.MM.DD.",LL:"YYYY. MMMM D.",LLL:"YYYY. MMMM D., LT",LLLL:"YYYY. MMMM D., dddd LT"},meridiemParse:/de|du/i,isPM:function(e){return"u"===e.charAt(1).toLowerCase()},meridiem:function(e,t,n){return 12>e?n===!0?"de":"DE":n===!0?"du":"DU"},calendar:{sameDay:"[ma] LT[-kor]",nextDay:"[holnap] LT[-kor]",nextWeek:function(){return r.call(this,!0)},lastDay:"[tegnap] LT[-kor]",lastWeek:function(){return r.call(this,!1)},sameElse:"L"},relativeTime:{future:"%s múlva",past:"%s",s:n,m:n,mm:n,h:n,hh:n,d:n,dd:n,M:n,MM:n,y:n,yy:n},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:7}}),e.fullCalendar.datepickerLang("hu","hu",{closeText:"bezár",prevText:"vissza",nextText:"előre",currentText:"ma",monthNames:["Január","Február","Március","Április","Május","Június","Július","Augusztus","Szeptember","Október","November","December"],monthNamesShort:["Jan","Feb","Már","Ápr","Máj","Jún","Júl","Aug","Szep","Okt","Nov","Dec"],dayNames:["Vasárnap","Hétfő","Kedd","Szerda","Csütörtök","Péntek","Szombat"],dayNamesShort:["Vas","Hét","Ked","Sze","Csü","Pén","Szo"],dayNamesMin:["V","H","K","Sze","Cs","P","Szo"],weekHeader:"Hét",dateFormat:"yy.mm.dd.",firstDay:1,isRTL:!1,showMonthAfterYear:!0,yearSuffix:""}),e.fullCalendar.lang("hu",{defaultButtonText:{month:"Hónap",week:"Hét",day:"Nap",list:"Napló"},allDayText:"Egész nap",eventLimitText:"további"})});(function(e){"function"==typeof define&&define.amd?define(["jquery","moment"],e):e(jQuery,moment)})(function(e,t){(t.defineLocale||t.lang).call(t,"it",{months:"gennaio_febbraio_marzo_aprile_maggio_giugno_luglio_agosto_settembre_ottobre_novembre_dicembre".split("_"),monthsShort:"gen_feb_mar_apr_mag_giu_lug_ago_set_ott_nov_dic".split("_"),weekdays:"Domenica_Lunedì_Martedì_Mercoledì_Giovedì_Venerdì_Sabato".split("_"),weekdaysShort:"Dom_Lun_Mar_Mer_Gio_Ven_Sab".split("_"),weekdaysMin:"D_L_Ma_Me_G_V_S".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd, D MMMM YYYY LT"},calendar:{sameDay:"[Oggi alle] LT",nextDay:"[Domani alle] LT",nextWeek:"dddd [alle] LT",lastDay:"[Ieri alle] LT",lastWeek:function(){switch(this.day()){case 0:return"[la scorsa] dddd [alle] LT";default:return"[lo scorso] dddd [alle] LT"}},sameElse:"L"},relativeTime:{future:function(e){return(/^[0-9].+$/.test(e)?"tra":"in")+" "+e},past:"%s fa",s:"alcuni secondi",m:"un minuto",mm:"%d minuti",h:"un'ora",hh:"%d ore",d:"un giorno",dd:"%d giorni",M:"un mese",MM:"%d mesi",y:"un anno",yy:"%d anni"},ordinalParse:/\d{1,2}º/,ordinal:"%dº",week:{dow:1,doy:4}}),e.fullCalendar.datepickerLang("it","it",{closeText:"Chiudi",prevText:"<Prec",nextText:"Succ>",currentText:"Oggi",monthNames:["Gennaio","Febbraio","Marzo","Aprile","Maggio","Giugno","Luglio","Agosto","Settembre","Ottobre","Novembre","Dicembre"],monthNamesShort:["Gen","Feb","Mar","Apr","Mag","Giu","Lug","Ago","Set","Ott","Nov","Dic"],dayNames:["Domenica","Lunedì","Martedì","Mercoledì","Giovedì","Venerdì","Sabato"],dayNamesShort:["Dom","Lun","Mar","Mer","Gio","Ven","Sab"],dayNamesMin:["Do","Lu","Ma","Me","Gi","Ve","Sa"],weekHeader:"Sm",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("it",{defaultButtonText:{month:"Mese",week:"Settimana",day:"Giorno",list:"Agenda"},allDayHtml:"Tutto il<br/>giorno",eventLimitText:function(e){return"+altri "+e}})});(function(e){"function"==typeof define&&define.amd?define(["jquery","moment"],e):e(jQuery,moment)})(function(e,t){(t.defineLocale||t.lang).call(t,"ja",{months:"1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月".split("_"),monthsShort:"1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月".split("_"),weekdays:"日曜日_月曜日_火曜日_水曜日_木曜日_金曜日_土曜日".split("_"),weekdaysShort:"日_月_火_水_木_金_土".split("_"),weekdaysMin:"日_月_火_水_木_金_土".split("_"),longDateFormat:{LT:"Ah時m分",LTS:"LTs秒",L:"YYYY/MM/DD",LL:"YYYY年M月D日",LLL:"YYYY年M月D日LT",LLLL:"YYYY年M月D日LT dddd"},meridiemParse:/午前|午後/i,isPM:function(e){return"午後"===e},meridiem:function(e){return 12>e?"午前":"午後"},calendar:{sameDay:"[今日] LT",nextDay:"[明日] LT",nextWeek:"[来週]dddd LT",lastDay:"[昨日] LT",lastWeek:"[前週]dddd LT",sameElse:"L"},relativeTime:{future:"%s後",past:"%s前",s:"数秒",m:"1分",mm:"%d分",h:"1時間",hh:"%d時間",d:"1日",dd:"%d日",M:"1ヶ月",MM:"%dヶ月",y:"1年",yy:"%d年"}}),e.fullCalendar.datepickerLang("ja","ja",{closeText:"閉じる",prevText:"<前",nextText:"次>",currentText:"今日",monthNames:["1月","2月","3月","4月","5月","6月","7月","8月","9月","10月","11月","12月"],monthNamesShort:["1月","2月","3月","4月","5月","6月","7月","8月","9月","10月","11月","12月"],dayNames:["日曜日","月曜日","火曜日","水曜日","木曜日","金曜日","土曜日"],dayNamesShort:["日","月","火","水","木","金","土"],dayNamesMin:["日","月","火","水","木","金","土"],weekHeader:"週",dateFormat:"yy/mm/dd",firstDay:0,isRTL:!1,showMonthAfterYear:!0,yearSuffix:"年"}),e.fullCalendar.lang("ja",{defaultButtonText:{month:"月",week:"週",day:"日",list:"予定リスト"},allDayText:"終日",eventLimitText:function(e){return"他 "+e+" 件"}})});(function(e){"function"==typeof define&&define.amd?define(["jquery","moment"],e):e(jQuery,moment)})(function(e,t){function n(e){return 5>e%10&&e%10>1&&1!==~~(e/10)%10}function i(e,t,i){var r=e+" ";switch(i){case"m":return t?"minuta":"minutę";case"mm":return r+(n(e)?"minuty":"minut");case"h":return t?"godzina":"godzinę";case"hh":return r+(n(e)?"godziny":"godzin");case"MM":return r+(n(e)?"miesiące":"miesięcy");case"yy":return r+(n(e)?"lata":"lat")}}var r="styczeń_luty_marzec_kwiecień_maj_czerwiec_lipiec_sierpień_wrzesień_październik_listopad_grudzień".split("_"),a="stycznia_lutego_marca_kwietnia_maja_czerwca_lipca_sierpnia_września_października_listopada_grudnia".split("_");(t.defineLocale||t.lang).call(t,"pl",{months:function(e,t){return/D MMMM/.test(t)?a[e.month()]:r[e.month()]},monthsShort:"sty_lut_mar_kwi_maj_cze_lip_sie_wrz_paź_lis_gru".split("_"),weekdays:"niedziela_poniedziałek_wtorek_środa_czwartek_piątek_sobota".split("_"),weekdaysShort:"nie_pon_wt_śr_czw_pt_sb".split("_"),weekdaysMin:"N_Pn_Wt_Śr_Cz_Pt_So".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd, D MMMM YYYY LT"},calendar:{sameDay:"[Dziś o] LT",nextDay:"[Jutro o] LT",nextWeek:"[W] dddd [o] LT",lastDay:"[Wczoraj o] LT",lastWeek:function(){switch(this.day()){case 0:return"[W zeszłą niedzielę o] LT";case 3:return"[W zeszłą środę o] LT";case 6:return"[W zeszłą sobotę o] LT";default:return"[W zeszły] dddd [o] LT"}},sameElse:"L"},relativeTime:{future:"za %s",past:"%s temu",s:"kilka sekund",m:i,mm:i,h:i,hh:i,d:"1 dzień",dd:"%d dni",M:"miesiąc",MM:i,y:"rok",yy:i},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),e.fullCalendar.datepickerLang("pl","pl",{closeText:"Zamknij",prevText:"<Poprzedni",nextText:"Następny>",currentText:"Dziś",monthNames:["Styczeń","Luty","Marzec","Kwiecień","Maj","Czerwiec","Lipiec","Sierpień","Wrzesień","Październik","Listopad","Grudzień"],monthNamesShort:["Sty","Lu","Mar","Kw","Maj","Cze","Lip","Sie","Wrz","Pa","Lis","Gru"],dayNames:["Niedziela","Poniedziałek","Wtorek","Środa","Czwartek","Piątek","Sobota"],dayNamesShort:["Nie","Pn","Wt","Śr","Czw","Pt","So"],dayNamesMin:["N","Pn","Wt","Śr","Cz","Pt","So"],weekHeader:"Tydz",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("pl",{defaultButtonText:{month:"Miesiąc",week:"Tydzień",day:"Dzień",list:"Plan dnia"},allDayText:"Cały dzień",eventLimitText:"więcej"})});(function(e){"function"==typeof define&&define.amd?define(["jquery","moment"],e):e(jQuery,moment)})(function(e,t){(t.defineLocale||t.lang).call(t,"pt-br",{months:"janeiro_fevereiro_março_abril_maio_junho_julho_agosto_setembro_outubro_novembro_dezembro".split("_"),monthsShort:"jan_fev_mar_abr_mai_jun_jul_ago_set_out_nov_dez".split("_"),weekdays:"domingo_segunda-feira_terça-feira_quarta-feira_quinta-feira_sexta-feira_sábado".split("_"),weekdaysShort:"dom_seg_ter_qua_qui_sex_sáb".split("_"),weekdaysMin:"dom_2ª_3ª_4ª_5ª_6ª_sáb".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D [de] MMMM [de] YYYY",LLL:"D [de] MMMM [de] YYYY [às] LT",LLLL:"dddd, D [de] MMMM [de] YYYY [às] LT"},calendar:{sameDay:"[Hoje às] LT",nextDay:"[Amanhã às] LT",nextWeek:"dddd [às] LT",lastDay:"[Ontem às] LT",lastWeek:function(){return 0===this.day()||6===this.day()?"[Último] dddd [às] LT":"[Última] dddd [às] LT"},sameElse:"L"},relativeTime:{future:"em %s",past:"%s atrás",s:"segundos",m:"um minuto",mm:"%d minutos",h:"uma hora",hh:"%d horas",d:"um dia",dd:"%d dias",M:"um mês",MM:"%d meses",y:"um ano",yy:"%d anos"},ordinalParse:/\d{1,2}º/,ordinal:"%dº"}),e.fullCalendar.datepickerLang("pt-br","pt-BR",{closeText:"Fechar",prevText:"<Anterior",nextText:"Próximo>",currentText:"Hoje",monthNames:["Janeiro","Fevereiro","Março","Abril","Maio","Junho","Julho","Agosto","Setembro","Outubro","Novembro","Dezembro"],monthNamesShort:["Jan","Fev","Mar","Abr","Mai","Jun","Jul","Ago","Set","Out","Nov","Dez"],dayNames:["Domingo","Segunda-feira","Terça-feira","Quarta-feira","Quinta-feira","Sexta-feira","Sábado"],dayNamesShort:["Dom","Seg","Ter","Qua","Qui","Sex","Sáb"],dayNamesMin:["Dom","Seg","Ter","Qua","Qui","Sex","Sáb"],weekHeader:"Sm",dateFormat:"dd/mm/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("pt-br",{defaultButtonText:{month:"Mês",week:"Semana",day:"Dia",list:"Compromissos"},allDayText:"dia inteiro",eventLimitText:function(e){return"mais +"+e}})});(function(e){"function"==typeof define&&define.amd?define(["jquery","moment"],e):e(jQuery,moment)})(function(e,t){function n(e,t){var n=e.split("_");return 1===t%10&&11!==t%100?n[0]:t%10>=2&&4>=t%10&&(10>t%100||t%100>=20)?n[1]:n[2]}function i(e,t,i){var a={mm:t?"минута_минуты_минут":"минуту_минуты_минут",hh:"час_часа_часов",dd:"день_дня_дней",MM:"месяц_месяца_месяцев",yy:"год_года_лет"};return"m"===i?t?"минута":"минуту":e+" "+n(a[i],+e)}function a(e,t){var n={nominative:"январь_февраль_март_апрель_май_июнь_июль_август_сентябрь_октябрь_ноябрь_декабрь".split("_"),accusative:"января_февраля_марта_апреля_мая_июня_июля_августа_сентября_октября_ноября_декабря".split("_")},i=/D[oD]?(\[[^\[\]]*\]|\s+)+MMMM?/.test(t)?"accusative":"nominative";return n[i][e.month()]}function r(e,t){var n={nominative:"янв_фев_март_апр_май_июнь_июль_авг_сен_окт_ноя_дек".split("_"),accusative:"янв_фев_мар_апр_мая_июня_июля_авг_сен_окт_ноя_дек".split("_")},i=/D[oD]?(\[[^\[\]]*\]|\s+)+MMMM?/.test(t)?"accusative":"nominative";return n[i][e.month()]}function s(e,t){var n={nominative:"воскресенье_понедельник_вторник_среда_четверг_пятница_суббота".split("_"),accusative:"воскресенье_понедельник_вторник_среду_четверг_пятницу_субботу".split("_")},i=/\[ ?[Вв] ?(?:прошлую|следующую|эту)? ?\] ?dddd/.test(t)?"accusative":"nominative";return n[i][e.day()]}(t.defineLocale||t.lang).call(t,"ru",{months:a,monthsShort:r,weekdays:s,weekdaysShort:"вс_пн_вт_ср_чт_пт_сб".split("_"),weekdaysMin:"вс_пн_вт_ср_чт_пт_сб".split("_"),monthsParse:[/^янв/i,/^фев/i,/^мар/i,/^апр/i,/^ма[й|я]/i,/^июн/i,/^июл/i,/^авг/i,/^сен/i,/^окт/i,/^ноя/i,/^дек/i],longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY г.",LLL:"D MMMM YYYY г., LT",LLLL:"dddd, D MMMM YYYY г., LT"},calendar:{sameDay:"[Сегодня в] LT",nextDay:"[Завтра в] LT",lastDay:"[Вчера в] LT",nextWeek:function(){return 2===this.day()?"[Во] dddd [в] LT":"[В] dddd [в] LT"},lastWeek:function(e){if(e.week()===this.week())return 2===this.day()?"[Во] dddd [в] LT":"[В] dddd [в] LT";switch(this.day()){case 0:return"[В прошлое] dddd [в] LT";case 1:case 2:case 4:return"[В прошлый] dddd [в] LT";case 3:case 5:case 6:return"[В прошлую] dddd [в] LT"}},sameElse:"L"},relativeTime:{future:"через %s",past:"%s назад",s:"несколько секунд",m:i,mm:i,h:"час",hh:i,d:"день",dd:i,M:"месяц",MM:i,y:"год",yy:i},meridiemParse:/ночи|утра|дня|вечера/i,isPM:function(e){return/^(дня|вечера)$/.test(e)},meridiem:function(e){return 4>e?"ночи":12>e?"утра":17>e?"дня":"вечера"},ordinalParse:/\d{1,2}-(й|го|я)/,ordinal:function(e,t){switch(t){case"M":case"d":case"DDD":return e+"-й";case"D":return e+"-го";case"w":case"W":return e+"-я";default:return e}},week:{dow:1,doy:7}}),e.fullCalendar.datepickerLang("ru","ru",{closeText:"Закрыть",prevText:"<Пред",nextText:"След>",currentText:"Сегодня",monthNames:["Январь","Февраль","Март","Апрель","Май","Июнь","Июль","Август","Сентябрь","Октябрь","Ноябрь","Декабрь"],monthNamesShort:["Янв","Фев","Мар","Апр","Май","Июн","Июл","Авг","Сен","Окт","Ноя","Дек"],dayNames:["воскресенье","понедельник","вторник","среда","четверг","пятница","суббота"],dayNamesShort:["вск","пнд","втр","срд","чтв","птн","сбт"],dayNamesMin:["Вс","Пн","Вт","Ср","Чт","Пт","Сб"],weekHeader:"Нед",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("ru",{defaultButtonText:{month:"Месяц",week:"Неделя",day:"День",list:"Повестка дня"},allDayText:"Весь день",eventLimitText:function(e){return"+ ещё "+e}})});(function(e){"function"==typeof define&&define.amd?define(["jquery","moment"],e):e(jQuery,moment)})(function(e,t){(t.defineLocale||t.lang).call(t,"sv",{months:"januari_februari_mars_april_maj_juni_juli_augusti_september_oktober_november_december".split("_"),monthsShort:"jan_feb_mar_apr_maj_jun_jul_aug_sep_okt_nov_dec".split("_"),weekdays:"söndag_måndag_tisdag_onsdag_torsdag_fredag_lördag".split("_"),weekdaysShort:"sön_mån_tis_ons_tor_fre_lör".split("_"),weekdaysMin:"sö_må_ti_on_to_fr_lö".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"YYYY-MM-DD",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd D MMMM YYYY LT"},calendar:{sameDay:"[Idag] LT",nextDay:"[Imorgon] LT",lastDay:"[Igår] LT",nextWeek:"dddd LT",lastWeek:"[Förra] dddd[en] LT",sameElse:"L"},relativeTime:{future:"om %s",past:"för %s sedan",s:"några sekunder",m:"en minut",mm:"%d minuter",h:"en timme",hh:"%d timmar",d:"en dag",dd:"%d dagar",M:"en månad",MM:"%d månader",y:"ett år",yy:"%d år"},ordinalParse:/\d{1,2}(e|a)/,ordinal:function(e){var t=e%10,n=1===~~(e%100/10)?"e":1===t?"a":2===t?"a":3===t?"e":"e";return e+n},week:{dow:1,doy:4}}),e.fullCalendar.datepickerLang("sv","sv",{closeText:"Stäng",prevText:"«Förra",nextText:"Nästa»",currentText:"Idag",monthNames:["Januari","Februari","Mars","April","Maj","Juni","Juli","Augusti","September","Oktober","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","Maj","Jun","Jul","Aug","Sep","Okt","Nov","Dec"],dayNamesShort:["Sön","Mån","Tis","Ons","Tor","Fre","Lör"],dayNames:["Söndag","Måndag","Tisdag","Onsdag","Torsdag","Fredag","Lördag"],dayNamesMin:["Sö","Må","Ti","On","To","Fr","Lö"],weekHeader:"Ve",dateFormat:"yy-mm-dd",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("sv",{defaultButtonText:{month:"Månad",week:"Vecka",day:"Dag",list:"Program"},allDayText:"Heldag",eventLimitText:"till"})});(function(e){"function"==typeof define&&define.amd?define(["jquery","moment"],e):e(jQuery,moment)})(function(e,t){(t.defineLocale||t.lang).call(t,"th",{months:"มกราคม_กุมภาพันธ์_มีนาคม_เมษายน_พฤษภาคม_มิถุนายน_กรกฎาคม_สิงหาคม_กันยายน_ตุลาคม_พฤศจิกายน_ธันวาคม".split("_"),monthsShort:"มกรา_กุมภา_มีนา_เมษา_พฤษภา_มิถุนา_กรกฎา_สิงหา_กันยา_ตุลา_พฤศจิกา_ธันวา".split("_"),weekdays:"อาทิตย์_จันทร์_อังคาร_พุธ_พฤหัสบดี_ศุกร์_เสาร์".split("_"),weekdaysShort:"อาทิตย์_จันทร์_อังคาร_พุธ_พฤหัส_ศุกร์_เสาร์".split("_"),weekdaysMin:"อา._จ._อ._พ._พฤ._ศ._ส.".split("_"),longDateFormat:{LT:"H นาฬิกา m นาที",LTS:"LT s วินาที",L:"YYYY/MM/DD",LL:"D MMMM YYYY",LLL:"D MMMM YYYY เวลา LT",LLLL:"วันddddที่ D MMMM YYYY เวลา LT"},meridiemParse:/ก่อนเที่ยง|หลังเที่ยง/,isPM:function(e){return"หลังเที่ยง"===e},meridiem:function(e){return 12>e?"ก่อนเที่ยง":"หลังเที่ยง"},calendar:{sameDay:"[วันนี้ เวลา] LT",nextDay:"[พรุ่งนี้ เวลา] LT",nextWeek:"dddd[หน้า เวลา] LT",lastDay:"[เมื่อวานนี้ เวลา] LT",lastWeek:"[วัน]dddd[ที่แล้ว เวลา] LT",sameElse:"L"},relativeTime:{future:"อีก %s",past:"%sที่แล้ว",s:"ไม่กี่วินาที",m:"1 นาที",mm:"%d นาที",h:"1 ชั่วโมง",hh:"%d ชั่วโมง",d:"1 วัน",dd:"%d วัน",M:"1 เดือน",MM:"%d เดือน",y:"1 ปี",yy:"%d ปี"}}),e.fullCalendar.datepickerLang("th","th",{closeText:"ปิด",prevText:"« ย้อน",nextText:"ถัดไป »",currentText:"วันนี้",monthNames:["มกราคม","กุมภาพันธ์","มีนาคม","เมษายน","พฤษภาคม","มิถุนายน","กรกฎาคม","สิงหาคม","กันยายน","ตุลาคม","พฤศจิกายน","ธันวาคม"],monthNamesShort:["ม.ค.","ก.พ.","มี.ค.","เม.ย.","พ.ค.","มิ.ย.","ก.ค.","ส.ค.","ก.ย.","ต.ค.","พ.ย.","ธ.ค."],dayNames:["อาทิตย์","จันทร์","อังคาร","พุธ","พฤหัสบดี","ศุกร์","เสาร์"],dayNamesShort:["อา.","จ.","อ.","พ.","พฤ.","ศ.","ส."],dayNamesMin:["อา.","จ.","อ.","พ.","พฤ.","ศ.","ส."],weekHeader:"Wk",dateFormat:"dd/mm/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("th",{defaultButtonText:{month:"เดือน",week:"สัปดาห์",day:"วัน",list:"แผนงาน"},allDayText:"ตลอดวัน",eventLimitText:"เพิ่มเติม"})});(function(e){"function"==typeof define&&define.amd?define(["jquery","moment"],e):e(jQuery,moment)})(function(e,t){(t.defineLocale||t.lang).call(t,"zh-cn",{months:"一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月".split("_"),monthsShort:"1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月".split("_"),weekdays:"星期日_星期一_星期二_星期三_星期四_星期五_星期六".split("_"),weekdaysShort:"周日_周一_周二_周三_周四_周五_周六".split("_"),weekdaysMin:"日_一_二_三_四_五_六".split("_"),longDateFormat:{LT:"Ah点mm",LTS:"Ah点m分s秒",L:"YYYY-MM-DD",LL:"YYYY年MMMD日",LLL:"YYYY年MMMD日LT",LLLL:"YYYY年MMMD日ddddLT",l:"YYYY-MM-DD",ll:"YYYY年MMMD日",lll:"YYYY年MMMD日LT",llll:"YYYY年MMMD日ddddLT"},meridiemParse:/凌晨|早上|上午|中午|下午|晚上/,meridiemHour:function(e,t){return 12===e&&(e=0),"凌晨"===t||"早上"===t||"上午"===t?e:"下午"===t||"晚上"===t?e+12:e>=11?e:e+12},meridiem:function(e,t){var n=100*e+t;return 600>n?"凌晨":900>n?"早上":1130>n?"上午":1230>n?"中午":1800>n?"下午":"晚上"},calendar:{sameDay:function(){return 0===this.minutes()?"[今天]Ah[点整]":"[今天]LT"},nextDay:function(){return 0===this.minutes()?"[明天]Ah[点整]":"[明天]LT"},lastDay:function(){return 0===this.minutes()?"[昨天]Ah[点整]":"[昨天]LT"},nextWeek:function(){var e,n;return e=t().startOf("week"),n=this.unix()-e.unix()>=604800?"[下]":"[本]",0===this.minutes()?n+"dddAh点整":n+"dddAh点mm"},lastWeek:function(){var e,n;return e=t().startOf("week"),n=this.unix()<e.unix()?"[上]":"[本]",0===this.minutes()?n+"dddAh点整":n+"dddAh点mm"},sameElse:"LL"},ordinalParse:/\d{1,2}(日|月|周)/,ordinal:function(e,t){switch(t){case"d":case"D":case"DDD":return e+"日";case"M":return e+"月";case"w":case"W":return e+"周";default:return e}},relativeTime:{future:"%s内",past:"%s前",s:"几秒",m:"1分钟",mm:"%d分钟",h:"1小时",hh:"%d小时",d:"1天",dd:"%d天",M:"1个月",MM:"%d个月",y:"1年",yy:"%d年"},week:{dow:1,doy:4}}),e.fullCalendar.datepickerLang("zh-cn","zh-CN",{closeText:"关闭",prevText:"<上月",nextText:"下月>",currentText:"今天",monthNames:["一月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月"],monthNamesShort:["一月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月"],dayNames:["星期日","星期一","星期二","星期三","星期四","星期五","星期六"],dayNamesShort:["周日","周一","周二","周三","周四","周五","周六"],dayNamesMin:["日","一","二","三","四","五","六"],weekHeader:"周",dateFormat:"yy-mm-dd",firstDay:1,isRTL:!1,showMonthAfterYear:!0,yearSuffix:"年"}),e.fullCalendar.lang("zh-cn",{defaultButtonText:{month:"月",week:"周",day:"日",list:"日程"},allDayText:"全天",eventLimitText:function(e){return"另外 "+e+" 个"}})});
\ No newline at end of file +Kanboard.Budget=function(){jQuery(document).ready(function(){if(Kanboard.Exists("budget-chart")){for(var a=$("#chart").data("labels"),c=$("#chart").data("serie"),b=["in","out","left"],d=[],e=0;e<c.length;e++)for(var f=0;f<b.length;f++){var g={};g[a.date]=c[e].date;g[a.value]=c[e][b[f]];g[a.type]=a[b[f]];d.push(g)}c=dimple.newSvg("#chart",750,600);d=new dimple.chart(c,d);d.addCategoryAxis("x",[a.date,a.type]).addOrderRule(a.date);d.addMeasureAxis("y",a.value);d.addSeries(a.type,dimple.plot.bar);d.addLegend(65, +10,510,20,"right");d.draw()}})}(); +(function(t){"function"==typeof define&&define.amd?define(["jquery","moment"],t):t(jQuery,moment)})(function(t,e){(e.defineLocale||e.lang).call(e,"da",{months:"januar_februar_marts_april_maj_juni_juli_august_september_oktober_november_december".split("_"),monthsShort:"jan_feb_mar_apr_maj_jun_jul_aug_sep_okt_nov_dec".split("_"),weekdays:"søndag_mandag_tirsdag_onsdag_torsdag_fredag_lørdag".split("_"),weekdaysShort:"søn_man_tir_ons_tor_fre_lør".split("_"),weekdaysMin:"sø_ma_ti_on_to_fr_lø".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY LT",LLLL:"dddd [d.] D. MMMM YYYY LT"},calendar:{sameDay:"[I dag kl.] LT",nextDay:"[I morgen kl.] LT",nextWeek:"dddd [kl.] LT",lastDay:"[I går kl.] LT",lastWeek:"[sidste] dddd [kl] LT",sameElse:"L"},relativeTime:{future:"om %s",past:"%s siden",s:"få sekunder",m:"et minut",mm:"%d minutter",h:"en time",hh:"%d timer",d:"en dag",dd:"%d dage",M:"en måned",MM:"%d måneder",y:"et år",yy:"%d år"},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),t.fullCalendar.datepickerLang("da","da",{closeText:"Luk",prevText:"<Forrige",nextText:"Næste>",currentText:"Idag",monthNames:["Januar","Februar","Marts","April","Maj","Juni","Juli","August","September","Oktober","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","Maj","Jun","Jul","Aug","Sep","Okt","Nov","Dec"],dayNames:["Søndag","Mandag","Tirsdag","Onsdag","Torsdag","Fredag","Lørdag"],dayNamesShort:["Søn","Man","Tir","Ons","Tor","Fre","Lør"],dayNamesMin:["Sø","Ma","Ti","On","To","Fr","Lø"],weekHeader:"Uge",dateFormat:"dd-mm-yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),t.fullCalendar.lang("da",{defaultButtonText:{month:"Måned",week:"Uge",day:"Dag",list:"Agenda"},allDayText:"Hele dagen",eventLimitText:"flere"})});(function(t){"function"==typeof define&&define.amd?define(["jquery","moment"],t):t(jQuery,moment)})(function(t,e){function n(t,e,n){var i={m:["eine Minute","einer Minute"],h:["eine Stunde","einer Stunde"],d:["ein Tag","einem Tag"],dd:[t+" Tage",t+" Tagen"],M:["ein Monat","einem Monat"],MM:[t+" Monate",t+" Monaten"],y:["ein Jahr","einem Jahr"],yy:[t+" Jahre",t+" Jahren"]};return e?i[n][0]:i[n][1]}(e.defineLocale||e.lang).call(e,"de",{months:"Januar_Februar_März_April_Mai_Juni_Juli_August_September_Oktober_November_Dezember".split("_"),monthsShort:"Jan._Febr._Mrz._Apr._Mai_Jun._Jul._Aug._Sept._Okt._Nov._Dez.".split("_"),weekdays:"Sonntag_Montag_Dienstag_Mittwoch_Donnerstag_Freitag_Samstag".split("_"),weekdaysShort:"So._Mo._Di._Mi._Do._Fr._Sa.".split("_"),weekdaysMin:"So_Mo_Di_Mi_Do_Fr_Sa".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY LT",LLLL:"dddd, D. MMMM YYYY LT"},calendar:{sameDay:"[Heute um] LT [Uhr]",sameElse:"L",nextDay:"[Morgen um] LT [Uhr]",nextWeek:"dddd [um] LT [Uhr]",lastDay:"[Gestern um] LT [Uhr]",lastWeek:"[letzten] dddd [um] LT [Uhr]"},relativeTime:{future:"in %s",past:"vor %s",s:"ein paar Sekunden",m:n,mm:"%d Minuten",h:n,hh:"%d Stunden",d:n,dd:n,M:n,MM:n,y:n,yy:n},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),t.fullCalendar.datepickerLang("de","de",{closeText:"Schließen",prevText:"<Zurück",nextText:"Vor>",currentText:"Heute",monthNames:["Januar","Februar","März","April","Mai","Juni","Juli","August","September","Oktober","November","Dezember"],monthNamesShort:["Jan","Feb","Mär","Apr","Mai","Jun","Jul","Aug","Sep","Okt","Nov","Dez"],dayNames:["Sonntag","Montag","Dienstag","Mittwoch","Donnerstag","Freitag","Samstag"],dayNamesShort:["So","Mo","Di","Mi","Do","Fr","Sa"],dayNamesMin:["So","Mo","Di","Mi","Do","Fr","Sa"],weekHeader:"KW",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),t.fullCalendar.lang("de",{defaultButtonText:{month:"Monat",week:"Woche",day:"Tag",list:"Terminübersicht"},allDayText:"Ganztägig",eventLimitText:function(t){return"+ weitere "+t}})});(function(e){"function"==typeof define&&define.amd?define(["jquery","moment"],e):e(jQuery,moment)})(function(e,t){var n="ene._feb._mar._abr._may._jun._jul._ago._sep._oct._nov._dic.".split("_"),i="ene_feb_mar_abr_may_jun_jul_ago_sep_oct_nov_dic".split("_");(t.defineLocale||t.lang).call(t,"es",{months:"enero_febrero_marzo_abril_mayo_junio_julio_agosto_septiembre_octubre_noviembre_diciembre".split("_"),monthsShort:function(e,t){return/-MMM-/.test(t)?i[e.month()]:n[e.month()]},weekdays:"domingo_lunes_martes_miércoles_jueves_viernes_sábado".split("_"),weekdaysShort:"dom._lun._mar._mié._jue._vie._sáb.".split("_"),weekdaysMin:"Do_Lu_Ma_Mi_Ju_Vi_Sá".split("_"),longDateFormat:{LT:"H:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D [de] MMMM [de] YYYY",LLL:"D [de] MMMM [de] YYYY LT",LLLL:"dddd, D [de] MMMM [de] YYYY LT"},calendar:{sameDay:function(){return"[hoy a la"+(1!==this.hours()?"s":"")+"] LT"},nextDay:function(){return"[mañana a la"+(1!==this.hours()?"s":"")+"] LT"},nextWeek:function(){return"dddd [a la"+(1!==this.hours()?"s":"")+"] LT"},lastDay:function(){return"[ayer a la"+(1!==this.hours()?"s":"")+"] LT"},lastWeek:function(){return"[el] dddd [pasado a la"+(1!==this.hours()?"s":"")+"] LT"},sameElse:"L"},relativeTime:{future:"en %s",past:"hace %s",s:"unos segundos",m:"un minuto",mm:"%d minutos",h:"una hora",hh:"%d horas",d:"un día",dd:"%d días",M:"un mes",MM:"%d meses",y:"un año",yy:"%d años"},ordinalParse:/\d{1,2}º/,ordinal:"%dº",week:{dow:1,doy:4}}),e.fullCalendar.datepickerLang("es","es",{closeText:"Cerrar",prevText:"<Ant",nextText:"Sig>",currentText:"Hoy",monthNames:["enero","febrero","marzo","abril","mayo","junio","julio","agosto","septiembre","octubre","noviembre","diciembre"],monthNamesShort:["ene","feb","mar","abr","may","jun","jul","ago","sep","oct","nov","dic"],dayNames:["domingo","lunes","martes","miércoles","jueves","viernes","sábado"],dayNamesShort:["dom","lun","mar","mié","jue","vie","sáb"],dayNamesMin:["D","L","M","X","J","V","S"],weekHeader:"Sm",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("es",{defaultButtonText:{month:"Mes",week:"Semana",day:"Día",list:"Agenda"},allDayHtml:"Todo<br/>el día",eventLimitText:"más"})});(function(e){"function"==typeof define&&define.amd?define(["jquery","moment"],e):e(jQuery,moment)})(function(e,t){function n(e,t,n,r){var s="";switch(n){case"s":return r?"muutaman sekunnin":"muutama sekunti";case"m":return r?"minuutin":"minuutti";case"mm":s=r?"minuutin":"minuuttia";break;case"h":return r?"tunnin":"tunti";case"hh":s=r?"tunnin":"tuntia";break;case"d":return r?"päivän":"päivä";case"dd":s=r?"päivän":"päivää";break;case"M":return r?"kuukauden":"kuukausi";case"MM":s=r?"kuukauden":"kuukautta";break;case"y":return r?"vuoden":"vuosi";case"yy":s=r?"vuoden":"vuotta"}return s=i(e,r)+" "+s}function i(e,t){return 10>e?t?s[e]:r[e]:e}var r="nolla yksi kaksi kolme neljä viisi kuusi seitsemän kahdeksan yhdeksän".split(" "),s=["nolla","yhden","kahden","kolmen","neljän","viiden","kuuden",r[7],r[8],r[9]];(t.defineLocale||t.lang).call(t,"fi",{months:"tammikuu_helmikuu_maaliskuu_huhtikuu_toukokuu_kesäkuu_heinäkuu_elokuu_syyskuu_lokakuu_marraskuu_joulukuu".split("_"),monthsShort:"tammi_helmi_maalis_huhti_touko_kesä_heinä_elo_syys_loka_marras_joulu".split("_"),weekdays:"sunnuntai_maanantai_tiistai_keskiviikko_torstai_perjantai_lauantai".split("_"),weekdaysShort:"su_ma_ti_ke_to_pe_la".split("_"),weekdaysMin:"su_ma_ti_ke_to_pe_la".split("_"),longDateFormat:{LT:"HH.mm",LTS:"HH.mm.ss",L:"DD.MM.YYYY",LL:"Do MMMM[ta] YYYY",LLL:"Do MMMM[ta] YYYY, [klo] LT",LLLL:"dddd, Do MMMM[ta] YYYY, [klo] LT",l:"D.M.YYYY",ll:"Do MMM YYYY",lll:"Do MMM YYYY, [klo] LT",llll:"ddd, Do MMM YYYY, [klo] LT"},calendar:{sameDay:"[tänään] [klo] LT",nextDay:"[huomenna] [klo] LT",nextWeek:"dddd [klo] LT",lastDay:"[eilen] [klo] LT",lastWeek:"[viime] dddd[na] [klo] LT",sameElse:"L"},relativeTime:{future:"%s päästä",past:"%s sitten",s:n,m:n,mm:n,h:n,hh:n,d:n,dd:n,M:n,MM:n,y:n,yy:n},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),e.fullCalendar.datepickerLang("fi","fi",{closeText:"Sulje",prevText:"«Edellinen",nextText:"Seuraava»",currentText:"Tänään",monthNames:["Tammikuu","Helmikuu","Maaliskuu","Huhtikuu","Toukokuu","Kesäkuu","Heinäkuu","Elokuu","Syyskuu","Lokakuu","Marraskuu","Joulukuu"],monthNamesShort:["Tammi","Helmi","Maalis","Huhti","Touko","Kesä","Heinä","Elo","Syys","Loka","Marras","Joulu"],dayNamesShort:["Su","Ma","Ti","Ke","To","Pe","La"],dayNames:["Sunnuntai","Maanantai","Tiistai","Keskiviikko","Torstai","Perjantai","Lauantai"],dayNamesMin:["Su","Ma","Ti","Ke","To","Pe","La"],weekHeader:"Vk",dateFormat:"d.m.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("fi",{defaultButtonText:{month:"Kuukausi",week:"Viikko",day:"Päivä",list:"Tapahtumat"},allDayText:"Koko päivä",eventLimitText:"lisää"})});(function(e){"function"==typeof define&&define.amd?define(["jquery","moment"],e):e(jQuery,moment)})(function(e,t){(t.defineLocale||t.lang).call(t,"fr",{months:"janvier_février_mars_avril_mai_juin_juillet_août_septembre_octobre_novembre_décembre".split("_"),monthsShort:"janv._févr._mars_avr._mai_juin_juil._août_sept._oct._nov._déc.".split("_"),weekdays:"dimanche_lundi_mardi_mercredi_jeudi_vendredi_samedi".split("_"),weekdaysShort:"dim._lun._mar._mer._jeu._ven._sam.".split("_"),weekdaysMin:"Di_Lu_Ma_Me_Je_Ve_Sa".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd D MMMM YYYY LT"},calendar:{sameDay:"[Aujourd'hui à] LT",nextDay:"[Demain à] LT",nextWeek:"dddd [à] LT",lastDay:"[Hier à] LT",lastWeek:"dddd [dernier à] LT",sameElse:"L"},relativeTime:{future:"dans %s",past:"il y a %s",s:"quelques secondes",m:"une minute",mm:"%d minutes",h:"une heure",hh:"%d heures",d:"un jour",dd:"%d jours",M:"un mois",MM:"%d mois",y:"un an",yy:"%d ans"},ordinalParse:/\d{1,2}(er|)/,ordinal:function(e){return e+(1===e?"er":"")},week:{dow:1,doy:4}}),e.fullCalendar.datepickerLang("fr","fr",{closeText:"Fermer",prevText:"Précédent",nextText:"Suivant",currentText:"Aujourd'hui",monthNames:["janvier","février","mars","avril","mai","juin","juillet","août","septembre","octobre","novembre","décembre"],monthNamesShort:["janv.","févr.","mars","avr.","mai","juin","juil.","août","sept.","oct.","nov.","déc."],dayNames:["dimanche","lundi","mardi","mercredi","jeudi","vendredi","samedi"],dayNamesShort:["dim.","lun.","mar.","mer.","jeu.","ven.","sam."],dayNamesMin:["D","L","M","M","J","V","S"],weekHeader:"Sem.",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("fr",{defaultButtonText:{month:"Mois",week:"Semaine",day:"Jour",list:"Mon planning"},allDayHtml:"Toute la<br/>journée",eventLimitText:"en plus"})});(function(e){"function"==typeof define&&define.amd?define(["jquery","moment"],e):e(jQuery,moment)})(function(e,t){function n(e,t,n,r){var i=e;switch(n){case"s":return r||t?"néhány másodperc":"néhány másodperce";case"m":return"egy"+(r||t?" perc":" perce");case"mm":return i+(r||t?" perc":" perce");case"h":return"egy"+(r||t?" óra":" órája");case"hh":return i+(r||t?" óra":" órája");case"d":return"egy"+(r||t?" nap":" napja");case"dd":return i+(r||t?" nap":" napja");case"M":return"egy"+(r||t?" hónap":" hónapja");case"MM":return i+(r||t?" hónap":" hónapja");case"y":return"egy"+(r||t?" év":" éve");case"yy":return i+(r||t?" év":" éve")}return""}function r(e){return(e?"":"[múlt] ")+"["+i[this.day()]+"] LT[-kor]"}var i="vasárnap hétfőn kedden szerdán csütörtökön pénteken szombaton".split(" ");(t.defineLocale||t.lang).call(t,"hu",{months:"január_február_március_április_május_június_július_augusztus_szeptember_október_november_december".split("_"),monthsShort:"jan_feb_márc_ápr_máj_jún_júl_aug_szept_okt_nov_dec".split("_"),weekdays:"vasárnap_hétfő_kedd_szerda_csütörtök_péntek_szombat".split("_"),weekdaysShort:"vas_hét_kedd_sze_csüt_pén_szo".split("_"),weekdaysMin:"v_h_k_sze_cs_p_szo".split("_"),longDateFormat:{LT:"H:mm",LTS:"LT:ss",L:"YYYY.MM.DD.",LL:"YYYY. MMMM D.",LLL:"YYYY. MMMM D., LT",LLLL:"YYYY. MMMM D., dddd LT"},meridiemParse:/de|du/i,isPM:function(e){return"u"===e.charAt(1).toLowerCase()},meridiem:function(e,t,n){return 12>e?n===!0?"de":"DE":n===!0?"du":"DU"},calendar:{sameDay:"[ma] LT[-kor]",nextDay:"[holnap] LT[-kor]",nextWeek:function(){return r.call(this,!0)},lastDay:"[tegnap] LT[-kor]",lastWeek:function(){return r.call(this,!1)},sameElse:"L"},relativeTime:{future:"%s múlva",past:"%s",s:n,m:n,mm:n,h:n,hh:n,d:n,dd:n,M:n,MM:n,y:n,yy:n},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:7}}),e.fullCalendar.datepickerLang("hu","hu",{closeText:"bezár",prevText:"vissza",nextText:"előre",currentText:"ma",monthNames:["Január","Február","Március","Április","Május","Június","Július","Augusztus","Szeptember","Október","November","December"],monthNamesShort:["Jan","Feb","Már","Ápr","Máj","Jún","Júl","Aug","Szep","Okt","Nov","Dec"],dayNames:["Vasárnap","Hétfő","Kedd","Szerda","Csütörtök","Péntek","Szombat"],dayNamesShort:["Vas","Hét","Ked","Sze","Csü","Pén","Szo"],dayNamesMin:["V","H","K","Sze","Cs","P","Szo"],weekHeader:"Hét",dateFormat:"yy.mm.dd.",firstDay:1,isRTL:!1,showMonthAfterYear:!0,yearSuffix:""}),e.fullCalendar.lang("hu",{defaultButtonText:{month:"Hónap",week:"Hét",day:"Nap",list:"Napló"},allDayText:"Egész nap",eventLimitText:"további"})});(function(e){"function"==typeof define&&define.amd?define(["jquery","moment"],e):e(jQuery,moment)})(function(e,t){(t.defineLocale||t.lang).call(t,"it",{months:"gennaio_febbraio_marzo_aprile_maggio_giugno_luglio_agosto_settembre_ottobre_novembre_dicembre".split("_"),monthsShort:"gen_feb_mar_apr_mag_giu_lug_ago_set_ott_nov_dic".split("_"),weekdays:"Domenica_Lunedì_Martedì_Mercoledì_Giovedì_Venerdì_Sabato".split("_"),weekdaysShort:"Dom_Lun_Mar_Mer_Gio_Ven_Sab".split("_"),weekdaysMin:"D_L_Ma_Me_G_V_S".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd, D MMMM YYYY LT"},calendar:{sameDay:"[Oggi alle] LT",nextDay:"[Domani alle] LT",nextWeek:"dddd [alle] LT",lastDay:"[Ieri alle] LT",lastWeek:function(){switch(this.day()){case 0:return"[la scorsa] dddd [alle] LT";default:return"[lo scorso] dddd [alle] LT"}},sameElse:"L"},relativeTime:{future:function(e){return(/^[0-9].+$/.test(e)?"tra":"in")+" "+e},past:"%s fa",s:"alcuni secondi",m:"un minuto",mm:"%d minuti",h:"un'ora",hh:"%d ore",d:"un giorno",dd:"%d giorni",M:"un mese",MM:"%d mesi",y:"un anno",yy:"%d anni"},ordinalParse:/\d{1,2}º/,ordinal:"%dº",week:{dow:1,doy:4}}),e.fullCalendar.datepickerLang("it","it",{closeText:"Chiudi",prevText:"<Prec",nextText:"Succ>",currentText:"Oggi",monthNames:["Gennaio","Febbraio","Marzo","Aprile","Maggio","Giugno","Luglio","Agosto","Settembre","Ottobre","Novembre","Dicembre"],monthNamesShort:["Gen","Feb","Mar","Apr","Mag","Giu","Lug","Ago","Set","Ott","Nov","Dic"],dayNames:["Domenica","Lunedì","Martedì","Mercoledì","Giovedì","Venerdì","Sabato"],dayNamesShort:["Dom","Lun","Mar","Mer","Gio","Ven","Sab"],dayNamesMin:["Do","Lu","Ma","Me","Gi","Ve","Sa"],weekHeader:"Sm",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("it",{defaultButtonText:{month:"Mese",week:"Settimana",day:"Giorno",list:"Agenda"},allDayHtml:"Tutto il<br/>giorno",eventLimitText:function(e){return"+altri "+e}})});(function(e){"function"==typeof define&&define.amd?define(["jquery","moment"],e):e(jQuery,moment)})(function(e,t){(t.defineLocale||t.lang).call(t,"ja",{months:"1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月".split("_"),monthsShort:"1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月".split("_"),weekdays:"日曜日_月曜日_火曜日_水曜日_木曜日_金曜日_土曜日".split("_"),weekdaysShort:"日_月_火_水_木_金_土".split("_"),weekdaysMin:"日_月_火_水_木_金_土".split("_"),longDateFormat:{LT:"Ah時m分",LTS:"LTs秒",L:"YYYY/MM/DD",LL:"YYYY年M月D日",LLL:"YYYY年M月D日LT",LLLL:"YYYY年M月D日LT dddd"},meridiemParse:/午前|午後/i,isPM:function(e){return"午後"===e},meridiem:function(e){return 12>e?"午前":"午後"},calendar:{sameDay:"[今日] LT",nextDay:"[明日] LT",nextWeek:"[来週]dddd LT",lastDay:"[昨日] LT",lastWeek:"[前週]dddd LT",sameElse:"L"},relativeTime:{future:"%s後",past:"%s前",s:"数秒",m:"1分",mm:"%d分",h:"1時間",hh:"%d時間",d:"1日",dd:"%d日",M:"1ヶ月",MM:"%dヶ月",y:"1年",yy:"%d年"}}),e.fullCalendar.datepickerLang("ja","ja",{closeText:"閉じる",prevText:"<前",nextText:"次>",currentText:"今日",monthNames:["1月","2月","3月","4月","5月","6月","7月","8月","9月","10月","11月","12月"],monthNamesShort:["1月","2月","3月","4月","5月","6月","7月","8月","9月","10月","11月","12月"],dayNames:["日曜日","月曜日","火曜日","水曜日","木曜日","金曜日","土曜日"],dayNamesShort:["日","月","火","水","木","金","土"],dayNamesMin:["日","月","火","水","木","金","土"],weekHeader:"週",dateFormat:"yy/mm/dd",firstDay:0,isRTL:!1,showMonthAfterYear:!0,yearSuffix:"年"}),e.fullCalendar.lang("ja",{defaultButtonText:{month:"月",week:"週",day:"日",list:"予定リスト"},allDayText:"終日",eventLimitText:function(e){return"他 "+e+" 件"}})});(function(e){"function"==typeof define&&define.amd?define(["jquery","moment"],e):e(jQuery,moment)})(function(e,t){function n(e){return 5>e%10&&e%10>1&&1!==~~(e/10)%10}function i(e,t,i){var r=e+" ";switch(i){case"m":return t?"minuta":"minutę";case"mm":return r+(n(e)?"minuty":"minut");case"h":return t?"godzina":"godzinę";case"hh":return r+(n(e)?"godziny":"godzin");case"MM":return r+(n(e)?"miesiące":"miesięcy");case"yy":return r+(n(e)?"lata":"lat")}}var r="styczeń_luty_marzec_kwiecień_maj_czerwiec_lipiec_sierpień_wrzesień_październik_listopad_grudzień".split("_"),a="stycznia_lutego_marca_kwietnia_maja_czerwca_lipca_sierpnia_września_października_listopada_grudnia".split("_");(t.defineLocale||t.lang).call(t,"pl",{months:function(e,t){return/D MMMM/.test(t)?a[e.month()]:r[e.month()]},monthsShort:"sty_lut_mar_kwi_maj_cze_lip_sie_wrz_paź_lis_gru".split("_"),weekdays:"niedziela_poniedziałek_wtorek_środa_czwartek_piątek_sobota".split("_"),weekdaysShort:"nie_pon_wt_śr_czw_pt_sb".split("_"),weekdaysMin:"N_Pn_Wt_Śr_Cz_Pt_So".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd, D MMMM YYYY LT"},calendar:{sameDay:"[Dziś o] LT",nextDay:"[Jutro o] LT",nextWeek:"[W] dddd [o] LT",lastDay:"[Wczoraj o] LT",lastWeek:function(){switch(this.day()){case 0:return"[W zeszłą niedzielę o] LT";case 3:return"[W zeszłą środę o] LT";case 6:return"[W zeszłą sobotę o] LT";default:return"[W zeszły] dddd [o] LT"}},sameElse:"L"},relativeTime:{future:"za %s",past:"%s temu",s:"kilka sekund",m:i,mm:i,h:i,hh:i,d:"1 dzień",dd:"%d dni",M:"miesiąc",MM:i,y:"rok",yy:i},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),e.fullCalendar.datepickerLang("pl","pl",{closeText:"Zamknij",prevText:"<Poprzedni",nextText:"Następny>",currentText:"Dziś",monthNames:["Styczeń","Luty","Marzec","Kwiecień","Maj","Czerwiec","Lipiec","Sierpień","Wrzesień","Październik","Listopad","Grudzień"],monthNamesShort:["Sty","Lu","Mar","Kw","Maj","Cze","Lip","Sie","Wrz","Pa","Lis","Gru"],dayNames:["Niedziela","Poniedziałek","Wtorek","Środa","Czwartek","Piątek","Sobota"],dayNamesShort:["Nie","Pn","Wt","Śr","Czw","Pt","So"],dayNamesMin:["N","Pn","Wt","Śr","Cz","Pt","So"],weekHeader:"Tydz",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("pl",{defaultButtonText:{month:"Miesiąc",week:"Tydzień",day:"Dzień",list:"Plan dnia"},allDayText:"Cały dzień",eventLimitText:"więcej"})});(function(e){"function"==typeof define&&define.amd?define(["jquery","moment"],e):e(jQuery,moment)})(function(e,t){(t.defineLocale||t.lang).call(t,"pt-br",{months:"janeiro_fevereiro_março_abril_maio_junho_julho_agosto_setembro_outubro_novembro_dezembro".split("_"),monthsShort:"jan_fev_mar_abr_mai_jun_jul_ago_set_out_nov_dez".split("_"),weekdays:"domingo_segunda-feira_terça-feira_quarta-feira_quinta-feira_sexta-feira_sábado".split("_"),weekdaysShort:"dom_seg_ter_qua_qui_sex_sáb".split("_"),weekdaysMin:"dom_2ª_3ª_4ª_5ª_6ª_sáb".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D [de] MMMM [de] YYYY",LLL:"D [de] MMMM [de] YYYY [às] LT",LLLL:"dddd, D [de] MMMM [de] YYYY [às] LT"},calendar:{sameDay:"[Hoje às] LT",nextDay:"[Amanhã às] LT",nextWeek:"dddd [às] LT",lastDay:"[Ontem às] LT",lastWeek:function(){return 0===this.day()||6===this.day()?"[Último] dddd [às] LT":"[Última] dddd [às] LT"},sameElse:"L"},relativeTime:{future:"em %s",past:"%s atrás",s:"segundos",m:"um minuto",mm:"%d minutos",h:"uma hora",hh:"%d horas",d:"um dia",dd:"%d dias",M:"um mês",MM:"%d meses",y:"um ano",yy:"%d anos"},ordinalParse:/\d{1,2}º/,ordinal:"%dº"}),e.fullCalendar.datepickerLang("pt-br","pt-BR",{closeText:"Fechar",prevText:"<Anterior",nextText:"Próximo>",currentText:"Hoje",monthNames:["Janeiro","Fevereiro","Março","Abril","Maio","Junho","Julho","Agosto","Setembro","Outubro","Novembro","Dezembro"],monthNamesShort:["Jan","Fev","Mar","Abr","Mai","Jun","Jul","Ago","Set","Out","Nov","Dez"],dayNames:["Domingo","Segunda-feira","Terça-feira","Quarta-feira","Quinta-feira","Sexta-feira","Sábado"],dayNamesShort:["Dom","Seg","Ter","Qua","Qui","Sex","Sáb"],dayNamesMin:["Dom","Seg","Ter","Qua","Qui","Sex","Sáb"],weekHeader:"Sm",dateFormat:"dd/mm/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("pt-br",{defaultButtonText:{month:"Mês",week:"Semana",day:"Dia",list:"Compromissos"},allDayText:"dia inteiro",eventLimitText:function(e){return"mais +"+e}})});(function(e){"function"==typeof define&&define.amd?define(["jquery","moment"],e):e(jQuery,moment)})(function(e,t){function n(e,t){var n=e.split("_");return 1===t%10&&11!==t%100?n[0]:t%10>=2&&4>=t%10&&(10>t%100||t%100>=20)?n[1]:n[2]}function i(e,t,i){var a={mm:t?"минута_минуты_минут":"минуту_минуты_минут",hh:"час_часа_часов",dd:"день_дня_дней",MM:"месяц_месяца_месяцев",yy:"год_года_лет"};return"m"===i?t?"минута":"минуту":e+" "+n(a[i],+e)}function a(e,t){var n={nominative:"январь_февраль_март_апрель_май_июнь_июль_август_сентябрь_октябрь_ноябрь_декабрь".split("_"),accusative:"января_февраля_марта_апреля_мая_июня_июля_августа_сентября_октября_ноября_декабря".split("_")},i=/D[oD]?(\[[^\[\]]*\]|\s+)+MMMM?/.test(t)?"accusative":"nominative";return n[i][e.month()]}function r(e,t){var n={nominative:"янв_фев_март_апр_май_июнь_июль_авг_сен_окт_ноя_дек".split("_"),accusative:"янв_фев_мар_апр_мая_июня_июля_авг_сен_окт_ноя_дек".split("_")},i=/D[oD]?(\[[^\[\]]*\]|\s+)+MMMM?/.test(t)?"accusative":"nominative";return n[i][e.month()]}function s(e,t){var n={nominative:"воскресенье_понедельник_вторник_среда_четверг_пятница_суббота".split("_"),accusative:"воскресенье_понедельник_вторник_среду_четверг_пятницу_субботу".split("_")},i=/\[ ?[Вв] ?(?:прошлую|следующую|эту)? ?\] ?dddd/.test(t)?"accusative":"nominative";return n[i][e.day()]}(t.defineLocale||t.lang).call(t,"ru",{months:a,monthsShort:r,weekdays:s,weekdaysShort:"вс_пн_вт_ср_чт_пт_сб".split("_"),weekdaysMin:"вс_пн_вт_ср_чт_пт_сб".split("_"),monthsParse:[/^янв/i,/^фев/i,/^мар/i,/^апр/i,/^ма[й|я]/i,/^июн/i,/^июл/i,/^авг/i,/^сен/i,/^окт/i,/^ноя/i,/^дек/i],longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY г.",LLL:"D MMMM YYYY г., LT",LLLL:"dddd, D MMMM YYYY г., LT"},calendar:{sameDay:"[Сегодня в] LT",nextDay:"[Завтра в] LT",lastDay:"[Вчера в] LT",nextWeek:function(){return 2===this.day()?"[Во] dddd [в] LT":"[В] dddd [в] LT"},lastWeek:function(e){if(e.week()===this.week())return 2===this.day()?"[Во] dddd [в] LT":"[В] dddd [в] LT";switch(this.day()){case 0:return"[В прошлое] dddd [в] LT";case 1:case 2:case 4:return"[В прошлый] dddd [в] LT";case 3:case 5:case 6:return"[В прошлую] dddd [в] LT"}},sameElse:"L"},relativeTime:{future:"через %s",past:"%s назад",s:"несколько секунд",m:i,mm:i,h:"час",hh:i,d:"день",dd:i,M:"месяц",MM:i,y:"год",yy:i},meridiemParse:/ночи|утра|дня|вечера/i,isPM:function(e){return/^(дня|вечера)$/.test(e)},meridiem:function(e){return 4>e?"ночи":12>e?"утра":17>e?"дня":"вечера"},ordinalParse:/\d{1,2}-(й|го|я)/,ordinal:function(e,t){switch(t){case"M":case"d":case"DDD":return e+"-й";case"D":return e+"-го";case"w":case"W":return e+"-я";default:return e}},week:{dow:1,doy:7}}),e.fullCalendar.datepickerLang("ru","ru",{closeText:"Закрыть",prevText:"<Пред",nextText:"След>",currentText:"Сегодня",monthNames:["Январь","Февраль","Март","Апрель","Май","Июнь","Июль","Август","Сентябрь","Октябрь","Ноябрь","Декабрь"],monthNamesShort:["Янв","Фев","Мар","Апр","Май","Июн","Июл","Авг","Сен","Окт","Ноя","Дек"],dayNames:["воскресенье","понедельник","вторник","среда","четверг","пятница","суббота"],dayNamesShort:["вск","пнд","втр","срд","чтв","птн","сбт"],dayNamesMin:["Вс","Пн","Вт","Ср","Чт","Пт","Сб"],weekHeader:"Нед",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("ru",{defaultButtonText:{month:"Месяц",week:"Неделя",day:"День",list:"Повестка дня"},allDayText:"Весь день",eventLimitText:function(e){return"+ ещё "+e}})});(function(e){"function"==typeof define&&define.amd?define(["jquery","moment"],e):e(jQuery,moment)})(function(e,t){(t.defineLocale||t.lang).call(t,"sv",{months:"januari_februari_mars_april_maj_juni_juli_augusti_september_oktober_november_december".split("_"),monthsShort:"jan_feb_mar_apr_maj_jun_jul_aug_sep_okt_nov_dec".split("_"),weekdays:"söndag_måndag_tisdag_onsdag_torsdag_fredag_lördag".split("_"),weekdaysShort:"sön_mån_tis_ons_tor_fre_lör".split("_"),weekdaysMin:"sö_må_ti_on_to_fr_lö".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"YYYY-MM-DD",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd D MMMM YYYY LT"},calendar:{sameDay:"[Idag] LT",nextDay:"[Imorgon] LT",lastDay:"[Igår] LT",nextWeek:"dddd LT",lastWeek:"[Förra] dddd[en] LT",sameElse:"L"},relativeTime:{future:"om %s",past:"för %s sedan",s:"några sekunder",m:"en minut",mm:"%d minuter",h:"en timme",hh:"%d timmar",d:"en dag",dd:"%d dagar",M:"en månad",MM:"%d månader",y:"ett år",yy:"%d år"},ordinalParse:/\d{1,2}(e|a)/,ordinal:function(e){var t=e%10,n=1===~~(e%100/10)?"e":1===t?"a":2===t?"a":3===t?"e":"e";return e+n},week:{dow:1,doy:4}}),e.fullCalendar.datepickerLang("sv","sv",{closeText:"Stäng",prevText:"«Förra",nextText:"Nästa»",currentText:"Idag",monthNames:["Januari","Februari","Mars","April","Maj","Juni","Juli","Augusti","September","Oktober","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","Maj","Jun","Jul","Aug","Sep","Okt","Nov","Dec"],dayNamesShort:["Sön","Mån","Tis","Ons","Tor","Fre","Lör"],dayNames:["Söndag","Måndag","Tisdag","Onsdag","Torsdag","Fredag","Lördag"],dayNamesMin:["Sö","Må","Ti","On","To","Fr","Lö"],weekHeader:"Ve",dateFormat:"yy-mm-dd",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("sv",{defaultButtonText:{month:"Månad",week:"Vecka",day:"Dag",list:"Program"},allDayText:"Heldag",eventLimitText:"till"})});(function(e){"function"==typeof define&&define.amd?define(["jquery","moment"],e):e(jQuery,moment)})(function(e,t){(t.defineLocale||t.lang).call(t,"th",{months:"มกราคม_กุมภาพันธ์_มีนาคม_เมษายน_พฤษภาคม_มิถุนายน_กรกฎาคม_สิงหาคม_กันยายน_ตุลาคม_พฤศจิกายน_ธันวาคม".split("_"),monthsShort:"มกรา_กุมภา_มีนา_เมษา_พฤษภา_มิถุนา_กรกฎา_สิงหา_กันยา_ตุลา_พฤศจิกา_ธันวา".split("_"),weekdays:"อาทิตย์_จันทร์_อังคาร_พุธ_พฤหัสบดี_ศุกร์_เสาร์".split("_"),weekdaysShort:"อาทิตย์_จันทร์_อังคาร_พุธ_พฤหัส_ศุกร์_เสาร์".split("_"),weekdaysMin:"อา._จ._อ._พ._พฤ._ศ._ส.".split("_"),longDateFormat:{LT:"H นาฬิกา m นาที",LTS:"LT s วินาที",L:"YYYY/MM/DD",LL:"D MMMM YYYY",LLL:"D MMMM YYYY เวลา LT",LLLL:"วันddddที่ D MMMM YYYY เวลา LT"},meridiemParse:/ก่อนเที่ยง|หลังเที่ยง/,isPM:function(e){return"หลังเที่ยง"===e},meridiem:function(e){return 12>e?"ก่อนเที่ยง":"หลังเที่ยง"},calendar:{sameDay:"[วันนี้ เวลา] LT",nextDay:"[พรุ่งนี้ เวลา] LT",nextWeek:"dddd[หน้า เวลา] LT",lastDay:"[เมื่อวานนี้ เวลา] LT",lastWeek:"[วัน]dddd[ที่แล้ว เวลา] LT",sameElse:"L"},relativeTime:{future:"อีก %s",past:"%sที่แล้ว",s:"ไม่กี่วินาที",m:"1 นาที",mm:"%d นาที",h:"1 ชั่วโมง",hh:"%d ชั่วโมง",d:"1 วัน",dd:"%d วัน",M:"1 เดือน",MM:"%d เดือน",y:"1 ปี",yy:"%d ปี"}}),e.fullCalendar.datepickerLang("th","th",{closeText:"ปิด",prevText:"« ย้อน",nextText:"ถัดไป »",currentText:"วันนี้",monthNames:["มกราคม","กุมภาพันธ์","มีนาคม","เมษายน","พฤษภาคม","มิถุนายน","กรกฎาคม","สิงหาคม","กันยายน","ตุลาคม","พฤศจิกายน","ธันวาคม"],monthNamesShort:["ม.ค.","ก.พ.","มี.ค.","เม.ย.","พ.ค.","มิ.ย.","ก.ค.","ส.ค.","ก.ย.","ต.ค.","พ.ย.","ธ.ค."],dayNames:["อาทิตย์","จันทร์","อังคาร","พุธ","พฤหัสบดี","ศุกร์","เสาร์"],dayNamesShort:["อา.","จ.","อ.","พ.","พฤ.","ศ.","ส."],dayNamesMin:["อา.","จ.","อ.","พ.","พฤ.","ศ.","ส."],weekHeader:"Wk",dateFormat:"dd/mm/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("th",{defaultButtonText:{month:"เดือน",week:"สัปดาห์",day:"วัน",list:"แผนงาน"},allDayText:"ตลอดวัน",eventLimitText:"เพิ่มเติม"})});(function(e){"function"==typeof define&&define.amd?define(["jquery","moment"],e):e(jQuery,moment)})(function(e,t){(t.defineLocale||t.lang).call(t,"zh-cn",{months:"一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月".split("_"),monthsShort:"1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月".split("_"),weekdays:"星期日_星期一_星期二_星期三_星期四_星期五_星期六".split("_"),weekdaysShort:"周日_周一_周二_周三_周四_周五_周六".split("_"),weekdaysMin:"日_一_二_三_四_五_六".split("_"),longDateFormat:{LT:"Ah点mm",LTS:"Ah点m分s秒",L:"YYYY-MM-DD",LL:"YYYY年MMMD日",LLL:"YYYY年MMMD日LT",LLLL:"YYYY年MMMD日ddddLT",l:"YYYY-MM-DD",ll:"YYYY年MMMD日",lll:"YYYY年MMMD日LT",llll:"YYYY年MMMD日ddddLT"},meridiemParse:/凌晨|早上|上午|中午|下午|晚上/,meridiemHour:function(e,t){return 12===e&&(e=0),"凌晨"===t||"早上"===t||"上午"===t?e:"下午"===t||"晚上"===t?e+12:e>=11?e:e+12},meridiem:function(e,t){var n=100*e+t;return 600>n?"凌晨":900>n?"早上":1130>n?"上午":1230>n?"中午":1800>n?"下午":"晚上"},calendar:{sameDay:function(){return 0===this.minutes()?"[今天]Ah[点整]":"[今天]LT"},nextDay:function(){return 0===this.minutes()?"[明天]Ah[点整]":"[明天]LT"},lastDay:function(){return 0===this.minutes()?"[昨天]Ah[点整]":"[昨天]LT"},nextWeek:function(){var e,n;return e=t().startOf("week"),n=this.unix()-e.unix()>=604800?"[下]":"[本]",0===this.minutes()?n+"dddAh点整":n+"dddAh点mm"},lastWeek:function(){var e,n;return e=t().startOf("week"),n=this.unix()<e.unix()?"[上]":"[本]",0===this.minutes()?n+"dddAh点整":n+"dddAh点mm"},sameElse:"LL"},ordinalParse:/\d{1,2}(日|月|周)/,ordinal:function(e,t){switch(t){case"d":case"D":case"DDD":return e+"日";case"M":return e+"月";case"w":case"W":return e+"周";default:return e}},relativeTime:{future:"%s内",past:"%s前",s:"几秒",m:"1分钟",mm:"%d分钟",h:"1小时",hh:"%d小时",d:"1天",dd:"%d天",M:"1个月",MM:"%d个月",y:"1年",yy:"%d年"},week:{dow:1,doy:4}}),e.fullCalendar.datepickerLang("zh-cn","zh-CN",{closeText:"关闭",prevText:"<上月",nextText:"下月>",currentText:"今天",monthNames:["一月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月"],monthNamesShort:["一月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月"],dayNames:["星期日","星期一","星期二","星期三","星期四","星期五","星期六"],dayNamesShort:["周日","周一","周二","周三","周四","周五","周六"],dayNamesMin:["日","一","二","三","四","五","六"],weekHeader:"周",dateFormat:"yy-mm-dd",firstDay:1,isRTL:!1,showMonthAfterYear:!0,yearSuffix:"年"}),e.fullCalendar.lang("zh-cn",{defaultButtonText:{month:"月",week:"周",day:"日",list:"日程"},allDayText:"全天",eventLimitText:function(e){return"另外 "+e+" 个"}})});(function(e){"function"==typeof define&&define.amd?define(["jquery","moment"],e):e(jQuery,moment)})(function(e,t){var n={1:"'inci",5:"'inci",8:"'inci",70:"'inci",80:"'inci",2:"'nci",7:"'nci",20:"'nci",50:"'nci",3:"'üncü",4:"'üncü",100:"'üncü",6:"'ncı",9:"'uncu",10:"'uncu",30:"'uncu",60:"'ıncı",90:"'ıncı"};(t.defineLocale||t.lang).call(t,"tr",{months:"Ocak_Şubat_Mart_Nisan_Mayıs_Haziran_Temmuz_Ağustos_Eylül_Ekim_Kasım_Aralık".split("_"),monthsShort:"Oca_Şub_Mar_Nis_May_Haz_Tem_Ağu_Eyl_Eki_Kas_Ara".split("_"),weekdays:"Pazar_Pazartesi_Salı_Çarşamba_Perşembe_Cuma_Cumartesi".split("_"),weekdaysShort:"Paz_Pts_Sal_Çar_Per_Cum_Cts".split("_"),weekdaysMin:"Pz_Pt_Sa_Ça_Pe_Cu_Ct".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd, D MMMM YYYY LT"},calendar:{sameDay:"[bugün saat] LT",nextDay:"[yarın saat] LT",nextWeek:"[haftaya] dddd [saat] LT",lastDay:"[dün] LT",lastWeek:"[geçen hafta] dddd [saat] LT",sameElse:"L"},relativeTime:{future:"%s sonra",past:"%s önce",s:"birkaç saniye",m:"bir dakika",mm:"%d dakika",h:"bir saat",hh:"%d saat",d:"bir gün",dd:"%d gün",M:"bir ay",MM:"%d ay",y:"bir yıl",yy:"%d yıl"},ordinalParse:/\d{1,2}'(inci|nci|üncü|ncı|uncu|ıncı)/,ordinal:function(e){if(0===e)return e+"'ıncı";var t=e%10,a=e%100-t,r=e>=100?100:null;return e+(n[t]||n[a]||n[r])},week:{dow:1,doy:7}}),e.fullCalendar.datepickerLang("tr","tr",{closeText:"kapat",prevText:"<geri",nextText:"ileri>",currentText:"bugün",monthNames:["Ocak","Şubat","Mart","Nisan","Mayıs","Haziran","Temmuz","Ağustos","Eylül","Ekim","Kasım","Aralık"],monthNamesShort:["Oca","Şub","Mar","Nis","May","Haz","Tem","Ağu","Eyl","Eki","Kas","Ara"],dayNames:["Pazar","Pazartesi","Salı","Çarşamba","Perşembe","Cuma","Cumartesi"],dayNamesShort:["Pz","Pt","Sa","Ça","Pe","Cu","Ct"],dayNamesMin:["Pz","Pt","Sa","Ça","Pe","Cu","Ct"],weekHeader:"Hf",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("tr",{defaultButtonText:{next:"ileri",month:"Ay",week:"Hafta",day:"Gün",list:"Ajanda"},allDayText:"Tüm gün",eventLimitText:"daha fazla"})});
\ No newline at end of file diff --git a/assets/js/src/base.js b/assets/js/src/base.js index 75e04b3a..05846ab8 100644 --- a/assets/js/src/base.js +++ b/assets/js/src/base.js @@ -168,7 +168,8 @@ var Kanboard = (function() { // Project select box $("#board-selector").chosen({ - width: 180 + width: 180, + no_results_text: $("#board-selector").data("notfound") }); $("#board-selector").change(function() { @@ -192,6 +193,22 @@ var Kanboard = (function() { $(".column-tooltip").tooltip({ content: function() { return '<div class="markdown">' + $(this).attr("title") + '</div>'; + }, + position: { + my: 'left-20 top', + at: 'center bottom+9', + using: function(position, feedback) { + + $(this).css(position); + + var arrow_pos = feedback.target.left + feedback.target.width / 2 - feedback.element.left - 20; + + $("<div>") + .addClass("tooltip-arrow") + .addClass(feedback.vertical) + .addClass(arrow_pos == 0 ? "align-left" : "align-right") + .appendTo(this); + } } }); @@ -224,7 +241,22 @@ var Kanboard = (function() { // Dropdown $(".dropit-submenu").hide(); - $('.dropdown').not(".dropit").dropit(); + $('.dropdown').not(".dropit").dropit({ triggerParentEl : "span" }); + + if ($(".task-autocomplete").length) { + $(".task-autocomplete").parent().find("input[type=submit]").attr('disabled','disabled'); + + $(".task-autocomplete").autocomplete({ + source: $(".task-autocomplete").data("search-url"), + minLength: 2, + select: function(event, ui) { + var field = $(".task-autocomplete").data("dst-field"); + $("input[name=" + field + "]").val(ui.item.id); + + $(".task-autocomplete").parent().find("input[type=submit]").removeAttr('disabled'); + } + }); + } } }; diff --git a/assets/js/src/board.js b/assets/js/src/board.js index 75c44f2e..9f93a869 100644 --- a/assets/js/src/board.js +++ b/assets/js/src/board.js @@ -23,6 +23,10 @@ Kanboard.Board = (function() { Mousetrap.bind("s", function() { stack_toggle(); }); + + Mousetrap.bind("c", function() { + compactview_toggle(); + }); } // Collapse/Expand tasks @@ -241,11 +245,12 @@ Kanboard.Board = (function() { board_load_events(); filter_apply(); stack_show(); + compactview_reload(); } }); } - // Check if a board have been changed by someone else + // Check if the board have been changed by someone else function board_check() { if (Kanboard.IsVisible()) { @@ -261,6 +266,7 @@ Kanboard.Board = (function() { board_load_events(); filter_apply(); stack_show(); + compactview_reload(); } } }); @@ -274,7 +280,7 @@ Kanboard.Board = (function() { var selectedCategoryId = $("#form-category_id").val(); var filterDueDate = $("#more-filters option[value=filter-due-date]").is(":selected") var filterRecent = $("#more-filters option[value=filter-recent]").is(":selected") - var projectId = $('#board').data('project-id'); + var projectId = $('#board').data('project-id'); $("[data-task-id]").each(function(index, item) { @@ -313,18 +319,21 @@ Kanboard.Board = (function() { // Load filter events function filter_load_events() { - var projectId = $('#board').data('project-id'); + var projectId = $('#board').data('project-id'); $("#form-user_id").chosen({ - width: "180px" + width: "180px", + no_results_text: $("#form-user_id").data("notfound") }); $("#form-category_id").chosen({ - width: "200px" + width: "200px", + no_results_text: $("#form-category_id").data("notfound") }); $("#more-filters").chosen({ - width: "30%" + width: "30%", + no_results_text: $("#more-filters").data("notfound") }); $(".apply-filters").change(function(e) { @@ -348,7 +357,44 @@ Kanboard.Board = (function() { $("#more-filters").trigger("chosen:updated"); - filter_apply(); + filter_apply(); + } + + function compactview_load_events() + { + jQuery(document).on('click', ".filter-toggle-scrolling", function(e) { + e.preventDefault(); + compactview_toggle(); + }); + + compactview_reload(); + } + + function compactview_toggle() + { + var scrolling = Kanboard.GetStorageItem("horizontal_scroll") || 1; + Kanboard.SetStorageItem("horizontal_scroll", scrolling == 0 ? 1 : 0); + compactview_reload(); + } + + function compactview_reload() + { + if (Kanboard.GetStorageItem("horizontal_scroll") == 0) { + + $(".filter-wide").show(); + $(".filter-compact").hide(); + + $("#board-container").addClass("board-container-compact"); + $("#board th").addClass("board-column-compact"); + } + else { + + $(".filter-wide").hide(); + $(".filter-compact").show(); + + $("#board-container").removeClass("board-container-compact"); + $("#board th").removeClass("board-column-compact"); + } } jQuery(document).ready(function() { @@ -357,6 +403,7 @@ Kanboard.Board = (function() { board_load_events(); filter_load_events(); stack_load_events(); + compactview_load_events(); keyboard_shortcuts(); } }); diff --git a/assets/js/src/budget.js b/assets/js/src/budget.js new file mode 100644 index 00000000..ee39c68c --- /dev/null +++ b/assets/js/src/budget.js @@ -0,0 +1,38 @@ +Kanboard.Budget = (function() { + + jQuery(document).ready(function() { + + if (Kanboard.Exists("budget-chart")) { + + var labels =$("#chart").data("labels"); + var serie = $("#chart").data("serie"); + var types = ["in", "out", "left"]; + var data = []; + + for (var i = 0; i < serie.length; i++) { + + for (var j = 0; j < types.length; j++) { + var row = {}; + row[labels["date"]] = serie[i]["date"]; + row[labels["value"]] = serie[i][types[j]]; + row[labels["type"]] = labels[types[j]]; + + data.push(row); + } + } + + var svg = dimple.newSvg("#chart", 750, 600); + var myChart = new dimple.chart(svg, data); + + var x = myChart.addCategoryAxis("x", [labels["date"], labels["type"]]); + x.addOrderRule(labels["date"]); + + myChart.addMeasureAxis("y", labels["value"]); + + myChart.addSeries(labels["type"], dimple.plot.bar); + myChart.addLegend(65, 10, 510, 20, "right"); + myChart.draw(); + } + }); + +})();
\ No newline at end of file diff --git a/assets/js/src/task.js b/assets/js/src/task.js deleted file mode 100644 index 2caf81cc..00000000 --- a/assets/js/src/task.js +++ /dev/null @@ -1,22 +0,0 @@ -Kanboard.Task = (function() { - - jQuery(document).ready(function() { - - if ($(".task-autocomplete").length) { - - $("input[type=submit]").attr('disabled','disabled'); - - $(".task-autocomplete").autocomplete({ - source: $(".task-autocomplete").data("search-url"), - minLength: 2, - select: function(event, ui) { - var field = $(".task-autocomplete").data("dst-field"); - $("input[name=" + field + "]").val(ui.item.id); - - $("input[type=submit]").removeAttr('disabled'); - } - }); - } - }); - -})(); diff --git a/assets/js/vendor/chosen.jquery.min.js b/assets/js/vendor/chosen.jquery.min.js index cece231b..cece231b 100755..100644 --- a/assets/js/vendor/chosen.jquery.min.js +++ b/assets/js/vendor/chosen.jquery.min.js diff --git a/assets/js/vendor/dimple.v2.1.0.min.js b/assets/js/vendor/dimple.v2.1.0.min.js deleted file mode 100644 index f7be9bbc..00000000 --- a/assets/js/vendor/dimple.v2.1.0.min.js +++ /dev/null @@ -1,3 +0,0 @@ -!function(a,b){"use strict";if("object"==typeof exports)module.exports=b(require("d3"));else if("function"==typeof define&&define.amd)define(["d3"],function(c){return a.dimple=b(c),a.dimple});else if(a.d3)a.dimple=b(a.d3);else{if(!console||!console.warn)throw"dimple requires d3 to run. Are you missing a reference to the d3 library?";console.warn("dimple requires d3 to run. Are you missing a reference to the d3 library?")}}(this,function(a){"use strict";var b={version:"2.1.0",plot:{},aggregateMethod:{}};return b.axis=function(c,d,e,f,g){this.chart=c,this.position=d,this.categoryFields=null===g||void 0===g?e:[].concat(g),this.measure=f,this.timeField=g,this.floatingBarWidth=5,this.hidden=!1,this.showPercent=!1,this.colors=null,this.overrideMin=null,this.overrideMax=null,this.shapes=null,this.showGridlines=null,this.gridlineShapes=null,this.titleShape=null,this.dateParseFormat=null,this.tickFormat=null,this.timePeriod=null,this.timeInterval=1,this.useLog=!1,this.logBase=10,this.title=void 0,this.clamp=!0,this.ticks=null,this.fontSize="10px",this.fontFamily="sans-serif",this._slaves=[],this._scale=null,this._min=0,this._max=0,this._previousOrigin=null,this._origin=null,this._orderRules=[],this._groupOrderRules=[],this._draw=null,this._getAxisData=function(){var a,b,c=[],d=!1;if(this.chart&&this.chart.series){for(a=0;a<this.chart.series.length;a+=1)b=this.chart.series[a],b[this.position]===this&&(b.data&&b.data.length>0?c=c.concat(b.data):d=!0);d&&this.chart.data&&(c=c.concat(this.chart.data))}return c},this._getFontSize=function(){var a;return a=this.fontSize&&"auto"!==this.fontSize.toString().toLowerCase()?isNaN(this.fontSize)?this.fontSize:this.fontSize+"px":(this.chart._heightPixels()/35>10?this.chart._heightPixels()/35:10)+"px"},this._getFormat=function(){var b,c,d,e,f,g,h;return null!==this.tickFormat&&void 0!==this.tickFormat?b=this._hasTimeField()?a.time.format(this.tickFormat):a.format(this.tickFormat):this.showPercent?b=a.format("%"):this.useLog&&null!==this.measure?b=function(b){var c=Math.floor(Math.abs(b),0).toString().length,d=Math.min(Math.floor((c-1)/3),4),e="kmBT".substring(d-1,d),f="0"===Math.round(10*(b/Math.pow(1e3,d))).toString().slice(-1)?0:1;return 0===b?0:a.format(",."+f+"f")(b/Math.pow(1e3,d))+e}:null!==this.measure?(c=Math.floor(Math.abs(this._max),0).toString(),d=Math.floor(Math.abs(this._min),0).toString(),e=Math.max(d.length,c.length),e>3?(f=Math.min(Math.floor((e-1)/3),4),g="kmBT".substring(f-1,f),h=1>=e-3*f?1:0,b=function(b){return 0===b?0:a.format(",."+h+"f")(b/Math.pow(1e3,f))+g}):(h=1>=e?1:0,b=a.format(",."+h+"f"))):b=function(a){return a},b},this._getTimePeriod=function(){var b=this.timePeriod,c=30,d=this._max-this._min;return this._hasTimeField()&&!this.timePeriod&&(b=c>=d/1e3?a.time.seconds:c>=d/6e4?a.time.minutes:c>=d/36e5?a.time.hours:c>=d/864e5?a.time.days:c>=d/6048e5?a.time.weeks:c>=d/26298e5?a.time.months:a.time.years),b},this._getTooltipText=function(b,c){if(this._hasTimeField())c[this.position+"Field"][0]&&b.push(this.timeField+": "+this._getFormat()(c[this.position+"Field"][0]));else if(this._hasCategories())this.categoryFields.forEach(function(a,d){null!==a&&void 0!==a&&c[this.position+"Field"][d]&&b.push(a+(c[this.position+"Field"][d]!==a?": "+c[this.position+"Field"][d]:""))},this);else if(this._hasMeasure())switch(this.position){case"x":b.push(this.measure+": "+this._getFormat()(c.width));break;case"y":b.push(this.measure+": "+this._getFormat()(c.height));break;case"p":b.push(this.measure+": "+this._getFormat()(c.angle)+" ("+a.format("%")(c.piePct)+")");break;default:b.push(this.measure+": "+this._getFormat()(c[this.position+"Value"]))}},this._getTopMaster=function(){var a=this;return null!==this.master&&void 0!==this.master&&(a=this.master._getTopMaster()),a},this._hasCategories=function(){return null!==this.categoryFields&&void 0!==this.categoryFields&&this.categoryFields.length>0},this._hasMeasure=function(){return null!==this.measure&&void 0!==this.measure},this._hasTimeField=function(){return null!==this.timeField&&void 0!==this.timeField},this._parseDate=function(b){var c;return c=null===this.dateParseFormat||void 0===this.dateParseFormat?isNaN(b)?Date.parse(b):new Date(b):a.time.format(this.dateParseFormat).parse(b)},this._update=function(c){var d,e,f,g,h=[],i=this.ticks||10,j=function(a,c,d){var e,f,g=a.categoryFields[0],h=a._getAxisData(),i=g,j=!1,k=!0,l=null;for(e=0;e<h.length;e+=1)if(l=a._parseDate(h[e][g]),null!==l&&void 0!==l&&isNaN(l)){k=!1;break}return k||a.chart.series.forEach(function(b){b[c]===a&&b[d]._hasMeasure()&&(i=b[d].measure,j=!0)},this),f=a._orderRules.concat({ordering:i,desc:j}),b._getOrderedList(h,g,f)};if(this._min=this.showPercent&&this._min<-1?-1:this._min,this._max=this.showPercent&&this._max>1?1:this._max,this._min=null!==this.overrideMin?this.overrideMin:this._min,this._max=null!==this.overrideMax?this.overrideMax:this._max,"x"!==this.position||null!==this._scale&&!c){if("y"!==this.position||null!==this._scale&&!c)this.position.length>0&&"z"===this.position[0]&&null===this._scale?this._scale=this.useLog?a.scale.log().range([this.chart._heightPixels()/300,this.chart._heightPixels()/10]).domain([0===this._min?Math.pow(this.logBase,-1):this._min,0===this._max?-1*Math.pow(this.logBase,-1):this._max]).clamp(this.clamp).base(this.logBase):a.scale.linear().range([1,this.chart._heightPixels()/10]).domain([this._min,this._max]).clamp(this.clamp):this.position.length>0&&"p"===this.position[0]&&null===this._scale?this._scale=this.useLog?a.scale.log().range([0,360]).domain([0===this._min?Math.pow(this.logBase,-1):this._min,0===this._max?-1*Math.pow(this.logBase,-1):this._max]).clamp(this.clamp).base(this.logBase):a.scale.linear().range([0,360]).domain([this._min,this._max]).clamp(this.clamp):this.position.length>0&&"c"===this.position[0]&&null===this._scale&&(this._scale=a.scale.linear().range([0,null===this.colors||1===this.colors.length?1:this.colors.length-1]).domain([this._min,this._max]).clamp(this.clamp));else if(this._hasTimeField()?this._scale=a.time.scale().range([this.chart._yPixels()+this.chart._heightPixels(),this.chart._yPixels()]).domain([this._min,this._max]).clamp(this.clamp):this.useLog?this._scale=a.scale.log().range([this.chart._yPixels()+this.chart._heightPixels(),this.chart._yPixels()]).domain([0===this._min?Math.pow(this.logBase,-1):this._min,0===this._max?-1*Math.pow(this.logBase,-1):this._max]).clamp(this.clamp).base(this.logBase).nice():null===this.measure||void 0===this.measure?(h=j(this,"y","x"),null!==this._slaves&&void 0!==this._slaves&&this._slaves.forEach(function(a){h=h.concat(j(a,"y","x"))},this),this._scale=a.scale.ordinal().rangePoints([this.chart._yPixels()+this.chart._heightPixels(),this.chart._yPixels()]).domain(h.concat([""]))):this._scale=a.scale.linear().range([this.chart._yPixels()+this.chart._heightPixels(),this.chart._yPixels()]).domain([this._min,this._max]).clamp(this.clamp).nice(),!this.hidden)switch(this.chart._axisIndex(this,"y")){case 0:this._draw=a.svg.axis().orient("left").scale(this._scale),this.ticks&&this._draw.ticks(i);break;case 1:this._draw=a.svg.axis().orient("right").scale(this._scale),this.ticks&&this._draw.ticks(i)}}else if(this._hasTimeField()?this._scale=a.time.scale().range([this.chart._xPixels(),this.chart._xPixels()+this.chart._widthPixels()]).domain([this._min,this._max]).clamp(this.clamp):this.useLog?this._scale=a.scale.log().range([this.chart._xPixels(),this.chart._xPixels()+this.chart._widthPixels()]).domain([0===this._min?Math.pow(this.logBase,-1):this._min,0===this._max?-1*Math.pow(this.logBase,-1):this._max]).clamp(this.clamp).base(this.logBase).nice():null===this.measure||void 0===this.measure?(h=j(this,"x","y"),null!==this._slaves&&void 0!==this._slaves&&this._slaves.forEach(function(a){h=h.concat(j(a,"x","y"))},this),this._scale=a.scale.ordinal().rangePoints([this.chart._xPixels(),this.chart._xPixels()+this.chart._widthPixels()]).domain(h.concat([""]))):this._scale=a.scale.linear().range([this.chart._xPixels(),this.chart._xPixels()+this.chart._widthPixels()]).domain([this._min,this._max]).clamp(this.clamp).nice(),!this.hidden)switch(this.chart._axisIndex(this,"x")){case 0:this._draw=a.svg.axis().orient("bottom").scale(this._scale),this.ticks&&this._draw.ticks(i);break;case 1:this._draw=a.svg.axis().orient("top").scale(this._scale),this.ticks&&this._draw.ticks(i)}return null!==this._slaves&&void 0!==this._slaves&&this._slaves.length>0&&this._slaves.forEach(function(a){a._scale=this._scale},this),null!==c&&void 0!==c&&c!==!1||this._hasTimeField()||null===this._scale||null===this._scale.ticks||void 0===this._scale.ticks||!(this._scale.ticks(i).length>0)||"x"!==this.position&&"y"!==this.position||(d=this._scale.ticks(i),e=d[1]-d[0],f=((this._max-this._min)%e).toFixed(0),0!==f&&(this._max=Math.ceil(this._max/e)*e,this._min=Math.floor(this._min/e)*e,this._update(!0))),g=null!==h&&void 0!==h&&h.length>0?this._scale.copy()(h[0]):this._min>0?this._scale.copy()(this._min):this._max<0?this._scale.copy()(this._max):this._scale.copy()(0),this._origin!==g&&(this._previousOrigin=null===this._origin?g:this._origin,this._origin=g),this},this.addGroupOrderRule=function(a,b){this._groupOrderRules.push({ordering:a,desc:b})},this.addOrderRule=function(a,b){this._orderRules.push({ordering:a,desc:b})}},b.chart=function(c,d){this.svg=c,this.x="10%",this.y="10%",this.width="80%",this.height="80%",this.data=d,this.noFormats=!1,this.axes=[],this.series=[],this.legends=[],this.storyboard=null,this.titleShape=null,this.shapes=null,this.ease="cubic-in-out",this.staggerDraw=!1,this._group=c.append("g"),this._tooltipGroup=null,this._assignedColors={},this._nextColor=0,this._axisIndex=function(a,b){var c=0,d=0,e=-1;for(c=0;c<this.axes.length;c+=1){if(this.axes[c]===a){e=d;break}(null===b||void 0===b||b[0]===this.axes[c].position[0])&&(d+=1)}return e},this._getAllData=function(){var a=[];return null!==this.data&&void 0!==this.data&&this.data.length>0&&(a=a.concat(this.data)),null!==this.series&&void 0!==this.series&&this.series.length>0&&this.series.forEach(function(b){null!==b.data&&void 0!==b.data&&b.data.length>0&&(a=a.concat(b.data))}),a},this._getData=function(c,d,e,f,g,h,i,j,k,l){var m,n,o=[],p=function(a,b){var c=[];return null!==a&&(a._hasTimeField()?c.push(a._parseDate(b[a.timeField])):a._hasCategories()&&a.categoryFields.forEach(function(a){c.push(b[a])},this)),c},q={x:!1,y:!1,z:!1,p:!1,c:!1},r={x:[],y:[]},s={x:[],y:[],z:[],p:[]},t={min:null,max:null},u={x:[],y:[],z:[],p:[]},v=[],w={},x={x:0,y:0,z:0,p:0},y="",z=[],A=[],B=[],C="",D=[],E="",F=[],G="",H=[],I=[],J=c,K=[];this.storyboard&&this.storyboard.categoryFields.length>0&&(y=this.storyboard.categoryFields[0],z=b._getOrderedList(J,y,this.storyboard._orderRules)),h&&h._hasCategories()&&h._hasMeasure()&&(C=h.categoryFields[0],D=b._getOrderedList(J,C,h._orderRules.concat([{ordering:h.measure,desc:!0}]))),i&&i._hasCategories()&&i._hasMeasure()&&(E=i.categoryFields[0],F=b._getOrderedList(J,E,i._orderRules.concat([{ordering:i.measure,desc:!0}]))),k&&k._hasCategories()&&k._hasMeasure()&&(G=k.categoryFields[0],H=b._getOrderedList(J,G,k._orderRules.concat([{ordering:k.measure,desc:!0}]))),J.length>0&&d&&d.length>0&&(I=[].concat(f),A=[],d.forEach(function(a){void 0!==J[0][a]&&A.push(a)},this),k&&k._hasMeasure()?I.push({ordering:k.measure,desc:!0}):l&&l._hasMeasure()?I.push({ordering:l.measure,desc:!0}):j&&j._hasMeasure()?I.push({ordering:j.measure,desc:!0}):h&&h._hasMeasure()?I.push({ordering:h.measure,desc:!0}):i&&i._hasMeasure()&&I.push({ordering:i.measure,desc:!0}),B=b._getOrderedList(J,A,I)),J.sort(function(a,b){var c,d,e,f,g,h,i=0;if(""!==y&&(i=z.indexOf(a[y])-z.indexOf(b[y])),""!==C&&0===i&&(i=D.indexOf(a[C])-D.indexOf(b[C])),""!==E&&0===i&&(i=F.indexOf(a[E])-F.indexOf(b[E])),""!==G&&0===i&&(i=H.indexOf(a[G])-F.indexOf(b[G])),A&&A.length>0&&0===i)for(c=[].concat(A),i=0,e=0;e<B.length;e+=1){for(d=[].concat(B[e]),g=!0,h=!0,f=0;f<c.length;f+=1)g=g&&a[c[f]]===d[f],h=h&&b[c[f]]===d[f];if(g&&h){i=0;break}if(g){i=-1;break}if(h){i=1;break}}return i}),J.forEach(function(a){var b,c,f,g,m,n=-1,r=p(h,a),s=p(i,a),t=p(j,a),u=p(k,a),v=[];if(d&&0!==d.length)for(f=0;f<d.length;f+=1)void 0===a[d[f]]?v.push(d[f]):v.push(a[d[f]]);else v=["All"];for(b=v.join("/")+"_"+r.join("/")+"_"+s.join("/")+"_"+u.join("/")+"_"+t.join("/"),c=0;c<o.length;c+=1)if(o[c].key===b){n=c;break}-1===n&&(g={key:b,aggField:v,xField:r,xValue:null,xCount:0,yField:s,yValue:null,yCount:0,pField:u,pValue:null,pCount:0,zField:t,zValue:null,zCount:0,cValue:0,cCount:0,x:0,y:0,xOffset:0,yOffset:0,width:0,height:0,cx:0,cy:0,xBound:0,yBound:0,xValueList:[],yValueList:[],zValueList:[],pValueList:[],cValueList:[],fill:{},stroke:{}},o.push(g),n=o.length-1),m=function(b,c){var d,f,g=!0,h={value:0,count:1},i={value:0,count:1},j="";null!==c&&(d=c.getFrameValue(),c.categoryFields.forEach(function(b,c){c>0&&(j+="/"),j+=a[b],g=j===d},this)),null!==b&&void 0!==b&&g&&(f=o[n],b._hasMeasure()&&null!==a[b.measure]&&void 0!==a[b.measure]&&(-1===f[b.position+"ValueList"].indexOf(a[b.measure])&&f[b.position+"ValueList"].push(a[b.measure]),isNaN(parseFloat(a[b.measure]))&&(q[b.position]=!0),h.value=f[b.position+"Value"],h.count=f[b.position+"Count"],i.value=a[b.measure],f[b.position+"Value"]=e(h,i),f[b.position+"Count"]+=1))},m(h,this.storyboard),m(i,this.storyboard),m(j,this.storyboard),m(k,this.storyboard),m(l,this.storyboard)},this),h&&h._hasCategories()&&h.categoryFields.length>1&&void 0!==r.x&&(K=[],i._hasMeasure()&&K.push({ordering:i.measure,desc:!0}),r.x=b._getOrderedList(J,h.categoryFields[1],h._groupOrderRules.concat(K))),i&&i._hasCategories()&&i.categoryFields.length>1&&void 0!==r.y&&(K=[],h._hasMeasure()&&K.push({ordering:h.measure,desc:!0}),r.y=b._getOrderedList(J,i.categoryFields[1],i._groupOrderRules.concat(K)),r.y.reverse()),o.forEach(function(a){null!==h&&(q.x===!0&&(a.xValue=a.xValueList.length),m=(s.x[a.xField.join("/")]||0)+(i._hasMeasure()?Math.abs(a.yValue):0),s.x[a.xField.join("/")]=m),null!==i&&(q.y===!0&&(a.yValue=a.yValueList.length),m=(s.y[a.yField.join("/")]||0)+(h._hasMeasure()?Math.abs(a.xValue):0),s.y[a.yField.join("/")]=m),null!==k&&(q.p===!0&&(a.pValue=a.pValueList.length),m=(s.p[a.pField.join("/")]||0)+(k._hasMeasure()?Math.abs(a.pValue):0),s.p[a.pField.join("/")]=m),null!==j&&(q.z===!0&&(a.zValue=a.zValueList.length),m=(s.z[a.zField.join("/")]||0)+(j._hasMeasure()?Math.abs(a.zValue):0),s.z[a.zField.join("/")]=m),null!==l&&((null===t.min||a.cValue<t.min)&&(t.min=a.cValue),(null===t.max||a.cValue>t.max)&&(t.max=a.cValue))},this);for(n in s.x)s.x.hasOwnProperty(n)&&(x.x+=s.x[n]);for(n in s.y)s.y.hasOwnProperty(n)&&(x.y+=s.y[n]);for(n in s.p)s.p.hasOwnProperty(n)&&(x.p+=s.p[n]);for(n in s.z)s.z.hasOwnProperty(n)&&(x.z+=s.z[n]);return o.forEach(function(b){var c,d,e,f,m,n=function(a,c,d){var e,f,h,i,j;null!==a&&void 0!==a&&(i=a.position,a._hasCategories()?a._hasMeasure()?(e=b[a.position+"Field"].join("/"),f=a.showPercent?s[a.position][e]/x[a.position]:s[a.position][e],-1===v.indexOf(e)&&(w[e]=f+(v.length>0?w[v[v.length-1]]:0),v.push(e)),h=b[i+"Bound"]=b["c"+i]="x"!==i&&"y"!==i||!g?f:w[e],b[d]=f,b[i]=h-("x"===i&&f>=0||"y"===i&&0>=f?f:0)):(b[i]=b["c"+i]=b[i+"Field"][0],b[d]=1,void 0!==r[i]&&null!==r[i]&&r[i].length>=2&&(b[i+"Offset"]=r[i].indexOf(b[i+"Field"][1]),b[d]=1/r[i].length)):(f=a.showPercent?b[i+"Value"]/s[c][b[c+"Field"].join("/")]:b[i+"Value"],e=b[c+"Field"].join("/")+(b[i+"Value"]>=0),j=u[i][e]=(null===u[i][e]||void 0===u[i][e]||"z"===i||"p"===i?0:u[i][e])+f,h=b[i+"Bound"]=b["c"+i]="x"!==i&&"y"!==i||!g?f:j,b[d]=f,b[i]=h-("x"===i&&f>=0||"y"===i&&0>=f?f:0)))};n(h,"y","width"),n(i,"x","height"),n(j,"z","r"),n(k,"p","angle"),null!==l&&null!==t.min&&null!==t.max&&(t.min===t.max&&(t.min-=.5,t.max+=.5),t.min=l.overrideMin||t.min,t.max=l.overrideMax||t.max,b.cValue=b.cValue>t.max?t.max:b.cValue<t.min?t.min:b.cValue,e=a.scale.linear().range([0,null===l.colors||1===l.colors.length?1:l.colors.length-1]).domain([t.min,t.max]),f=e(b.cValue),m=f-Math.floor(f),b.cValue===t.max&&(m=1),l.colors&&1===l.colors.length?(c=a.rgb(l.colors[0]),d=a.rgb(this.getColor(b.aggField.slice(-1)[0]).fill)):l.colors&&l.colors.length>1?(c=a.rgb(l.colors[Math.floor(f)]),d=a.rgb(l.colors[Math.ceil(f)])):(c=a.rgb("white"),d=a.rgb(this.getColor(b.aggField.slice(-1)[0]).fill)),c.r=Math.floor(c.r+(d.r-c.r)*m),c.g=Math.floor(c.g+(d.g-c.g)*m),c.b=Math.floor(c.b+(d.b-c.b)*m),b.fill=c.toString(),b.stroke=c.darker(.5).toString())},this),o},this._getDelay=function(a,c,d){return function(e){var f=0;return d&&c.staggerDraw&&(d.x._hasCategories()?f=b._helpers.cx(e,c,d)/c._widthPixels()*a:d.y._hasCategories()&&(f=(1-b._helpers.cy(e,c,d)/c._heightPixels())*a)),f}},this._getSeriesData=function(){null!==this.series&&void 0!==this.series&&this.series.forEach(function(a){var b,c,d,e,f,g,h=a.data||this.data||[],i=[].concat(a.categoryFields||"All"),j=this._getData(h,i,a.aggregate,a._orderRules,a._isStacked(),a.x,a.y,a.z,a.p,a.c),k=[],l={},m=a.startAngle*(Math.PI/180)||0,n=(a.endAngle||360)*(Math.PI/180);if(m>n&&(m-=2*Math.PI),a.p&&i.length>0){if(a.x&&a.y){for(i.pop(),k=this._getData(h,["__dimple_placeholder__"].concat(i),a.aggregate,a._orderRules,a._isStacked(),a.x,a.y,a.z,a.p,a.c),b=0;b<j.length;b+=1)for(d=["__dimple_placeholder__"].concat(j[b].aggField),d.pop(),a.x&&a.x._hasCategories()&&(d=d.concat(j[b].xField)),a.y&&a.y._hasCategories()&&(d=d.concat(j[b].yField)),e=d.join("|"),c=0;c<k.length;c+=1)if(f=[].concat(k[c].aggField),a.x&&a.x._hasCategories()&&(f=f.concat(k[c].xField)),a.y&&a.y._hasCategories()&&(f=f.concat(k[c].yField)),g=f.join("|"),e===g){j[b].xField=k[c].xField,j[b].xValue=k[c].xValue,j[b].xCount=k[c].xCount,j[b].yField=k[c].yField,j[b].yValue=k[c].yValue,j[b].yCount=k[c].yCount,j[b].zField=k[c].zField,j[b].zValue=k[c].zValue,j[b].zCount=k[c].zCount,j[b].x=k[c].x,j[b].y=k[c].y,j[b].r=k[c].r,j[b].xOffset=k[c].xOffset,j[b].yOffset=k[c].yOffset,j[b].width=k[c].width,j[b].height=k[c].height,j[b].cx=k[c].cx,j[b].cy=k[c].cy,j[b].xBound=k[c].xBound,j[b].yBound=k[c].yBound,j[b].xValueList=k[c].xValueList,j[b].yValueList=k[c].yValueList,j[b].zValueList=k[c].zValueList,j[b].cValueList=k[c].cValueList,j[b].pieKey=k[c].key,j[b].value=j.pValue,l[k[c].key]||(l[k[c].key]={total:0,angle:m}),l[k[c].key].total+=j[b].pValue;break}}else for(b=0;b<j.length;b+=1)j[b].pieKey="All",j[b].value=j.pValue,l[j[b].pieKey]||(l[j[b].pieKey]={total:0,angle:m}),l[j[b].pieKey].total+=j[b].pValue;for(b=0;b<j.length;b+=1)j[b].piePct=j[b].pValue/l[j[b].pieKey].total,j[b].startAngle=l[j[b].pieKey].angle,j[b].endAngle=j[b].startAngle+j[b].piePct*(n-m),l[j[b].pieKey].angle=j[b].endAngle}a._positionData=j},this)},this._handleTransition=function(a,b,c,d){var e=null;return e=0===b?a:a.transition().duration(b).delay(c._getDelay(b,c,d)).ease(c.ease)},this._heightPixels=function(){return b._parseYPosition(this.height,this.svg.node())},this._registerEventHandlers=function(c){null!==c._eventHandlers&&c._eventHandlers.length>0&&c._eventHandlers.forEach(function(d){var e,f=function(e){var f=new b.eventArgs;null!==c.chart.storyboard&&(f.frameValue=c.chart.storyboard.getFrameValue()),f.seriesValue=e.aggField,f.xValue=e.x,f.yValue=e.y,f.zValue=e.z,f.pValue=e.p,f.colorValue=e.cValue,f.seriesShapes=c.shapes,f.selectedShape=a.select(this),d.handler(f)};if(null!==d.handler&&"function"==typeof d.handler)if(null!==c._markers&&void 0!==c._markers)for(e in c._markers)c._markers.hasOwnProperty(e)&&c._markers[e].on(d.event,f);else c.shapes.on(d.event,f)},this)},this._widthPixels=function(){return b._parseXPosition(this.width,this.svg.node())},this._xPixels=function(){return b._parseXPosition(this.x,this.svg.node())},this._yPixels=function(){return b._parseYPosition(this.y,this.svg.node())},this.addAxis=function(a,c,d,e){var f=null,g=null;if(null!==c&&void 0!==c&&(c=[].concat(c)),"string"==typeof a||a instanceof String)f=new b.axis(this,a,c,d,e),this.axes.push(f);else{if(g=a,f=new b.axis(this,g.position,c,d,e),f._hasMeasure()!==g._hasMeasure())throw"You have specified a composite axis where some but not all axes have a measure - this is not supported, all axes must be of the same type.";if(f._hasTimeField()!==g._hasTimeField())throw"You have specified a composite axis where some but not all axes have a time field - this is not supported, all axes must be of the same type.";if((null===f.categoryFields||void 0===f.categoryFields?0:f.categoryFields.length)!==(null===g.categoryFields||void 0===g.categoryFields?0:g.categoryFields.length))throw"You have specified a composite axis where axes have differing numbers of category fields - this is not supported, all axes must be of the same type.";g._slaves.push(f)}return f},this.addCategoryAxis=function(a,b){return this.addAxis(a,b,null)},this.addColorAxis=function(a,b){var c=this.addAxis("c",null,a);return c.colors=null===b||void 0===b?null:[].concat(b),c},this.addLegend=function(a,c,d,e,f,g){g=null===g||void 0===g?this.series:[].concat(g),f=null===f||void 0===f?"left":f;var h=new b.legend(this,a,c,d,e,f,g);return this.legends.push(h),h},this.addLogAxis=function(a,b,c){var d=this.addAxis(a,null,b,null);return null!==c&&void 0!==c&&(d.logBase=c),d.useLog=!0,d},this.addMeasureAxis=function(a,b){return this.addAxis(a,null,b)},this.addPctAxis=function(a,b,c){var d=null;return d=null!==c&&void 0!==c?this.addAxis(a,c,b):this.addMeasureAxis(a,b),d.showPercent=!0,d},this.addSeries=function(a,c,d){(null===d||void 0===d)&&(d=this.axes),(null===c||void 0===c)&&(c=b.plot.bubble);var e,f=null,g=null,h=null,i=null,j=null;return d.forEach(function(a){null!==a&&c.supportedAxes.indexOf(a.position)>-1&&(null===f&&"x"===a.position[0]?f=a:null===g&&"y"===a.position[0]?g=a:null===h&&"z"===a.position[0]?h=a:null===i&&"c"===a.position[0]?i=a:null===i&&"p"===a.position[0]&&(j=a))},this),a&&(a=[].concat(a)),e=new b.series(this,a,f,g,h,i,j,c,b.aggregateMethod.sum,c.stacked),this.series.push(e),e},this.addTimeAxis=function(a,b,c,d){var e=this.addAxis(a,null,null,b);return e.tickFormat=d,e.dateParseFormat=c,e},this.assignColor=function(a,c,d,e){return this._assignedColors[a]=new b.color(c,d,e),this._assignedColors[a]},this.defaultColors=[new b.color("#80B1D3"),new b.color("#FB8072"),new b.color("#FDB462"),new b.color("#B3DE69"),new b.color("#FFED6F"),new b.color("#BC80BD"),new b.color("#8DD3C7"),new b.color("#CCEBC5"),new b.color("#FFFFB3"),new b.color("#BEBADA"),new b.color("#FCCDE5"),new b.color("#D9D9D9")],this.draw=function(b,c){b=b||0;var d,e,f=null,g=null,h=!1,i=!1,j=this._xPixels(),k=this._yPixels(),l=this._widthPixels(),m=this._heightPixels();return(void 0===c||null===c||c===!1)&&this._getSeriesData(),this.axes.forEach(function(a){a._scale=null},this),this.axes.forEach(function(a){if(a._min=0,a._max=0,e=[],a._hasMeasure()){var b=!1;this.series.forEach(function(c){if(c._deepMatch(a)){var d=c._axisBounds(a.position);a._min>d.min&&(a._min=d.min),a._max<d.max&&(a._max=d.max),b=!0}},this),b||this._getAllData().forEach(function(b){a._min>b[a.measure]&&(a._min=b[a.measure]),a._max<b[a.measure]&&(a._max=b[a.measure])},this)}else a._hasTimeField()?(a._min=null,a._max=null,this.series.forEach(function(b){b._deepMatch(a)&&null!==b[a.position].timeField&&void 0!==b[a.position].timeField&&-1===e.indexOf(b[a.position].timeField)&&e.push(b[a.position].timeField)},this),a._getAxisData().forEach(function(b){e.forEach(function(c){var d=a._parseDate(b[c]);(null===a._min||d<a._min)&&(a._min=d),(null===a._max||d>a._max)&&(a._max=d)},this)},this)):a._hasCategories()&&(a._min=0,d=[],this.series.forEach(function(b){b._deepMatch(a)&&null!==b[a.position].categoryFields[0]&&void 0!==b[a.position].categoryFields[0]&&-1===e.indexOf(b[a.position].categoryFields[0])&&e.push(b[a.position].categoryFields[0])},this),a._getAxisData().forEach(function(a){e.forEach(function(b){-1===d.indexOf(a[b])&&d.push(a[b])},this)},this),a._max=d.length);null!==a._slaves&&void 0!==a._slaves&&a._slaves.length>0&&a._slaves.forEach(function(b){b._min=a._min,b._max=a._max},this),a._update(),null===f&&"x"===a.position?f=a:null===g&&"y"===a.position&&(g=a)},this),this.axes.forEach(function(c){var d=!1,e=null,n=0,o=null,p=!1,q=0,r={l:null,t:null,r:null,b:null},s=0,t=0,u="",v=this,w=function(a){var c;return c=null===e||0===b||d?a:v._handleTransition(a,b,v)},x=function(){c.measure||("x"===c.position?a.select(this).selectAll("text").attr("x",l/c._max/2):"y"===c.position&&a.select(this).selectAll("text").attr("y",-1*(m/c._max)/2)),c.categoryFields&&c.categoryFields.length>0&&(c!==f||null!==g.categoryFields&&0!==g.categoryFields.length||a.select(this).selectAll("text").attr("y",k+m-g._scale(0)+9),c!==g||null!==f.categoryFields&&0!==f.categoryFields.length||a.select(this).selectAll("text").attr("x",-1*(f._scale(0)-j)-9))};null===c.gridlineShapes?(c.showGridlines||null===c.showGridlines&&!c._hasCategories()&&(!h&&"x"===c.position||!i&&"y"===c.position))&&(c.gridlineShapes=this._group.append("g").attr("class","dimple-gridline"),"x"===c.position?h=!0:i=!0):"x"===c.position?h=!0:i=!0,null===c.shapes&&(c.shapes=this._group.append("g").attr("class","dimple-axis").each(function(){v.noFormats||a.select(this).style("font-family",c.fontFamily).style("font-size",c._getFontSize())}),d=!0),c===f&&null!==g?(e="translate(0, "+(null===g.categoryFields||0===g.categoryFields.length?g._scale(0):k+m)+")",o="translate(0, "+(c===f?k+m:k)+")",n=-m):c===g&&null!==f?(e="translate("+(null===f.categoryFields||0===f.categoryFields.length?f._scale(0):j)+", 0)",o="translate("+(c===g?j:j+l)+", 0)",n=-l):"x"===c.position?(o=e="translate(0, "+(c===f?k+m:k)+")",n=-m):"y"===c.position&&(o=e="translate("+(c===g?j:j+l)+", 0)",n=-l),null!==e&&null!==c._draw&&(c._hasTimeField()?w(c.shapes).call(c._draw.ticks(c._getTimePeriod(),c.timeInterval).tickFormat(c._getFormat())).attr("transform",e).each(x):c.useLog?w(c.shapes).call(c._draw.ticks(4,c._getFormat())).attr("transform",e).each(x):w(c.shapes).call(c._draw.tickFormat(c._getFormat())).attr("transform",e).each(x),null!==c.gridlineShapes&&w(c.gridlineShapes).call(c._draw.tickSize(n,0,0).tickFormat("")).attr("transform",o)),this.noFormats||(w(c.shapes.selectAll("text")).style("font-family",c.fontFamily).style("font-size",c._getFontSize()),w(c.shapes.selectAll("path, line")).style("fill","none").style("stroke","black").style("shape-rendering","crispEdges"),null!==c.gridlineShapes&&w(c.gridlineShapes.selectAll("line")).style("fill","none").style("stroke","lightgray").style("opacity",.8)),(null===c.measure||void 0===c.measure)&&(c===f?(q=0,c.shapes.selectAll("text").each(function(){var a=this.getComputedTextLength();q=a>q?a:q}),q>l/c.shapes.selectAll("text")[0].length?(p=!0,c.shapes.selectAll("text").style("text-anchor","start").each(function(){var b=this.getBBox();a.select(this).attr("transform","rotate(90,"+b.x+","+(b.y+b.height/2)+") translate(-5, 0)")})):(p=!1,c.shapes.selectAll("text").style("text-anchor","middle").attr("transform",""))):"x"===c.position&&(q=0,c.shapes.selectAll("text").each(function(){var a=this.getComputedTextLength();q=a>q?a:q}),q>l/c.shapes.selectAll("text")[0].length?(p=!0,c.shapes.selectAll("text").style("text-anchor","end").each(function(){var b=this.getBBox();a.select(this).attr("transform","rotate(90,"+(b.x+b.width)+","+(b.y+b.height/2)+") translate(5, 0)")})):(p=!1,c.shapes.selectAll("text").style("text-anchor","middle").attr("transform","")))),null!==c.titleShape&&void 0!==c.titleShape&&c.titleShape.remove(),c.shapes.selectAll("text").each(function(){var a=this.getBBox();(null===r.l||-9-a.width<r.l)&&(r.l=-9-a.width),(null===r.r||a.x+a.width>r.r)&&(r.r=a.x+a.width),p?((null===r.t||a.y+a.height-a.width<r.t)&&(r.t=a.y+a.height-a.width),(null===r.b||a.height+a.width>r.b)&&(r.b=a.height+a.width)):((null===r.t||a.y<r.t)&&(r.t=a.y),(null===r.b||9+a.height>r.b)&&(r.b=9+a.height))}),"x"===c.position?(t=c===f?k+m+r.b+5:k+r.t-10,s=j+l/2):"y"===c.position&&(s=c===g?j+r.l-10:j+l+r.r+20,t=k+m/2,u="rotate(270, "+s+", "+t+")"),c.hidden||"x"!==c.position&&"y"!==c.position||null===c.title||(c.titleShape=this._group.append("text").attr("class","dimple-axis dimple-title"),c.titleShape.attr("x",s).attr("y",t).attr("text-anchor","middle").attr("transform",u).text(void 0!==c.title?c.title:null===c.categoryFields||void 0===c.categoryFields||0===c.categoryFields.length?c.measure:c.categoryFields.join("/")).each(function(){v.noFormats||a.select(this).style("font-family",c.fontFamily).style("font-size",c._getFontSize())}),c===f?c.titleShape.each(function(){a.select(this).attr("y",t+this.getBBox().height/1.65)}):c===g&&c.titleShape.each(function(){a.select(this).attr("x",s+this.getBBox().height/1.65)}))},this),this.series.forEach(function(a){a.plot.draw(this,a,b),this._registerEventHandlers(a)},this),this.legends.forEach(function(a){a._draw()},this),this.storyboard&&(this.storyboard._drawText(),this.storyboard.autoplay&&this.storyboard.startAnimation()),this},this.getColor=function(a){return(null===this._assignedColors[a]||void 0===this._assignedColors[a])&&(this._assignedColors[a]=this.defaultColors[this._nextColor],this._nextColor=(this._nextColor+1)%this.defaultColors.length),this._assignedColors[a]},this.setBounds=function(a,c,d,e){return this.x=a,this.y=c,this.width=d,this.height=e,this._xPixels=function(){return b._parseXPosition(this.x,this.svg.node())},this.draw(0,!0),this._yPixels=function(){return b._parseYPosition(this.y,this.svg.node())},this._widthPixels=function(){return b._parseXPosition(this.width,this.svg.node())},this._heightPixels=function(){return b._parseYPosition(this.height,this.svg.node())},this},this.setMargins=function(a,c,d,e){return this.x=a,this.y=c,this.width=0,this.height=0,this._xPixels=function(){return b._parseXPosition(this.x,this.svg.node())},this._yPixels=function(){return b._parseYPosition(this.y,this.svg.node())},this._widthPixels=function(){return b._parentWidth(this.svg.node())-this._xPixels()-b._parseXPosition(d,this.svg.node())},this._heightPixels=function(){return b._parentHeight(this.svg.node())-this._yPixels()-b._parseYPosition(e,this.svg.node())},this.draw(0,!0),this},this.setStoryboard=function(a,c){return this.storyboard=new b.storyboard(this,a),null!==c&&void 0!==c&&(this.storyboard.onTick=c),this.storyboard}},b.color=function(b,c,d){this.fill=b,this.stroke=null===c||void 0===c?a.rgb(b).darker(.5).toString():c,this.opacity=null===d||void 0===d?.8:d},b.eventArgs=function(){this.seriesValue=null,this.xValue=null,this.yValue=null,this.zValue=null,this.pValue=null,this.colorValue=null,this.frameValue=null,this.seriesShapes=null,this.selectedShape=null},b.legend=function(c,d,e,f,g,h,i){this.chart=c,this.series=i,this.x=d,this.y=e,this.width=f,this.height=g,this.horizontalAlign=h,this.shapes=null,this.fontSize="10px",this.fontFamily="sans-serif",this._draw=function(){var c,d=this._getEntries(),e=0,f=0,g=0,h=0,i=15,j=9,k=this;this.shapes&&this.shapes.remove(),c=this.chart._group.selectAll(".dimple-dont-select-any").data(d).enter().append("g").attr("class",function(a){return"dimple-legend "+b._createClass(a.aggField)}).attr("opacity",1),c.append("text").attr("class",function(a){return"dimple-legend dimple-legend-text "+b._createClass(a.aggField)}).text(function(a){return a.key}).call(function(){k.chart.noFormats||this.style("font-family",k.fontFamily).style("font-size",k._getFontSize()).style("shape-rendering","crispEdges")}).each(function(){var a=this.getBBox();a.width>e&&(e=a.width),a.height>f&&(f=a.height)}),c.append("rect").attr("class",function(a){return"dimple-legend dimple-legend-key "+b._createClass(a.aggField)}).attr("height",j).attr("width",i),f=(j>f?j:f)+2,e+=i+20,c.each(function(c){g+e>k._widthPixels()&&(g=0,h+=f),h>k._heightPixels()?a.select(this).remove():(a.select(this).select("text").attr("x","left"===k.horizontalAlign?k._xPixels()+i+5+g:k._xPixels()+(k._widthPixels()-g-e)+i+5).attr("y",function(){return k._yPixels()+h+this.getBBox().height/1.65}).attr("width",k._widthPixels()).attr("height",k._heightPixels()),a.select(this).select("rect").attr("class",function(a){return"dimple-legend dimple-legend-key "+b._createClass(a.aggField) -}).attr("x","left"===k.horizontalAlign?k._xPixels()+g:k._xPixels()+(k._widthPixels()-g-e)).attr("y",k._yPixels()+h).attr("height",j).attr("width",i).style("fill",c.fill).style("stroke",c.stroke).style("opacity",c.opacity).style("shape-rendering","crispEdges"),g+=e)}),this.shapes=c},this._getEntries=function(){var a=[];return this.series&&this.series.forEach(function(b){var c=b._positionData;c.forEach(function(c){var d,e=-1,f=b.plot.grouped&&!b.x._hasCategories()&&!b.y._hasCategories()&&c.aggField.length<2?"All":c.aggField.slice(-1)[0];for(d=0;d<a.length;d+=1)if(a[d].key===f){e=d;break}-1===e&&b.chart._assignedColors[f]&&(a.push({key:f,fill:b.chart._assignedColors[f].fill,stroke:b.chart._assignedColors[f].stroke,opacity:b.chart._assignedColors[f].opacity,series:b,aggField:c.aggField}),e=a.length-1)})},this),a},this._getFontSize=function(){var a;return a=this.fontSize&&"auto"!==this.fontSize.toString().toLowerCase()?isNaN(this.fontSize)?this.fontSize:this.fontSize+"px":(this.chart._heightPixels()/35>10?this.chart._heightPixels()/35:10)+"px"},this._heightPixels=function(){return b._parseYPosition(this.height,this.chart.svg.node())},this._widthPixels=function(){return b._parseXPosition(this.width,this.chart.svg.node())},this._xPixels=function(){return b._parseXPosition(this.x,this.chart.svg.node())},this._yPixels=function(){return b._parseYPosition(this.y,this.chart.svg.node())}},b.series=function(a,b,c,d,e,f,g,h,i,j){this.chart=a,this.x=c,this.y=d,this.z=e,this.c=f,this.p=g,this.plot=h,this.categoryFields=b,this.aggregate=i,this.stacked=j,this.barGap=.2,this.clusterBarGap=.1,this.lineWeight=2,this.lineMarkers=!1,this.afterDraw=null,this.interpolation="linear",this.tooltipFontSize="10px",this.tooltipFontFamily="sans-serif",this.radius="auto",this._eventHandlers=[],this._positionData=[],this._orderRules=[],this._axisBounds=function(a){var b,c,d,e={min:0,max:0},f=null,g=null,h=[],i=0,j=this._positionData;return"x"===a?(f=this.x,g=this.y):"y"===a?(f=this.y,g=this.x):"z"===a?f=this.z:"p"===a?f=this.p:"c"===a&&(f=this.c),f.showPercent?j.forEach(function(a){a[f.position+"Bound"]<e.min&&(e.min=a[f.position+"Bound"]),a[f.position+"Bound"]>e.max&&(e.max=a[f.position+"Bound"])},this):null===g||null===g.categoryFields||0===g.categoryFields.length?j.forEach(function(a){!this._isStacked()||"x"!==f.position&&"y"!==f.position?(a[f.position+"Value"]<e.min&&(e.min=a[f.position+"Value"]),a[f.position+"Value"]>e.max&&(e.max=a[f.position+"Value"])):a[f.position+"Value"]<0?e.min=e.min+a[f.position+"Value"]:e.max=e.max+a[f.position+"Value"]},this):(b=f.position+"Value",c=g.position+"Field",d=[],j.forEach(function(a){var e=a[c].join("/"),f=d.indexOf(e);-1===f&&(d.push(e),f=d.length-1),void 0===h[f]&&(h[f]={min:0,max:0},f>=i&&(i=f+1)),this.stacked?a[b]<0?h[f].min=h[f].min+a[b]:h[f].max=h[f].max+a[b]:(a[b]<h[f].min&&(h[f].min=a[b]),a[b]>h[f].max&&(h[f].max=a[b]))},this),h.forEach(function(a){void 0!==a&&(a.min<e.min&&(e.min=a.min),a.max>e.max&&(e.max=a.max))},this)),e},this._deepMatch=function(a){var b=!1;return this[a.position]===a?b=!0:void 0!==a._slaves&&null!==a._slaves&&a._slaves.length>0&&a._slaves.forEach(function(a){b=b||this._deepMatch(a)},this),b},this._dropLineOrigin=function(){var a=0,b=0,c={x:null,y:null},d={x:null,y:null};return this.chart.axes.forEach(function(a){"x"===a.position&&null===d.x?d.x=a._hasTimeField()?this.chart._xPixels():a._origin:"y"===a.position&&null===d.y&&(d.y=a._hasTimeField()?this.chart._yPixels()+this.chart._heightPixels():a._origin)},this),this.chart.axes.forEach(function(e){"x"!==e.position||this.x.hidden?"y"!==e.position||this.y.hidden||(this._deepMatch(e)&&(0===b?c.x=d.x:1===b&&(c.x=this.chart._xPixels()+this.chart._widthPixels())),b+=1):(this._deepMatch(e)&&(0===a?c.y=d.y:1===a&&(c.y=this.chart._yPixels())),a+=1)},this),c},this._getTooltipFontSize=function(){var a;return a=this.tooltipFontSize&&"auto"!==this.tooltipFontSize.toString().toLowerCase()?isNaN(this.tooltipFontSize)?this.tooltipFontSize:this.tooltipFontSize+"px":(this.chart._heightPixels()/35>10?this.chart._heightPixels()/35:10)+"px"},this._isStacked=function(){return this.stacked&&(this.x._hasCategories()||this.y._hasCategories())},this.addEventHandler=function(a,b){this._eventHandlers.push({event:a,handler:b})},this.addOrderRule=function(a,b){this._orderRules.push({ordering:a,desc:b})},this.getTooltipText=function(a){var b=[];return null!==this.categoryFields&&void 0!==this.categoryFields&&this.categoryFields.length>0&&this.categoryFields.forEach(function(c,d){null!==c&&void 0!==c&&null!==a.aggField[d]&&void 0!==a.aggField[d]&&b.push(c+(a.aggField[d]!==c?": "+a.aggField[d]:""))},this),this.p?(this.x&&this.x._hasCategories()&&this.x._getTooltipText(b,a),this.y&&this.y._hasCategories()&&this.y._getTooltipText(b,a),this.z&&this.z._hasCategories()&&this.z._getTooltipText(b,a),this.p._getTooltipText(b,a)):(this.x&&this.x._getTooltipText(b,a),this.y&&this.y._getTooltipText(b,a),this.z&&this.z._getTooltipText(b,a)),this.c&&this.c._getTooltipText(b,a),b.filter(function(a,c){return b.indexOf(a)===c})}},b.storyboard=function(a,b){null!==b&&void 0!==b&&(b=[].concat(b)),this.chart=a,this.categoryFields=b,this.autoplay=!0,this.frameDuration=3e3,this.storyLabel=null,this.onTick=null,this.fontSize="10px",this.fontFamily="sans-serif",this._frame=0,this._animationTimer=null,this._categories=[],this._cachedCategoryFields=[],this._orderRules=[],this._drawText=function(){if(!this.storyLabel){var a=this.chart,b=this,c=0;this.chart.axes.forEach(function(a){"x"===a.position&&(c+=1)},this),this.storyLabel=this.chart._group.append("text").attr("class","dimple-storyboard-label").attr("opacity",1).attr("x",this.chart._xPixels()+.01*this.chart._widthPixels()).attr("y",this.chart._yPixels()+(this.chart._heightPixels()/35>10?this.chart._heightPixels()/35:10)*(c>1?1.25:-1)).call(function(){a.noFormats||this.style("font-family",b.fontFamily).style("font-size",b._getFontSize())})}this.storyLabel.text(this.categoryFields.join("\\")+": "+this.getFrameValue())},this._getCategories=function(){return this._categoryFields!==this._cachedCategoryFields&&(this._categories=[],this.chart._getAllData().forEach(function(a){var b=-1,c="";null!==this.categoryFields&&(this.categoryFields.forEach(function(b,d){d>0&&(c+="/"),c+=a[b]},this),b=this._categories.indexOf(c),-1===b&&(this._categories.push(c),b=this._categories.length-1))},this),this._cachedCategoryFields=this._categoryFields),this._categories},this._getFontSize=function(){var a;return a=this.fontSize&&"auto"!==this.fontSize.toString().toLowerCase()?isNaN(this.fontSize)?this.fontSize:this.fontSize+"px":(this.chart._heightPixels()/35>10?this.chart._heightPixels()/35:10)+"px"},this._goToFrameIndex=function(a){this._frame=a%this._getCategories().length,this.chart.draw(this.frameDuration/2)},this.addOrderRule=function(a,b){this._orderRules.push({ordering:a,desc:b})},this.getFrameValue=function(){var a=null;return this._frame>=0&&this._getCategories().length>this._frame&&(a=this._getCategories()[this._frame]),a},this.goToFrame=function(a){if(this._getCategories().length>0){var b=this._getCategories().indexOf(a);this._goToFrameIndex(b)}},this.pauseAnimation=function(){null!==this._animationTimer&&(window.clearInterval(this._animationTimer),this._animationTimer=null)},this.startAnimation=function(){null===this._animationTimer&&(null!==this.onTick&&this.onTick(this.getFrameValue()),this._animationTimer=window.setInterval(function(a){return function(){a._goToFrameIndex(a._frame+1),null!==a.onTick&&a.onTick(a.getFrameValue()),a._drawText(a.frameDuration/2)}}(this),this.frameDuration))},this.stopAnimation=function(){null!==this._animationTimer&&(window.clearInterval(this._animationTimer),this._animationTimer=null,this._frame=0)}},b.aggregateMethod.avg=function(a,b){return a.value=null===a.value||void 0===a.value?0:parseFloat(a.value),a.count=null===a.count||void 0===a.count?1:parseFloat(a.count),b.value=null===b.value||void 0===b.value?0:parseFloat(b.value),b.count=null===b.count||void 0===b.count?1:parseFloat(b.count),(a.value*a.count+b.value*b.count)/(a.count+b.count)},b.aggregateMethod.count=function(a,b){return a.count=null===a.count||void 0===a.count?0:parseFloat(a.count),b.count=null===b.count||void 0===b.count?0:parseFloat(b.count),a.count+b.count},b.aggregateMethod.max=function(a,b){return a.value=null===a.value||void 0===a.value?0:parseFloat(a.value),b.value=null===b.value||void 0===b.value?0:parseFloat(b.value),a.value>b.value?a.value:b.value},b.aggregateMethod.min=function(a,b){return null===a.value?parseFloat(b.value):parseFloat(a.value)<parseFloat(b.value)?parseFloat(a.value):parseFloat(b.value)},b.aggregateMethod.sum=function(a,b){return a.value=null===a.value||void 0===a.value?0:parseFloat(a.value),b.value=null===b.value||void 0===b.value?0:parseFloat(b.value),a.value+b.value},b.plot.area={stacked:!0,grouped:!0,supportedAxes:["x","y","c"],draw:function(c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,A,B=d._positionData,C=[],D=null,E="dimple-series-"+c.series.indexOf(d),F=d.x._hasCategories()||d.y._hasCategories()?0:1,G=!1,H={},I=[],J=[],K=function(){return function(c,d,e,f){a.select(d).style("opacity",1),b._showPointTooltip(c,d,e,f)}},L=function(c){return function(d,e,f,g){a.select(e).style("opacity",g.lineMarkers||c.data.length<2?b._helpers.opacity(d,f,g):0),b._removeTooltip(d,e,f,g)}},M=function(a){b._drawMarkers(a,c,d,e,E,G,K(a),L(a))},N=function(a,e){var f;return"step"===d.interpolation&&d[a]._hasCategories()?(f=b._helpers[a](e,c,d)+("y"===a?b._helpers.height(e,c,d):0),d[a].categoryFields.length<2&&(f+=("y"===a?1:-1)*b._helpers[a+"Gap"](c,d))):f=b._helpers["c"+a](e,c,d),parseFloat(f)},O=function(b,c){return a.svg.line().x(function(a){return d.x._hasCategories()||!c?a.x:d.x[c]}).y(function(a){return d.y._hasCategories()||!c?a.y:d.y[c]}).interpolate(b)},P=function(a,b){return parseFloat(a)-parseFloat(b)},Q=function(a,b){return parseFloat(a.x)-parseFloat(b.x)},R=function(a,b,c){var d,e,f=b[b.length-1],g=9999,h=f;for(d=0;d<a.length;d+=1)(a[d].x!==f.x||a[d].y!==f.y)&&(e=180-Math.atan2(a[d].x-f.x,a[d].y-f.y)*(180/Math.PI),e>c&&g>e&&(h=a[d],g=e));return b.push(h),g};for(f="step"===d.interpolation?"step-after":d.interpolation,o=b._getSeriesOrder(d.data||c.data,d),d.c&&(d.x._hasCategories()&&d.y._hasMeasure()||d.y._hasCategories()&&d.x._hasMeasure())&&(G=!0),d.x._hasCategories()?(z="x",A="y"):d.y._hasCategories()&&(z="y",A="x"),g=0;g<B.length;g+=1){for(j=[],l=-1,i=F;i<B[g].aggField.length;i+=1)j.push(B[g].aggField[i]);for(k=b._createClass(j),i=0;i<C.length;i+=1)if(C[i].keyString===k){l=i;break}-1===l&&(l=C.length,C.push({key:j,keyString:k,color:"white",data:[],points:[],area:{},entry:{},exit:{},group:z&&B[g][z+"Field"]&&B[g][z+"Field"].length>=2?B[g][z+"Field"][0]:"All"})),C[l].data.push(B[g])}for(o&&C.sort(function(a,c){return b._arrayIndexCompare(o,a.key,c.key)}),g=0;g<C.length;g+=1){for(C[g].data.sort(b._getSeriesSortPredicate(c,d,o)),h=0;h<C[g].data.length;h+=1)C[g].points.push({x:N("x",C[g].data[h]),y:N("y",C[g].data[h])}),z&&(H[C[g].group]||(H[C[g].group]={}),H[C[g].group][C[g].points[C[g].points.length-1][z]]=d[A]._origin);p=C[g].points,"step"===d.interpolation&&p.length>1&&z&&(d.x._hasCategories()?(p.push({x:2*p[p.length-1].x-p[p.length-2].x,y:p[p.length-1].y}),H[C[g].group][p[p.length-1][z]]=d[A]._origin):d.y._hasCategories()&&(p=[{x:p[0].x,y:2*p[0].y-p[1].y}].concat(p),H[C[g].group][p[0][z]]=d[A]._origin,C[g].points=p))}for(s in H)if(H.hasOwnProperty(s)){I[s]=[];for(t in H[s])H[s].hasOwnProperty(t)&&I[s].push(parseFloat(t));I[s].sort(P)}for(g=0;g<C.length;g+=1){if(p=C[g].points,u=C[g].group,q=[],J=[],G&&b._addGradient(C[g].key,"fill-area-gradient-"+C[g].keyString,d.x._hasCategories()?d.x:d.y,B,c,e,"fill"),I[u]&&I[u].length>0)for(h=0,i=0;h<I[u].length;h+=1)I[u][h]>=p[0][z]&&I[u][h]<=p[p.length-1][z]&&(r={},r[z]=I[u][h],r[A]=H[u][I[u][h]],q.push(r),p[i][z]>I[u][h]?J.push(r):(J.push(p[i]),H[C[g].group][I[u][h]]=p[i][A],i+=1));else if(d._orderRules&&d._orderRules.length>0)J=p.concat(p[0]);else{p=p.sort(Q),J.push(p[0]),y=0;do y=R(p,J,y);while(J.length<=p.length&&(J[0].x!==J[J.length-1].x||J[0].y!==J[J.length-1].y))}q=q.reverse(),v=O(f,"_previousOrigin")(J),w=O("step-after"===f?"step-before":"step-before"===f?"step-after":f,"_previousOrigin")(q),x=O("linear","_previousOrigin")(J),C[g].entry=v+(w&&w.length>0?"L"+w.substring(1):"")+(x&&x.length>0?"L"+x.substring(1,x.indexOf("L")):0),v=O(f)(J),w=O("step-after"===f?"step-before":"step-before"===f?"step-after":f)(q),x=O("linear")(J),C[g].update=v+(w&&w.length>0?"L"+w.substring(1):"")+(x&&x.length>0?"L"+x.substring(1,x.indexOf("L")):0),v=O(f,"_origin")(J),w=O("step-after"===f?"step-before":"step-before"===f?"step-after":f,"_origin")(q),x=O("linear","_origin")(J),C[g].exit=v+(w&&w.length>0?"L"+w.substring(1):"")+(x&&x.length>0?"L"+x.substring(1,x.indexOf("L")):0),C[g].color=c.getColor(C[g].key.length>0?C[g].key[C[g].key.length-1]:"All")}null!==c._tooltipGroup&&void 0!==c._tooltipGroup&&c._tooltipGroup.remove(),D=null===d.shapes||void 0===d.shapes?c._group.selectAll("."+E).data(C):d.shapes.data(C,function(a){return a.key}),D.enter().append("path").attr("id",function(a){return a.key}).attr("class",function(a){return E+" dimple-line "+a.keyString}).attr("d",function(a){return a.entry}).call(function(){c.noFormats||this.attr("opacity",function(a){return G?1:a.color.opacity}).attr("fill",function(a){return G?"url(#fill-area-gradient-"+a.keyString+")":a.color.fill}).attr("stroke",function(a){return G?"url(#stroke-area-gradient-"+a.keyString+")":a.color.stroke}).attr("stroke-width",d.lineWeight)}).each(function(a){a.markerData=a.data,M(a)}),m=c._handleTransition(D,e,c).attr("d",function(a){return a.update}).each(function(a){a.markerData=a.data,M(a)}),n=c._handleTransition(D.exit(),e,c).attr("d",function(a){return a.exit}).each(function(a){a.markerData=[],M(a)}),b._postDrawHandling(d,m,n,e),d.shapes=D}},b.plot.bar={stacked:!0,grouped:!1,supportedAxes:["x","y","c"],draw:function(a,c,d){var e,f,g=c._positionData,h=null,i=["dimple-series-"+a.series.indexOf(c),"dimple-bar"],j=!c._isStacked()&&c.x._hasMeasure(),k=!c._isStacked()&&c.y._hasMeasure(),l="none";c.x._hasCategories()&&c.y._hasCategories()?l="both":c.x._hasCategories()?l="x":c.y._hasCategories()&&(l="y"),null!==a._tooltipGroup&&void 0!==a._tooltipGroup&&a._tooltipGroup.remove(),h=null===c.shapes||void 0===c.shapes?a._group.selectAll("."+i.join(".")).data(g):c.shapes.data(g,function(a){return a.key}),h.enter().append("rect").attr("id",function(a){return a.key}).attr("class",function(a){var c=[];return c=c.concat(a.aggField),c=c.concat(a.xField),c=c.concat(a.yField),i.join(" ")+" "+b._createClass(c)}).attr("x",function(d){var e=c.x._previousOrigin;return"x"===l?e=b._helpers.x(d,a,c):"both"===l&&(e=b._helpers.cx(d,a,c)),e}).attr("y",function(d){var e=c.y._previousOrigin;return"y"===l?e=b._helpers.y(d,a,c):"both"===l&&(e=b._helpers.cy(d,a,c)),e}).attr("width",function(d){return"x"===l?b._helpers.width(d,a,c):0}).attr("height",function(d){return"y"===l?b._helpers.height(d,a,c):0}).attr("opacity",function(d){return b._helpers.opacity(d,a,c)}).on("mouseover",function(d){b._showBarTooltip(d,this,a,c)}).on("mouseleave",function(d){b._removeTooltip(d,this,a,c)}).call(function(){a.noFormats||this.attr("fill",function(d){return b._helpers.fill(d,a,c)}).attr("stroke",function(d){return b._helpers.stroke(d,a,c)})}),e=a._handleTransition(h,d,a,c).attr("x",function(d){return j?b._helpers.cx(d,a,c)-c.x.floatingBarWidth/2:b._helpers.x(d,a,c)}).attr("y",function(d){return k?b._helpers.cy(d,a,c)-c.y.floatingBarWidth/2:b._helpers.y(d,a,c)}).attr("width",function(d){return j?c.x.floatingBarWidth:b._helpers.width(d,a,c)}).attr("height",function(d){return k?c.y.floatingBarWidth:b._helpers.height(d,a,c)}).call(function(){a.noFormats||this.attr("fill",function(d){return b._helpers.fill(d,a,c)}).attr("stroke",function(d){return b._helpers.stroke(d,a,c)})}),f=a._handleTransition(h.exit(),d,a,c).attr("x",function(d){var e=c.x._origin;return"x"===l?e=b._helpers.x(d,a,c):"both"===l&&(e=b._helpers.cx(d,a,c)),e}).attr("y",function(d){var e=c.y._origin;return"y"===l?e=b._helpers.y(d,a,c):"both"===l&&(e=b._helpers.cy(d,a,c)),e}).attr("width",function(d){return"x"===l?b._helpers.width(d,a,c):0}).attr("height",function(d){return"y"===l?b._helpers.height(d,a,c):0}),b._postDrawHandling(c,e,f,d),c.shapes=h}},b.plot.bubble={stacked:!1,grouped:!1,supportedAxes:["x","y","z","c"],draw:function(a,c,d){var e,f,g=c._positionData,h=null,i=["dimple-series-"+a.series.indexOf(c),"dimple-bubble"];null!==a._tooltipGroup&&void 0!==a._tooltipGroup&&a._tooltipGroup.remove(),h=null===c.shapes||void 0===c.shapes?a._group.selectAll("."+i.join(".")).data(g):c.shapes.data(g,function(a){return a.key}),h.enter().append("circle").attr("id",function(a){return a.key}).attr("class",function(a){var c=[];return c=c.concat(a.aggField),c=c.concat(a.xField),c=c.concat(a.yField),c=c.concat(a.zField),i.join(" ")+" "+b._createClass(c)}).attr("cx",function(d){return c.x._hasCategories()?b._helpers.cx(d,a,c):c.x._previousOrigin}).attr("cy",function(d){return c.y._hasCategories()?b._helpers.cy(d,a,c):c.y._previousOrigin}).attr("r",0).attr("opacity",function(d){return b._helpers.opacity(d,a,c)}).on("mouseover",function(d){b._showPointTooltip(d,this,a,c)}).on("mouseleave",function(d){b._removeTooltip(d,this,a,c)}).call(function(){a.noFormats||this.attr("fill",function(d){return b._helpers.fill(d,a,c)}).attr("stroke",function(d){return b._helpers.stroke(d,a,c)})}),e=a._handleTransition(h,d,a,c).attr("cx",function(d){return b._helpers.cx(d,a,c)}).attr("cy",function(d){return b._helpers.cy(d,a,c)}).attr("r",function(d){return b._helpers.r(d,a,c)}).call(function(){a.noFormats||this.attr("fill",function(d){return b._helpers.fill(d,a,c)}).attr("stroke",function(d){return b._helpers.stroke(d,a,c)})}),f=a._handleTransition(h.exit(),d,a,c).attr("r",0).attr("cx",function(d){return c.x._hasCategories()?b._helpers.cx(d,a,c):c.x._origin}).attr("cy",function(d){return c.y._hasCategories()?b._helpers.cy(d,a,c):c.y._origin}),b._postDrawHandling(c,e,f,d),c.shapes=h}},b.plot.line={stacked:!1,grouped:!0,supportedAxes:["x","y","c"],draw:function(c,d,e){var f,g,h,i,j,k,l,m,n,o,p=d._positionData,q=[],r=null,s="dimple-series-"+c.series.indexOf(d),t=d.x._hasCategories()||d.y._hasCategories()?0:1,u=!1,v=function(){return function(c,d,e,f){a.select(d).style("opacity",1),b._showPointTooltip(c,d,e,f)}},w=function(c){return function(d,e,f,g){a.select(e).style("opacity",g.lineMarkers||c.data.length<2?b._helpers.opacity(d,f,g):0),b._removeTooltip(d,e,f,g)}},x=function(a){b._drawMarkers(a,c,d,e,s,u,v(a),w(a))},y=function(a,e){var f;return"step"===d.interpolation&&d[a]._hasCategories()?(d.barGap=0,d.clusterBarGap=0,f=b._helpers[a](e,c,d)+("y"===a?b._helpers.height(e,c,d):0)):f=b._helpers["c"+a](e,c,d),parseFloat(f.toFixed(1))},z=function(b,c){return a.svg.line().x(function(a){return d.x._hasCategories()||!c?a.x:d.x[c]}).y(function(a){return d.y._hasCategories()||!c?a.y:d.y[c]}).interpolate(b)};for(f="step"===d.interpolation?"step-after":d.interpolation,o=b._getSeriesOrder(d.data||c.data,d),d.c&&(d.x._hasCategories()&&d.y._hasMeasure()||d.y._hasCategories()&&d.x._hasMeasure())&&(u=!0),g=0;g<p.length;g+=1){for(j=[],l=-1,i=t;i<p[g].aggField.length;i+=1)j.push(p[g].aggField[i]);for(k=b._createClass(j),i=0;i<q.length;i+=1)if(q[i].keyString===k){l=i;break}-1===l&&(l=q.length,q.push({key:j,keyString:k,color:"white",data:[],markerData:[],points:[],line:{},entry:{},exit:{}})),q[l].data.push(p[g])}for(o&&q.sort(function(a,c){return b._arrayIndexCompare(o,a.key,c.key)}),g=0;g<q.length;g+=1){for(q[g].data.sort(b._getSeriesSortPredicate(c,d,o)),u&&b._addGradient(q[g].key,"fill-line-gradient-"+q[g].keyString,d.x._hasCategories()?d.x:d.y,p,c,e,"fill"),h=0;h<q[g].data.length;h+=1)q[g].points.push({x:y("x",q[g].data[h]),y:y("y",q[g].data[h])});"step"===d.interpolation&&q[g].points.length>1&&(d.x._hasCategories()?q[g].points.push({x:2*q[g].points[q[g].points.length-1].x-q[g].points[q[g].points.length-2].x,y:q[g].points[q[g].points.length-1].y}):d.y._hasCategories()&&(q[g].points=[{x:q[g].points[0].x,y:2*q[g].points[0].y-q[g].points[1].y}].concat(q[g].points))),q[g].entry=z(f,"_previousOrigin")(q[g].points),q[g].update=z(f)(q[g].points),q[g].exit=z(f,"_origin")(q[g].points),q[g].color=c.getColor(q[g].key.length>0?q[g].key[q[g].key.length-1]:"All")}null!==c._tooltipGroup&&void 0!==c._tooltipGroup&&c._tooltipGroup.remove(),r=null===d.shapes||void 0===d.shapes?c._group.selectAll("."+s).data(q):d.shapes.data(q,function(a){return a.key}),r.enter().append("path").attr("id",function(a){return a.key}).attr("class",function(a){return s+" dimple-line "+a.keyString}).attr("d",function(a){return a.entry}).call(function(){c.noFormats||this.attr("opacity",function(a){return u?1:a.color.opacity}).attr("fill","none").attr("stroke",function(a){return u?"url(#fill-line-gradient-"+a.keyString+")":a.color.stroke}).attr("stroke-width",d.lineWeight)}).each(function(a){a.markerData=a.data,x(a)}),m=c._handleTransition(r,e,c).attr("d",function(a){return a.update}).each(function(a){a.markerData=a.data,x(a)}),n=c._handleTransition(r.exit(),e,c).attr("d",function(a){return a.exit}).each(function(a){a.markerData=[],x(a)}),b._postDrawHandling(d,m,n,e),d.shapes=r}},b.plot.pie={stacked:!1,grouped:!1,supportedAxes:["x","y","c","z","p"],draw:function(c,d,e){var f,g,h=d._positionData,i=null,j=["dimple-series-"+c.series.indexOf(d),"dimple-pie"],k=function(a){var e;return e=d.x&&d.y?b._helpers.r(a,c,d):c._widthPixels()<c._heightPixels()?c._widthPixels()/2:c._heightPixels()/2},l=function(a){var c=k(a);return d.outerRadius&&(c=b._parsePosition(d.outerRadius,c)),Math.max(c,0)},m=function(a){var c=0;return d.innerRadius&&(c=b._parsePosition(d.innerRadius,k(a))),Math.max(c,0)},n=function(b){var c;return c=a.svg.arc().innerRadius(m(b)).outerRadius(l(b)),c(b)},o=function(b){b.innerRadius=m(b),b.outerRadius=l(b);var c,d=a.interpolate(this._current,b);return c=a.svg.arc().innerRadius(function(a){return a.innerRadius}).outerRadius(function(a){return a.outerRadius}),this._current=d(0),function(a){return c(d(a))}},p=function(a){return function(e){var f,g;return d.x&&d.y?(f=!a||d.x._hasCategories()?b._helpers.cx(e,c,d):d.x._previousOrigin,g=!a||d.y._hasCategories()?b._helpers.cy(e,c,d):d.y._previousOrigin):(f=c._xPixels()+c._widthPixels()/2,g=c._yPixels()+c._heightPixels()/2),"translate("+f+","+g+")"}};null!==c._tooltipGroup&&void 0!==c._tooltipGroup&&c._tooltipGroup.remove(),i=null===d.shapes||void 0===d.shapes?c._group.selectAll("."+j.join(".")).data(h):d.shapes.data(h,function(a){return a.key}),i.enter().append("path").attr("id",function(a){return a.key}).attr("class",function(a){var c=[];return c=c.concat(a.aggField),c=c.concat(a.pField),j.join(" ")+" "+b._createClass(c)}).attr("d",n).attr("opacity",function(a){return b._helpers.opacity(a,c,d)}).on("mouseover",function(a){b._showBarTooltip(a,this,c,d)}).on("mouseleave",function(a){b._removeTooltip(a,this,c,d)}).call(function(){c.noFormats||this.attr("fill",function(a){return b._helpers.fill(a,c,d)}).attr("stroke",function(a){return b._helpers.stroke(a,c,d)})}).attr("transform",p(!0)).each(function(a){this._current=a,a.innerRadius=m(a),a.outerRadius=l(a)}),f=c._handleTransition(i,e,c,d).call(function(){e&&e>0?this.attrTween("d",o):this.attr("d",n),c.noFormats||this.attr("fill",function(a){return b._helpers.fill(a,c,d)}).attr("stroke",function(a){return b._helpers.stroke(a,c,d)})}).attr("transform",p(!1)),g=c._handleTransition(i.exit(),e,c,d).attr("transform",p(!0)).attr("d",n),b._postDrawHandling(d,f,g,e),d.shapes=i}},b._addGradient=function(a,b,c,d,e,f,g){var h=[].concat(a),i=e._group.select("#"+b),j=[],k=c.position+"Field",l=!0,m=[];d.forEach(function(a){-1===j.indexOf(a[k])&&a.aggField.join("_")===h.join("_")&&j.push(a[k])},this),j=j.sort(function(a,b){return c._scale(a)-c._scale(b)}),null===i.node()&&(l=!1,i=e._group.append("linearGradient").attr("id",b).attr("gradientUnits","userSpaceOnUse").attr("x1","x"===c.position?c._scale(j[0])+e._widthPixels()/j.length/2:0).attr("y1","y"===c.position?c._scale(j[0])-e._heightPixels()/j.length/2:0).attr("x2","x"===c.position?c._scale(j[j.length-1])+e._widthPixels()/j.length/2:0).attr("y2","y"===c.position?c._scale(j[j.length-1])-e._heightPixels()/j.length/2:0)),j.forEach(function(a,b){var c={},e=0;for(e=0;e<d.length;e+=1)if(d[e].aggField.join("_")===h.join("_")&&d[e][k].join("_")===a.join("_")){c=d[e];break}m.push({offset:Math.round(100*(b/(j.length-1)))+"%",color:c[g]})},this),l?e._handleTransition(i.selectAll("stop").data(m),f,e).attr("offset",function(a){return a.offset}).attr("stop-color",function(a){return a.color}):i.selectAll("stop").data(m).enter().append("stop").attr("offset",function(a){return a.offset}).attr("stop-color",function(a){return a.color})},b._arrayIndexCompare=function(a,b,c){var d,e,f,g,h,i;for(e=0;e<a.length;e+=1){for(g=!0,h=!0,i=[].concat(a[e]),f=0;f<b.length;f+=1)g=g&&b[f]===i[f];for(f=0;f<c.length;f+=1)h=h&&c[f]===i[f];if(g&&h){d=0;break}if(g){d=-1;break}if(h){d=1;break}}return d},b._createClass=function(a){var b,c=[],d=function(a){var b=a.charCodeAt(0),c="-";return b>=65&&90>=b&&(c=a.toLowerCase()),c};if(a.length>0)for(b=0;b<a.length;b+=1)c.push("dimple-"+a[b].toString().replace(/[^a-z0-9]/g,d));else c=["dimple-all"];return c.join(" ")},b._drawMarkerBacks=function(c,d,e,f,g){var h,i,j=["dimple-marker-back",g,c.keyString];e.lineMarkers&&(h=null===e._markerBacks||void 0===e._markerBacks||void 0===e._markerBacks[c.keyString]?d._group.selectAll("."+j.join(".")).data(c.markerData):e._markerBacks[c.keyString].data(c.markerData,function(a){return a.key}),h.enter().append("circle").attr("id",function(a){return a.key}).attr("class",function(a){var c=[];return e.x._hasCategories()&&(c=c.concat(a.xField)),e.y._hasCategories()&&(c=c.concat(a.yField)),b._createClass(c)+" "+j.join(" ")}).attr("cx",function(a){return e.x._hasCategories()?b._helpers.cx(a,d,e):e.x._previousOrigin}).attr("cy",function(a){return e.y._hasCategories()?b._helpers.cy(a,d,e):e.y._previousOrigin}).attr("r",0).attr("fill","white").attr("stroke","none"),d._handleTransition(h,f,d).attr("cx",function(a){return b._helpers.cx(a,d,e)}).attr("cy",function(a){return b._helpers.cy(a,d,e)}).attr("r",2+e.lineWeight),i=d._handleTransition(h.exit(),f,d).attr("cx",function(a){return e.x._hasCategories()?b._helpers.cx(a,d,e):e.x._origin}).attr("cy",function(a){return e.y._hasCategories()?b._helpers.cy(a,d,e):e.y._origin}).attr("r",0),0===f?i.remove():i.each("end",function(){a.select(this).remove()}),(void 0===e._markerBacks||null===e._markerBacks)&&(e._markerBacks={}),e._markerBacks[c.keyString]=h)},b._drawMarkers=function(c,d,e,f,g,h,i,j){var k,l,m=["dimple-marker",g,c.keyString];b._drawMarkerBacks(c,d,e,f,g),k=null===e._markers||void 0===e._markers||void 0===e._markers[c.keyString]?d._group.selectAll("."+m.join(".")).data(c.markerData):e._markers[c.keyString].data(c.markerData,function(a){return a.key}),k.enter().append("circle").attr("id",function(a){return a.key}).attr("class",function(a){var c=[];return e.x._hasCategories()&&(c=c.concat(a.xField)),e.y._hasCategories()&&(c=c.concat(a.yField)),b._createClass(c)+" "+m.join(" ")}).on("mouseover",function(a){i(a,this,d,e)}).on("mouseleave",function(a){j(a,this,d,e)}).attr("cx",function(a){return e.x._hasCategories()?b._helpers.cx(a,d,e):e.x._previousOrigin}).attr("cy",function(a){return e.y._hasCategories()?b._helpers.cy(a,d,e):e.y._previousOrigin}).attr("r",0).attr("opacity",e.lineMarkers||c.data.length<2?c.color.opacity:0).call(function(){d.noFormats||this.attr("fill","white").style("stroke-width",e.lineWeight).attr("stroke",function(a){return h?b._helpers.fill(a,d,e):c.color.stroke})}),d._handleTransition(k,f,d).attr("cx",function(a){return b._helpers.cx(a,d,e)}).attr("cy",function(a){return b._helpers.cy(a,d,e)}).attr("r",2+e.lineWeight).call(function(){d.noFormats||this.attr("fill","white").style("stroke-width",e.lineWeight).attr("stroke",function(a){return h?b._helpers.fill(a,d,e):c.color.stroke})}),l=d._handleTransition(k.exit(),f,d).attr("cx",function(a){return e.x._hasCategories()?b._helpers.cx(a,d,e):e.x._origin}).attr("cy",function(a){return e.y._hasCategories()?b._helpers.cy(a,d,e):e.y._origin}).attr("r",0),0===f?l.remove():l.each("end",function(){a.select(this).remove()}),(void 0===e._markers||null===e._markers)&&(e._markers={}),e._markers[c.keyString]=k},b._getOrderedList=function(a,c,d){var e,f=[],g=[],h=[].concat(c),i=[].concat(c),j=[];return null!==d&&void 0!==d&&(j=j.concat(d)),j=j.concat({ordering:h,desc:!1}),j.forEach(function(b){var c,d=[],e=[];if("function"==typeof b.ordering)for(c in a[0])a[0].hasOwnProperty(c)&&-1===i.indexOf(c)&&i.push(c);else if(b.ordering instanceof Array){for(c=0;c<b.ordering.length;c+=1)a[0].hasOwnProperty(b.ordering[c])&&e.push(b.ordering[c]),d.push(b.ordering[c]);e.length>d.length/2?i.concat(e):b.values=d}else i.push(b.ordering)},this),e=b._rollUp(a,h,i),j.length>=1&&(j.forEach(function(a){var b=null===a.desc||void 0===a.desc?!1:a.desc,c=a.ordering,d=[],e=function(a){var b,c=0;for(b=0;b<a.length;b+=1){if(isNaN(a[b])){c=void 0;break}c+=parseFloat(a[b])}return c},g=function(a,b){var c=0,d=e(a),f=e(b);return isNaN(d)||isNaN(f)?isNaN(Date.parse(a[0]))||isNaN(Date.parse(b[0]))?a[0]<b[0]?c=-1:a[0]>b[0]&&(c=1):c=Date.parse(a[0])-Date.parse(b[0]):c=parseFloat(d)-parseFloat(f),c};"function"==typeof c?f.push(function(a,d){return(b?-1:1)*c(a,d)}):a.values&&a.values.length>0?(a.values.forEach(function(a){d.push([].concat(a).join("|"))},this),f.push(function(a,c){var e,f,g,i="",j="";for(g=0;g<h.length;g+=1)g>0&&(i+="|",j+="|"),i+=a[h[g]],j+=c[h[g]];return e=d.indexOf(i),f=d.indexOf(j),e=0>e?b?-1:d.length:e,f=0>f?b?-1:d.length:f,(b?-1:1)*(e-f)})):[].concat(a.ordering).forEach(function(a){f.push(function(c,d){var e=0;return void 0!==c[a]&&void 0!==d[a]&&(e=g([].concat(c[a]),[].concat(d[a]))),(b?-1:1)*e})})}),e.sort(function(a,b){for(var c=0,d=0;c<f.length&&0===d;)d=f[c](a,b),c+=1;return d}),e.forEach(function(a){var b,c=[];if(1===h.length)g.push(a[h[0]]);else{for(b=0;b<h.length;b+=1)c.push(a[h[b]]);g.push(c)}},this)),g},b._getSeriesOrder=function(a,c){var d=[].concat(c._orderRules),e=c.categoryFields,f=[];return null!==e&&void 0!==e&&e.length>0&&(null!==c.c&&void 0!==c.c&&c.c._hasMeasure()&&d.push({ordering:c.c.measure,desc:!0}),c.x._hasMeasure()&&d.push({ordering:c.x.measure,desc:!0}),c.y._hasMeasure()&&d.push({ordering:c.y.measure,desc:!0}),f=b._getOrderedList(a,e,d)),f},b._getSeriesSortPredicate=function(a,c,d){return function(e,f){var g=0;return c.x._hasCategories()&&(g=b._helpers.cx(e,a,c)-b._helpers.cx(f,a,c)),0===g&&c.y._hasCategories()&&(g=b._helpers.cy(e,a,c)-b._helpers.cy(f,a,c)),0===g&&d&&(g=b._arrayIndexCompare(d,e.aggField,f.aggField)),g}},b._helpers={cx:function(a,c,d){var e=0;return e=null!==d.x.measure&&void 0!==d.x.measure?d.x._scale(a.cx):d.x._hasCategories()&&d.x.categoryFields.length>=2?d.x._scale(a.cx)+b._helpers.xGap(c,d)+(a.xOffset+.5)*(c._widthPixels()/d.x._max-2*b._helpers.xGap(c,d))*a.width:d.x._scale(a.cx)+c._widthPixels()/d.x._max/2},cy:function(a,c,d){var e=0;return e=null!==d.y.measure&&void 0!==d.y.measure?d.y._scale(a.cy):null!==d.y.categoryFields&&void 0!==d.y.categoryFields&&d.y.categoryFields.length>=2?d.y._scale(a.cy)-c._heightPixels()/d.y._max+b._helpers.yGap(c,d)+(a.yOffset+.5)*(c._heightPixels()/d.y._max-2*b._helpers.yGap(c,d))*a.height:d.y._scale(a.cy)-c._heightPixels()/d.y._max/2},r:function(a,b,c){var d=0,e=1;return null===c.z||void 0===c.z?d=c.radius&&"auto"!==c.radius?c.radius:5:(c.radius&&"auto"!==c.radius&&c.radius>1&&(e=c.radius/c.z._scale(c.z._max)),d=c.z._hasMeasure()?c.z._scale(a.r)*e:c.z._scale(b._heightPixels()/100)*e),d},xGap:function(a,b){var c=0;return(null===b.x.measure||void 0===b.x.measure)&&b.barGap>0&&(c=a._widthPixels()/b.x._max*(b.barGap>.99?.99:b.barGap)/2),c},xClusterGap:function(a,c,d){var e=0;return null!==d.x.categoryFields&&void 0!==d.x.categoryFields&&d.x.categoryFields.length>=2&&d.clusterBarGap>0&&!d.x._hasMeasure()&&(e=a.width*(c._widthPixels()/d.x._max-2*b._helpers.xGap(c,d))*(d.clusterBarGap>.99?.99:d.clusterBarGap)/2),e -},yGap:function(a,b){var c=0;return(null===b.y.measure||void 0===b.y.measure)&&b.barGap>0&&(c=a._heightPixels()/b.y._max*(b.barGap>.99?.99:b.barGap)/2),c},yClusterGap:function(a,c,d){var e=0;return null!==d.y.categoryFields&&void 0!==d.y.categoryFields&&d.y.categoryFields.length>=2&&d.clusterBarGap>0&&!d.y._hasMeasure()&&(e=a.height*(c._heightPixels()/d.y._max-2*b._helpers.yGap(c,d))*(d.clusterBarGap>.99?.99:d.clusterBarGap)/2),e},x:function(a,c,d){var e=0;return e=d.x._hasTimeField()?d.x._scale(a.x)-b._helpers.width(a,c,d)/2:null!==d.x.measure&&void 0!==d.x.measure?d.x._scale(a.x):d.x._scale(a.x)+b._helpers.xGap(c,d)+a.xOffset*(b._helpers.width(a,c,d)+2*b._helpers.xClusterGap(a,c,d))+b._helpers.xClusterGap(a,c,d)},y:function(a,c,d){var e=0;return e=d.y._hasTimeField()?d.y._scale(a.y)-b._helpers.height(a,c,d)/2:null!==d.y.measure&&void 0!==d.y.measure?d.y._scale(a.y):d.y._scale(a.y)-c._heightPixels()/d.y._max+b._helpers.yGap(c,d)+a.yOffset*(b._helpers.height(a,c,d)+2*b._helpers.yClusterGap(a,c,d))+b._helpers.yClusterGap(a,c,d)},width:function(a,c,d){var e=0;return e=null!==d.x.measure&&void 0!==d.x.measure?Math.abs(d.x._scale(a.x<0?a.x-a.width:a.x+a.width)-d.x._scale(a.x)):d.x._hasTimeField()?d.x.floatingBarWidth:a.width*(c._widthPixels()/d.x._max-2*b._helpers.xGap(c,d))-2*b._helpers.xClusterGap(a,c,d)},height:function(a,c,d){var e=0;return e=d.y._hasTimeField()?d.y.floatingBarWidth:null!==d.y.measure&&void 0!==d.y.measure?Math.abs(d.y._scale(a.y)-d.y._scale(a.y<=0?a.y+a.height:a.y-a.height)):a.height*(c._heightPixels()/d.y._max-2*b._helpers.yGap(c,d))-2*b._helpers.yClusterGap(a,c,d)},opacity:function(a,b,c){var d=0;return d=null!==c.c&&void 0!==c.c?a.opacity:b.getColor(a.aggField.slice(-1)[0]).opacity},fill:function(a,b,c){var d=0;return d=null!==c.c&&void 0!==c.c?a.fill:b.getColor(a.aggField.slice(-1)[0]).fill},stroke:function(a,b,c){var d=0;return d=null!==c.c&&void 0!==c.c?a.stroke:b.getColor(a.aggField.slice(-1)[0]).stroke}},b._parentHeight=function(a){var c=a.offsetHeight;return(0>=c||null===c||void 0===c)&&(c=a.clientHeight),(0>=c||null===c||void 0===c)&&(c=null===a.parentNode||void 0===a.parentNode?0:b._parentHeight(a.parentNode)),c},b._parentWidth=function(a){var c=a.offsetWidth;return(!c||0>c)&&(c=a.clientWidth),(!c||0>c)&&(c=a.parentNode?b._parentWidth(a.parentNode):0),c},b._parsePosition=function(a,b){var c,d=0;return a&&(c=a.toString().split(","),c.forEach(function(c){c&&(isNaN(c)?"%"===c.slice(-1)?d+=b*(parseFloat(c.slice(0,c.length-1))/100):"px"===c.slice(-2)?d+=parseFloat(c.slice(0,c.length-2)):d=a:d+=parseFloat(c))},this)),0>d&&(d=b+d),d},b._parseXPosition=function(a,c){return b._parsePosition(a,b._parentWidth(c))},b._parseYPosition=function(a,c){return b._parsePosition(a,b._parentHeight(c))},b._postDrawHandling=function(b,c,d,e){0===e?(c.each(function(a,c){null!==b.afterDraw&&void 0!==b.afterDraw&&b.afterDraw(this,a,c)}),d.remove()):(c.each("end",function(a,c){null!==b.afterDraw&&void 0!==b.afterDraw&&b.afterDraw(this,a,c)}),d.each("end",function(){a.select(this).remove()}))},b._removeTooltip=function(a,b,c){c._tooltipGroup&&c._tooltipGroup.remove()},b._rollUp=function(a,b,c){var d=[];return b=null!==b&&void 0!==b?[].concat(b):[],a.forEach(function(a){var e=-1,f={},g=!0;d.forEach(function(c,d){-1===e&&(g=!0,b.forEach(function(b){g=g&&a[b]===c[b]},this),g&&(e=d))},this),-1!==e?f=d[e]:(b.forEach(function(b){f[b]=a[b]},this),d.push(f),e=d.length-1),c.forEach(function(c){-1===b.indexOf(c)&&(void 0===f[c]&&(f[c]=[]),f[c]=f[c].concat(a[c]))},this),d[e]=f},this),d},b._showBarTooltip=function(b,c,d,e){var f,g,h,i,j,k,l=5,m=10,n=750,o=a.select(c),p=o.node().getBBox().x,q=o.node().getBBox().y,r=o.node().getBBox().width,s=o.node().getBBox().height,t=o.attr("opacity"),u=o.attr("fill"),v=e._dropLineOrigin(),w=a.rgb(a.rgb(u).r+.6*(255-a.rgb(u).r),a.rgb(u).g+.6*(255-a.rgb(u).g),a.rgb(u).b+.6*(255-a.rgb(u).b)),x=a.rgb(a.rgb(u).r+.8*(255-a.rgb(u).r),a.rgb(u).g+.8*(255-a.rgb(u).g),a.rgb(u).b+.8*(255-a.rgb(u).b)),y=e.getTooltipText(b),z=0,A=0,B=0,C=function(a,b){var c=o.node().getCTM(),e=d.svg.node().createSVGPoint();return e.x=a||0,e.y=b||0,e.matrixTransform(c)};null!==d._tooltipGroup&&void 0!==d._tooltipGroup&&d._tooltipGroup.remove(),d._tooltipGroup=d.svg.append("g"),e.p||(k=e._isStacked()?1:r/2,e.x._hasCategories()||null===v.y||d._tooltipGroup.append("line").attr("x1",p<e.x._origin?p+k:p+r-k).attr("y1",q<v.y?q+s:q).attr("x2",p<e.x._origin?p+k:p+r-k).attr("y2",q<v.y?q+s:q).style("fill","none").style("stroke",u).style("stroke-width",2).style("stroke-dasharray","3, 3").style("opacity",t).transition().delay(n/2).duration(n/2).ease("linear").attr("y2",q<v.y?v.y-1:v.y+1),k=e._isStacked()?1:s/2,e.y._hasCategories()||null===v.x||d._tooltipGroup.append("line").attr("x1",p<v.x?p+r:p).attr("y1",q<e.y._origin?q+k:q+s-k).attr("x2",p<v.x?p+r:p).attr("y2",q<e.y._origin?q+k:q+s-k).style("fill","none").style("stroke",u).style("stroke-width",2).style("stroke-dasharray","3, 3").style("opacity",t).transition().delay(n/2).duration(n/2).ease("linear").attr("x2",p<v.x?v.x-1:v.x+1)),f=d._tooltipGroup.append("g"),g=f.append("rect").attr("class","dimple-tooltip"),f.selectAll(".dimple-dont-select-any").data(y).enter().append("text").attr("class","dimple-tooltip").text(function(a){return a}).style("font-family",e.tooltipFontFamily).style("font-size",e._getTooltipFontSize()),f.each(function(){A=this.getBBox().width>A?this.getBBox().width:A,B=this.getBBox().width>B?this.getBBox().height:B}),f.selectAll("text").attr("x",0).attr("y",function(){return z+=this.getBBox().height,z-this.getBBox().height/2}),g.attr("x",-l).attr("y",-l).attr("height",Math.floor(z+l)-.5).attr("width",A+2*l).attr("rx",5).attr("ry",5).style("fill",x).style("stroke",w).style("stroke-width",2).style("opacity",.95),C(p+r+l+m+A).x<parseFloat(d.svg.node().getBBox().width)?(h=p+r+l+m,i=q+s/2-(z-(B-l))/2):C(p-(l+m+A)).x>0?(h=p-(l+m+A),i=q+s/2-(z-(B-l))/2):C(0,q+s+z+m+l).y<parseFloat(d.svg.node().getBBox().height)?(h=p+r/2-(2*l+A)/2,h=h>0?h:m,h=h+A<parseFloat(d.svg.node().getBBox().width)?h:parseFloat(d.svg.node().getBBox().width)-A-m,i=q+s+2*l):(h=p+r/2-(2*l+A)/2,h=h>0?h:m,h=h+A<parseFloat(d.svg.node().getBBox().width)?h:parseFloat(d.svg.node().getBBox().width)-A-m,i=q-z-(B-l)),j=C(h,i),f.attr("transform","translate("+j.x+" , "+j.y+")")},b._showPointTooltip=function(c,d,e,f){var g,h,i,j,k=5,l=10,m=750,n=a.select(d),o=parseFloat(n.attr("cx")),p=parseFloat(n.attr("cy")),q=parseFloat(n.attr("r")),r=b._helpers.opacity(c,e,f),s=n.attr("stroke"),t=f._dropLineOrigin(),u=a.rgb(a.rgb(s).r+.6*(255-a.rgb(s).r),a.rgb(s).g+.6*(255-a.rgb(s).g),a.rgb(s).b+.6*(255-a.rgb(s).b)),v=a.rgb(a.rgb(s).r+.8*(255-a.rgb(s).r),a.rgb(s).g+.8*(255-a.rgb(s).g),a.rgb(s).b+.8*(255-a.rgb(s).b)),w=0,x=0,y=0,z=f.getTooltipText(c);null!==e._tooltipGroup&&void 0!==e._tooltipGroup&&e._tooltipGroup.remove(),e._tooltipGroup=e.svg.append("g"),e._tooltipGroup.append("circle").attr("cx",o).attr("cy",p).attr("r",q).attr("opacity",0).style("fill","none").style("stroke",s).style("stroke-width",1).transition().duration(m/2).ease("linear").attr("opacity",1).attr("r",q+f.lineWeight+2).style("stroke-width",2),null!==t.y&&e._tooltipGroup.append("line").attr("x1",o).attr("y1",p<t.y?p+q+f.lineWeight+2:p-q-f.lineWeight-2).attr("x2",o).attr("y2",p<t.y?p+q+f.lineWeight+2:p-q-f.lineWeight-2).style("fill","none").style("stroke",s).style("stroke-width",2).style("stroke-dasharray","3, 3").style("opacity",r).transition().delay(m/2).duration(m/2).ease("linear").attr("y2",p<t.y?t.y-1:t.y+1),null!==t.x&&e._tooltipGroup.append("line").attr("x1",o<t.x?o+q+f.lineWeight+2:o-q-f.lineWeight-2).attr("y1",p).attr("x2",o<t.x?o+q+f.lineWeight+2:o-q-f.lineWeight-2).attr("y2",p).style("fill","none").style("stroke",s).style("stroke-width",2).style("stroke-dasharray","3, 3").style("opacity",r).transition().delay(m/2).duration(m/2).ease("linear").attr("x2",o<t.x?t.x-1:t.x+1),g=e._tooltipGroup.append("g"),h=g.append("rect").attr("class","dimple-tooltip"),g.selectAll(".dont-select-any").data(z).enter().append("text").attr("class","dimple-tooltip").text(function(a){return a}).style("font-family",f.tooltipFontFamily).style("font-size",f._getTooltipFontSize()),g.each(function(){x=this.getBBox().width>x?this.getBBox().width:x,y=this.getBBox().width>y?this.getBBox().height:y}),g.selectAll("text").attr("x",0).attr("y",function(){return w+=this.getBBox().height,w-this.getBBox().height/2}),h.attr("x",-k).attr("y",-k).attr("height",Math.floor(w+k)-.5).attr("width",x+2*k).attr("rx",5).attr("ry",5).style("fill",v).style("stroke",u).style("stroke-width",2).style("opacity",.95),o+q+k+l+x<parseFloat(e.svg.node().getBBox().width)?(i=o+q+k+l,j=p-(w-(y-k))/2):o-q-(k+l+x)>0?(i=o-q-(k+l+x),j=p-(w-(y-k))/2):p+q+w+l+k<parseFloat(e.svg.node().getBBox().height)?(i=o-(2*k+x)/2,i=i>0?i:l,i=i+x<parseFloat(e.svg.node().getBBox().width)?i:parseFloat(e.svg.node().getBBox().width)-x-l,j=p+q+2*k):(i=o-(2*k+x)/2,i=i>0?i:l,i=i+x<parseFloat(e.svg.node().getBBox().width)?i:parseFloat(e.svg.node().getBBox().width)-x-l,j=p-w-(y-k)),g.attr("transform","translate("+i+" , "+j+")")},b.filterData=function(a,b,c){var d=a;return null!==b&&null!==c&&(null!==c&&void 0!==c&&(c=[].concat(c)),d=[],a.forEach(function(a){null===a[b]?d.push(a):c.indexOf([].concat(a[b]).join("/"))>-1&&d.push(a)},this)),d},b.getUniqueValues=function(a,b){var c=[];return null!==b&&void 0!==b&&(b=[].concat(b),a.forEach(function(a){var d="";b.forEach(function(b,c){c>0&&(d+="/"),d+=a[b]},this),-1===c.indexOf(d)&&c.push(d)},this)),c},b.newSvg=function(b,c,d){var e=null;if((null===b||void 0===b)&&(b="body"),e=a.select(b),e.empty())throw"The '"+b+"' selector did not match any elements. Please prefix with '#' to select by id or '.' to select by class";return e.append("svg").attr("width",c).attr("height",d)},b});
\ No newline at end of file diff --git a/assets/js/vendor/dimple.v2.1.2.min.js b/assets/js/vendor/dimple.v2.1.2.min.js new file mode 100644 index 00000000..fc25e18f --- /dev/null +++ b/assets/js/vendor/dimple.v2.1.2.min.js @@ -0,0 +1,3 @@ +!function(a,b){"use strict";if("object"==typeof exports)module.exports=b(require("d3"));else if("function"==typeof define&&define.amd)define(["d3"],function(c){return a.dimple=b(c),a.dimple});else if(a.d3)a.dimple=b(a.d3);else{if(!console||!console.warn)throw"dimple requires d3 to run. Are you missing a reference to the d3 library?";console.warn("dimple requires d3 to run. Are you missing a reference to the d3 library?")}}(this,function(a){"use strict";var b={version:"2.1.2",plot:{},aggregateMethod:{}};return b.axis=function(c,d,e,f,g){this.chart=c,this.position=d,this.categoryFields=null===g||void 0===g?e:[].concat(g),this.measure=f,this.timeField=g,this.floatingBarWidth=5,this.hidden=!1,this.showPercent=!1,this.colors=null,this.overrideMin=null,this.overrideMax=null,this.shapes=null,this.showGridlines=null,this.gridlineShapes=null,this.titleShape=null,this.dateParseFormat=null,this.tickFormat=null,this.timePeriod=null,this.timeInterval=1,this.useLog=!1,this.logBase=10,this.title=void 0,this.clamp=!0,this.ticks=null,this.fontSize="10px",this.fontFamily="sans-serif",this._slaves=[],this._scale=null,this._min=0,this._max=0,this._previousOrigin=null,this._origin=null,this._orderRules=[],this._groupOrderRules=[],this._draw=null,this._getAxisData=function(){var a,b,c=[],d=!1;if(this.chart&&this.chart.series){for(a=0;a<this.chart.series.length;a+=1)b=this.chart.series[a],b[this.position]===this&&(b.data&&b.data.length>0?c=c.concat(b.data):d=!0);d&&this.chart.data&&(c=c.concat(this.chart.data))}return c},this._getFontSize=function(){var a;return a=this.fontSize&&"auto"!==this.fontSize.toString().toLowerCase()?isNaN(this.fontSize)?this.fontSize:this.fontSize+"px":(this.chart._heightPixels()/35>10?this.chart._heightPixels()/35:10)+"px"},this._getFormat=function(){var b,c,d,e,f,g,h;return null!==this.tickFormat&&void 0!==this.tickFormat?b=this._hasTimeField()?a.time.format(this.tickFormat):a.format(this.tickFormat):this.showPercent?b=a.format("%"):this.useLog&&null!==this.measure?b=function(b){var c=Math.floor(Math.abs(b),0).toString().length,d=Math.min(Math.floor((c-1)/3),4),e="kmBT".substring(d-1,d),f="0"===Math.round(10*(b/Math.pow(1e3,d))).toString().slice(-1)?0:1;return 0===b?0:a.format(",."+f+"f")(b/Math.pow(1e3,d))+e}:null!==this.measure?(c=Math.floor(Math.abs(this._max),0).toString(),d=Math.floor(Math.abs(this._min),0).toString(),e=Math.max(d.length,c.length),e>3?(f=Math.min(Math.floor((e-1)/3),4),g="kmBT".substring(f-1,f),h=1>=e-3*f?1:0,b=function(b){return 0===b?0:a.format(",."+h+"f")(b/Math.pow(1e3,f))+g}):(h=-Math.floor(Math.log(this._tick_step)/Math.LN10),b=a.format(",."+h+"f"))):b=function(a){return a},b},this._getTimePeriod=function(){var b=this.timePeriod,c=30,d=this._max-this._min;return this._hasTimeField()&&!this.timePeriod&&(b=c>=d/1e3?a.time.seconds:c>=d/6e4?a.time.minutes:c>=d/36e5?a.time.hours:c>=d/864e5?a.time.days:c>=d/6048e5?a.time.weeks:c>=d/26298e5?a.time.months:a.time.years),b},this._getTooltipText=function(b,c){if(this._hasTimeField())c[this.position+"Field"][0]&&b.push(this.timeField+": "+this._getFormat()(c[this.position+"Field"][0]));else if(this._hasCategories())this.categoryFields.forEach(function(a,d){null!==a&&void 0!==a&&c[this.position+"Field"][d]&&b.push(a+(c[this.position+"Field"][d]!==a?": "+c[this.position+"Field"][d]:""))},this);else if(this._hasMeasure())switch(this.position){case"x":b.push(this.measure+": "+this._getFormat()(c.width));break;case"y":b.push(this.measure+": "+this._getFormat()(c.height));break;case"p":b.push(this.measure+": "+this._getFormat()(c.angle)+" ("+a.format("%")(c.piePct)+")");break;default:b.push(this.measure+": "+this._getFormat()(c[this.position+"Value"]))}},this._getTopMaster=function(){var a=this;return null!==this.master&&void 0!==this.master&&(a=this.master._getTopMaster()),a},this._hasCategories=function(){return null!==this.categoryFields&&void 0!==this.categoryFields&&this.categoryFields.length>0},this._hasMeasure=function(){return null!==this.measure&&void 0!==this.measure},this._hasTimeField=function(){return null!==this.timeField&&void 0!==this.timeField},this._parseDate=function(b){var c;return c=null===this.dateParseFormat||void 0===this.dateParseFormat?isNaN(b)?Date.parse(b):new Date(b):a.time.format(this.dateParseFormat).parse(b)},this._update=function(c){var d,e,f,g,h=[],i=this.ticks||10,j=function(a,c,d){var e,f,g=a.categoryFields[0],h=a._getAxisData(),i=g,j=!1,k=!0,l=null;for(e=0;e<h.length;e+=1)if(l=a._parseDate(h[e][g]),null!==l&&void 0!==l&&isNaN(l)){k=!1;break}return k||a.chart.series.forEach(function(b){b[c]===a&&b[d]._hasMeasure()&&(i=b[d].measure,j=!0)},this),f=a._orderRules.concat({ordering:i,desc:j}),b._getOrderedList(h,g,f)};if(this._min=this.showPercent&&this._min<-1?-1:this._min,this._max=this.showPercent&&this._max>1?1:this._max,this._min=null!==this.overrideMin?this.overrideMin:this._min,this._max=null!==this.overrideMax?this.overrideMax:this._max,"x"!==this.position||null!==this._scale&&!c){if("y"!==this.position||null!==this._scale&&!c)this.position.length>0&&"z"===this.position[0]&&null===this._scale?this._scale=this.useLog?a.scale.log().range([this.chart._heightPixels()/300,this.chart._heightPixels()/10]).domain([0===this._min?Math.pow(this.logBase,-1):this._min,0===this._max?-1*Math.pow(this.logBase,-1):this._max]).clamp(this.clamp).base(this.logBase):a.scale.linear().range([1,this.chart._heightPixels()/10]).domain([this._min,this._max]).clamp(this.clamp):this.position.length>0&&"p"===this.position[0]&&null===this._scale?this._scale=this.useLog?a.scale.log().range([0,360]).domain([0===this._min?Math.pow(this.logBase,-1):this._min,0===this._max?-1*Math.pow(this.logBase,-1):this._max]).clamp(this.clamp).base(this.logBase):a.scale.linear().range([0,360]).domain([this._min,this._max]).clamp(this.clamp):this.position.length>0&&"c"===this.position[0]&&null===this._scale&&(this._scale=a.scale.linear().range([0,null===this.colors||1===this.colors.length?1:this.colors.length-1]).domain([this._min,this._max]).clamp(this.clamp));else if(this._hasTimeField()?this._scale=a.time.scale().range([this.chart._yPixels()+this.chart._heightPixels(),this.chart._yPixels()]).domain([this._min,this._max]).clamp(this.clamp):this.useLog?this._scale=a.scale.log().range([this.chart._yPixels()+this.chart._heightPixels(),this.chart._yPixels()]).domain([0===this._min?Math.pow(this.logBase,-1):this._min,0===this._max?-1*Math.pow(this.logBase,-1):this._max]).clamp(this.clamp).base(this.logBase).nice():null===this.measure||void 0===this.measure?(h=j(this,"y","x"),null!==this._slaves&&void 0!==this._slaves&&this._slaves.forEach(function(a){h=h.concat(j(a,"y","x"))},this),this._scale=a.scale.ordinal().rangePoints([this.chart._yPixels()+this.chart._heightPixels(),this.chart._yPixels()]).domain(h.concat([""]))):this._scale=a.scale.linear().range([this.chart._yPixels()+this.chart._heightPixels(),this.chart._yPixels()]).domain([this._min,this._max]).clamp(this.clamp).nice(),!this.hidden)switch(this.chart._axisIndex(this,"y")){case 0:this._draw=a.svg.axis().orient("left").scale(this._scale),this.ticks&&this._draw.ticks(i);break;case 1:this._draw=a.svg.axis().orient("right").scale(this._scale),this.ticks&&this._draw.ticks(i)}}else if(this._hasTimeField()?this._scale=a.time.scale().range([this.chart._xPixels(),this.chart._xPixels()+this.chart._widthPixels()]).domain([this._min,this._max]).clamp(this.clamp):this.useLog?this._scale=a.scale.log().range([this.chart._xPixels(),this.chart._xPixels()+this.chart._widthPixels()]).domain([0===this._min?Math.pow(this.logBase,-1):this._min,0===this._max?-1*Math.pow(this.logBase,-1):this._max]).clamp(this.clamp).base(this.logBase).nice():null===this.measure||void 0===this.measure?(h=j(this,"x","y"),null!==this._slaves&&void 0!==this._slaves&&this._slaves.forEach(function(a){h=h.concat(j(a,"x","y"))},this),this._scale=a.scale.ordinal().rangePoints([this.chart._xPixels(),this.chart._xPixels()+this.chart._widthPixels()]).domain(h.concat([""]))):this._scale=a.scale.linear().range([this.chart._xPixels(),this.chart._xPixels()+this.chart._widthPixels()]).domain([this._min,this._max]).clamp(this.clamp).nice(),!this.hidden)switch(this.chart._axisIndex(this,"x")){case 0:this._draw=a.svg.axis().orient("bottom").scale(this._scale),this.ticks&&this._draw.ticks(i);break;case 1:this._draw=a.svg.axis().orient("top").scale(this._scale),this.ticks&&this._draw.ticks(i)}return null!==this._slaves&&void 0!==this._slaves&&this._slaves.length>0&&this._slaves.forEach(function(a){a._scale=this._scale},this),null!==c&&void 0!==c&&c!==!1||this._hasTimeField()||null===this._scale||null===this._scale.ticks||void 0===this._scale.ticks||!(this._scale.ticks(i).length>0)||"x"!==this.position&&"y"!==this.position||(d=this._scale.ticks(i),e=d[1]-d[0],f=((this._max-this._min)%e).toFixed(0),this._tick_step=e,0!==f&&(this._max=Math.ceil(this._max/e)*e,this._min=Math.floor(this._min/e)*e,this._update(!0))),g=null!==h&&void 0!==h&&h.length>0?this._scale.copy()(h[0]):this._min>0?this._scale.copy()(this._min):this._max<0?this._scale.copy()(this._max):this._scale.copy()(0),this._origin!==g&&(this._previousOrigin=null===this._origin?g:this._origin,this._origin=g),this},this.addGroupOrderRule=function(a,b){this._groupOrderRules.push({ordering:a,desc:b})},this.addOrderRule=function(a,b){this._orderRules.push({ordering:a,desc:b})}},b.chart=function(c,d){this.svg=c,this.x="10%",this.y="10%",this.width="80%",this.height="80%",this.data=d,this.noFormats=!1,this.axes=[],this.series=[],this.legends=[],this.storyboard=null,this.titleShape=null,this.shapes=null,this.ease="cubic-in-out",this.staggerDraw=!1,this._group=c.append("g"),this._tooltipGroup=null,this._assignedColors={},this._assignedClasses={},this._nextColor=0,this._nextClass=0,this._axisIndex=function(a,b){var c=0,d=0,e=-1;for(c=0;c<this.axes.length;c+=1){if(this.axes[c]===a){e=d;break}(null===b||void 0===b||b[0]===this.axes[c].position[0])&&(d+=1)}return e},this._getAllData=function(){var a=[];return null!==this.data&&void 0!==this.data&&this.data.length>0&&(a=a.concat(this.data)),null!==this.series&&void 0!==this.series&&this.series.length>0&&this.series.forEach(function(b){null!==b.data&&void 0!==b.data&&b.data.length>0&&(a=a.concat(b.data))}),a},this._getData=function(c,d,e,f,g,h,i,j,k,l){var m,n,o=[],p=function(a,b){var c=[];return null!==a&&(a._hasTimeField()?c.push(a._parseDate(b[a.timeField])):a._hasCategories()&&a.categoryFields.forEach(function(a){c.push(b[a])},this)),c},q={x:!1,y:!1,z:!1,p:!1,c:!1},r={x:[],y:[]},s={x:[],y:[],z:[],p:[]},t={min:null,max:null},u={x:[],y:[],z:[],p:[]},v=[],w={},x={x:0,y:0,z:0,p:0},y="",z=[],A=[],B=[],C="",D=[],E="",F=[],G="",H=[],I=[],J=c,K=[];this.storyboard&&this.storyboard.categoryFields.length>0&&(y=this.storyboard.categoryFields[0],z=b._getOrderedList(J,y,this.storyboard._orderRules)),h&&h._hasCategories()&&h._hasMeasure()&&(C=h.categoryFields[0],D=b._getOrderedList(J,C,h._orderRules.concat([{ordering:h.measure,desc:!0}]))),i&&i._hasCategories()&&i._hasMeasure()&&(E=i.categoryFields[0],F=b._getOrderedList(J,E,i._orderRules.concat([{ordering:i.measure,desc:!0}]))),k&&k._hasCategories()&&k._hasMeasure()&&(G=k.categoryFields[0],H=b._getOrderedList(J,G,k._orderRules.concat([{ordering:k.measure,desc:!0}]))),J.length>0&&d&&d.length>0&&(I=[].concat(f),A=[],d.forEach(function(a){void 0!==J[0][a]&&A.push(a)},this),k&&k._hasMeasure()?I.push({ordering:k.measure,desc:!0}):l&&l._hasMeasure()?I.push({ordering:l.measure,desc:!0}):j&&j._hasMeasure()?I.push({ordering:j.measure,desc:!0}):h&&h._hasMeasure()?I.push({ordering:h.measure,desc:!0}):i&&i._hasMeasure()&&I.push({ordering:i.measure,desc:!0}),B=b._getOrderedList(J,A,I)),J.sort(function(a,b){var c,d,e,f,g,h,i=0;if(""!==y&&(i=z.indexOf(a[y])-z.indexOf(b[y])),""!==C&&0===i&&(i=D.indexOf(a[C])-D.indexOf(b[C])),""!==E&&0===i&&(i=F.indexOf(a[E])-F.indexOf(b[E])),""!==G&&0===i&&(i=H.indexOf(a[G])-F.indexOf(b[G])),A&&A.length>0&&0===i)for(c=[].concat(A),i=0,e=0;e<B.length;e+=1){for(d=[].concat(B[e]),g=!0,h=!0,f=0;f<c.length;f+=1)g=g&&a[c[f]]===d[f],h=h&&b[c[f]]===d[f];if(g&&h){i=0;break}if(g){i=-1;break}if(h){i=1;break}}return i}),J.forEach(function(a){var b,c,f,g,m,n=-1,r=p(h,a),s=p(i,a),t=p(j,a),u=p(k,a),v=[];if(d&&0!==d.length)for(f=0;f<d.length;f+=1)void 0===a[d[f]]?v.push(d[f]):v.push(a[d[f]]);else v=["All"];for(b=v.join("/")+"_"+r.join("/")+"_"+s.join("/")+"_"+u.join("/")+"_"+t.join("/"),c=0;c<o.length;c+=1)if(o[c].key===b){n=c;break}-1===n&&(g={key:b,aggField:v,xField:r,xValue:null,xCount:0,yField:s,yValue:null,yCount:0,pField:u,pValue:null,pCount:0,zField:t,zValue:null,zCount:0,cValue:0,cCount:0,x:0,y:0,xOffset:0,yOffset:0,width:0,height:0,cx:0,cy:0,xBound:0,yBound:0,xValueList:[],yValueList:[],zValueList:[],pValueList:[],cValueList:[],fill:{},stroke:{}},o.push(g),n=o.length-1),m=function(b,c){var d,f,g=!0,h={value:0,count:1},i={value:0,count:1},j="";null!==c&&(d=c.getFrameValue(),c.categoryFields.forEach(function(b,c){c>0&&(j+="/"),j+=a[b],g=j===d},this)),null!==b&&void 0!==b&&g&&(f=o[n],b._hasMeasure()&&null!==a[b.measure]&&void 0!==a[b.measure]&&(-1===f[b.position+"ValueList"].indexOf(a[b.measure])&&f[b.position+"ValueList"].push(a[b.measure]),isNaN(parseFloat(a[b.measure]))&&(q[b.position]=!0),h.value=f[b.position+"Value"],h.count=f[b.position+"Count"],i.value=a[b.measure],f[b.position+"Value"]=e(h,i),f[b.position+"Count"]+=1))},m(h,this.storyboard),m(i,this.storyboard),m(j,this.storyboard),m(k,this.storyboard),m(l,this.storyboard)},this),h&&h._hasCategories()&&h.categoryFields.length>1&&void 0!==r.x&&(K=[],i._hasMeasure()&&K.push({ordering:i.measure,desc:!0}),r.x=b._getOrderedList(J,h.categoryFields[1],h._groupOrderRules.concat(K))),i&&i._hasCategories()&&i.categoryFields.length>1&&void 0!==r.y&&(K=[],h._hasMeasure()&&K.push({ordering:h.measure,desc:!0}),r.y=b._getOrderedList(J,i.categoryFields[1],i._groupOrderRules.concat(K)),r.y.reverse()),o.forEach(function(a){null!==h&&(q.x===!0&&(a.xValue=a.xValueList.length),m=(s.x[a.xField.join("/")]||0)+(i._hasMeasure()?Math.abs(a.yValue):0),s.x[a.xField.join("/")]=m),null!==i&&(q.y===!0&&(a.yValue=a.yValueList.length),m=(s.y[a.yField.join("/")]||0)+(h._hasMeasure()?Math.abs(a.xValue):0),s.y[a.yField.join("/")]=m),null!==k&&(q.p===!0&&(a.pValue=a.pValueList.length),m=(s.p[a.pField.join("/")]||0)+(k._hasMeasure()?Math.abs(a.pValue):0),s.p[a.pField.join("/")]=m),null!==j&&(q.z===!0&&(a.zValue=a.zValueList.length),m=(s.z[a.zField.join("/")]||0)+(j._hasMeasure()?Math.abs(a.zValue):0),s.z[a.zField.join("/")]=m),null!==l&&((null===t.min||a.cValue<t.min)&&(t.min=a.cValue),(null===t.max||a.cValue>t.max)&&(t.max=a.cValue))},this);for(n in s.x)s.x.hasOwnProperty(n)&&(x.x+=s.x[n]);for(n in s.y)s.y.hasOwnProperty(n)&&(x.y+=s.y[n]);for(n in s.p)s.p.hasOwnProperty(n)&&(x.p+=s.p[n]);for(n in s.z)s.z.hasOwnProperty(n)&&(x.z+=s.z[n]);return o.forEach(function(b){var c,d,e,f,m,n=function(a,c,d){var e,f,h,i,j;null!==a&&void 0!==a&&(i=a.position,a._hasCategories()?a._hasMeasure()?(e=b[a.position+"Field"].join("/"),f=a.showPercent?s[a.position][e]/x[a.position]:s[a.position][e],-1===v.indexOf(e)&&(w[e]=f+(v.length>0?w[v[v.length-1]]:0),v.push(e)),h=b[i+"Bound"]=b["c"+i]="x"!==i&&"y"!==i||!g?f:w[e],b[d]=f,b[i]=h-("x"===i&&f>=0||"y"===i&&0>=f?f:0)):(b[i]=b["c"+i]=b[i+"Field"][0],b[d]=1,void 0!==r[i]&&null!==r[i]&&r[i].length>=2&&(b[i+"Offset"]=r[i].indexOf(b[i+"Field"][1]),b[d]=1/r[i].length)):(f=a.showPercent?b[i+"Value"]/s[c][b[c+"Field"].join("/")]:b[i+"Value"],e=b[c+"Field"].join("/")+(b[i+"Value"]>=0),j=u[i][e]=(null===u[i][e]||void 0===u[i][e]||"z"===i||"p"===i?0:u[i][e])+f,h=b[i+"Bound"]=b["c"+i]="x"!==i&&"y"!==i||!g?f:j,b[d]=f,b[i]=h-("x"===i&&f>=0||"y"===i&&0>=f?f:0)))};n(h,"y","width"),n(i,"x","height"),n(j,"z","r"),n(k,"p","angle"),null!==l&&null!==t.min&&null!==t.max&&(t.min===t.max&&(t.min-=.5,t.max+=.5),t.min=l.overrideMin||t.min,t.max=l.overrideMax||t.max,b.cValue=b.cValue>t.max?t.max:b.cValue<t.min?t.min:b.cValue,e=a.scale.linear().range([0,null===l.colors||1===l.colors.length?1:l.colors.length-1]).domain([t.min,t.max]),f=e(b.cValue),m=f-Math.floor(f),b.cValue===t.max&&(m=1),l.colors&&1===l.colors.length?(c=a.rgb(l.colors[0]),d=a.rgb(this.getColor(b.aggField.slice(-1)[0]).fill)):l.colors&&l.colors.length>1?(c=a.rgb(l.colors[Math.floor(f)]),d=a.rgb(l.colors[Math.ceil(f)])):(c=a.rgb("white"),d=a.rgb(this.getColor(b.aggField.slice(-1)[0]).fill)),c.r=Math.floor(c.r+(d.r-c.r)*m),c.g=Math.floor(c.g+(d.g-c.g)*m),c.b=Math.floor(c.b+(d.b-c.b)*m),b.fill=c.toString(),b.stroke=c.darker(.5).toString())},this),o},this._getDelay=function(a,c,d){return function(e){var f=0;return d&&c.staggerDraw&&(d.x._hasCategories()?f=b._helpers.cx(e,c,d)/c._widthPixels()*a:d.y._hasCategories()&&(f=(1-b._helpers.cy(e,c,d)/c._heightPixels())*a)),f}},this._getSeriesData=function(){null!==this.series&&void 0!==this.series&&this.series.forEach(function(a){var b,c,d,e,f,g,h=a.data||this.data||[],i=[].concat(a.categoryFields||"All"),j=this._getData(h,i,a.aggregate,a._orderRules,a._isStacked(),a.x,a.y,a.z,a.p,a.c),k=[],l={},m=a.startAngle*(Math.PI/180)||0,n=(a.endAngle||360)*(Math.PI/180);if(m>n&&(m-=2*Math.PI),a.p&&i.length>0){if(a.x&&a.y){for(i.pop(),k=this._getData(h,["__dimple_placeholder__"].concat(i),a.aggregate,a._orderRules,a._isStacked(),a.x,a.y,a.z,a.p,a.c),b=0;b<j.length;b+=1)for(d=["__dimple_placeholder__"].concat(j[b].aggField),d.pop(),a.x&&a.x._hasCategories()&&(d=d.concat(j[b].xField)),a.y&&a.y._hasCategories()&&(d=d.concat(j[b].yField)),e=d.join("|"),c=0;c<k.length;c+=1)if(f=[].concat(k[c].aggField),a.x&&a.x._hasCategories()&&(f=f.concat(k[c].xField)),a.y&&a.y._hasCategories()&&(f=f.concat(k[c].yField)),g=f.join("|"),e===g){j[b].xField=k[c].xField,j[b].xValue=k[c].xValue,j[b].xCount=k[c].xCount,j[b].yField=k[c].yField,j[b].yValue=k[c].yValue,j[b].yCount=k[c].yCount,j[b].zField=k[c].zField,j[b].zValue=k[c].zValue,j[b].zCount=k[c].zCount,j[b].x=k[c].x,j[b].y=k[c].y,j[b].r=k[c].r,j[b].xOffset=k[c].xOffset,j[b].yOffset=k[c].yOffset,j[b].width=k[c].width,j[b].height=k[c].height,j[b].cx=k[c].cx,j[b].cy=k[c].cy,j[b].xBound=k[c].xBound,j[b].yBound=k[c].yBound,j[b].xValueList=k[c].xValueList,j[b].yValueList=k[c].yValueList,j[b].zValueList=k[c].zValueList,j[b].cValueList=k[c].cValueList,j[b].pieKey=k[c].key,j[b].value=j.pValue,l[k[c].key]||(l[k[c].key]={total:0,angle:m}),l[k[c].key].total+=j[b].pValue;break}}else for(b=0;b<j.length;b+=1)j[b].pieKey="All",j[b].value=j.pValue,l[j[b].pieKey]||(l[j[b].pieKey]={total:0,angle:m}),l[j[b].pieKey].total+=j[b].pValue;for(b=0;b<j.length;b+=1)j[b].piePct=j[b].pValue/l[j[b].pieKey].total,j[b].startAngle=l[j[b].pieKey].angle,j[b].endAngle=j[b].startAngle+j[b].piePct*(n-m),l[j[b].pieKey].angle=j[b].endAngle}a._positionData=j},this)},this._handleTransition=function(a,b,c,d){var e=null;return e=0===b?a:a.transition().duration(b).delay(c._getDelay(b,c,d)).ease(c.ease)},this._heightPixels=function(){return b._parseYPosition(this.height,this.svg.node())},this._registerEventHandlers=function(c){null!==c._eventHandlers&&c._eventHandlers.length>0&&c._eventHandlers.forEach(function(d){var e,f=function(e){var f=new b.eventArgs;null!==c.chart.storyboard&&(f.frameValue=c.chart.storyboard.getFrameValue()),f.seriesValue=e.aggField,f.xValue=e.x,f.yValue=e.y,f.zValue=e.z,f.pValue=e.p,f.colorValue=e.cValue,f.seriesShapes=c.shapes,f.selectedShape=a.select(this),d.handler(f)};if(null!==d.handler&&"function"==typeof d.handler)if(null!==c._markers&&void 0!==c._markers)for(e in c._markers)c._markers.hasOwnProperty(e)&&c._markers[e].on(d.event,f);else c.shapes.on(d.event,f)},this)},this._widthPixels=function(){return b._parseXPosition(this.width,this.svg.node())},this._xPixels=function(){return b._parseXPosition(this.x,this.svg.node())},this._yPixels=function(){return b._parseYPosition(this.y,this.svg.node())},this.addAxis=function(a,c,d,e){var f=null,g=null;if(null!==c&&void 0!==c&&(c=[].concat(c)),"string"==typeof a||a instanceof String)f=new b.axis(this,a,c,d,e),this.axes.push(f);else{if(g=a,f=new b.axis(this,g.position,c,d,e),f._hasMeasure()!==g._hasMeasure())throw"You have specified a composite axis where some but not all axes have a measure - this is not supported, all axes must be of the same type.";if(f._hasTimeField()!==g._hasTimeField())throw"You have specified a composite axis where some but not all axes have a time field - this is not supported, all axes must be of the same type.";if((null===f.categoryFields||void 0===f.categoryFields?0:f.categoryFields.length)!==(null===g.categoryFields||void 0===g.categoryFields?0:g.categoryFields.length))throw"You have specified a composite axis where axes have differing numbers of category fields - this is not supported, all axes must be of the same type.";g._slaves.push(f)}return f},this.addCategoryAxis=function(a,b){return this.addAxis(a,b,null)},this.addColorAxis=function(a,b){var c=this.addAxis("c",null,a);return c.colors=null===b||void 0===b?null:[].concat(b),c},this.addLegend=function(a,c,d,e,f,g){g=null===g||void 0===g?this.series:[].concat(g),f=null===f||void 0===f?"left":f;var h=new b.legend(this,a,c,d,e,f,g);return this.legends.push(h),h},this.addLogAxis=function(a,b,c){var d=this.addAxis(a,null,b,null);return null!==c&&void 0!==c&&(d.logBase=c),d.useLog=!0,d},this.addMeasureAxis=function(a,b){return this.addAxis(a,null,b)},this.addPctAxis=function(a,b,c){var d=null;return d=null!==c&&void 0!==c?this.addAxis(a,c,b):this.addMeasureAxis(a,b),d.showPercent=!0,d},this.addSeries=function(a,c,d){(null===d||void 0===d)&&(d=this.axes),(null===c||void 0===c)&&(c=b.plot.bubble);var e,f=null,g=null,h=null,i=null,j=null;return d.forEach(function(a){null!==a&&c.supportedAxes.indexOf(a.position)>-1&&(null===f&&"x"===a.position[0]?f=a:null===g&&"y"===a.position[0]?g=a:null===h&&"z"===a.position[0]?h=a:null===i&&"c"===a.position[0]?i=a:null===i&&"p"===a.position[0]&&(j=a))},this),a&&(a=[].concat(a)),e=new b.series(this,a,f,g,h,i,j,c,b.aggregateMethod.sum,c.stacked),this.series.push(e),e},this.addTimeAxis=function(a,b,c,d){var e=this.addAxis(a,null,null,b);return e.tickFormat=d,e.dateParseFormat=c,e},this.assignClass=function(a,b){return this._assignedClasses[a]=b,this._assignedClasses[a]},this.assignColor=function(a,c,d,e){return this._assignedColors[a]=new b.color(c,d,e),this._assignedColors[a]},this.customClassList={axisLine:"dimple-custom-axis-line",axisLabel:"dimple-custom-axis-label",axisTitle:"dimple-custom-axis-title",tooltipBox:"dimple-custom-tooltip-box",tooltipLabel:"dimple-custom-tooltip-label",lineMarker:"dimple-custom-line-marker",legendLabel:"dimple-custom-legend-label",legendKey:"dimple-custom-legend-key",areaSeries:"dimple-custom-series-area",barSeries:"dimple-custom-series-bar",bubbleSeries:"dimple-custom-series-bubble",lineSeries:"dimple-custom-series-line",pieSeries:"dimple-custom-series-pie",gridline:"dimple-custom-gridline",colorClasses:["dimple-custom-format-1","dimple-custom-format-2","dimple-custom-format-3","dimple-custom-format-4","dimple-custom-format-5","dimple-custom-format-6","dimple-custom-format-7","dimple-custom-format-8","dimple-custom-format-9","dimple-custom-format-10"]},this.defaultColors=[new b.color("#80B1D3"),new b.color("#FB8072"),new b.color("#FDB462"),new b.color("#B3DE69"),new b.color("#FFED6F"),new b.color("#BC80BD"),new b.color("#8DD3C7"),new b.color("#CCEBC5"),new b.color("#FFFFB3"),new b.color("#BEBADA"),new b.color("#FCCDE5"),new b.color("#D9D9D9")],this.draw=function(b,c){b=b||0;var d,e,f=null,g=null,h=!1,i=!1,j=this._xPixels(),k=this._yPixels(),l=this._widthPixels(),m=this._heightPixels();return(void 0===c||null===c||c===!1)&&this._getSeriesData(),this.axes.forEach(function(a){a._scale=null},this),this.axes.forEach(function(a){if(a._min=0,a._max=0,e=[],a._hasMeasure()){var b=!1;this.series.forEach(function(c){if(c._deepMatch(a)){var d=c._axisBounds(a.position);a._min>d.min&&(a._min=d.min),a._max<d.max&&(a._max=d.max),b=!0}},this),b||this._getAllData().forEach(function(b){a._min>b[a.measure]&&(a._min=b[a.measure]),a._max<b[a.measure]&&(a._max=b[a.measure])},this)}else a._hasTimeField()?(a._min=null,a._max=null,this.series.forEach(function(b){b._deepMatch(a)&&null!==b[a.position].timeField&&void 0!==b[a.position].timeField&&-1===e.indexOf(b[a.position].timeField)&&e.push(b[a.position].timeField)},this),a._getAxisData().forEach(function(b){e.forEach(function(c){var d=a._parseDate(b[c]);(null===a._min||d<a._min)&&(a._min=d),(null===a._max||d>a._max)&&(a._max=d)},this)},this)):a._hasCategories()&&(a._min=0,d=[],this.series.forEach(function(b){b._deepMatch(a)&&null!==b[a.position].categoryFields[0]&&void 0!==b[a.position].categoryFields[0]&&-1===e.indexOf(b[a.position].categoryFields[0])&&e.push(b[a.position].categoryFields[0])},this),a._getAxisData().forEach(function(a){e.forEach(function(b){-1===d.indexOf(a[b])&&d.push(a[b])},this)},this),a._max=d.length);null!==a._slaves&&void 0!==a._slaves&&a._slaves.length>0&&a._slaves.forEach(function(b){b._min=a._min,b._max=a._max},this),a._update(),null===f&&"x"===a.position?f=a:null===g&&"y"===a.position&&(g=a)},this),this.axes.forEach(function(c){var d=!1,e=null,n=0,o=null,p=!1,q=0,r={l:null,t:null,r:null,b:null},s=0,t=0,u="",v=this,w=function(a){var c;return c=null===e||0===b||d?a:v._handleTransition(a,b,v)},x=function(){var b=a.select(this).selectAll("text");return!c.measure&&c._max>0&&("x"===c.position?b.attr("x",l/c._max/2):"y"===c.position&&b.attr("y",-1*(m/c._max)/2)),c.categoryFields&&c.categoryFields.length>0&&(c!==f||null!==g.categoryFields&&0!==g.categoryFields.length||b.attr("y",k+m-g._scale(0)+9),c!==g||null!==f.categoryFields&&0!==f.categoryFields.length||b.attr("x",-1*(f._scale(0)-j)-9)),this},y=function(b){return function(){var c=a.select(this).attr("class")||"";return-1===c.indexOf(b)&&(c+=" "+b),c.trim()}};null===c.gridlineShapes?(c.showGridlines||null===c.showGridlines&&!c._hasCategories()&&(!h&&"x"===c.position||!i&&"y"===c.position))&&(c.gridlineShapes=this._group.append("g").attr("class","dimple-gridline"),"x"===c.position?h=!0:i=!0):"x"===c.position?h=!0:i=!0,null===c.shapes&&(c.shapes=this._group.append("g").attr("class","dimple-axis dimple-axis-"+c.position).each(function(){v.noFormats||a.select(this).style("font-family",c.fontFamily).style("font-size",c._getFontSize())}),d=!0),c===f&&null!==g?(e="translate(0, "+(null===g.categoryFields||0===g.categoryFields.length?g._scale(0):k+m)+")",o="translate(0, "+(c===f?k+m:k)+")",n=-m):c===g&&null!==f?(e="translate("+(null===f.categoryFields||0===f.categoryFields.length?f._scale(0):j)+", 0)",o="translate("+(c===g?j:j+l)+", 0)",n=-l):"x"===c.position?(o=e="translate(0, "+(c===f?k+m:k)+")",n=-m):"y"===c.position&&(o=e="translate("+(c===g?j:j+l)+", 0)",n=-l),null!==e&&null!==c._draw&&(c._hasTimeField()?w(c.shapes).call(c._draw.ticks(c._getTimePeriod(),c.timeInterval).tickFormat(c._getFormat())).attr("transform",e).each(x):c.useLog?w(c.shapes).call(c._draw.ticks(4,c._getFormat())).attr("transform",e).each(x):w(c.shapes).call(c._draw.tickFormat(c._getFormat())).attr("transform",e).each(x),null!==c.gridlineShapes&&w(c.gridlineShapes).call(c._draw.tickSize(n,0,0).tickFormat("")).attr("transform",o)),w(c.shapes.selectAll("text")).attr("class",y(v.customClassList.axisLabel)).call(function(){v.noFormats||this.style("font-family",c.fontFamily).style("font-size",c._getFontSize())}),w(c.shapes.selectAll("path, line")).attr("class",y(v.customClassList.axisLine)).call(function(){v.noFormats||this.style("fill","none").style("stroke","black").style("shape-rendering","crispEdges")}),null!==c.gridlineShapes&&w(c.gridlineShapes.selectAll("line")).attr("class",y(v.customClassList.gridline)).call(function(){v.noFormats||this.style("fill","none").style("stroke","lightgray").style("opacity",.8)}),(null===c.measure||void 0===c.measure)&&(c===f?(q=0,c.shapes.selectAll("text").each(function(){var a=this.getComputedTextLength();q=a>q?a:q}),q>l/c.shapes.selectAll("text")[0].length?(p=!0,c.shapes.selectAll("text").style("text-anchor","start").each(function(){var b=this.getBBox();a.select(this).attr("transform","rotate(90,"+b.x+","+(b.y+b.height/2)+") translate(-5, 0)")})):(p=!1,c.shapes.selectAll("text").style("text-anchor","middle").attr("transform",""))):"x"===c.position&&(q=0,c.shapes.selectAll("text").each(function(){var a=this.getComputedTextLength();q=a>q?a:q}),q>l/c.shapes.selectAll("text")[0].length?(p=!0,c.shapes.selectAll("text").style("text-anchor","end").each(function(){var b=this.getBBox();a.select(this).attr("transform","rotate(90,"+(b.x+b.width)+","+(b.y+b.height/2)+") translate(5, 0)")})):(p=!1,c.shapes.selectAll("text").style("text-anchor","middle").attr("transform","")))),null!==c.titleShape&&void 0!==c.titleShape&&c.titleShape.remove(),c.shapes.selectAll("text").each(function(){var a=this.getBBox();(null===r.l||-9-a.width<r.l)&&(r.l=-9-a.width),(null===r.r||a.x+a.width>r.r)&&(r.r=a.x+a.width),p?((null===r.t||a.y+a.height-a.width<r.t)&&(r.t=a.y+a.height-a.width),(null===r.b||a.height+a.width>r.b)&&(r.b=a.height+a.width)):((null===r.t||a.y<r.t)&&(r.t=a.y),(null===r.b||9+a.height>r.b)&&(r.b=9+a.height))}),"x"===c.position?(t=c===f?k+m+r.b+5:k+r.t-10,s=j+l/2):"y"===c.position&&(s=c===g?j+r.l-10:j+l+r.r+20,t=k+m/2,u="rotate(270, "+s+", "+t+")"),c.hidden||"x"!==c.position&&"y"!==c.position||null===c.title||(c.titleShape=this._group.append("text").attr("class","dimple-axis dimple-title "+v.customClassList.axisTitle+"dimple-axis-"+c.position),c.titleShape.attr("x",s).attr("y",t).attr("text-anchor","middle").attr("transform",u).text(void 0!==c.title?c.title:null===c.categoryFields||void 0===c.categoryFields||0===c.categoryFields.length?c.measure:c.categoryFields.join("/")).each(function(){v.noFormats||a.select(this).style("font-family",c.fontFamily).style("font-size",c._getFontSize())}),c===f?c.titleShape.each(function(){a.select(this).attr("y",t+this.getBBox().height/1.65)}):c===g&&c.titleShape.each(function(){a.select(this).attr("x",s+this.getBBox().height/1.65)}))},this),this.series.forEach(function(a){a.plot.draw(this,a,b),this._registerEventHandlers(a)},this),this.legends.forEach(function(a){a._draw()},this),this.storyboard&&(this.storyboard._drawText(),this.storyboard.autoplay&&this.storyboard.startAnimation()),this},this.getClass=function(a){return this._assignedClasses[a]||(this._assignedClasses[a]=this.customClassList.colorClasses[this._nextClass],this._nextClass=(this._nextClass+1)%this.customClassList.colorClasses.length),this._assignedClasses[a]},this.getColor=function(a){return(null===this._assignedColors[a]||void 0===this._assignedColors[a])&&(this._assignedColors[a]=this.defaultColors[this._nextColor],this._nextColor=(this._nextColor+1)%this.defaultColors.length),this._assignedColors[a]},this.setBounds=function(a,c,d,e){return this.x=a,this.y=c,this.width=d,this.height=e,this._xPixels=function(){return b._parseXPosition(this.x,this.svg.node())},this.draw(0,!0),this._yPixels=function(){return b._parseYPosition(this.y,this.svg.node())},this._widthPixels=function(){return b._parseXPosition(this.width,this.svg.node())},this._heightPixels=function(){return b._parseYPosition(this.height,this.svg.node())},this},this.setMargins=function(a,c,d,e){return this.x=a,this.y=c,this.width=0,this.height=0,this._xPixels=function(){return b._parseXPosition(this.x,this.svg.node())},this._yPixels=function(){return b._parseYPosition(this.y,this.svg.node())},this._widthPixels=function(){return b._parentWidth(this.svg.node())-this._xPixels()-b._parseXPosition(d,this.svg.node())},this._heightPixels=function(){return b._parentHeight(this.svg.node())-this._yPixels()-b._parseYPosition(e,this.svg.node())},this.draw(0,!0),this},this.setStoryboard=function(a,c){return this.storyboard=new b.storyboard(this,a),null!==c&&void 0!==c&&(this.storyboard.onTick=c),this.storyboard}},b.color=function(b,c,d){this.fill=b,this.stroke=null===c||void 0===c?a.rgb(b).darker(.5).toString():c,this.opacity=null===d||void 0===d?.8:d},b.eventArgs=function(){this.seriesValue=null,this.xValue=null,this.yValue=null,this.zValue=null,this.pValue=null,this.colorValue=null,this.frameValue=null,this.seriesShapes=null,this.selectedShape=null
+},b.legend=function(c,d,e,f,g,h,i){this.chart=c,this.series=i,this.x=d,this.y=e,this.width=f,this.height=g,this.horizontalAlign=h,this.shapes=null,this.fontSize="10px",this.fontFamily="sans-serif",this._draw=function(){var c,d=this._getEntries(),e=0,f=0,g=0,h=0,i=15,j=9,k=this;this.shapes&&this.shapes.remove(),c=this.chart._group.selectAll(".dimple-dont-select-any").data(d).enter().append("g").attr("class",function(a){return"dimple-legend "+b._createClass(a.aggField)}).attr("opacity",1),c.append("text").attr("class",function(a){return"dimple-legend dimple-legend-text "+b._createClass(a.aggField)+" "+k.chart.customClassList.legendLabel}).text(function(a){return a.key}).call(function(){k.chart.noFormats||this.style("font-family",k.fontFamily).style("font-size",k._getFontSize()).style("shape-rendering","crispEdges")}).each(function(){var a=this.getBBox();a.width>e&&(e=a.width),a.height>f&&(f=a.height)}),c.append("rect").attr("class",function(a){return"dimple-legend dimple-legend-key "+b._createClass(a.aggField)}).attr("height",j).attr("width",i),f=(j>f?j:f)+2,e+=i+20,c.each(function(c){g+e>k._widthPixels()&&(g=0,h+=f),h>k._heightPixels()?a.select(this).remove():(a.select(this).select("text").attr("x","left"===k.horizontalAlign?k._xPixels()+i+5+g:k._xPixels()+(k._widthPixels()-g-e)+i+5).attr("y",function(){return k._yPixels()+h+this.getBBox().height/1.65}).attr("width",k._widthPixels()).attr("height",k._heightPixels()),a.select(this).select("rect").attr("class",function(a){return"dimple-legend dimple-legend-key "+b._createClass(a.aggField)+" "+k.chart.customClassList.legendKey+" "+a.css}).attr("x","left"===k.horizontalAlign?k._xPixels()+g:k._xPixels()+(k._widthPixels()-g-e)).attr("y",k._yPixels()+h).attr("height",j).attr("width",i).call(function(){k.chart.noFormats||this.style("fill",c.fill).style("stroke",c.stroke).style("opacity",c.opacity).style("shape-rendering","crispEdges")}),g+=e)}),this.shapes=c},this._getEntries=function(){var a=[];return this.series&&this.series.forEach(function(b){var c=b._positionData;c.forEach(function(c){var d,e=-1,f=b.plot.grouped&&!b.x._hasCategories()&&!b.y._hasCategories()&&c.aggField.length<2?"All":c.aggField.slice(-1)[0];for(d=0;d<a.length;d+=1)if(a[d].key===f){e=d;break}-1===e&&b.chart._assignedColors[f]&&(a.push({key:f,fill:b.chart._assignedColors[f].fill,stroke:b.chart._assignedColors[f].stroke,opacity:b.chart._assignedColors[f].opacity,css:b.chart._assignedClasses[f],series:b,aggField:c.aggField}),e=a.length-1)})},this),a},this._getFontSize=function(){var a;return a=this.fontSize&&"auto"!==this.fontSize.toString().toLowerCase()?isNaN(this.fontSize)?this.fontSize:this.fontSize+"px":(this.chart._heightPixels()/35>10?this.chart._heightPixels()/35:10)+"px"},this._heightPixels=function(){return b._parseYPosition(this.height,this.chart.svg.node())},this._widthPixels=function(){return b._parseXPosition(this.width,this.chart.svg.node())},this._xPixels=function(){return b._parseXPosition(this.x,this.chart.svg.node())},this._yPixels=function(){return b._parseYPosition(this.y,this.chart.svg.node())}},b.series=function(a,b,c,d,e,f,g,h,i,j){this.chart=a,this.x=c,this.y=d,this.z=e,this.c=f,this.p=g,this.plot=h,this.categoryFields=b,this.aggregate=i,this.stacked=j,this.barGap=.2,this.clusterBarGap=.1,this.lineWeight=2,this.lineMarkers=!1,this.afterDraw=null,this.interpolation="linear",this.tooltipFontSize="10px",this.tooltipFontFamily="sans-serif",this.radius="auto",this._eventHandlers=[],this._positionData=[],this._orderRules=[],this._axisBounds=function(a){var b,c,d,e={min:0,max:0},f=null,g=null,h=[],i=0,j=this._positionData;return"x"===a?(f=this.x,g=this.y):"y"===a?(f=this.y,g=this.x):"z"===a?f=this.z:"p"===a?f=this.p:"c"===a&&(f=this.c),f.showPercent?j.forEach(function(a){a[f.position+"Bound"]<e.min&&(e.min=a[f.position+"Bound"]),a[f.position+"Bound"]>e.max&&(e.max=a[f.position+"Bound"])},this):null===g||null===g.categoryFields||0===g.categoryFields.length?j.forEach(function(a){!this._isStacked()||"x"!==f.position&&"y"!==f.position?(a[f.position+"Value"]<e.min&&(e.min=a[f.position+"Value"]),a[f.position+"Value"]>e.max&&(e.max=a[f.position+"Value"])):a[f.position+"Value"]<0?e.min=e.min+a[f.position+"Value"]:e.max=e.max+a[f.position+"Value"]},this):(b=f.position+"Value",c=g.position+"Field",d=[],j.forEach(function(a){var e=a[c].join("/"),f=d.indexOf(e);-1===f&&(d.push(e),f=d.length-1),void 0===h[f]&&(h[f]={min:0,max:0},f>=i&&(i=f+1)),this.stacked?a[b]<0?h[f].min=h[f].min+a[b]:h[f].max=h[f].max+a[b]:(a[b]<h[f].min&&(h[f].min=a[b]),a[b]>h[f].max&&(h[f].max=a[b]))},this),h.forEach(function(a){void 0!==a&&(a.min<e.min&&(e.min=a.min),a.max>e.max&&(e.max=a.max))},this)),e},this._deepMatch=function(a){var b=!1;return this[a.position]===a?b=!0:void 0!==a._slaves&&null!==a._slaves&&a._slaves.length>0&&a._slaves.forEach(function(a){b=b||this._deepMatch(a)},this),b},this._dropLineOrigin=function(){var a=0,b=0,c={x:null,y:null},d={x:null,y:null};return this.chart.axes.forEach(function(a){"x"===a.position&&null===d.x?d.x=a._hasTimeField()?this.chart._xPixels():a._origin:"y"===a.position&&null===d.y&&(d.y=a._hasTimeField()?this.chart._yPixels()+this.chart._heightPixels():a._origin)},this),this.chart.axes.forEach(function(e){"x"!==e.position||this.x.hidden?"y"!==e.position||this.y.hidden||(this._deepMatch(e)&&(0===b?c.x=d.x:1===b&&(c.x=this.chart._xPixels()+this.chart._widthPixels())),b+=1):(this._deepMatch(e)&&(0===a?c.y=d.y:1===a&&(c.y=this.chart._yPixels())),a+=1)},this),c},this._getTooltipFontSize=function(){var a;return a=this.tooltipFontSize&&"auto"!==this.tooltipFontSize.toString().toLowerCase()?isNaN(this.tooltipFontSize)?this.tooltipFontSize:this.tooltipFontSize+"px":(this.chart._heightPixels()/35>10?this.chart._heightPixels()/35:10)+"px"},this._isStacked=function(){return this.stacked&&(this.x._hasCategories()||this.y._hasCategories())},this.addEventHandler=function(a,b){this._eventHandlers.push({event:a,handler:b})},this.addOrderRule=function(a,b){this._orderRules.push({ordering:a,desc:b})},this.getTooltipText=function(a){var b=[];return null!==this.categoryFields&&void 0!==this.categoryFields&&this.categoryFields.length>0&&this.categoryFields.forEach(function(c,d){null!==c&&void 0!==c&&null!==a.aggField[d]&&void 0!==a.aggField[d]&&b.push(c+(a.aggField[d]!==c?": "+a.aggField[d]:""))},this),this.p?(this.x&&this.x._hasCategories()&&this.x._getTooltipText(b,a),this.y&&this.y._hasCategories()&&this.y._getTooltipText(b,a),this.z&&this.z._hasCategories()&&this.z._getTooltipText(b,a),this.p._getTooltipText(b,a)):(this.x&&this.x._getTooltipText(b,a),this.y&&this.y._getTooltipText(b,a),this.z&&this.z._getTooltipText(b,a)),this.c&&this.c._getTooltipText(b,a),b.filter(function(a,c){return b.indexOf(a)===c})}},b.storyboard=function(a,b){null!==b&&void 0!==b&&(b=[].concat(b)),this.chart=a,this.categoryFields=b,this.autoplay=!0,this.frameDuration=3e3,this.storyLabel=null,this.onTick=null,this.fontSize="10px",this.fontFamily="sans-serif",this._frame=0,this._animationTimer=null,this._categories=[],this._cachedCategoryFields=[],this._orderRules=[],this._drawText=function(){if(!this.storyLabel){var a=this.chart,b=this,c=0;this.chart.axes.forEach(function(a){"x"===a.position&&(c+=1)},this),this.storyLabel=this.chart._group.append("text").attr("class","dimple-storyboard-label").attr("opacity",1).attr("x",this.chart._xPixels()+.01*this.chart._widthPixels()).attr("y",this.chart._yPixels()+(this.chart._heightPixels()/35>10?this.chart._heightPixels()/35:10)*(c>1?1.25:-1)).call(function(){a.noFormats||this.style("font-family",b.fontFamily).style("font-size",b._getFontSize())})}this.storyLabel.text(this.categoryFields.join("\\")+": "+this.getFrameValue())},this._getCategories=function(){return this._categoryFields!==this._cachedCategoryFields&&(this._categories=[],this.chart._getAllData().forEach(function(a){var b=-1,c="";null!==this.categoryFields&&(this.categoryFields.forEach(function(b,d){d>0&&(c+="/"),c+=a[b]},this),b=this._categories.indexOf(c),-1===b&&(this._categories.push(c),b=this._categories.length-1))},this),this._cachedCategoryFields=this._categoryFields),this._categories},this._getFontSize=function(){var a;return a=this.fontSize&&"auto"!==this.fontSize.toString().toLowerCase()?isNaN(this.fontSize)?this.fontSize:this.fontSize+"px":(this.chart._heightPixels()/35>10?this.chart._heightPixels()/35:10)+"px"},this._goToFrameIndex=function(a){this._frame=a%this._getCategories().length,this.chart.draw(this.frameDuration/2)},this.addOrderRule=function(a,b){this._orderRules.push({ordering:a,desc:b})},this.getFrameValue=function(){var a=null;return this._frame>=0&&this._getCategories().length>this._frame&&(a=this._getCategories()[this._frame]),a},this.goToFrame=function(a){if(this._getCategories().length>0){var b=this._getCategories().indexOf(a);this._goToFrameIndex(b)}},this.pauseAnimation=function(){null!==this._animationTimer&&(window.clearInterval(this._animationTimer),this._animationTimer=null)},this.startAnimation=function(){null===this._animationTimer&&(null!==this.onTick&&this.onTick(this.getFrameValue()),this._animationTimer=window.setInterval(function(a){return function(){a._goToFrameIndex(a._frame+1),null!==a.onTick&&a.onTick(a.getFrameValue()),a._drawText(a.frameDuration/2)}}(this),this.frameDuration))},this.stopAnimation=function(){null!==this._animationTimer&&(window.clearInterval(this._animationTimer),this._animationTimer=null,this._frame=0)}},b.aggregateMethod.avg=function(a,b){return a.value=null===a.value||void 0===a.value?0:parseFloat(a.value),a.count=null===a.count||void 0===a.count?1:parseFloat(a.count),b.value=null===b.value||void 0===b.value?0:parseFloat(b.value),b.count=null===b.count||void 0===b.count?1:parseFloat(b.count),(a.value*a.count+b.value*b.count)/(a.count+b.count)},b.aggregateMethod.count=function(a,b){return a.count=null===a.count||void 0===a.count?0:parseFloat(a.count),b.count=null===b.count||void 0===b.count?0:parseFloat(b.count),a.count+b.count},b.aggregateMethod.max=function(a,b){return a.value=null===a.value||void 0===a.value?0:parseFloat(a.value),b.value=null===b.value||void 0===b.value?0:parseFloat(b.value),a.value>b.value?a.value:b.value},b.aggregateMethod.min=function(a,b){return null===a.value?parseFloat(b.value):parseFloat(a.value)<parseFloat(b.value)?parseFloat(a.value):parseFloat(b.value)},b.aggregateMethod.sum=function(a,b){return a.value=null===a.value||void 0===a.value?0:parseFloat(a.value),b.value=null===b.value||void 0===b.value?0:parseFloat(b.value),a.value+b.value},b.plot.area={stacked:!0,grouped:!0,supportedAxes:["x","y","c"],draw:function(c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,A,B=d._positionData,C=[],D=null,E="dimple-series-"+c.series.indexOf(d),F=d.x._hasCategories()||d.y._hasCategories()?0:1,G=!1,H={},I=[],J=[],K=function(){return function(c,d,e,f){a.select(d).style("opacity",1),b._showPointTooltip(c,d,e,f)}},L=function(c){return function(d,e,f,g){a.select(e).style("opacity",g.lineMarkers||c.data.length<2?b._helpers.opacity(d,f,g):0),b._removeTooltip(d,e,f,g)}},M=function(a){b._drawMarkers(a,c,d,e,E,G,K(a),L(a))},N=function(a,e){var f;return"step"===d.interpolation&&d[a]._hasCategories()?(f=b._helpers[a](e,c,d)+("y"===a?b._helpers.height(e,c,d):0),d[a].categoryFields.length<2&&(f+=("y"===a?1:-1)*b._helpers[a+"Gap"](c,d))):f=b._helpers["c"+a](e,c,d),parseFloat(f)},O=function(b,c){return a.svg.line().x(function(a){return d.x._hasCategories()||!c?a.x:d.x[c]}).y(function(a){return d.y._hasCategories()||!c?a.y:d.y[c]}).interpolate(b)},P=function(a,b){return parseFloat(a)-parseFloat(b)},Q=function(a,b){return parseFloat(a.x)-parseFloat(b.x)},R=function(a,b,c){var d,e,f=b[b.length-1],g=9999,h=f;for(d=0;d<a.length;d+=1)(a[d].x!==f.x||a[d].y!==f.y)&&(e=180-Math.atan2(a[d].x-f.x,a[d].y-f.y)*(180/Math.PI),e>c&&g>e&&(h=a[d],g=e));return b.push(h),g};for(f="step"===d.interpolation?"step-after":d.interpolation,o=b._getSeriesOrder(d.data||c.data,d),d.c&&(d.x._hasCategories()&&d.y._hasMeasure()||d.y._hasCategories()&&d.x._hasMeasure())&&(G=!0),d.x._hasCategories()?(z="x",A="y"):d.y._hasCategories()&&(z="y",A="x"),g=0;g<B.length;g+=1){for(j=[],l=-1,i=F;i<B[g].aggField.length;i+=1)j.push(B[g].aggField[i]);for(k=b._createClass(j),i=0;i<C.length;i+=1)if(C[i].keyString===k){l=i;break}-1===l&&(l=C.length,C.push({key:j,keyString:k,color:"white",data:[],points:[],area:{},entry:{},exit:{},group:z&&B[g][z+"Field"]&&B[g][z+"Field"].length>=2?B[g][z+"Field"][0]:"All"})),C[l].data.push(B[g])}for(o&&C.sort(function(a,c){return b._arrayIndexCompare(o,a.key,c.key)}),g=0;g<C.length;g+=1){for(C[g].data.sort(b._getSeriesSortPredicate(c,d,o)),h=0;h<C[g].data.length;h+=1)C[g].points.push({x:N("x",C[g].data[h]),y:N("y",C[g].data[h])}),z&&(H[C[g].group]||(H[C[g].group]={}),H[C[g].group][C[g].points[C[g].points.length-1][z]]=d[A]._origin);p=C[g].points,"step"===d.interpolation&&p.length>1&&z&&(d.x._hasCategories()?(p.push({x:2*p[p.length-1].x-p[p.length-2].x,y:p[p.length-1].y}),H[C[g].group][p[p.length-1][z]]=d[A]._origin):d.y._hasCategories()&&(p=[{x:p[0].x,y:2*p[0].y-p[1].y}].concat(p),H[C[g].group][p[0][z]]=d[A]._origin,C[g].points=p))}for(s in H)if(H.hasOwnProperty(s)){I[s]=[];for(t in H[s])H[s].hasOwnProperty(t)&&I[s].push(parseFloat(t));I[s].sort(P)}for(g=0;g<C.length;g+=1){if(p=C[g].points,u=C[g].group,q=[],J=[],G&&b._addGradient(C[g].key,"fill-area-gradient-"+C[g].keyString,d.x._hasCategories()?d.x:d.y,B,c,e,"fill"),I[u]&&I[u].length>0)for(h=0,i=0;h<I[u].length;h+=1)I[u][h]>=p[0][z]&&I[u][h]<=p[p.length-1][z]&&(r={},r[z]=I[u][h],r[A]=H[u][I[u][h]],q.push(r),p[i][z]>I[u][h]?J.push(r):(J.push(p[i]),H[C[g].group][I[u][h]]=p[i][A],i+=1));else if(d._orderRules&&d._orderRules.length>0)J=p.concat(p[0]);else{p=p.sort(Q),J.push(p[0]),y=0;do y=R(p,J,y);while(J.length<=p.length&&(J[0].x!==J[J.length-1].x||J[0].y!==J[J.length-1].y))}q=q.reverse(),v=O(f,"_previousOrigin")(J),w=O("step-after"===f?"step-before":"step-before"===f?"step-after":f,"_previousOrigin")(q),x=O("linear","_previousOrigin")(J),C[g].entry=v+(w&&w.length>0?"L"+w.substring(1):"")+(x&&x.length>0?"L"+x.substring(1,x.indexOf("L")):0),v=O(f)(J),w=O("step-after"===f?"step-before":"step-before"===f?"step-after":f)(q),x=O("linear")(J),C[g].update=v+(w&&w.length>0?"L"+w.substring(1):"")+(x&&x.length>0?"L"+x.substring(1,x.indexOf("L")):0),v=O(f,"_origin")(J),w=O("step-after"===f?"step-before":"step-before"===f?"step-after":f,"_origin")(q),x=O("linear","_origin")(J),C[g].exit=v+(w&&w.length>0?"L"+w.substring(1):"")+(x&&x.length>0?"L"+x.substring(1,x.indexOf("L")):0),C[g].color=c.getColor(C[g].key.length>0?C[g].key[C[g].key.length-1]:"All"),C[g].css=c.getClass(C[g].key.length>0?C[g].key[C[g].key.length-1]:"All")}null!==c._tooltipGroup&&void 0!==c._tooltipGroup&&c._tooltipGroup.remove(),D=null===d.shapes||void 0===d.shapes?c._group.selectAll("."+E).data(C):d.shapes.data(C,function(a){return a.key}),D.enter().append("path").attr("id",function(a){return a.key}).attr("class",function(a){return E+" dimple-line "+a.keyString+" "+c.customClassList.areaSeries+" "+a.css}).attr("d",function(a){return a.entry}).call(function(){c.noFormats||this.attr("opacity",function(a){return G?1:a.color.opacity}).attr("fill",function(a){return G?"url(#fill-area-gradient-"+a.keyString+")":a.color.fill}).attr("stroke",function(a){return G?"url(#stroke-area-gradient-"+a.keyString+")":a.color.stroke}).attr("stroke-width",d.lineWeight)}).each(function(a){a.markerData=a.data,M(a)}),m=c._handleTransition(D,e,c).attr("d",function(a){return a.update}).each(function(a){a.markerData=a.data,M(a)}),n=c._handleTransition(D.exit(),e,c).attr("d",function(a){return a.exit}).each(function(a){a.markerData=[],M(a)}),b._postDrawHandling(d,m,n,e),d.shapes=D}},b.plot.bar={stacked:!0,grouped:!1,supportedAxes:["x","y","c"],draw:function(a,c,d){var e,f,g=c._positionData,h=null,i=["dimple-series-"+a.series.indexOf(c),"dimple-bar"],j=!c._isStacked()&&c.x._hasMeasure(),k=!c._isStacked()&&c.y._hasMeasure(),l="none";c.x._hasCategories()&&c.y._hasCategories()?l="both":c.x._hasCategories()?l="x":c.y._hasCategories()&&(l="y"),null!==a._tooltipGroup&&void 0!==a._tooltipGroup&&a._tooltipGroup.remove(),h=null===c.shapes||void 0===c.shapes?a._group.selectAll("."+i.join(".")).data(g):c.shapes.data(g,function(a){return a.key}),h.enter().append("rect").attr("id",function(a){return a.key}).attr("class",function(c){var d=[];return d=d.concat(c.aggField),d=d.concat(c.xField),d=d.concat(c.yField),i.join(" ")+" "+b._createClass(d)+" "+a.customClassList.barSeries+" "+b._helpers.css(c,a)}).attr("x",function(d){var e=c.x._previousOrigin;return"x"===l?e=b._helpers.x(d,a,c):"both"===l&&(e=b._helpers.cx(d,a,c)),e}).attr("y",function(d){var e=c.y._previousOrigin;return"y"===l?e=b._helpers.y(d,a,c):"both"===l&&(e=b._helpers.cy(d,a,c)),e}).attr("width",function(d){return"x"===l?b._helpers.width(d,a,c):0}).attr("height",function(d){return"y"===l?b._helpers.height(d,a,c):0}).attr("opacity",function(d){return b._helpers.opacity(d,a,c)}).on("mouseover",function(d){b._showBarTooltip(d,this,a,c)}).on("mouseleave",function(d){b._removeTooltip(d,this,a,c)}).call(function(){a.noFormats||this.attr("fill",function(d){return b._helpers.fill(d,a,c)}).attr("stroke",function(d){return b._helpers.stroke(d,a,c)})}),e=a._handleTransition(h,d,a,c).attr("x",function(d){return j?b._helpers.cx(d,a,c)-c.x.floatingBarWidth/2:b._helpers.x(d,a,c)}).attr("y",function(d){return k?b._helpers.cy(d,a,c)-c.y.floatingBarWidth/2:b._helpers.y(d,a,c)}).attr("width",function(d){return j?c.x.floatingBarWidth:b._helpers.width(d,a,c)}).attr("height",function(d){return k?c.y.floatingBarWidth:b._helpers.height(d,a,c)}).call(function(){a.noFormats||this.attr("fill",function(d){return b._helpers.fill(d,a,c)}).attr("stroke",function(d){return b._helpers.stroke(d,a,c)})}),f=a._handleTransition(h.exit(),d,a,c).attr("x",function(d){var e=c.x._origin;return"x"===l?e=b._helpers.x(d,a,c):"both"===l&&(e=b._helpers.cx(d,a,c)),e}).attr("y",function(d){var e=c.y._origin;return"y"===l?e=b._helpers.y(d,a,c):"both"===l&&(e=b._helpers.cy(d,a,c)),e}).attr("width",function(d){return"x"===l?b._helpers.width(d,a,c):0}).attr("height",function(d){return"y"===l?b._helpers.height(d,a,c):0}),b._postDrawHandling(c,e,f,d),c.shapes=h}},b.plot.bubble={stacked:!1,grouped:!1,supportedAxes:["x","y","z","c"],draw:function(a,c,d){var e,f,g=c._positionData,h=null,i=["dimple-series-"+a.series.indexOf(c),"dimple-bubble"];null!==a._tooltipGroup&&void 0!==a._tooltipGroup&&a._tooltipGroup.remove(),h=null===c.shapes||void 0===c.shapes?a._group.selectAll("."+i.join(".")).data(g):c.shapes.data(g,function(a){return a.key}),h.enter().append("circle").attr("id",function(a){return a.key}).attr("class",function(c){var d=[];return d=d.concat(c.aggField),d=d.concat(c.xField),d=d.concat(c.yField),d=d.concat(c.zField),i.join(" ")+" "+b._createClass(d)+" "+a.customClassList.bubbleSeries+" "+b._helpers.css(c,a)}).attr("cx",function(d){return c.x._hasCategories()?b._helpers.cx(d,a,c):c.x._previousOrigin}).attr("cy",function(d){return c.y._hasCategories()?b._helpers.cy(d,a,c):c.y._previousOrigin}).attr("r",0).attr("opacity",function(d){return b._helpers.opacity(d,a,c)}).on("mouseover",function(d){b._showPointTooltip(d,this,a,c)}).on("mouseleave",function(d){b._removeTooltip(d,this,a,c)}).call(function(){a.noFormats||this.attr("fill",function(d){return b._helpers.fill(d,a,c)}).attr("stroke",function(d){return b._helpers.stroke(d,a,c)})}),e=a._handleTransition(h,d,a,c).attr("cx",function(d){return b._helpers.cx(d,a,c)}).attr("cy",function(d){return b._helpers.cy(d,a,c)}).attr("r",function(d){return b._helpers.r(d,a,c)}).call(function(){a.noFormats||this.attr("fill",function(d){return b._helpers.fill(d,a,c)}).attr("stroke",function(d){return b._helpers.stroke(d,a,c)})}),f=a._handleTransition(h.exit(),d,a,c).attr("r",0).attr("cx",function(d){return c.x._hasCategories()?b._helpers.cx(d,a,c):c.x._origin}).attr("cy",function(d){return c.y._hasCategories()?b._helpers.cy(d,a,c):c.y._origin}),b._postDrawHandling(c,e,f,d),c.shapes=h}},b.plot.line={stacked:!1,grouped:!0,supportedAxes:["x","y","c"],draw:function(c,d,e){var f,g,h,i,j,k,l,m,n,o,p=d._positionData,q=[],r=null,s="dimple-series-"+c.series.indexOf(d),t=d.x._hasCategories()||d.y._hasCategories()?0:1,u=!1,v=function(){return function(c,d,e,f){a.select(d).style("opacity",1),b._showPointTooltip(c,d,e,f)}},w=function(c){return function(d,e,f,g){a.select(e).style("opacity",g.lineMarkers||c.data.length<2?b._helpers.opacity(d,f,g):0),b._removeTooltip(d,e,f,g)}},x=function(a){b._drawMarkers(a,c,d,e,s,u,v(a),w(a))},y=function(a,e){var f;return"step"===d.interpolation&&d[a]._hasCategories()?(d.barGap=0,d.clusterBarGap=0,f=b._helpers[a](e,c,d)+("y"===a?b._helpers.height(e,c,d):0)):f=b._helpers["c"+a](e,c,d),parseFloat(f.toFixed(1))},z=function(b,c){return a.svg.line().x(function(a){return d.x._hasCategories()||!c?a.x:d.x[c]}).y(function(a){return d.y._hasCategories()||!c?a.y:d.y[c]}).interpolate(b)};for(f="step"===d.interpolation?"step-after":d.interpolation,o=b._getSeriesOrder(d.data||c.data,d),d.c&&(d.x._hasCategories()&&d.y._hasMeasure()||d.y._hasCategories()&&d.x._hasMeasure())&&(u=!0),g=0;g<p.length;g+=1){for(j=[],l=-1,i=t;i<p[g].aggField.length;i+=1)j.push(p[g].aggField[i]);for(k=b._createClass(j),i=0;i<q.length;i+=1)if(q[i].keyString===k){l=i;break}-1===l&&(l=q.length,q.push({key:j,keyString:k,color:"white",data:[],markerData:[],points:[],line:{},entry:{},exit:{}})),q[l].data.push(p[g])}for(o&&q.sort(function(a,c){return b._arrayIndexCompare(o,a.key,c.key)}),g=0;g<q.length;g+=1){for(q[g].data.sort(b._getSeriesSortPredicate(c,d,o)),u&&b._addGradient(q[g].key,"fill-line-gradient-"+q[g].keyString,d.x._hasCategories()?d.x:d.y,p,c,e,"fill"),h=0;h<q[g].data.length;h+=1)q[g].points.push({x:y("x",q[g].data[h]),y:y("y",q[g].data[h])});"step"===d.interpolation&&q[g].points.length>1&&(d.x._hasCategories()?q[g].points.push({x:2*q[g].points[q[g].points.length-1].x-q[g].points[q[g].points.length-2].x,y:q[g].points[q[g].points.length-1].y}):d.y._hasCategories()&&(q[g].points=[{x:q[g].points[0].x,y:2*q[g].points[0].y-q[g].points[1].y}].concat(q[g].points))),q[g].entry=z(f,"_previousOrigin")(q[g].points),q[g].update=z(f)(q[g].points),q[g].exit=z(f,"_origin")(q[g].points),q[g].color=c.getColor(q[g].key.length>0?q[g].key[q[g].key.length-1]:"All"),q[g].css=c.getClass(q[g].key.length>0?q[g].key[q[g].key.length-1]:"All")}null!==c._tooltipGroup&&void 0!==c._tooltipGroup&&c._tooltipGroup.remove(),r=null===d.shapes||void 0===d.shapes?c._group.selectAll("."+s).data(q):d.shapes.data(q,function(a){return a.key}),r.enter().append("path").attr("id",function(a){return a.key}).attr("class",function(a){return s+" dimple-line "+a.keyString+" "+c.customClassList.lineSeries+" "+a.css}).attr("d",function(a){return a.entry}).call(function(){c.noFormats||this.attr("opacity",function(a){return u?1:a.color.opacity}).attr("fill","none").attr("stroke",function(a){return u?"url(#fill-line-gradient-"+a.keyString+")":a.color.stroke}).attr("stroke-width",d.lineWeight)}).each(function(a){a.markerData=a.data,x(a)}),m=c._handleTransition(r,e,c).attr("d",function(a){return a.update}).each(function(a){a.markerData=a.data,x(a)}),n=c._handleTransition(r.exit(),e,c).attr("d",function(a){return a.exit}).each(function(a){a.markerData=[],x(a)}),b._postDrawHandling(d,m,n,e),d.shapes=r}},b.plot.pie={stacked:!1,grouped:!1,supportedAxes:["x","y","c","z","p"],draw:function(c,d,e){var f,g,h=d._positionData,i=null,j=["dimple-series-"+c.series.indexOf(d),"dimple-pie"],k=function(a){var e;return e=d.x&&d.y?b._helpers.r(a,c,d):c._widthPixels()<c._heightPixels()?c._widthPixels()/2:c._heightPixels()/2},l=function(a){var c=k(a);return d.outerRadius&&(c=b._parsePosition(d.outerRadius,c)),Math.max(c,0)},m=function(a){var c=0;return d.innerRadius&&(c=b._parsePosition(d.innerRadius,k(a))),Math.max(c,0)},n=function(b){var c;return c=a.svg.arc().innerRadius(m(b)).outerRadius(l(b)),c(b)},o=function(b){b.innerRadius=m(b),b.outerRadius=l(b);var c,d=a.interpolate(this._current,b);return c=a.svg.arc().innerRadius(function(a){return a.innerRadius}).outerRadius(function(a){return a.outerRadius}),this._current=d(0),function(a){return c(d(a))}},p=function(a){return function(e){var f,g;return d.x&&d.y?(f=!a||d.x._hasCategories()?b._helpers.cx(e,c,d):d.x._previousOrigin,g=!a||d.y._hasCategories()?b._helpers.cy(e,c,d):d.y._previousOrigin):(f=c._xPixels()+c._widthPixels()/2,g=c._yPixels()+c._heightPixels()/2),"translate("+f+","+g+")"}};null!==c._tooltipGroup&&void 0!==c._tooltipGroup&&c._tooltipGroup.remove(),i=null===d.shapes||void 0===d.shapes?c._group.selectAll("."+j.join(".")).data(h):d.shapes.data(h,function(a){return a.key}),i.enter().append("path").attr("id",function(a){return a.key}).attr("class",function(a){var d=[];return d=d.concat(a.aggField),d=d.concat(a.pField),j.join(" ")+" "+b._createClass(d)+" "+c.customClassList.pieSeries+" "+b._helpers.css(a,c)}).attr("d",n).attr("opacity",function(a){return b._helpers.opacity(a,c,d)}).on("mouseover",function(a){b._showBarTooltip(a,this,c,d)}).on("mouseleave",function(a){b._removeTooltip(a,this,c,d)}).call(function(){c.noFormats||this.attr("fill",function(a){return b._helpers.fill(a,c,d)}).attr("stroke",function(a){return b._helpers.stroke(a,c,d)})}).attr("transform",p(!0)).each(function(a){this._current=a,a.innerRadius=m(a),a.outerRadius=l(a)}),f=c._handleTransition(i,e,c,d).call(function(){e&&e>0?this.attrTween("d",o):this.attr("d",n),c.noFormats||this.attr("fill",function(a){return b._helpers.fill(a,c,d)}).attr("stroke",function(a){return b._helpers.stroke(a,c,d)})}).attr("transform",p(!1)),g=c._handleTransition(i.exit(),e,c,d).attr("transform",p(!0)).attr("d",n),b._postDrawHandling(d,f,g,e),d.shapes=i}},b._addGradient=function(a,b,c,d,e,f,g){var h=[].concat(a),i=e._group.select("#"+b),j=[],k=c.position+"Field",l=!0,m=[];d.forEach(function(a){-1===j.indexOf(a[k])&&a.aggField.join("_")===h.join("_")&&j.push(a[k])},this),j=j.sort(function(a,b){return c._scale(a)-c._scale(b)}),null===i.node()&&(l=!1,i=e._group.append("linearGradient").attr("id",b).attr("gradientUnits","userSpaceOnUse").attr("x1","x"===c.position?c._scale(j[0])+e._widthPixels()/j.length/2:0).attr("y1","y"===c.position?c._scale(j[0])-e._heightPixels()/j.length/2:0).attr("x2","x"===c.position?c._scale(j[j.length-1])+e._widthPixels()/j.length/2:0).attr("y2","y"===c.position?c._scale(j[j.length-1])-e._heightPixels()/j.length/2:0)),j.forEach(function(a,b){var c={},e=0;for(e=0;e<d.length;e+=1)if(d[e].aggField.join("_")===h.join("_")&&d[e][k].join("_")===a.join("_")){c=d[e];break}m.push({offset:Math.round(100*(b/(j.length-1)))+"%",color:c[g]})},this),l?e._handleTransition(i.selectAll("stop").data(m),f,e).attr("offset",function(a){return a.offset}).attr("stop-color",function(a){return a.color}):i.selectAll("stop").data(m).enter().append("stop").attr("offset",function(a){return a.offset}).attr("stop-color",function(a){return a.color})},b._arrayIndexCompare=function(a,b,c){var d,e,f,g,h,i;for(e=0;e<a.length;e+=1){for(g=!0,h=!0,i=[].concat(a[e]),f=0;f<b.length;f+=1)g=g&&b[f]===i[f];for(f=0;f<c.length;f+=1)h=h&&c[f]===i[f];if(g&&h){d=0;break}if(g){d=-1;break}if(h){d=1;break}}return d},b._createClass=function(a){var b,c=[],d=function(a){var b=a.charCodeAt(0),c="-";return b>=65&&90>=b&&(c=a.toLowerCase()),c};if(a.length>0)for(b=0;b<a.length;b+=1)c.push("dimple-"+a[b].toString().replace(/[^a-z0-9]/g,d));else c=["dimple-all"];return c.join(" ")},b._drawMarkerBacks=function(c,d,e,f,g){var h,i,j=["dimple-marker-back",g,c.keyString];e.lineMarkers&&(h=null===e._markerBacks||void 0===e._markerBacks||void 0===e._markerBacks[c.keyString]?d._group.selectAll("."+j.join(".")).data(c.markerData):e._markerBacks[c.keyString].data(c.markerData,function(a){return a.key}),h.enter().append("circle").attr("id",function(a){return a.key}).attr("class",function(a){var c=[];return e.x._hasCategories()&&(c=c.concat(a.xField)),e.y._hasCategories()&&(c=c.concat(a.yField)),b._createClass(c)+" "+j.join(" ")}).attr("cx",function(a){return e.x._hasCategories()?b._helpers.cx(a,d,e):e.x._previousOrigin}).attr("cy",function(a){return e.y._hasCategories()?b._helpers.cy(a,d,e):e.y._previousOrigin}).attr("r",0).attr("fill","white").attr("stroke","none"),d._handleTransition(h,f,d).attr("cx",function(a){return b._helpers.cx(a,d,e)}).attr("cy",function(a){return b._helpers.cy(a,d,e)}).attr("r",2+e.lineWeight),i=d._handleTransition(h.exit(),f,d).attr("cx",function(a){return e.x._hasCategories()?b._helpers.cx(a,d,e):e.x._origin}).attr("cy",function(a){return e.y._hasCategories()?b._helpers.cy(a,d,e):e.y._origin}).attr("r",0),0===f?i.remove():i.each("end",function(){a.select(this).remove()}),(void 0===e._markerBacks||null===e._markerBacks)&&(e._markerBacks={}),e._markerBacks[c.keyString]=h)},b._drawMarkers=function(c,d,e,f,g,h,i,j){var k,l,m=["dimple-marker",g,c.keyString];b._drawMarkerBacks(c,d,e,f,g),k=null===e._markers||void 0===e._markers||void 0===e._markers[c.keyString]?d._group.selectAll("."+m.join(".")).data(c.markerData):e._markers[c.keyString].data(c.markerData,function(a){return a.key}),k.enter().append("circle").attr("id",function(a){return a.key}).attr("class",function(a){var c=[];return e.x._hasCategories()&&(c=c.concat(a.xField)),e.y._hasCategories()&&(c=c.concat(a.yField)),b._createClass(c)+" "+m.join(" ")+" "+d.customClassList.lineMarker}).on("mouseover",function(a){i(a,this,d,e)}).on("mouseleave",function(a){j(a,this,d,e)}).attr("cx",function(a){return e.x._hasCategories()?b._helpers.cx(a,d,e):e.x._previousOrigin}).attr("cy",function(a){return e.y._hasCategories()?b._helpers.cy(a,d,e):e.y._previousOrigin}).attr("r",0).attr("opacity",e.lineMarkers||c.data.length<2?c.color.opacity:0).call(function(){d.noFormats||this.attr("fill","white").style("stroke-width",e.lineWeight).attr("stroke",function(a){return h?b._helpers.fill(a,d,e):c.color.stroke})}),d._handleTransition(k,f,d).attr("cx",function(a){return b._helpers.cx(a,d,e)}).attr("cy",function(a){return b._helpers.cy(a,d,e)}).attr("r",2+e.lineWeight).attr("opacity",e.lineMarkers||c.data.length<2?c.color.opacity:0).call(function(){d.noFormats||this.attr("fill","white").style("stroke-width",e.lineWeight).attr("stroke",function(a){return h?b._helpers.fill(a,d,e):c.color.stroke})}),l=d._handleTransition(k.exit(),f,d).attr("cx",function(a){return e.x._hasCategories()?b._helpers.cx(a,d,e):e.x._origin}).attr("cy",function(a){return e.y._hasCategories()?b._helpers.cy(a,d,e):e.y._origin}).attr("r",0),0===f?l.remove():l.each("end",function(){a.select(this).remove()}),(void 0===e._markers||null===e._markers)&&(e._markers={}),e._markers[c.keyString]=k},b._getOrderedList=function(a,c,d){var e,f=[],g=[],h=[].concat(c),i=[].concat(c),j=[];return null!==d&&void 0!==d&&(j=j.concat(d)),j=j.concat({ordering:h,desc:!1}),j.forEach(function(b){var c,d=[],e=[];if("function"==typeof b.ordering){if(a&&a.length>0)for(c in a[0])a[0].hasOwnProperty(c)&&-1===i.indexOf(c)&&i.push(c)}else if(b.ordering instanceof Array){for(c=0;c<b.ordering.length;c+=1)a&&a.length>0&&a[0].hasOwnProperty(b.ordering[c])&&e.push(b.ordering[c]),d.push(b.ordering[c]);e.length>d.length/2?i.concat(e):b.values=d}else i.push(b.ordering)},this),e=b._rollUp(a,h,i),j.length>=1&&(j.forEach(function(a){var b=null===a.desc||void 0===a.desc?!1:a.desc,c=a.ordering,d=[],e=function(a){var b,c=0;for(b=0;b<a.length;b+=1){if(isNaN(a[b])){c=void 0;break}c+=parseFloat(a[b])}return c},g=function(a,b){var c=0,d=e(a),f=e(b);return isNaN(d)||isNaN(f)?isNaN(Date.parse(a[0]))||isNaN(Date.parse(b[0]))?a[0]<b[0]?c=-1:a[0]>b[0]&&(c=1):c=Date.parse(a[0])-Date.parse(b[0]):c=parseFloat(d)-parseFloat(f),c};"function"==typeof c?f.push(function(a,d){return(b?-1:1)*c(a,d)}):a.values&&a.values.length>0?(a.values.forEach(function(a){d.push([].concat(a).join("|"))},this),f.push(function(a,c){var e,f,g,i="",j="";for(g=0;g<h.length;g+=1)g>0&&(i+="|",j+="|"),i+=a[h[g]],j+=c[h[g]];return e=d.indexOf(i),f=d.indexOf(j),e=0>e?b?-1:d.length:e,f=0>f?b?-1:d.length:f,(b?-1:1)*(e-f)})):[].concat(a.ordering).forEach(function(a){f.push(function(c,d){var e=0;
+return void 0!==c[a]&&void 0!==d[a]&&(e=g([].concat(c[a]),[].concat(d[a]))),(b?-1:1)*e})})}),e.sort(function(a,b){for(var c=0,d=0;c<f.length&&0===d;)d=f[c](a,b),c+=1;return d}),e.forEach(function(a){var b,c=[];if(1===h.length)g.push(a[h[0]]);else{for(b=0;b<h.length;b+=1)c.push(a[h[b]]);g.push(c)}},this)),g},b._getSeriesOrder=function(a,c){var d=[].concat(c._orderRules),e=c.categoryFields,f=[];return null!==e&&void 0!==e&&e.length>0&&(null!==c.c&&void 0!==c.c&&c.c._hasMeasure()&&d.push({ordering:c.c.measure,desc:!0}),c.x._hasMeasure()&&d.push({ordering:c.x.measure,desc:!0}),c.y._hasMeasure()&&d.push({ordering:c.y.measure,desc:!0}),f=b._getOrderedList(a,e,d)),f},b._getSeriesSortPredicate=function(a,c,d){return function(e,f){var g=0;return c.x._hasCategories()&&(g=b._helpers.cx(e,a,c)-b._helpers.cx(f,a,c)),0===g&&c.y._hasCategories()&&(g=b._helpers.cy(e,a,c)-b._helpers.cy(f,a,c)),0===g&&d&&(g=b._arrayIndexCompare(d,e.aggField,f.aggField)),g}},b._helpers={cx:function(a,c,d){var e=0;return e=null!==d.x.measure&&void 0!==d.x.measure?d.x._scale(a.cx):d.x._hasCategories()&&d.x.categoryFields.length>=2?d.x._scale(a.cx)+b._helpers.xGap(c,d)+(a.xOffset+.5)*(c._widthPixels()/d.x._max-2*b._helpers.xGap(c,d))*a.width:d.x._scale(a.cx)+c._widthPixels()/d.x._max/2},cy:function(a,c,d){var e=0;return e=null!==d.y.measure&&void 0!==d.y.measure?d.y._scale(a.cy):null!==d.y.categoryFields&&void 0!==d.y.categoryFields&&d.y.categoryFields.length>=2?d.y._scale(a.cy)-c._heightPixels()/d.y._max+b._helpers.yGap(c,d)+(a.yOffset+.5)*(c._heightPixels()/d.y._max-2*b._helpers.yGap(c,d))*a.height:d.y._scale(a.cy)-c._heightPixels()/d.y._max/2},r:function(a,b,c){var d=0,e=1;return null===c.z||void 0===c.z?d=c.radius&&"auto"!==c.radius?c.radius:5:(c.radius&&"auto"!==c.radius&&c.radius>1&&(e=c.radius/c.z._scale(c.z._max)),d=c.z._hasMeasure()?c.z._scale(a.r)*e:c.z._scale(b._heightPixels()/100)*e),d},xGap:function(a,b){var c=0;return(null===b.x.measure||void 0===b.x.measure)&&b.barGap>0&&(c=a._widthPixels()/b.x._max*(b.barGap>.99?.99:b.barGap)/2),c},xClusterGap:function(a,c,d){var e=0;return null!==d.x.categoryFields&&void 0!==d.x.categoryFields&&d.x.categoryFields.length>=2&&d.clusterBarGap>0&&!d.x._hasMeasure()&&(e=a.width*(c._widthPixels()/d.x._max-2*b._helpers.xGap(c,d))*(d.clusterBarGap>.99?.99:d.clusterBarGap)/2),e},yGap:function(a,b){var c=0;return(null===b.y.measure||void 0===b.y.measure)&&b.barGap>0&&(c=a._heightPixels()/b.y._max*(b.barGap>.99?.99:b.barGap)/2),c},yClusterGap:function(a,c,d){var e=0;return null!==d.y.categoryFields&&void 0!==d.y.categoryFields&&d.y.categoryFields.length>=2&&d.clusterBarGap>0&&!d.y._hasMeasure()&&(e=a.height*(c._heightPixels()/d.y._max-2*b._helpers.yGap(c,d))*(d.clusterBarGap>.99?.99:d.clusterBarGap)/2),e},x:function(a,c,d){var e=0;return e=d.x._hasTimeField()?d.x._scale(a.x)-b._helpers.width(a,c,d)/2:null!==d.x.measure&&void 0!==d.x.measure?d.x._scale(a.x):d.x._scale(a.x)+b._helpers.xGap(c,d)+a.xOffset*(b._helpers.width(a,c,d)+2*b._helpers.xClusterGap(a,c,d))+b._helpers.xClusterGap(a,c,d)},y:function(a,c,d){var e=0;return e=d.y._hasTimeField()?d.y._scale(a.y)-b._helpers.height(a,c,d)/2:null!==d.y.measure&&void 0!==d.y.measure?d.y._scale(a.y):d.y._scale(a.y)-c._heightPixels()/d.y._max+b._helpers.yGap(c,d)+a.yOffset*(b._helpers.height(a,c,d)+2*b._helpers.yClusterGap(a,c,d))+b._helpers.yClusterGap(a,c,d)},width:function(a,c,d){var e=0;return e=null!==d.x.measure&&void 0!==d.x.measure?Math.abs(d.x._scale(a.x<0?a.x-a.width:a.x+a.width)-d.x._scale(a.x)):d.x._hasTimeField()?d.x.floatingBarWidth:a.width*(c._widthPixels()/d.x._max-2*b._helpers.xGap(c,d))-2*b._helpers.xClusterGap(a,c,d)},height:function(a,c,d){var e=0;return e=d.y._hasTimeField()?d.y.floatingBarWidth:null!==d.y.measure&&void 0!==d.y.measure?Math.abs(d.y._scale(a.y)-d.y._scale(a.y<=0?a.y+a.height:a.y-a.height)):a.height*(c._heightPixels()/d.y._max-2*b._helpers.yGap(c,d))-2*b._helpers.yClusterGap(a,c,d)},opacity:function(a,b,c){var d=0;return d=null!==c.c&&void 0!==c.c?a.opacity:b.getColor(a.aggField.slice(-1)[0]).opacity},fill:function(a,b,c){var d=0;return d=null!==c.c&&void 0!==c.c?a.fill:b.getColor(a.aggField.slice(-1)[0]).fill},stroke:function(a,b,c){var d=0;return d=null!==c.c&&void 0!==c.c?a.stroke:b.getColor(a.aggField.slice(-1)[0]).stroke},css:function(a,b){return b.getClass(a.aggField.slice(-1)[0])}},b._parentHeight=function(a){var c=a.offsetHeight;return(0>=c||null===c||void 0===c)&&(c=a.clientHeight),(0>=c||null===c||void 0===c)&&(c=null===a.parentNode||void 0===a.parentNode?0:b._parentHeight(a.parentNode)),c},b._parentWidth=function(a){var c=a.offsetWidth;return(!c||0>c)&&(c=a.clientWidth),(!c||0>c)&&(c=a.parentNode?b._parentWidth(a.parentNode):0),c},b._parsePosition=function(a,b){var c,d=0;return a&&(c=a.toString().split(","),c.forEach(function(c){c&&(isNaN(c)?"%"===c.slice(-1)?d+=b*(parseFloat(c.slice(0,c.length-1))/100):"px"===c.slice(-2)?d+=parseFloat(c.slice(0,c.length-2)):d=a:d+=parseFloat(c))},this)),0>d&&(d=b+d),d},b._parseXPosition=function(a,c){return b._parsePosition(a,b._parentWidth(c))},b._parseYPosition=function(a,c){return b._parsePosition(a,b._parentHeight(c))},b._postDrawHandling=function(b,c,d,e){0===e?(c.each(function(a,c){null!==b.afterDraw&&void 0!==b.afterDraw&&b.afterDraw(this,a,c)}),d.remove()):(c.each("end",function(a,c){null!==b.afterDraw&&void 0!==b.afterDraw&&b.afterDraw(this,a,c)}),d.each("end",function(){a.select(this).remove()}))},b._removeTooltip=function(a,b,c){c._tooltipGroup&&c._tooltipGroup.remove()},b._rollUp=function(a,b,c){var d=[];return b=null!==b&&void 0!==b?[].concat(b):[],a.forEach(function(a){var e=-1,f={},g=!0;d.forEach(function(c,d){-1===e&&(g=!0,b.forEach(function(b){g=g&&a[b]===c[b]},this),g&&(e=d))},this),-1!==e?f=d[e]:(b.forEach(function(b){f[b]=a[b]},this),d.push(f),e=d.length-1),c.forEach(function(c){-1===b.indexOf(c)&&(void 0===f[c]&&(f[c]=[]),f[c]=f[c].concat(a[c]))},this),d[e]=f},this),d},b._showBarTooltip=function(b,c,d,e){var f,g,h,i,j,k,l=5,m=10,n=750,o=a.select(c),p=o.node().getBBox().x,q=o.node().getBBox().y,r=o.node().getBBox().width,s=o.node().getBBox().height,t=o.attr("opacity"),u=o.attr("fill"),v=e._dropLineOrigin(),w=a.rgb(a.rgb(u).r+.6*(255-a.rgb(u).r),a.rgb(u).g+.6*(255-a.rgb(u).g),a.rgb(u).b+.6*(255-a.rgb(u).b)),x=a.rgb(a.rgb(u).r+.8*(255-a.rgb(u).r),a.rgb(u).g+.8*(255-a.rgb(u).g),a.rgb(u).b+.8*(255-a.rgb(u).b)),y=e.getTooltipText(b),z=0,A=0,B=0,C=function(a,b){var c=o.node().getCTM(),e=d.svg.node().createSVGPoint();return e.x=a||0,e.y=b||0,e.matrixTransform(c)};null!==d._tooltipGroup&&void 0!==d._tooltipGroup&&d._tooltipGroup.remove(),d._tooltipGroup=d.svg.append("g"),e.p||(k=e._isStacked()?1:r/2,e.x._hasCategories()||null===v.y||d._tooltipGroup.append("line").attr("x1",p<e.x._origin?p+k:p+r-k).attr("y1",q<v.y?q+s:q).attr("x2",p<e.x._origin?p+k:p+r-k).attr("y2",q<v.y?q+s:q).style("fill","none").style("stroke",u).style("stroke-width",2).style("stroke-dasharray","3, 3").style("opacity",t).transition().delay(n/2).duration(n/2).ease("linear").attr("y2",q<v.y?v.y-1:v.y+1),k=e._isStacked()?1:s/2,e.y._hasCategories()||null===v.x||d._tooltipGroup.append("line").attr("x1",p<v.x?p+r:p).attr("y1",q<e.y._origin?q+k:q+s-k).attr("x2",p<v.x?p+r:p).attr("y2",q<e.y._origin?q+k:q+s-k).style("fill","none").style("stroke",u).style("stroke-width",2).style("stroke-dasharray","3, 3").style("opacity",t).transition().delay(n/2).duration(n/2).ease("linear").attr("x2",p<v.x?v.x-1:v.x+1)),f=d._tooltipGroup.append("g"),g=f.append("rect").attr("class","dimple-tooltip "+d.customClassList.tooltipBox),f.selectAll(".dimple-dont-select-any").data(y).enter().append("text").attr("class","dimple-tooltip "+d.customClassList.tooltipLabel).text(function(a){return a}).style("font-family",e.tooltipFontFamily).style("font-size",e._getTooltipFontSize()),f.each(function(){A=this.getBBox().width>A?this.getBBox().width:A,B=this.getBBox().width>B?this.getBBox().height:B}),f.selectAll("text").attr("x",0).attr("y",function(){return z+=this.getBBox().height,z-this.getBBox().height/2}),g.attr("x",-l).attr("y",-l).attr("height",Math.floor(z+l)-.5).attr("width",A+2*l).attr("rx",5).attr("ry",5).style("fill",x).style("stroke",w).style("stroke-width",2).style("opacity",.95),C(p+r+l+m+A).x<parseFloat(d.svg.node().getBBox().width)?(h=p+r+l+m,i=q+s/2-(z-(B-l))/2):C(p-(l+m+A)).x>0?(h=p-(l+m+A),i=q+s/2-(z-(B-l))/2):C(0,q+s+z+m+l).y<parseFloat(d.svg.node().getBBox().height)?(h=p+r/2-(2*l+A)/2,h=h>0?h:m,h=h+A<parseFloat(d.svg.node().getBBox().width)?h:parseFloat(d.svg.node().getBBox().width)-A-m,i=q+s+2*l):(h=p+r/2-(2*l+A)/2,h=h>0?h:m,h=h+A<parseFloat(d.svg.node().getBBox().width)?h:parseFloat(d.svg.node().getBBox().width)-A-m,i=q-z-(B-l)),j=C(h,i),f.attr("transform","translate("+j.x+" , "+j.y+")")},b._showPointTooltip=function(c,d,e,f){var g,h,i,j,k=5,l=10,m=750,n=a.select(d),o=parseFloat(n.attr("cx")),p=parseFloat(n.attr("cy")),q=parseFloat(n.attr("r")),r=b._helpers.opacity(c,e,f),s=n.attr("stroke"),t=f._dropLineOrigin(),u=a.rgb(a.rgb(s).r+.6*(255-a.rgb(s).r),a.rgb(s).g+.6*(255-a.rgb(s).g),a.rgb(s).b+.6*(255-a.rgb(s).b)),v=a.rgb(a.rgb(s).r+.8*(255-a.rgb(s).r),a.rgb(s).g+.8*(255-a.rgb(s).g),a.rgb(s).b+.8*(255-a.rgb(s).b)),w=0,x=0,y=0,z=f.getTooltipText(c);null!==e._tooltipGroup&&void 0!==e._tooltipGroup&&e._tooltipGroup.remove(),e._tooltipGroup=e.svg.append("g"),e._tooltipGroup.append("circle").attr("cx",o).attr("cy",p).attr("r",q).attr("opacity",0).style("fill","none").style("stroke",s).style("stroke-width",1).transition().duration(m/2).ease("linear").attr("opacity",1).attr("r",q+f.lineWeight+2).style("stroke-width",2),null!==t.y&&e._tooltipGroup.append("line").attr("x1",o).attr("y1",p<t.y?p+q+f.lineWeight+2:p-q-f.lineWeight-2).attr("x2",o).attr("y2",p<t.y?p+q+f.lineWeight+2:p-q-f.lineWeight-2).style("fill","none").style("stroke",s).style("stroke-width",2).style("stroke-dasharray","3, 3").style("opacity",r).transition().delay(m/2).duration(m/2).ease("linear").attr("y2",p<t.y?t.y-1:t.y+1),null!==t.x&&e._tooltipGroup.append("line").attr("x1",o<t.x?o+q+f.lineWeight+2:o-q-f.lineWeight-2).attr("y1",p).attr("x2",o<t.x?o+q+f.lineWeight+2:o-q-f.lineWeight-2).attr("y2",p).style("fill","none").style("stroke",s).style("stroke-width",2).style("stroke-dasharray","3, 3").style("opacity",r).transition().delay(m/2).duration(m/2).ease("linear").attr("x2",o<t.x?t.x-1:t.x+1),g=e._tooltipGroup.append("g"),h=g.append("rect").attr("class","dimple-tooltip"),g.selectAll(".dont-select-any").data(z).enter().append("text").attr("class","dimple-tooltip").text(function(a){return a}).style("font-family",f.tooltipFontFamily).style("font-size",f._getTooltipFontSize()),g.each(function(){x=this.getBBox().width>x?this.getBBox().width:x,y=this.getBBox().width>y?this.getBBox().height:y}),g.selectAll("text").attr("x",0).attr("y",function(){return w+=this.getBBox().height,w-this.getBBox().height/2}),h.attr("x",-k).attr("y",-k).attr("height",Math.floor(w+k)-.5).attr("width",x+2*k).attr("rx",5).attr("ry",5).style("fill",v).style("stroke",u).style("stroke-width",2).style("opacity",.95),o+q+k+l+x<parseFloat(e.svg.node().getBBox().width)?(i=o+q+k+l,j=p-(w-(y-k))/2):o-q-(k+l+x)>0?(i=o-q-(k+l+x),j=p-(w-(y-k))/2):p+q+w+l+k<parseFloat(e.svg.node().getBBox().height)?(i=o-(2*k+x)/2,i=i>0?i:l,i=i+x<parseFloat(e.svg.node().getBBox().width)?i:parseFloat(e.svg.node().getBBox().width)-x-l,j=p+q+2*k):(i=o-(2*k+x)/2,i=i>0?i:l,i=i+x<parseFloat(e.svg.node().getBBox().width)?i:parseFloat(e.svg.node().getBBox().width)-x-l,j=p-w-(y-k)),g.attr("transform","translate("+i+" , "+j+")")},b.filterData=function(a,b,c){var d=a;return null!==b&&null!==c&&(null!==c&&void 0!==c&&(c=[].concat(c)),d=[],a.forEach(function(a){null===a[b]?d.push(a):c.indexOf([].concat(a[b]).join("/"))>-1&&d.push(a)},this)),d},b.getUniqueValues=function(a,b){var c=[];return null!==b&&void 0!==b&&(b=[].concat(b),a.forEach(function(a){var d="";b.forEach(function(b,c){c>0&&(d+="/"),d+=a[b]},this),-1===c.indexOf(d)&&c.push(d)},this)),c},b.newSvg=function(b,c,d){var e=null;if((null===b||void 0===b)&&(b="body"),e=a.select(b),e.empty())throw"The '"+b+"' selector did not match any elements. Please prefix with '#' to select by id or '.' to select by class";return e.append("svg").attr("width",c).attr("height",d)},b});
\ No newline at end of file diff --git a/assets/js/vendor/jquery-ui.min.js b/assets/js/vendor/jquery-ui.min.js index 174de81c..174de81c 100755..100644 --- a/assets/js/vendor/jquery-ui.min.js +++ b/assets/js/vendor/jquery-ui.min.js diff --git a/composer.lock b/composer.lock index ff209d51..3d68e4eb 100644 --- a/composer.lock +++ b/composer.lock @@ -88,12 +88,12 @@ "source": { "type": "git", "url": "https://github.com/fguillot/picoDb.git", - "reference": "88d9af320a4eee57691474b3ba9c84d6a882135c" + "reference": "d7ef5561d6d76c50717492822813125f9699700a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/fguillot/picoDb/zipball/88d9af320a4eee57691474b3ba9c84d6a882135c", - "reference": "88d9af320a4eee57691474b3ba9c84d6a882135c", + "url": "https://api.github.com/repos/fguillot/picoDb/zipball/d7ef5561d6d76c50717492822813125f9699700a", + "reference": "d7ef5561d6d76c50717492822813125f9699700a", "shasum": "" }, "require": { @@ -117,7 +117,7 @@ ], "description": "Minimalist database query builder", "homepage": "https://github.com/fguillot/picoDb", - "time": "2015-02-15 21:17:50" + "time": "2015-03-15 21:03:40" }, { "name": "fguillot/simple-validator", @@ -341,16 +341,16 @@ }, { "name": "swiftmailer/swiftmailer", - "version": "v5.3.1", + "version": "v5.4.0", "source": { "type": "git", "url": "https://github.com/swiftmailer/swiftmailer.git", - "reference": "c5f963e7f9d6f6438fda4f22d5cc2db296ec621a" + "reference": "31454f258f10329ae7c48763eb898a75c39e0a9f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/c5f963e7f9d6f6438fda4f22d5cc2db296ec621a", - "reference": "c5f963e7f9d6f6438fda4f22d5cc2db296ec621a", + "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/31454f258f10329ae7c48763eb898a75c39e0a9f", + "reference": "31454f258f10329ae7c48763eb898a75c39e0a9f", "shasum": "" }, "require": { @@ -362,7 +362,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.3-dev" + "dev-master": "5.4-dev" } }, "autoload": { @@ -389,7 +389,7 @@ "mail", "mailer" ], - "time": "2014-12-05 14:17:14" + "time": "2015-03-14 06:06:39" }, { "name": "symfony/console", diff --git a/config.default.php b/config.default.php index ab3e3b8c..eb9ad1b8 100644 --- a/config.default.php +++ b/config.default.php @@ -40,6 +40,9 @@ define('DB_HOSTNAME', 'localhost'); // Mysql/Postgres database name define('DB_NAME', 'kanboard'); +// Mysql/Postgres custom port (null = default port) +define('DB_PORT', null); + // Enable LDAP authentication (false by default) define('LDAP_AUTH', false); @@ -121,3 +124,6 @@ define('REVERSE_PROXY_DEFAULT_DOMAIN', ''); // Enable or disable "Strict-Transport-Security" HTTP header define('ENABLE_HSTS', true); + +// Enable or disable "X-Frame-Options: DENY" HTTP header +define('ENABLE_XFRAME', true); diff --git a/docs/api-json-rpc.markdown b/docs/api-json-rpc.markdown index 346b1dec..dcf92ea8 100644 --- a/docs/api-json-rpc.markdown +++ b/docs/api-json-rpc.markdown @@ -144,6 +144,32 @@ Array Procedures ---------- +### getVersion + +- Purpose: **Get the application version** +- Parameters: none +- Result: **version** (Example: 1.0.12, master) + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "getVersion", + "id": 1661138292 +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 1661138292, + "result": "1.0.13" +} +``` + ### getTimezone - Purpose: **Get the application timezone** @@ -176,6 +202,7 @@ Response example: - Purpose: **Create a new project** - Parameters: - **name** (string, required) + - **description** (string, optional) - Result on success: **project_id** - Result on failure: **false** @@ -235,7 +262,8 @@ Response example: "is_active": "1", "token": "", "last_modified": "1410263246", - "is_public": "0" + "is_public": "0", + "description": "A sample project" } } ``` @@ -273,7 +301,8 @@ Response example: "is_active": "1", "token": "", "last_modified": "0", - "is_public": "0" + "is_public": "0", + "description": "A sample project" } } ``` @@ -309,7 +338,8 @@ Response example: "is_active": "1", "token": "", "last_modified": "0", - "is_public": "0" + "is_public": "0", + "description": "PHP client project" }, { "id": "1", @@ -317,7 +347,8 @@ Response example: "is_active": "1", "token": "", "last_modified": "0", - "is_public": "0" + "is_public": "0", + "description": "Test project" } ] } @@ -332,6 +363,7 @@ Response example: - **is_active** (integer, optional) - **token** (string, optional) - **is_public** (integer, optional) + - **description** (string, optional) - Result on success: **true** - Result on failure: **false** @@ -973,6 +1005,600 @@ Response example: } ``` +### getSwimlanes + +- Purpose: **Get the list of enabled swimlanes of a project** +- Parameters: + - **project_id** (integer, required) +- Result on success: **swimlane properties** +- Result on failure: **null** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "getSwimlanes", + "id": 1242049935, + "params": [ + 2 + ] +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 1242049935, + "result": [ + { + "id": "0", + "name": "Default" + }, + { + "id": "2", + "name": "Version 7.0" + }, + ] +} +``` + +### getAllSwimlanes + +- Purpose: **Get the list of all swimlanes of a project** +- Parameters: + - **project_id** (integer, required) +- Result on success: **swimlane properties** +- Result on failure: **null** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "getAllSwimlanes", + "id": 1242049935, + "params": [ + 2 + ] +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 1242049935, + "result": [ + { + "id": "0", + "name": "Default" + }, + { + "id": "3", + "name": "Version 1.0", + "is_active": "0", + "position": 1, + "project_id": 2 + }, + { + "id": "2", + "name": "Version 7.0", + "is_active": "1", + "position": 2, + "project_id": 2 + } + ] +} +``` + +### getSwimlane + +- Purpose: **Get the a swimlane** +- Parameters: + - **project_id** (integer, required) + - **name** (string, required) +- Result on success: **swimlane properties** +- Result on failure: **null** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "getSwimlane", + "id": 1242049935, + "params": [ + 2, + "Version 1.0" + ] +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 1242049935, + "result": { + "id": "3", + "name": "Version 1.0", + "is_active": "0", + "position": 2, + "project_id": 2 + } +} +``` + +### moveSwimlaneUp + +- Purpose: **Move up the swimlane position** +- Parameters: + - **project_id** (integer, required) + - **swimlane_id** (integer, required) +- Result on success: **true** +- Result on failure: **false** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "moveSwimlaneUp", + "id": 99275573, + "params": [ + 1, + 2 + ] +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 99275573, + "result": true +} +``` + +### moveSwimlaneDown + +- Purpose: **Move down the swimlane position** +- Parameters: + - **project_id** (integer, required) + - **swimlane_id** (integer, required) +- Result on success: **true** +- Result on failure: **false** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "moveSwimlaneDown", + "id": 957090649, + "params": { + "project_id": 1, + "swimlane_id": 2 + } +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 957090649, + "result": true +} +``` + +### updateSwimlane + +- Purpose: **Update swimlane properties** +- Parameters: + - **swimlane_id** (integer, required) + - **name** (string, required) +- Result on success: **true** +- Result on failure: **false** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "updateSwimlane", + "id": 480740641, + "params": [ + 2, + "Version 4.1" + ] +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 480740641, + "result": true +} +``` + +### addSwimlane + +- Purpose: **Add a new swimlane** +- Parameters: + - **project_id** (integer, required) + - **name** (string, required) +- Result on success: **swimlane_id** +- Result on failure: **false** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "addSwimlane", + "id": 638544704, + "params": [ + 1, + "Version 1.0" + ] +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 638544704, + "result": 5 +} +``` + +### removeSwimlane + +- Purpose: **Remove a swimlane** +- Parameters: + - **project_id** (integer, required) + - **swimlane_id** (integer, required) +- Result on success: **true** +- Result on failure: **false** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "removeSwimlane", + "id": 1433237746, + "params": [ + 2, + 1 + ] +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 1433237746, + "result": true +} +``` + +### disableSwimlane + +- Purpose: **Enable a swimlane** +- Parameters: + - **project_id** (integer, required) + - **swimlane_id** (integer, required) +- Result on success: **true** +- Result on failure: **false** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "disableSwimlane", + "id": 1433237746, + "params": [ + 2, + 1 + ] +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 1433237746, + "result": true +} +``` + +### enableSwimlane + +- Purpose: **Enable a swimlane** +- Parameters: + - **project_id** (integer, required) + - **swimlane_id** (integer, required) +- Result on success: **true** +- Result on failure: **false** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "enableSwimlane", + "id": 1433237746, + "params": [ + 2, + 1 + ] +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 1433237746, + "result": true +} +``` + +### getAvailableActions + +- Purpose: **Get list of available actions** +- Parameters: none +- Result on success: **list of actions** +- Result on failure: **false** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "getAvailableActions", + "id": 1433237746, +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 1433237746, + "result": { + "TaskLogMoveAnotherColumn" : "Add a comment logging moving the task between columns", + "TaskAssignColorUser" : "Assign a color to a specific user", + "TaskAssignCategoryColor" : "Assign automatically a category based on a color", + "TaskAssignColorCategory" : "Assign automatically a color based on a category", + "TaskAssignSpecificUser" : "Assign the task to a specific user", + "TaskAssignCurrentUser" : "Assign the task to the person who does the action", + "TaskAssignUser" : "Change the assignee based on an external username", + "TaskAssignCategoryLabel" : "Change the category based on an external label", + "TaskClose" : "Close a task", + "CommentCreation" : "Create a comment from an external provider", + "TaskCreation" : "Create a task from an external provider", + "TaskDuplicateAnotherProject" : "Duplicate the task to another project", + "TaskMoveAnotherProject" : "Move the task to another project", + "TaskOpen" : "Open a task" + } +} +``` + +### getAvailableEvents + +- Purpose: **Get list of available events** +- Parameters: none +- Result on success: **list of events** +- Result on failure: **false** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "getAvailableEvents", + "id": 1433237746, +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 1433237746, + "result": { + "bitbucket.webhook.commit" : "Bitbucket commit received", + "task.close" : "Closing a task", + "github.webhook.commit" : "Github commit received", + "github.webhook.issue.assignee" : "Github issue assignee change", + "github.webhook.issue.closed" : "Github issue closed", + "github.webhook.issue.commented" : "Github issue comment created", + "github.webhook.issue.label" : "Github issue label change", + "github.webhook.issue.opened" : "Github issue opened", + "github.webhook.issue.reopened" : "Github issue reopened", + "gitlab.webhook.commit" : "Gitlab commit received", + "gitlab.webhook.issue.closed" : "Gitlab issue closed", + "gitlab.webhook.issue.opened" : "Gitlab issue opened", + "task.move.column" : "Move a task to another column", + "task.open" : "Open a closed task", + "task.assignee_change" : "Task assignee change", + "task.create" : "Task creation", + "task.create_update" : "Task creation or modification", + "task.update" : "Task modification" + } +} +``` + +### getCompatibleEvents + +- Purpose: **Get list of events compatible with an action** +- Parameters: + - **action_name** (string, required) +- Result on success: **list of events** +- Result on failure: **false** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "getCompatibleEvents", + "id": 1433237746, + "params": [ + "TaskAssignSpecificUser" + ] +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 1433237746, + "result": { + "task.move.column" : "Move a task to another column", + "task.create_update" : "Task creation or modification", + } +} +``` + +### getActions + +- Purpose: **Get list of actions for a project** +- Parameters: + - **project_id** (integer, required) +- Result on success: **list of actions info** +- Result on failure: **false** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "getActions", + "id": 1433237746, + "params": [ + "1" + ] +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 1433237746, + "result": [ + { + "id" : "13", + "project_id" : "2", + "event_name" : "task.move.column", + "action_name" : "TaskAssignSpecificUser", + "params" : { + "column_id" : "5", + "user_id" : "1" + } + } + ] +} +``` + +### createAction + +- Purpose: **Create an action** +- Parameters: + - **project_id** (integer, required) + - **event_name** (string, required) + - **action_name** (string, required) + - **params** (list of string pairs, required) +- Result on success: **action_id** +- Result on failure: **false** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "createAction", + "id": 1433237746, + "params": { + "project_id" : "2", + "event_name" : "task.move.column", + "action_name" : "TaskAssignSpecificUser", + "params" : { + "column_id" : "3", + "user_id" : "2" + } + } +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 1433237746, + "result": 14 +} +``` + +### removeAction + +- Purpose: **Remove an action** +- Parameters: + - **action_id** (integer, required) +- Result on success: **true** +- Result on failure: **false** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "getAvailableEvents", + "id": 1433237746, + "params": [ + "2", + ] +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 1433237746, + "result": true +} +``` ### createTask diff --git a/docs/faq.markdown b/docs/faq.markdown index 0b7a3562..68bc686f 100644 --- a/docs/faq.markdown +++ b/docs/faq.markdown @@ -91,3 +91,8 @@ How to install Kanboard on Yunohost? [YunoHost](https://yunohost.org/) is a server operating system aiming to make self-hosting accessible to everyone. There is a [package to install Kanboard on Yunohost easily](https://github.com/mbugeia/kanboard_ynh). + + +Are there some tutorials about Kanboard in other languages? +------------------------------------ + - [German article series about Kanboard](http://demaya.de/wp/2014/07/kanboard-eine-jira-alternative-im-detail-installation/) diff --git a/jsonrpc.php b/jsonrpc.php index 566976cd..afc1ed18 100644 --- a/jsonrpc.php +++ b/jsonrpc.php @@ -22,13 +22,16 @@ $server->bind('enableProjectPublicAccess', $container['project'], 'enablePublicA $server->bind('disableProjectPublicAccess', $container['project'], 'disablePublicAccess'); $server->bind('getProjectActivity', $container['projectActivity'], 'getProjects'); -$server->register('createProject', function($name) use ($container) { - $values = array('name' => $name); +$server->register('createProject', function($name, $description = null) use ($container) { + $values = array( + 'name' => $name, + 'description' => $description + ); list($valid,) = $container['project']->validateCreation($values); return $valid ? $container['project']->create($values) : false; }); -$server->register('updateProject', function($id, $name, $is_active = null, $is_public = null, $token = null) use ($container) { +$server->register('updateProject', function($id, $name, $is_active = null, $is_public = null, $token = null, $description = null) use ($container) { $values = array( 'id' => $id, @@ -36,6 +39,7 @@ $server->register('updateProject', function($id, $name, $is_active = null, $is_p 'is_active' => $is_active, 'is_public' => $is_public, 'token' => $token, + 'description' => $description ); foreach ($values as $key => $value) { @@ -61,6 +65,91 @@ $server->bind('addColumn', $container['board'], 'addColumn'); $server->bind('removeColumn', $container['board'], 'removeColumn'); /** + * Swimlane procedures + */ +$server->bind('getSwimlanes', $container['swimlane'], 'getSwimlanes'); +$server->bind('getAllSwimlanes', $container['swimlane'], 'getAll'); +$server->bind('getSwimlane', $container['swimlane'], 'getByName'); +$server->bind('addSwimlane', $container['swimlane'], 'create'); +$server->bind('updateSwimlane', $container['swimlane'], 'rename'); +$server->bind('removeSwimlane', $container['swimlane'], 'remove'); +$server->bind('disableSwimlane', $container['swimlane'], 'disable'); +$server->bind('enableSwimlane', $container['swimlane'], 'enable'); +$server->bind('moveSwimlaneUp', $container['swimlane'], 'moveUp'); +$server->bind('moveSwimlaneDown', $container['swimlane'], 'moveDown'); + +/** + * Actions procedures + */ +$server->bind('getAvailableActions', $container['action'], 'getAvailableActions'); +$server->bind('getAvailableEvents', $container['action'], 'getAvailableEvents'); +$server->bind('getCompatibleEvents', $container['action'], 'getCompatibleEvents'); +$server->bind('removeAction', $container['action'], 'remove'); + +$server->register('getActions', function($project_id) use ($container) { + $actions = $container['action']->getAllByProject($project_id); + + foreach ($actions as $index => $action) { + $params = array(); + + foreach($action['params'] as $param) { + $params[$param['name']] = $param['value']; + } + + $actions[$index]['params'] = $params; + } + + return $actions; +}); + +$server->register('createAction', function($project_id, $event_name, $action_name, $params) use ($container) { + + $values = array( + 'project_id' => $project_id, + 'event_name' => $event_name, + 'action_name' => $action_name, + 'params' => $params, + ); + + list($valid,) = $container['action']->validateCreation($values); + + if (! $valid) { + return false; + } + + // Check the action exists + if (! isset($container['action']->getAvailableActions()[$action_name])) { + return false; + } + + // Check the event + $action = $container['action']->load($action_name, $project_id, $event_name); + + if (! in_array($event_name, $action->getCompatibleEvents())) { + return false; + } + + $required_params = $action->getActionRequiredParameters(); + + // Check missing parameters + foreach($required_params as $param => $value) { + if (! isset($params[$param])) { + return false; + } + } + + // Check extra parameters + foreach($params as $param => $value) { + if (! isset($required_params[$param])) { + return false; + } + } + + return $container['action']->create($values); +}); + + +/** * Project permissions procedures */ $server->bind('getMembers', $container['projectPermission'], 'getMembers'); @@ -331,6 +420,10 @@ $server->register('getTimezone', function() use ($container) { return $container['config']->get('application_timezone'); }); +$server->register('getVersion', function() use ($container) { + return APP_VERSION; +}); + /** * Parse incoming requests */ diff --git a/scripts/make-assets.sh b/scripts/make-assets.sh index d9562ee0..e27e537d 100755 --- a/scripts/make-assets.sh +++ b/scripts/make-assets.sh @@ -3,9 +3,9 @@ app_css="base links title table form button alert tooltip header board task comment subtask markdown listing activity dashboard pagination popover confirm sidebar responsive dropdown" vendor_css="jquery-ui.min chosen.min fullcalendar.min font-awesome.min" -app_js="base board calendar analytic task swimlane dashboard" +app_js="base board calendar analytic swimlane dashboard budget" vendor_js="jquery-1.11.1.min jquery-ui.min jquery.ui.touch-punch.min chosen.jquery.min dropit.min moment.min fullcalendar.min mousetrap.min mousetrap-global-bind.min app.min" -lang_js="da de es fi fr hu it ja pl pt-br ru sv th zh-cn" +lang_js="da de es fi fr hu it ja pl pt-br ru sv th zh-cn tr" function merge_css { diff --git a/tests/functionals/ApiTest.php b/tests/functionals/ApiTest.php index f778d1ca..9fdfd1ba 100644 --- a/tests/functionals/ApiTest.php +++ b/tests/functionals/ApiTest.php @@ -39,7 +39,7 @@ class Api extends PHPUnit_Framework_TestCase { $this->client = new JsonRPC\Client(API_URL); $this->client->authentication('jsonrpc', API_KEY); - //$this->client->debug = true; + // $this->client->debug = true; } private function getTaskId() @@ -53,8 +53,12 @@ class Api extends PHPUnit_Framework_TestCase public function testGetTimezone() { - $timezone = $this->client->getTimezone(); - $this->assertEquals('Europe/Paris', $timezone); + $this->assertEquals('Europe/Paris', $this->client->getTimezone()); + } + + public function testGetVersion() + { + $this->assertEquals('master', $this->client->getVersion()); } public function testRemoveAll() diff --git a/tests/units/ActionTest.php b/tests/units/ActionTest.php index 429a181a..67957a26 100644 --- a/tests/units/ActionTest.php +++ b/tests/units/ActionTest.php @@ -27,7 +27,7 @@ class ActionTest extends Base $this->assertEquals(1, $project->create(array('name' => 'unit_test'))); // We create a new action - $this->assertTrue($action->create(array( + $this->assertEquals(1, $action->create(array( 'project_id' => 1, 'event_name' => Task::EVENT_MOVE_COLUMN, 'action_name' => 'TaskClose', @@ -78,14 +78,14 @@ class ActionTest extends Base $this->assertEquals(1, $c->create(array('name' => 'unit_test'))); // We create a new action - $this->assertTrue($a->create(array( + $this->assertEquals(1, $a->create(array( 'project_id' => 1, 'event_name' => GithubWebhook::EVENT_ISSUE_OPENED, 'action_name' => 'TaskCreation', 'params' => array() ))); - $this->assertTrue($a->create(array( + $this->assertEquals(2, $a->create(array( 'project_id' => 1, 'event_name' => GithubWebhook::EVENT_ISSUE_LABEL_CHANGE, 'action_name' => 'TaskAssignCategoryLabel', @@ -95,7 +95,7 @@ class ActionTest extends Base ) ))); - $this->assertTrue($a->create(array( + $this->assertEquals(3, $a->create(array( 'project_id' => 1, 'event_name' => Task::EVENT_CREATE_UPDATE, 'action_name' => 'TaskAssignColorCategory', diff --git a/tests/units/DateParserTest.php b/tests/units/DateParserTest.php index 5828fc48..9403063b 100644 --- a/tests/units/DateParserTest.php +++ b/tests/units/DateParserTest.php @@ -6,6 +6,34 @@ use Model\DateParser; class DateParserTest extends Base { + public function testDateRange() + { + $d = new DateParser($this->container); + + $this->assertTrue($d->withinDateRange(new DateTime('2015-03-14 15:30:00'), new DateTime('2015-03-14 15:00:00'), new DateTime('2015-03-14 16:00:00'))); + $this->assertFalse($d->withinDateRange(new DateTime('2015-03-14 15:30:00'), new DateTime('2015-03-14 16:00:00'), new DateTime('2015-03-14 17:00:00'))); + } + + public function testRoundSeconds() + { + $d = new DateParser($this->container); + $this->assertEquals('16:30', date('H:i', $d->getRoundedSeconds(strtotime('16:28')))); + $this->assertEquals('16:00', date('H:i', $d->getRoundedSeconds(strtotime('16:02')))); + $this->assertEquals('16:15', date('H:i', $d->getRoundedSeconds(strtotime('16:14')))); + $this->assertEquals('17:00', date('H:i', $d->getRoundedSeconds(strtotime('16:58')))); + } + + public function testGetHours() + { + $d = new DateParser($this->container); + + $this->assertEquals(1, $d->getHours(new DateTime('2015-03-14 15:00:00'), new DateTime('2015-03-14 16:00:00'))); + $this->assertEquals(2.5, $d->getHours(new DateTime('2015-03-14 15:00:00'), new DateTime('2015-03-14 17:30:00'))); + $this->assertEquals(2.75, $d->getHours(new DateTime('2015-03-14 15:00:00'), new DateTime('2015-03-14 17:45:00'))); + $this->assertEquals(3, $d->getHours(new DateTime('2015-03-14 14:57:00'), new DateTime('2015-03-14 17:58:00'))); + $this->assertEquals(3, $d->getHours(new DateTime('2015-03-14 14:57:00'), new DateTime('2015-03-14 11:58:00'))); + } + public function testValidDate() { $d = new DateParser($this->container); diff --git a/tests/units/HourlyRate.php b/tests/units/HourlyRate.php new file mode 100644 index 00000000..5daf0446 --- /dev/null +++ b/tests/units/HourlyRate.php @@ -0,0 +1,43 @@ +<?php + +require_once __DIR__.'/Base.php'; + +use Model\User; +use Model\HourlyRate; + +class HourlyRateTest extends Base +{ + public function testCreation() + { + $hr = new HourlyRate($this->container); + $this->assertEquals(1, $hr->create(1, 32.4, 'EUR', '2015-01-01')); + $this->assertEquals(2, $hr->create(1, 42, 'CAD', '2015-02-01')); + + $rates = $hr->getAllByUser(0); + $this->assertEmpty($rates); + + $rates = $hr->getAllByUser(1); + $this->assertNotEmpty($rates); + $this->assertCount(2, $rates); + + $this->assertEquals(42, $rates[0]['rate']); + $this->assertEquals('CAD', $rates[0]['currency']); + $this->assertEquals('2015-02-01', date('Y-m-d', $rates[0]['date_effective'])); + + $this->assertEquals(32.4, $rates[1]['rate']); + $this->assertEquals('EUR', $rates[1]['currency']); + $this->assertEquals('2015-01-01', date('Y-m-d', $rates[1]['date_effective'])); + + $this->assertEquals(0, $hr->getCurrentRate(0)); + $this->assertEquals(42, $hr->getCurrentRate(1)); + + $this->assertTrue($hr->remove(2)); + $this->assertEquals(32.4, $hr->getCurrentRate(1)); + + $this->assertTrue($hr->remove(1)); + $this->assertEquals(0, $hr->getCurrentRate(1)); + + $rates = $hr->getAllByUser(1); + $this->assertEmpty($rates); + } +} diff --git a/tests/units/ProjectDuplicationTest.php b/tests/units/ProjectDuplicationTest.php index b35575aa..311ecc4a 100644 --- a/tests/units/ProjectDuplicationTest.php +++ b/tests/units/ProjectDuplicationTest.php @@ -156,7 +156,7 @@ class ProjectDuplicationTest extends Base $this->assertEquals(1, $p->create(array('name' => 'P1'))); - $this->assertTrue($a->create(array( + $this->assertEquals(1, $a->create(array( 'project_id' => 1, 'event_name' => Task::EVENT_MOVE_COLUMN, 'action_name' => 'TaskAssignCurrentUser', @@ -186,7 +186,7 @@ class ProjectDuplicationTest extends Base $this->assertEquals(2, $c->create(array('name' => 'C2', 'project_id' => 1))); $this->assertEquals(3, $c->create(array('name' => 'C3', 'project_id' => 1))); - $this->assertTrue($a->create(array( + $this->assertEquals(1, $a->create(array( 'project_id' => 1, 'event_name' => Task::EVENT_CREATE_UPDATE, 'action_name' => 'TaskAssignColorCategory', diff --git a/tests/units/ProjectPermissionTest.php b/tests/units/ProjectPermissionTest.php index 3cbd6bec..66406392 100644 --- a/tests/units/ProjectPermissionTest.php +++ b/tests/units/ProjectPermissionTest.php @@ -25,6 +25,22 @@ class ProjectPermissionTest extends Base $this->assertEquals(array(), $pp->getMembers(1)); $this->assertEquals(array('Unassigned'), $pp->getMemberList(1)); + $this->assertEmpty($pp->getMemberProjects(1)); + $this->assertEmpty($pp->getMemberProjects(2)); + $this->assertEmpty($pp->getMemberProjects(3)); + + $this->assertEmpty($pp->getMemberProjectIds(1)); + $this->assertEmpty($pp->getMemberProjectIds(2)); + $this->assertEmpty($pp->getMemberProjectIds(3)); + + $this->assertEmpty($pp->getActiveMemberProjectIds(1)); + $this->assertEmpty($pp->getActiveMemberProjectIds(2)); + $this->assertEmpty($pp->getActiveMemberProjectIds(3)); + + $this->assertEmpty($pp->getActiveMemberProjects(1)); + $this->assertEmpty($pp->getActiveMemberProjects(2)); + $this->assertEmpty($pp->getActiveMemberProjects(3)); + $this->assertTrue($p->update(array('id' => 1, 'is_everybody_allowed' => 1))); $this->assertTrue($pp->isEverybodyAllowed(1)); $this->assertTrue($pp->isUserAllowed(1, 1)); @@ -32,6 +48,32 @@ class ProjectPermissionTest extends Base $this->assertTrue($pp->isUserAllowed(1, 3)); $this->assertEquals(array('1' => 'admin', '2' => 'unittest#1', '3' => 'unittest#2'), $pp->getMembers(1)); $this->assertEquals(array('Unassigned', '1' => 'admin', '2' => 'unittest#1', '3' => 'unittest#2'), $pp->getMemberList(1)); + + $this->assertNotEmpty($pp->getMemberProjects(1)); + $this->assertNotEmpty($pp->getMemberProjects(2)); + $this->assertNotEmpty($pp->getMemberProjects(3)); + + $this->assertNotEmpty($pp->getMemberProjectIds(1)); + $this->assertNotEmpty($pp->getMemberProjectIds(2)); + $this->assertNotEmpty($pp->getMemberProjectIds(3)); + + $this->assertNotEmpty($pp->getActiveMemberProjectIds(1)); + $this->assertNotEmpty($pp->getActiveMemberProjectIds(2)); + $this->assertNotEmpty($pp->getActiveMemberProjectIds(3)); + + $this->assertNotEmpty($pp->getActiveMemberProjects(1)); + $this->assertNotEmpty($pp->getActiveMemberProjects(2)); + $this->assertNotEmpty($pp->getActiveMemberProjects(3)); + + $this->assertTrue($p->disable(1)); + + $this->assertEmpty($pp->getActiveMemberProjectIds(1)); + $this->assertEmpty($pp->getActiveMemberProjectIds(2)); + $this->assertEmpty($pp->getActiveMemberProjectIds(3)); + + $this->assertEmpty($pp->getActiveMemberProjects(1)); + $this->assertEmpty($pp->getActiveMemberProjects(2)); + $this->assertEmpty($pp->getActiveMemberProjects(3)); } public function testDisallowEverybody() @@ -56,11 +98,23 @@ class ProjectPermissionTest extends Base $pp = new ProjectPermission($this->container); $user = new User($this->container); - $user->create(array('username' => 'unittest', 'password' => 'unittest')); + $this->assertNotFalse($user->create(array('username' => 'unittest', 'password' => 'unittest'))); // We create a project $this->assertEquals(1, $p->create(array('name' => 'UnitTest'))); + $this->assertEmpty($pp->getMemberProjects(1)); + $this->assertEmpty($pp->getMemberProjects(2)); + + $this->assertEmpty($pp->getMemberProjectIds(1)); + $this->assertEmpty($pp->getMemberProjectIds(2)); + + $this->assertEmpty($pp->getActiveMemberProjectIds(1)); + $this->assertEmpty($pp->getActiveMemberProjectIds(2)); + + $this->assertEmpty($pp->getActiveMemberProjects(1)); + $this->assertEmpty($pp->getActiveMemberProjects(2)); + // We allow the admin user $this->assertTrue($pp->addMember(1, 1)); $this->assertTrue($pp->addMember(1, 2)); @@ -75,6 +129,18 @@ class ProjectPermissionTest extends Base $this->assertEquals(array('1' => 'admin', '2' => 'unittest'), $pp->getMembers(1)); $this->assertTrue($pp->isUserAllowed(1, 1)); $this->assertTrue($pp->isUserAllowed(1, 2)); + + $this->assertNotEmpty($pp->getMemberProjects(1)); + $this->assertNotEmpty($pp->getMemberProjects(2)); + + $this->assertNotEmpty($pp->getMemberProjectIds(1)); + $this->assertNotEmpty($pp->getMemberProjectIds(2)); + + $this->assertNotEmpty($pp->getActiveMemberProjectIds(1)); + $this->assertNotEmpty($pp->getActiveMemberProjectIds(2)); + + $this->assertNotEmpty($pp->getActiveMemberProjects(1)); + $this->assertNotEmpty($pp->getActiveMemberProjects(2)); } public function testRevokeUser() diff --git a/tests/units/SubtaskTest.php b/tests/units/SubtaskTest.php index 62475186..eb1a3fd3 100644 --- a/tests/units/SubtaskTest.php +++ b/tests/units/SubtaskTest.php @@ -11,6 +11,78 @@ use Model\User; class SubTaskTest extends Base { + public function testMoveUp() + { + $tc = new TaskCreation($this->container); + $s = new Subtask($this->container); + $p = new Project($this->container); + + $this->assertEquals(1, $p->create(array('name' => 'test1'))); + $this->assertEquals(1, $tc->create(array('title' => 'test 1', 'project_id' => 1))); + + $this->assertEquals(1, $s->create(array('title' => 'subtask #1', 'task_id' => 1))); + $this->assertEquals(2, $s->create(array('title' => 'subtask #2', 'task_id' => 1))); + $this->assertEquals(3, $s->create(array('title' => 'subtask #3', 'task_id' => 1))); + + $subtask = $s->getById(1); + $this->assertNotEmpty($subtask); + $this->assertEquals(1, $subtask['position']); + + $subtask = $s->getById(2); + $this->assertNotEmpty($subtask); + $this->assertEquals(2, $subtask['position']); + + $subtask = $s->getById(3); + $this->assertNotEmpty($subtask); + $this->assertEquals(3, $subtask['position']); + + $this->assertTrue($s->moveUp(1, 2)); + + $subtask = $s->getById(1); + $this->assertNotEmpty($subtask); + $this->assertEquals(2, $subtask['position']); + + $subtask = $s->getById(2); + $this->assertNotEmpty($subtask); + $this->assertEquals(1, $subtask['position']); + + $subtask = $s->getById(3); + $this->assertNotEmpty($subtask); + $this->assertEquals(3, $subtask['position']); + + $this->assertFalse($s->moveUp(1, 2)); + } + + public function testMoveDown() + { + $tc = new TaskCreation($this->container); + $s = new Subtask($this->container); + $p = new Project($this->container); + + $this->assertEquals(1, $p->create(array('name' => 'test1'))); + $this->assertEquals(1, $tc->create(array('title' => 'test 1', 'project_id' => 1))); + + $this->assertEquals(1, $s->create(array('title' => 'subtask #1', 'task_id' => 1))); + $this->assertEquals(2, $s->create(array('title' => 'subtask #2', 'task_id' => 1))); + $this->assertEquals(3, $s->create(array('title' => 'subtask #3', 'task_id' => 1))); + + $this->assertTrue($s->moveDown(1, 1)); + + $subtask = $s->getById(1); + $this->assertNotEmpty($subtask); + $this->assertEquals(2, $subtask['position']); + + $subtask = $s->getById(2); + $this->assertNotEmpty($subtask); + $this->assertEquals(1, $subtask['position']); + + $subtask = $s->getById(3); + $this->assertNotEmpty($subtask); + $this->assertEquals(3, $subtask['position']); + + $this->assertFalse($s->moveDown(1, 3)); + } + public function testDuplicate() { $tc = new TaskCreation($this->container); @@ -53,5 +125,8 @@ class SubTaskTest extends Base $this->assertEquals(0, $subtasks[0]['user_id']); $this->assertEquals(0, $subtasks[1]['user_id']); + + $this->assertEquals(1, $subtasks[0]['position']); + $this->assertEquals(2, $subtasks[1]['position']); } } diff --git a/tests/units/SubtaskTimeTrackingTest.php b/tests/units/SubtaskTimeTrackingTest.php index 90650e42..e15e60da 100644 --- a/tests/units/SubtaskTimeTrackingTest.php +++ b/tests/units/SubtaskTimeTrackingTest.php @@ -176,38 +176,35 @@ class SubtaskTimeTrackingTest extends Base $this->assertEquals(7, $s->create(array('title' => 'subtask #7', 'task_id' => 2))); $this->assertEquals(8, $s->create(array('title' => 'subtask #8', 'task_id' => 2))); - // Create a couple of time slots - $now = time(); - // Slot start before and finish inside the calendar time range - $this->container['db']->table(SubtaskTimeTracking::TABLE)->insert(array('user_id' => 1, 'subtask_id' => 1, 'start' => $now - 86400, 'end' => $now + 3600)); + $this->container['db']->table(SubtaskTimeTracking::TABLE)->insert(array('user_id' => 1, 'subtask_id' => 1, 'start' => strtotime('-1 day'), 'end' => strtotime('+1 hour'))); // Slot start inside time range and finish after the time range - $this->container['db']->table(SubtaskTimeTracking::TABLE)->insert(array('user_id' => 1, 'subtask_id' => 2, 'start' => $now + 3600, 'end' => $now + 2*86400)); + $this->container['db']->table(SubtaskTimeTracking::TABLE)->insert(array('user_id' => 1, 'subtask_id' => 2, 'start' => strtotime('+1 hour'), 'end' => strtotime('+2 days'))); // Start before time range and finish inside time range - $this->container['db']->table(SubtaskTimeTracking::TABLE)->insert(array('user_id' => 1, 'subtask_id' => 3, 'start' => $now - 86400, 'end' => $now + 1.5*86400)); + $this->container['db']->table(SubtaskTimeTracking::TABLE)->insert(array('user_id' => 1, 'subtask_id' => 3, 'start' => strtotime('-1 day'), 'end' => strtotime('+1.5 days'))); // Start and finish inside time range - $this->container['db']->table(SubtaskTimeTracking::TABLE)->insert(array('user_id' => 1, 'subtask_id' => 4, 'start' => $now + 3600, 'end' => $now + 2*3600)); + $this->container['db']->table(SubtaskTimeTracking::TABLE)->insert(array('user_id' => 1, 'subtask_id' => 4, 'start' => strtotime('+1 hour'), 'end' => strtotime('+2 hours'))); // Start and finish after the time range - $this->container['db']->table(SubtaskTimeTracking::TABLE)->insert(array('user_id' => 1, 'subtask_id' => 5, 'start' => $now + 2*86400, 'end' => $now + 3*86400)); + $this->container['db']->table(SubtaskTimeTracking::TABLE)->insert(array('user_id' => 1, 'subtask_id' => 5, 'start' => strtotime('+2 days'), 'end' => strtotime('+3 days'))); // Start and finish before the time range - $this->container['db']->table(SubtaskTimeTracking::TABLE)->insert(array('user_id' => 1, 'subtask_id' => 6, 'start' => $now - 2*86400, 'end' => $now - 86400)); + $this->container['db']->table(SubtaskTimeTracking::TABLE)->insert(array('user_id' => 1, 'subtask_id' => 6, 'start' => strtotime('-2 days'), 'end' => strtotime('-1 day'))); // Start before time range and not finished - $this->container['db']->table(SubtaskTimeTracking::TABLE)->insert(array('user_id' => 1, 'subtask_id' => 7, 'start' => $now - 86400)); + $this->container['db']->table(SubtaskTimeTracking::TABLE)->insert(array('user_id' => 1, 'subtask_id' => 7, 'start' => strtotime('-1 day'))); // Start inside time range and not finish - $this->container['db']->table(SubtaskTimeTracking::TABLE)->insert(array('user_id' => 1, 'subtask_id' => 8, 'start' => $now + 3200)); + $this->container['db']->table(SubtaskTimeTracking::TABLE)->insert(array('user_id' => 1, 'subtask_id' => 8, 'start' => strtotime('+3200 seconds'))); $timesheet = $st->getUserTimesheet(1); $this->assertNotEmpty($timesheet); $this->assertCount(8, $timesheet); - $events = $st->getUserCalendarEvents(1, date('Y-m-d', $now), date('Y-m-d', $now + 86400)); + $events = $st->getUserCalendarEvents(1, date('Y-m-d'), date('Y-m-d', strtotime('+2 day'))); $this->assertNotEmpty($events); $this->assertCount(6, $events); $this->assertEquals(1, $events[0]['subtask_id']); @@ -217,14 +214,14 @@ class SubtaskTimeTrackingTest extends Base $this->assertEquals(7, $events[4]['subtask_id']); $this->assertEquals(8, $events[5]['subtask_id']); - $events = $st->getProjectCalendarEvents(1, date('Y-m-d', $now), date('Y-m-d', $now + 86400)); + $events = $st->getProjectCalendarEvents(1, date('Y-m-d'), date('Y-m-d', strtotime('+2 days'))); $this->assertNotEmpty($events); $this->assertCount(3, $events); $this->assertEquals(1, $events[0]['subtask_id']); $this->assertEquals(2, $events[1]['subtask_id']); $this->assertEquals(3, $events[2]['subtask_id']); - $events = $st->getProjectCalendarEvents(2, date('Y-m-d', $now), date('Y-m-d', $now + 86400)); + $events = $st->getProjectCalendarEvents(2, date('Y-m-d'), date('Y-m-d', strtotime('+2 days'))); $this->assertNotEmpty($events); $this->assertCount(3, $events); $this->assertEquals(4, $events[0]['subtask_id']); diff --git a/tests/units/TimetableTest.php b/tests/units/TimetableTest.php new file mode 100644 index 00000000..2f38b534 --- /dev/null +++ b/tests/units/TimetableTest.php @@ -0,0 +1,253 @@ +<?php + +require_once __DIR__.'/Base.php'; + +use Model\User; +use Model\Timetable; +use Model\TimetableDay; +use Model\TimetableWeek; +use Model\TimetableOff; +use Model\TimetableExtra; + +class TimetableTest extends Base +{ + public function testCalculateWorkDays() + { + $w = new TimetableWeek($this->container); + $t = new Timetable($this->container); + + $this->assertNotFalse($w->create(1, 1, '09:30', '12:00')); + $this->assertNotFalse($w->create(1, 1, '13:00', '17:00')); + $this->assertNotFalse($w->create(1, 2, '09:30', '12:00')); + $this->assertNotFalse($w->create(1, 2, '13:00', '17:00')); + + $monday = new DateTime('next Monday'); + + $timetable = $t->calculate(1, $monday, new DateTime('next Monday + 6 days')); + $this->assertNotEmpty($timetable); + $this->assertCount(4, $timetable); + + $this->assertEquals($monday->format('Y-m-d').' 09:30', $timetable[0][0]->format('Y-m-d H:i')); + $this->assertEquals($monday->format('Y-m-d').' 12:00', $timetable[0][1]->format('Y-m-d H:i')); + $this->assertEquals($monday->format('Y-m-d').' 13:00', $timetable[1][0]->format('Y-m-d H:i')); + $this->assertEquals($monday->format('Y-m-d').' 17:00', $timetable[1][1]->format('Y-m-d H:i')); + + $this->assertEquals($monday->add(new DateInterval('P1D'))->format('Y-m-d').' 09:30', $timetable[2][0]->format('Y-m-d H:i')); + $this->assertEquals($monday->format('Y-m-d').' 12:00', $timetable[2][1]->format('Y-m-d H:i')); + $this->assertEquals($monday->format('Y-m-d').' 13:00', $timetable[3][0]->format('Y-m-d H:i')); + $this->assertEquals($monday->format('Y-m-d').' 17:00', $timetable[3][1]->format('Y-m-d H:i')); + } + + public function testCalculateOverTime() + { + $d = new TimetableDay($this->container); + $w = new TimetableWeek($this->container); + $e = new TimetableExtra($this->container); + $t = new Timetable($this->container); + + $monday = new DateTime('next Monday'); + $tuesday = new DateTime('next Monday + 1 day'); + $friday = new DateTime('next Monday + 4 days'); + + $this->assertNotFalse($d->create(1, '08:00', '12:00')); + $this->assertNotFalse($d->create(1, '14:00', '18:00')); + + $this->assertNotFalse($w->create(1, 1, '09:30', '12:00')); + $this->assertNotFalse($w->create(1, 1, '13:00', '17:00')); + $this->assertNotFalse($w->create(1, 2, '09:30', '12:00')); + $this->assertNotFalse($w->create(1, 2, '13:00', '17:00')); + + $this->assertNotFalse($e->create(1, $tuesday->format('Y-m-d'), 0, '17:00', '22:00')); + $this->assertNotFalse($e->create(1, $friday->format('Y-m-d'), 1)); + + $timetable = $t->calculate(1, $monday, new DateTime('next Monday + 6 days')); + $this->assertNotEmpty($timetable); + $this->assertCount(7, $timetable); + + $this->assertEquals($monday->format('Y-m-d').' 09:30', $timetable[0][0]->format('Y-m-d H:i')); + $this->assertEquals($monday->format('Y-m-d').' 12:00', $timetable[0][1]->format('Y-m-d H:i')); + $this->assertEquals($monday->format('Y-m-d').' 13:00', $timetable[1][0]->format('Y-m-d H:i')); + $this->assertEquals($monday->format('Y-m-d').' 17:00', $timetable[1][1]->format('Y-m-d H:i')); + + $this->assertEquals($tuesday->format('Y-m-d').' 09:30', $timetable[2][0]->format('Y-m-d H:i')); + $this->assertEquals($tuesday->format('Y-m-d').' 12:00', $timetable[2][1]->format('Y-m-d H:i')); + $this->assertEquals($tuesday->format('Y-m-d').' 13:00', $timetable[3][0]->format('Y-m-d H:i')); + $this->assertEquals($tuesday->format('Y-m-d').' 17:00', $timetable[3][1]->format('Y-m-d H:i')); + + $this->assertEquals($tuesday->format('Y-m-d').' 17:00', $timetable[4][0]->format('Y-m-d H:i')); + $this->assertEquals($tuesday->format('Y-m-d').' 22:00', $timetable[4][1]->format('Y-m-d H:i')); + + $this->assertEquals($friday->format('Y-m-d').' 08:00', $timetable[5][0]->format('Y-m-d H:i')); + $this->assertEquals($friday->format('Y-m-d').' 12:00', $timetable[5][1]->format('Y-m-d H:i')); + + $this->assertEquals($friday->format('Y-m-d').' 14:00', $timetable[6][0]->format('Y-m-d H:i')); + $this->assertEquals($friday->format('Y-m-d').' 18:00', $timetable[6][1]->format('Y-m-d H:i')); + } + + public function testCalculateTimeOff() + { + $d = new TimetableDay($this->container); + $w = new TimetableWeek($this->container); + $o = new TimetableOff($this->container); + $t = new Timetable($this->container); + + $monday = new DateTime('next Monday'); + $tuesday = new DateTime('next Monday + 1 day'); + $friday = new DateTime('next Monday + 4 days'); + + $this->assertNotFalse($d->create(1, '08:00', '12:00')); + $this->assertNotFalse($d->create(1, '14:00', '18:00')); + + $this->assertNotFalse($w->create(1, 1, '09:30', '12:00')); + $this->assertNotFalse($w->create(1, 1, '13:00', '17:00')); + $this->assertNotFalse($w->create(1, 2, '09:30', '12:00')); + $this->assertNotFalse($w->create(1, 2, '13:00', '17:00')); + $this->assertNotFalse($w->create(1, 5, '09:30', '12:00')); + $this->assertNotFalse($w->create(1, 5, '13:00', '17:00')); + + $this->assertNotFalse($o->create(1, $tuesday->format('Y-m-d'), 0, '14:00', '15:00')); + $this->assertNotFalse($o->create(1, $monday->format('Y-m-d'), 1)); + + $timetable = $t->calculate(1, $monday, new DateTime('next Monday + 6 days')); + $this->assertNotEmpty($timetable); + $this->assertCount(5, $timetable); + + $this->assertEquals($tuesday->format('Y-m-d').' 09:30', $timetable[0][0]->format('Y-m-d H:i')); + $this->assertEquals($tuesday->format('Y-m-d').' 12:00', $timetable[0][1]->format('Y-m-d H:i')); + + $this->assertEquals($tuesday->format('Y-m-d').' 13:00', $timetable[1][0]->format('Y-m-d H:i')); + $this->assertEquals($tuesday->format('Y-m-d').' 14:00', $timetable[1][1]->format('Y-m-d H:i')); + + $this->assertEquals($tuesday->format('Y-m-d').' 15:00', $timetable[2][0]->format('Y-m-d H:i')); + $this->assertEquals($tuesday->format('Y-m-d').' 17:00', $timetable[2][1]->format('Y-m-d H:i')); + + $this->assertEquals($friday->format('Y-m-d').' 09:30', $timetable[3][0]->format('Y-m-d H:i')); + $this->assertEquals($friday->format('Y-m-d').' 12:00', $timetable[3][1]->format('Y-m-d H:i')); + + $this->assertEquals($friday->format('Y-m-d').' 13:00', $timetable[4][0]->format('Y-m-d H:i')); + $this->assertEquals($friday->format('Y-m-d').' 17:00', $timetable[4][1]->format('Y-m-d H:i')); + } + + public function testClosestTimeSlot() + { + $w = new TimetableWeek($this->container); + $t = new Timetable($this->container); + + $this->assertNotFalse($w->create(1, 1, '09:30', '12:00')); + $this->assertNotFalse($w->create(1, 1, '13:00', '17:00')); + $this->assertNotFalse($w->create(1, 2, '09:30', '12:00')); + $this->assertNotFalse($w->create(1, 2, '13:00', '17:00')); + + $monday = new DateTime('Monday'); + $tuesday = new DateTime('Tuesday'); + + $timetable = $t->calculate(1, new DateTime('Monday'), new DateTime('Monday + 6 days')); + $this->assertNotEmpty($timetable); + $this->assertCount(4, $timetable); + + // Start to work before timetable + $date = new DateTime('Monday'); + $date->setTime(5, 02); + + $slot = $t->findClosestTimeSlot($date, $timetable); + $this->assertNotEmpty($slot); + $this->assertEquals($monday->format('Y-m-d').' 09:30', $slot[0]->format('Y-m-d H:i')); + $this->assertEquals($monday->format('Y-m-d').' 12:00', $slot[1]->format('Y-m-d H:i')); + + // Start to work at the end of the timeslot + $date = new DateTime('Monday'); + $date->setTime(12, 02); + + $slot = $t->findClosestTimeSlot($date, $timetable); + $this->assertNotEmpty($slot); + $this->assertEquals($monday->format('Y-m-d').' 09:30', $slot[0]->format('Y-m-d H:i')); + $this->assertEquals($monday->format('Y-m-d').' 12:00', $slot[1]->format('Y-m-d H:i')); + + // Start to work at lunch time + $date = new DateTime('Monday'); + $date->setTime(12, 32); + + $slot = $t->findClosestTimeSlot($date, $timetable); + $this->assertNotEmpty($slot); + $this->assertEquals($monday->format('Y-m-d').' 13:00', $slot[0]->format('Y-m-d H:i')); + $this->assertEquals($monday->format('Y-m-d').' 17:00', $slot[1]->format('Y-m-d H:i')); + + // Start to work early in the morning + $date = new DateTime('Tuesday'); + $date->setTime(8, 02); + + $slot = $t->findClosestTimeSlot($date, $timetable); + $this->assertNotEmpty($slot); + $this->assertEquals($tuesday->format('Y-m-d').' 09:30', $slot[0]->format('Y-m-d H:i')); + $this->assertEquals($tuesday->format('Y-m-d').' 12:00', $slot[1]->format('Y-m-d H:i')); + } + + public function testCalculateDuration() + { + $w = new TimetableWeek($this->container); + $t = new Timetable($this->container); + + $this->assertNotFalse($w->create(1, 1, '09:30', '12:00')); + $this->assertNotFalse($w->create(1, 1, '13:00', '17:00')); + $this->assertNotFalse($w->create(1, 2, '09:30', '12:00')); + $this->assertNotFalse($w->create(1, 2, '13:00', '17:00')); + + // Different day + $start = new DateTime('Monday'); + $start->setTime(16, 02); + + $end = new DateTime('Tuesday'); + $end->setTime(10, 03); + + $this->assertEquals(1.5, $t->calculateEffectiveDuration(1, $start, $end)); + + // Same time slot + $start = new DateTime('Monday'); + $start->setTime(16, 02); + + $end = new DateTime('Monday'); + $end->setTime(17, 03); + + $this->assertEquals(1, $t->calculateEffectiveDuration(1, $start, $end)); + + // Intermediate time slot + $start = new DateTime('Monday'); + $start->setTime(10, 02); + + $end = new DateTime('Tuesday'); + $end->setTime(16, 03); + + $this->assertEquals(11.5, $t->calculateEffectiveDuration(1, $start, $end)); + + // Different day + $start = new DateTime('Monday'); + $start->setTime(9, 02); + + $end = new DateTime('Tuesday'); + $end->setTime(10, 03); + + $this->assertEquals(7, $t->calculateEffectiveDuration(1, $start, $end)); + + // Start before first time slot + $start = new DateTime('Monday'); + $start->setTime(5, 32); + + $end = new DateTime('Tuesday'); + $end->setTime(11, 17); + + $this->assertEquals(8.25, $t->calculateEffectiveDuration(1, $start, $end)); + } + + public function testCalculateDurationWithEmptyTimetable() + { + $t = new Timetable($this->container); + + $start = new DateTime('Monday'); + $start->setTime(16, 02); + + $end = new DateTime('Monday'); + $end->setTime(17, 03); + + $this->assertEquals(1, $t->calculateEffectiveDuration(1, $start, $end)); + } +} |