From 331788c0d3d27e7e8c59078670c6845a478c3bcf Mon Sep 17 00:00:00 2001 From: Skylar Ittner Date: Wed, 3 Jan 2018 22:30:24 -0700 Subject: [PATCH] Add jobs feature (close #4) --- action.php | 126 +++++++++++++++++++++++++++++ database.mwb | Bin 9289 -> 11792 bytes database.sql | 60 +++++++++++++- database_upgrade/1.0.1_1.1.sql | 47 +++++++++++ lang/en_us.php | 28 ++++++- lang/messages.php | 28 +++++++ lib/getjobhistorytable.php | 143 +++++++++++++++++++++++++++++++++ pages.php | 48 ++++++++++- pages/editjob.php | 104 ++++++++++++++++++++++++ pages/editjobhistory.php | 103 ++++++++++++++++++++++++ pages/editjobs.php | 45 +++++++++++ pages/jobs.php | 81 +++++++++++++++++++ static/css/app.css | 9 +++ static/js/editjobhistory.js | 17 ++++ static/js/editjobs.js | 34 ++++++++ static/js/jobs.js | 53 ++++++++++++ 16 files changed, 921 insertions(+), 5 deletions(-) create mode 100644 database_upgrade/1.0.1_1.1.sql create mode 100644 lib/getjobhistorytable.php create mode 100644 pages/editjob.php create mode 100644 pages/editjobhistory.php create mode 100644 pages/editjobs.php create mode 100644 pages/jobs.php create mode 100644 static/js/editjobhistory.js create mode 100644 static/js/editjobs.js create mode 100644 static/js/jobs.js diff --git a/action.php b/action.php index c489f8f..a8767ec 100644 --- a/action.php +++ b/action.php @@ -68,6 +68,8 @@ switch ($VARS['action']) { if (!$database->has('punches', ['AND' => ['uid' => $_SESSION['uid'], 'out' => null]])) { returnToSender("already_out"); } + // Stop active job + $database->update('job_tracking', ['end' => date("Y-m-d H:i:s")], ['AND' => ['uid' => $_SESSION['uid'], 'end' => null]]); $database->update('punches', ['uid' => $_SESSION['uid'], 'out' => date("Y-m-d H:i:s")], ['out' => null]); returnToSender("punched_out"); case "gettime": @@ -242,6 +244,130 @@ switch ($VARS['action']) { } returnToSender($removefailed ? "shift_assigned_removefailed" : "shift_assigned"); break; + case "setjob": + if ($database->count("job_groups") > 0) { + require_once __DIR__ . "/lib/userinfo.php"; + $groups = getGroupsByUID($_SESSION['uid']); + $gids = []; + foreach ($groups as $g) { + $gids[] = $g['id']; + } + $job = $database->has('jobs', ['[>]job_groups' => ['jobid']], ["AND" => ['groupid' => $gids, 'jobs.jobid' => $VARS['job']]]); + } else { + $job = $database->has('jobs', 'jobid', ['jobid' => $VARS['job']]); + } + if ($job) { + // Stop other jobs + $database->update('job_tracking', ['end' => date("Y-m-d H:i:s")], ['AND' => ['uid' => $_SESSION['uid'], 'end' => null]]); + $database->insert('job_tracking', ['uid' => $_SESSION['uid'], 'jobid' => $VARS['job'], 'start' => date("Y-m-d H:i:s")]); + returnToSender("job_changed"); + } else if ($VARS['job'] == "-1") { + $database->update('job_tracking', ['end' => date("Y-m-d H:i:s")], ['AND' => ['uid' => $_SESSION['uid'], 'end' => null]]); + returnToSender("job_changed"); + } else { + returnToSender("job_invalid"); + } + break; + case "editjob": + if (account_has_permission($_SESSION['username'], "QWIKCLOCK_ADMIN")) { + $name = htmlentities($VARS['jobname']); + $code = $VARS['jobcode']; + $color = $VARS['color']; + + if (is_empty($VARS['jobid'])) { + if ($database->has('jobs', ['jobname' => $name])) { + returnToSender("job_name_used"); + } + $database->insert('jobs', ["jobname" => $name, "jobcode" => $code, "color" => $color]); + returnToSender("job_added"); + } else if ($database->has('jobs', ['jobid' => $VARS['jobid']])) { + $database->update('jobs', ["jobname" => $name, "jobcode" => $code, "color" => $color], ["jobid" => $VARS['jobid']]); + returnToSender("job_saved"); + } else { + returnToSender("invalid_jobid"); + } + } else { + returnToSender("no_permission"); + } + break; + case "deletejob": + if (account_has_permission($_SESSION['username'], "QWIKCLOCK_ADMIN")) { + if (is_empty($VARS['jobid'])) { + returnToSender("invalid_jobid"); + } else if ($database->has('jobs', ['jobid' => $VARS['jobid']])) { + $database->update('jobs', ["deleted" => 1], ["jobid" => $VARS['jobid']]); + returnToSender("job_deleted"); + } else { + returnToSender("invalid_jobid"); + } + } else { + returnToSender("no_permission"); + } + break; + case "editjobhistory": + require_once __DIR__ . "/lib/userinfo.php"; + + if ($database->has('job_tracking', ['id' => $VARS['jobid']])) { + $uid = $database->get('job_tracking', 'uid', ['id' => $VARS['jobid']]); + } else { + returnToSender("invalid_parameters"); + } + + if (!$database->has("jobs", ['jobid' => $VARS['job']])) { + returnToSender("invalid_jobid"); + } + + $start = strtotime($VARS['start']); + $end = strtotime($VARS['end']); + if ($start === false) { + returnToSender("invalid_datetime"); + } + if ($end === false) { + returnToSender("invalid_datetime"); + } + if ($end < $start) { + returnToSender("in_before_out"); + } + + if ( + account_has_permission($_SESSION['username'], "QWIKCLOCK_ADMIN") || ( + account_has_permission($_SESSION['username'], "QWIKCLOCK_MANAGE") && isManagerOf($_SESSION['uid'], $uid) + ) || ( + account_has_permission($_SESSION['username'], "QWIKCLOCK_EDITSELF") && $_SESSION['uid'] == $uid + ) + ) { + $data = [ + "jobid" => $VARS['job'], + "start" => date('Y-m-d H:i:s', $start), + "end" => date('Y-m-d H:i:s', $end) + ]; + $database->update("job_tracking", $data, ["id" => $VARS['jobid']]); + returnToSender("job_saved"); + } else { + returnToSender("no_permission"); + } + case "deletejobhistory": + require_once __DIR__ . "/lib/userinfo.php"; + + if ($database->has('job_tracking', ['id' => $VARS['jobid']])) { + $uid = $database->get('job_tracking', 'uid', ['id' => $VARS['jobid']]); + } else { + returnToSender("invalid_parameters"); + } + + if ( + account_has_permission($_SESSION['username'], "QWIKCLOCK_ADMIN") || ( + account_has_permission($_SESSION['username'], "QWIKCLOCK_MANAGE") && isManagerOf($_SESSION['uid'], $uid) + ) || ( + account_has_permission($_SESSION['username'], "QWIKCLOCK_EDITSELF") && $_SESSION['uid'] == $uid + ) + ) { + + $database->delete("job_tracking", ["id" => $VARS['jobid']]); + returnToSender("job_deleted"); + } else { + returnToSender("no_permission"); + } case "autocomplete_user": header("Content-Type: application/json"); $client = new GuzzleHttp\Client(); diff --git a/database.mwb b/database.mwb index 398ab2f38e633230dff8a6c96f10d879402990eb..e114da2463e69f5ba6017e57a29c4dab134afb20 100644 GIT binary patch literal 11792 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 literal 9289 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 Gkp2fQc+Q6a diff --git a/database.sql b/database.sql index 7df9f63..3caafb8 100644 --- a/database.sql +++ b/database.sql @@ -1,5 +1,5 @@ -- MySQL Script generated by MySQL Workbench --- Mon 20 Nov 2017 08:04:01 PM MST +-- Wed 03 Jan 2018 07:18:58 PM MST -- Model: New Model Version: 1.0 -- MySQL Workbench Forward Engineering @@ -89,6 +89,64 @@ CREATE TABLE IF NOT EXISTS `qwikclock`.`report_access_codes` ( ENGINE = InnoDB; +-- ----------------------------------------------------- +-- Table `qwikclock`.`jobs` +-- ----------------------------------------------------- +DROP TABLE IF EXISTS `qwikclock`.`jobs` ; + +CREATE TABLE IF NOT EXISTS `qwikclock`.`jobs` ( + `jobid` INT NOT NULL AUTO_INCREMENT, + `jobname` VARCHAR(200) NOT NULL, + `jobcode` VARCHAR(200) NULL, + `color` VARCHAR(45) NULL, + `deleted` TINYINT(1) NOT NULL DEFAULT 0, + PRIMARY KEY (`jobid`), + UNIQUE INDEX `jobid_UNIQUE` (`jobid` ASC)) +ENGINE = InnoDB; + + +-- ----------------------------------------------------- +-- Table `qwikclock`.`job_groups` +-- ----------------------------------------------------- +DROP TABLE IF EXISTS `qwikclock`.`job_groups` ; + +CREATE TABLE IF NOT EXISTS `qwikclock`.`job_groups` ( + `id` INT NOT NULL AUTO_INCREMENT, + `groupid` VARCHAR(45) NOT NULL, + `jobid` INT NOT NULL, + PRIMARY KEY (`id`, `groupid`, `jobid`), + UNIQUE INDEX `id_UNIQUE` (`id` ASC), + INDEX `fk_job_groups_jobs1_idx` (`jobid` ASC), + CONSTRAINT `fk_job_groups_jobs1` + FOREIGN KEY (`jobid`) + REFERENCES `qwikclock`.`jobs` (`jobid`) + ON DELETE NO ACTION + ON UPDATE NO ACTION) +ENGINE = InnoDB; + + +-- ----------------------------------------------------- +-- Table `qwikclock`.`job_tracking` +-- ----------------------------------------------------- +DROP TABLE IF EXISTS `qwikclock`.`job_tracking` ; + +CREATE TABLE IF NOT EXISTS `qwikclock`.`job_tracking` ( + `id` INT NOT NULL AUTO_INCREMENT, + `uid` INT NOT NULL, + `jobid` INT NOT NULL, + `start` DATETIME NULL, + `end` DATETIME NULL, + PRIMARY KEY (`id`, `uid`, `jobid`), + INDEX `fk_job_tracking_jobs1_idx` (`jobid` ASC), + UNIQUE INDEX `id_UNIQUE` (`id` ASC), + CONSTRAINT `fk_job_tracking_jobs1` + FOREIGN KEY (`jobid`) + REFERENCES `qwikclock`.`jobs` (`jobid`) + ON DELETE NO ACTION + ON UPDATE NO ACTION) +ENGINE = InnoDB; + + SET SQL_MODE=@OLD_SQL_MODE; SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS; SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS; diff --git a/database_upgrade/1.0.1_1.1.sql b/database_upgrade/1.0.1_1.1.sql new file mode 100644 index 0000000..5777f8f --- /dev/null +++ b/database_upgrade/1.0.1_1.1.sql @@ -0,0 +1,47 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +CREATE TABLE IF NOT EXISTS `jobs` ( + `jobid` INT(11) NOT NULL AUTO_INCREMENT, + `jobname` VARCHAR(200) NOT NULL, + `jobcode` VARCHAR(200) NULL DEFAULT NULL, + `color` VARCHAR(45) NULL DEFAULT NULL, + `deleted` TINYINT(1) NOT NULL DEFAULT 0, + PRIMARY KEY (`jobid`), + UNIQUE INDEX `jobid_UNIQUE` (`jobid` ASC)) +ENGINE = InnoDB +DEFAULT CHARACTER SET = utf8; + +CREATE TABLE IF NOT EXISTS `job_groups` ( + `id` INT(11) NOT NULL AUTO_INCREMENT, + `groupid` VARCHAR(45) NOT NULL, + `jobid` INT(11) NOT NULL, + PRIMARY KEY (`id`, `groupid`, `jobid`), + UNIQUE INDEX `id_UNIQUE` (`id` ASC), + INDEX `fk_job_groups_jobs1_idx` (`jobid` ASC), + CONSTRAINT `fk_job_groups_jobs1` + FOREIGN KEY (`jobid`) + REFERENCES `qwikclock`.`jobs` (`jobid`) + ON DELETE NO ACTION + ON UPDATE NO ACTION) +ENGINE = InnoDB +DEFAULT CHARACTER SET = utf8; + +CREATE TABLE IF NOT EXISTS `job_tracking` ( + `id` INT(11) NOT NULL AUTO_INCREMENT, + `uid` INT(11) NOT NULL, + `jobid` INT(11) NOT NULL, + `start` DATETIME NULL DEFAULT NULL, + `end` DATETIME NULL DEFAULT NULL, + PRIMARY KEY (`id`, `uid`, `jobid`), + INDEX `fk_job_tracking_jobs1_idx` (`jobid` ASC), + UNIQUE INDEX `id_UNIQUE` (`id` ASC), + CONSTRAINT `fk_job_tracking_jobs1` + FOREIGN KEY (`jobid`) + REFERENCES `qwikclock`.`jobs` (`jobid`) + ON DELETE NO ACTION + ON UPDATE NO ACTION) +ENGINE = InnoDB +DEFAULT CHARACTER SET = utf8; diff --git a/lang/en_us.php b/lang/en_us.php index 37741d4..aa040a0 100644 --- a/lang/en_us.php +++ b/lang/en_us.php @@ -32,6 +32,7 @@ define("STRINGS", [ "login server user data error" => "The login server refused to provide account information. Try again or contact technical support.", "captcha error" => "There was a problem with the CAPTCHA (robot test). Try again.", "home" => "Home", + "more" => "More", "punch in out" => "Punch In/Out", "punch in" => "Punch in", "punch out" => "Punch out", @@ -42,6 +43,7 @@ define("STRINGS", [ "punched in" => "You are now on the clock.", "punched out" => "You are now off the clock.", "punch card" => "Punch Card", + "punches" => "Punches", "in" => "In", "out" => "Out", "notes" => "Notes", @@ -56,6 +58,7 @@ define("STRINGS", [ "show all punches" => "Show other users", "name" => "Name", "start" => "Start", + "stop" => "Stop", "end" => "End", "days" => "Days", "sunday" => "Sunday", @@ -86,7 +89,7 @@ define("STRINGS", [ "choose a shift" => "Choose a shift", "shift assigned" => "Shift assigned.", "shift assigned but removal failed" => "Shift assigned successfully, but one or more users are not managed by you and were not removed.", - "report export" => "Reports/Export", + "reports" => "Reports", "report type" => "Report type", "format" => "Format", "generate report" => "Generate report", @@ -121,5 +124,26 @@ define("STRINGS", [ "punch deleted" => "Punch deleted.", "hours" => "Hours", "hours:minutes" => "H:MM", - "totals" => "Totals" + "totals" => "Totals", + "select a job" => "Select a Job", + "jobs" => "Jobs", + "job history" => "Job History", + "job changed" => "Job changed.", + "edit jobs" => "Edit Jobs", + "assign job" => "Assign Job", + "none" => "None", + "job" => "Job", + "add job" => "Add Job", + "code" => "Code", + "new job" => "New Job", + "edit job" => "Edit Job", + "save" => "Save", + "color" => "Color", + "delete" => "Delete", + "job name in use" => "Job name already used. Pick another.", + "invalid job" => "Invalid job", + "job saved" => "Job saved.", + "job added" => "Job added.", + "job deleted" => "Job deleted.", + "show all" => "Show all", ]); \ No newline at end of file diff --git a/lang/messages.php b/lang/messages.php index 4b6f43b..a4016a7 100644 --- a/lang/messages.php +++ b/lang/messages.php @@ -106,4 +106,32 @@ define("MESSAGES", [ "string" => "punch deleted", "type" => "success" ], + "job_changed" => [ + "string" => "job changed", + "type" => "success" + ], + "job_invalid" => [ + "string" => "invalid job", + "type" => "danger" + ], + "job_added" => [ + "string" => "job added", + "type" => "success" + ], + "job_saved" => [ + "string" => "job saved", + "type" => "success" + ], + "job_deleted" => [ + "string" => "job deleted", + "type" => "success" + ], + "job_name_used" => [ + "string" => "job name in use", + "type" => "danger" + ], + "invalid_jobid" => [ + "string" => "invalid job", + "type" => "danger" + ], ]); diff --git a/lib/getjobhistorytable.php b/lib/getjobhistorytable.php new file mode 100644 index 0000000..59307d7 --- /dev/null +++ b/lib/getjobhistorytable.php @@ -0,0 +1,143 @@ +count('job_tracking'); +} else { + $out['recordsTotal'] = $database->count('job_tracking', ['uid' => $managed_uids]); +} + +$filter = false; + +// sort +$order = null; +$sortby = "DESC"; +if ($VARS['order'][0]['dir'] == 'asc') { + $sortby = "ASC"; +} +switch ($VARS['order'][0]['column']) { + case 2: + $order = ["jobname" => $sortby]; + break; + case 3: + $order = ["start" => $sortby]; + break; + case 4: + $order = ["end" => $sortby]; + break; +} + +// search +if (!is_empty($VARS['search']['value'])) { + $filter = true; + $wherenolimit = [ + "AND" => [ + "OR" => [ + "jobname[~]" => $VARS['search']['value'], + "jobcode[~]" => $VARS['search']['value'], + "start[~]" => $VARS['search']['value'], + "end[~]" => $VARS['search']['value'], + ], + "uid" => $managed_uids + ] + ]; + if ($managed_uids !== false) { + $where["AND"]["uid"] = $managed_uids; + } + $where = $wherenolimit; + $where["LIMIT"] = [$VARS['start'], $VARS['length']]; +} else { + $where = ["LIMIT" => [$VARS['start'], $VARS['length']]]; + if ($managed_uids !== false) { + $where["uid"] = $managed_uids; + } +} + +if (!is_null($order)) { + $where["ORDER"] = $order; +} + + +$jobs = $database->select('job_tracking', ['[>]jobs' => ['jobid']], [ + 'id', + 'job_tracking.jobid', + 'uid', + 'start', + 'end', + 'jobname', + 'jobcode', + 'color', + 'deleted' + ], $where); + +$usercache = []; + +$editself = account_has_permission($_SESSION['username'], "QWIKCLOCK_EDITSELF"); + +for ($i = 0; $i < count($jobs); $i++) { + // Get user info + if (!isset($usercache[$jobs[$i]['uid']])) { + $usercache[$jobs[$i]['uid']] = getUserByID($jobs[$i]['uid']); + } + + $jobs[$i][0] = ""; + if ($_SESSION['uid'] == $jobs[$i]['uid']) { + if ($editself) { + $jobs[$i][1] = ' ' . lang("edit", false) . ''; + } else { + $jobs[$i][1] = ""; + } + } else if ($showmanaged) { + $jobs[$i][1] = ' ' . lang("edit", false) . ''; + } else { + $jobs[$i][1] = ""; + } + $jobs[$i][2] = '   ' . ($jobs[$i]['deleted'] == 1 ? "" : "") . $jobs[$i]['jobname'] . ($jobs[$i]['deleted'] == 1 ? "" : ""); + $jobs[$i][3] = date(DATETIME_FORMAT, strtotime($jobs[$i]['start'])); + if (is_null($jobs[$i]['end'])) { + $jobs[$i][4] = lang("na", false); + } else { + $jobs[$i][4] = date(DATETIME_FORMAT, strtotime($jobs[$i]['end'])); + } + $jobs[$i][5] = $usercache[$jobs[$i]['uid']]['name']; +} + +$out['status'] = "OK"; +if ($filter) { + $recordsFiltered = $database->count('job_tracking', ['[>]jobs' => ['jobid']], 'job_tracking.id', $wherenolimit); +} else { + $recordsFiltered = $out['recordsTotal']; +} +$out['recordsFiltered'] = $recordsFiltered; +$out['data'] = $jobs; + +echo json_encode($out); diff --git a/pages.php b/pages.php index 28fcb79..2f720a3 100644 --- a/pages.php +++ b/pages.php @@ -19,7 +19,7 @@ define("PAGES", [ "title" => "404 error" ], "punches" => [ - "title" => "punch card", + "title" => "punches", "navbar" => true, "icon" => "clock-o", "styles" => [ @@ -31,6 +31,50 @@ define("PAGES", [ "static/js/punches.js" ] ], + "jobs" => [ + "title" => "jobs", + "navbar" => true, + "icon" => "briefcase", + "styles" => [ + "static/css/datatables.min.css", + "static/css/tables.css" + ], + "scripts" => [ + "static/js/datatables.min.js", + "static/js/jobs.js" + ] + ], + "editjobs" => [ + "title" => "jobs", + "navbar" => false, + "icon" => "briefcase", + "styles" => [ + "static/css/datatables.min.css", + "static/css/tables.css" + ], + "scripts" => [ + "static/js/datatables.min.js", + "static/js/editjobs.js" + ] + ], + "editjob" => [ + "title" => "edit job", + "navbar" => false + ], + "editjobhistory" => [ + "title" => "edit job", + "navbar" => false, + "styles" => [ + "static/css/bootstrap-datetimepicker.min.css", + "static/css/easy-autocomplete.min.css" + ], + "scripts" => [ + "static/js/moment.min.js", + "static/js/bootstrap-datetimepicker.min.js", + "static/js/jquery.easy-autocomplete.min.js", + "static/js/editjobhistory.js" + ] + ], "shifts" => [ "title" => "shifts", "navbar" => true, @@ -71,7 +115,7 @@ define("PAGES", [ ] ], "export" => [ - "title" => "report export", + "title" => "reports", "navbar" => true, "icon" => "download", "styles" => [ diff --git a/pages/editjob.php b/pages/editjob.php new file mode 100644 index 0000000..7ae42ff --- /dev/null +++ b/pages/editjob.php @@ -0,0 +1,104 @@ + "", + "jobname" => "", + "jobcode" => "", + "color" => "" +]; + +$editing = false; +if (isset($VARS['job']) && $database->has('jobs', ['jobid' => $VARS['job']])) { + $editing = true; + + $data = $database->get('jobs', [ + "jobid", + "jobname", + "jobcode", + "color", + ], [ + 'jobid' => $VARS['job'] + ]); +} +?> + +
+
+
+

+ + + + + +

+
+
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + + +
+
+
+
+ + + + + + +
+
\ No newline at end of file diff --git a/pages/editjobhistory.php b/pages/editjobhistory.php new file mode 100644 index 0000000..ab81faf --- /dev/null +++ b/pages/editjobhistory.php @@ -0,0 +1,103 @@ + "", + "start" => "", + "end" => "", + "jobid" => "", +]; + +$editing = false; +if (isset($VARS['job']) && $database->has('job_tracking', ['id' => $VARS['job']])) { + $editing = true; + + $data = $database->get('job_tracking', [ + "id", + "start", + "end", + "jobid" + ], [ + 'id' => $VARS['job'] + ]); +} +?> + +
+
+
+

+ + + + + +

+
+
+
+
+
+ + +
+
+
+
+ + " required /> +
+
+
+
+ + " /> +
+
+
+
+ + + + + + +
+
\ No newline at end of file diff --git a/pages/editjobs.php b/pages/editjobs.php new file mode 100644 index 0000000..574785b --- /dev/null +++ b/pages/editjobs.php @@ -0,0 +1,45 @@ + +
+ + + +
+ + + + + + + + + + + + select('jobs', ['jobid (id)', 'jobname (name)', 'jobcode (code)', 'color'], ['deleted' => 0]); + foreach ($jobs as $j) { + echo ""; + } + ?> + + + + + + + + + +
" . ' ' . lang("edit", false) . '' . "" . '   ' . $j['name'] . "" . $j['code'] . "
\ No newline at end of file diff --git a/pages/jobs.php b/pages/jobs.php new file mode 100644 index 0000000..8a3f90a --- /dev/null +++ b/pages/jobs.php @@ -0,0 +1,81 @@ + +
+ + + +
+ + +
+ count("job_groups") > 0) { + require_once __DIR__ . "/../lib/userinfo.php"; + $groups = getGroupsByUID($_SESSION['uid']); + $gids = []; + foreach ($groups as $g) { + $gids[] = $g['id']; + } + $jobs = $database->select('jobs', ['[>]job_groups' => ['jobid']], ['jobs.jobid', 'jobname', 'jobcode', 'color'], ["AND" => ['groupid' => $gids, 'deleted' => 0]]); + } else { + $jobs = $database->select('jobs', ['jobid', 'jobname', 'jobcode', 'color'], ['deleted' => 0]); + } + + foreach ($jobs as $job) { + $color = "default"; + if (!is_null($job['color']) && $job['color'] != "") { + $color = $job['color']; + } + ?> + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ \ No newline at end of file diff --git a/static/css/app.css b/static/css/app.css index 7a1c42e..6c3bafd 100644 --- a/static/css/app.css +++ b/static/css/app.css @@ -90,6 +90,15 @@ color: red; } +#job-btn-bin { + display: flex; + flex-wrap: wrap; +} + +.job-btn { + margin: 5px 5px; +} + /* ============================== THEMING diff --git a/static/js/editjobhistory.js b/static/js/editjobhistory.js new file mode 100644 index 0000000..3e597a6 --- /dev/null +++ b/static/js/editjobhistory.js @@ -0,0 +1,17 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + + +$(function () { + $('#start').datetimepicker({ + format: "ddd MMMM D YYYY h:mm a", + useCurrent: false + }); + $('#end').datetimepicker({ + format: "ddd MMMM D YYYY h:mm a", + useCurrent: false + }); +}); \ No newline at end of file diff --git a/static/js/editjobs.js b/static/js/editjobs.js new file mode 100644 index 0000000..ef72adb --- /dev/null +++ b/static/js/editjobs.js @@ -0,0 +1,34 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var jobtable = $('#jobtable').DataTable({ + responsive: { + details: { + display: $.fn.dataTable.Responsive.display.modal({ + header: function (row) { + var data = row.data(); + return " " + data[1]; + } + }), + renderer: $.fn.dataTable.Responsive.renderer.tableAll({ + tableClass: 'table' + }), + type: "column" + } + }, + columnDefs: [ + { + targets: 0, + className: 'control', + orderable: false + }, + { + targets: 1, + orderable: false + } + ], + order: [ + [2, 'desc'] + ] +}); \ No newline at end of file diff --git a/static/js/jobs.js b/static/js/jobs.js new file mode 100644 index 0000000..4a4d3d8 --- /dev/null +++ b/static/js/jobs.js @@ -0,0 +1,53 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var jobtable = $('#jobtable').DataTable({ + responsive: { + details: { + display: $.fn.dataTable.Responsive.display.modal({ + header: function (row) { + var data = row.data(); + return " " + data[1]; + } + }), + renderer: $.fn.dataTable.Responsive.renderer.tableAll({ + tableClass: 'table' + }), + type: "column" + } + }, + columnDefs: [ + { + targets: 0, + className: 'control', + orderable: false + }, + { + targets: 1, + orderable: false + }, + { + targets: 5, + orderable: false + } + ], + order: [ + [3, 'desc'] + ], + serverSide: true, + ajax: { + url: "lib/getjobhistorytable.php", + data: function (d) { + if ($('#show_all_checkbox').is(':checked')) { + d.show_all = 1; + } + }, + } +}); + +$('#jobtable_filter').append("
"); + +$('#show_all_checkbox').click(function () { + jobtable.ajax.reload(); +}); \ No newline at end of file