From 7f9f9bce1b598bcca6e26d86857e54d3eb895e52 Mon Sep 17 00:00:00 2001 From: Kevin MacMartin Date: Fri, 24 Apr 2020 00:22:42 -0400 Subject: [PATCH] Add support for linking dashboard tables, adding lists connected to other tables, formatted currency inputs, multi-line text fields without markdown, paginated edit lists (with working search), errors for columns not being unique or required (with modal popup), improve comments, and use links instead of javascript for the edit and new buttons --- app/Http/Controllers/DashboardController.php | 112 +++++++- app/Models/Blog.php | 10 +- app/Models/BlogTags.php | 14 + app/Models/DashboardModel.php | 132 ++++++++- .../2018_04_17_222542_add_blog_table.php | 1 - .../2020_04_23_234824_add_blog_tags_table.php | 35 +++ gulpfile.js | 3 +- package-lock.json | 5 + package.json | 1 + readme.md | 28 +- resources/js/dashboard.js | 180 +++++++++++-- resources/sass/dashboard.scss | 251 ++++++++++++++++++ .../views/dashboard/pages/edit-item.blade.php | 49 +++- .../views/dashboard/pages/edit-list.blade.php | 168 +++++++++--- 14 files changed, 893 insertions(+), 96 deletions(-) create mode 100644 app/Models/BlogTags.php create mode 100644 database/migrations/2020_04_23_234824_add_blog_tags_table.php diff --git a/app/Http/Controllers/DashboardController.php b/app/Http/Controllers/DashboardController.php index 8fbf9d7..1bc76aa 100644 --- a/app/Http/Controllers/DashboardController.php +++ b/app/Http/Controllers/DashboardController.php @@ -52,17 +52,23 @@ class DashboardController extends Controller { $model_class = Dashboard::getModel($model, 'edit'); if ($model_class != null) { + $data = $model_class::getDashboardData(true); + return view('dashboard.pages.edit-list', [ - 'heading' => $model_class::getDashboardHeading($model), - 'model' => $model, - 'rows' => $model_class::getDashboardData(), - 'display' => $model_class::$dashboard_display, - 'button' => $model_class::$dashboard_button, - 'sortcol' => $model_class::$dashboard_reorder ? $model_class::$dashboard_sort_column : false, - 'create' => $model_class::$create, - 'delete' => $model_class::$delete, - 'filter' => $model_class::$filter, - 'export' => $model_class::$export + 'heading' => $model_class::getDashboardHeading($model), + 'model' => $model, + 'rows' => $data['rows'], + 'paramdisplay' => $data['paramdisplay'], + 'query' => $model_class::getQueryString(), + 'display' => $model_class::$dashboard_display, + 'button' => $model_class::$dashboard_button, + 'idlink' => $model_class::$dashboard_id_link, + 'sortcol' => $model_class::$dashboard_reorder ? $model_class::$dashboard_sort_column : false, + 'paginate' => $model_class::$items_per_page !== 0, + 'create' => $model_class::$create, + 'delete' => $model_class::$delete, + 'filter' => $model_class::$filter, + 'export' => $model_class::$export ]); } else { abort(404); @@ -81,6 +87,13 @@ class DashboardController extends Controller { if ($model_class::where('id', $id)->exists()) { $item = $model_class::find($id); + foreach ($model_class::$dashboard_columns as $column) { + if ($column['type'] === 'list') { + $list_model_class = 'App\\Models\\' . $column['model']; + $item->{$column['name']} = $list_model_class::where($column['foreign'], $item->id)->orderBy($column['sort'])->get(); + } + } + if (is_null($item) || !$item->userCheck()) { return view('errors.no-such-record'); } @@ -181,12 +194,83 @@ class DashboardController extends Controller { } } - // populate the eloquent object with the remaining items in $request - foreach ($request['columns'] as $column) { - $item->$column = $request[$column]; + // check to ensure required columns have values + $empty = []; + + foreach ($model_class::$dashboard_columns as $column) { + if ($request->has($column['name']) && array_key_exists('required', $column) && $column['required'] && ($request[$column['name']] == '' || $request[$column['name']] == null)) { + if (array_key_exists('title', $column)) { + array_push($empty, "'" . $column['title'] . "'"); + } else { + array_push($empty, "'" . ucfirst($column['name']) . "'"); + } + } } - // save the new or updated item + if (count($empty) > 0) { + return 'required:' . implode(',', $empty); + } + + // check to ensure unique columns are unique + $not_unique = []; + + foreach ($model_class::$dashboard_columns as $column) { + if ($request->has($column['name']) && array_key_exists('unique', $column) && $column['unique'] && $model_class::where($column['name'], $request[$column['name']])->where('id', '!=', $item->id)->exists()) { + if (array_key_exists('title', $column)) { + array_push($not_unique, "'" . $column['title'] . "'"); + } else { + array_push($not_unique, "'" . ucfirst($column['name']) . "'"); + } + } + } + + if (count($not_unique) > 0) { + return 'not-unique:' . implode(',', $not_unique); + } + + // populate the eloquent object with the non-list items in $request + foreach ($request['columns'] as $column) { + if ($column['type'] !== 'list') { + $column_name = $column['name']; + $item->$column_name = $request[$column_name]; + } + } + + // save the item if it's new so we can access its id + if ($request['id'] == 'new') { + $item->save(); + } + + // populate connected lists with list items in $request + foreach ($request['columns'] as $column) { + if ($column['type'] === 'list') { + $column_name = $column['name']; + + foreach ($model_class::$dashboard_columns as $dashboard_column) { + if ($dashboard_column['name'] === $column_name) { + $foreign = $dashboard_column['foreign']; + $list_model_class = 'App\\Models\\' . $dashboard_column['model']; + $list_model_class::where($foreign, $item->id)->delete(); + + if ($request->has($column_name)) { + foreach ($request[$column_name] as $index => $row) { + $list_model_item = new $list_model_class; + $list_model_item->$foreign = $item->id; + $list_model_item->{$dashboard_column['sort']} = $index; + + foreach ($row as $key => $value) { + $list_model_item->$key = $value; + } + + $list_model_item->save(); + } + } + } + } + } + } + + // update the item $item->save(); // return the id number in the format '^id:[0-9][0-9]*$' on success diff --git a/app/Models/Blog.php b/app/Models/Blog.php index ae0bfaf..e927866 100644 --- a/app/Models/Blog.php +++ b/app/Models/Blog.php @@ -9,6 +9,8 @@ class Blog extends DashboardModel { protected $table = 'blog'; + public static $items_per_page = 10; + public static $dashboard_type = 'edit'; public static $dashboard_help_text = 'NOTE: Tags should be separated by semicolons'; @@ -18,10 +20,10 @@ class Blog extends DashboardModel public static $dashboard_columns = [ [ 'name' => 'user_id', 'type' => 'user' ], [ 'name' => 'created_at', 'title' => 'Date', 'type' => 'display' ], - [ 'name' => 'title', 'type' => 'text' ], - [ 'name' => 'body', 'type' => 'mkd' ], - [ 'name' => 'tags', 'type' => 'text' ], - [ 'name' => 'header-image', 'title' => 'Header Image', 'type' => 'image', 'delete' => true ] + [ 'name' => 'title', 'required' => true, 'unique' => true, 'type' => 'string' ], + [ 'name' => 'body', 'required' => true, 'type' => 'mkd' ], + [ 'name' => 'header-image', 'title' => 'Header Image', 'type' => 'image', 'delete' => true ], + [ 'name' => 'tags', 'type' => 'list', 'model' => 'BlogTags', 'columns' => [ 'name' ], 'foreign' => 'blog_id', 'sort' => 'order' ] ]; public static function getBlogEntries() diff --git a/app/Models/BlogTags.php b/app/Models/BlogTags.php new file mode 100644 index 0000000..ad94fd5 --- /dev/null +++ b/app/Models/BlogTags.php @@ -0,0 +1,14 @@ +where($display, 'LIKE', '%' . $term . '%'); + } else { + $query->orWhere($display, 'LIKE', '%' . $term . '%'); + } + + $first = false; + } + } + + return $query; + } else { + return []; + } + } + /** * Returns data for the dashboard * * @return array */ - public static function getDashboardData() + public static function getDashboardData($include_param_display = false) { $sort_direction = static::$dashboard_reorder ? 'desc' : static::$dashboard_sort_direction; $query = self::orderBy(static::$dashboard_sort_column, $sort_direction); + $query_param_display = []; foreach (static::$dashboard_columns as $column) { if (array_key_exists('type', $column) && $column['type'] == 'user') { @@ -147,7 +209,71 @@ class DashboardModel extends Model } } - return $query->get(); + if (count(static::$valid_query_params) > 0) { + foreach (static::$valid_query_params as $param) { + if (request()->query($param['key'], null) != null) { + if ($include_param_display) { + $query_param_model = 'App\\Models\\' . $param['model']; + $query_param_column = $query_param_model::find(request()->query($param['key'])); + + if ($query_param_column !== null) { + array_push($query_param_display, [ + 'title' => $param['title'], + 'value' => $query_param_column[$param['display']] + ]); + } + } + + $query->where($param['column'], request()->query($param['key'])); + } + } + } + + if (static::$items_per_page === 0) { + $results = $query->get(); + } else { + if (static::$filter && request()->query('search', null) != null) { + $query = static::searchDisplay(request()->query('search'), $query); + } + + $results = $query->paginate(static::$items_per_page); + } + + if ($include_param_display) { + return [ + 'rows' => $results, + 'paramdisplay' => $query_param_display + ]; + } else { + return $results; + } + } + + /** + * Retrieves the current query string containing valid query parameters + * + * @return string + */ + public static function getQueryString() + { + $valid_query_params = static::$valid_query_params; + $string = ''; + + if (static::$items_per_page !== 0 && static::$filter) { + array_push($valid_query_params, [ 'key' => 'search' ]); + } + + foreach ($valid_query_params as $param) { + if (request()->query($param['key'], null) != null) { + if ($string !== '') { + $string .= '&'; + } + + $string .= $param['key'] . '=' . request()->query($param['key']); + } + } + + return $string; } /** diff --git a/database/migrations/2018_04_17_222542_add_blog_table.php b/database/migrations/2018_04_17_222542_add_blog_table.php index 117cbc3..b227642 100644 --- a/database/migrations/2018_04_17_222542_add_blog_table.php +++ b/database/migrations/2018_04_17_222542_add_blog_table.php @@ -19,7 +19,6 @@ class AddBlogTable extends Migration $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); $table->string('title')->nullable(); $table->text('body')->nullable(); - $table->text('tags')->nullable(); $table->timestamps(); }); } diff --git a/database/migrations/2020_04_23_234824_add_blog_tags_table.php b/database/migrations/2020_04_23_234824_add_blog_tags_table.php new file mode 100644 index 0000000..e1e30df --- /dev/null +++ b/database/migrations/2020_04_23_234824_add_blog_tags_table.php @@ -0,0 +1,35 @@ +id(); + $table->bigInteger('blog_id')->unsigned(); + $table->foreign('blog_id')->references('id')->on('blog')->onDelete('cascade'); + $table->string('name')->nullable(); + $table->integer('order')->default(0); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::drop('blog_tags'); + } +} diff --git a/gulpfile.js b/gulpfile.js index 010e013..77d4b5b 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -63,7 +63,8 @@ const jsDashboardLibs = [ "node_modules/flatpickr/dist/flatpickr.js", "node_modules/sortablejs/Sortable.js", "node_modules/list.js/dist/list.js", - "node_modules/easymde/dist/easymde.min.js" + "node_modules/easymde/dist/easymde.min.js", + "node_modules/autonumeric/dist/autoNumeric.js" ]; // CSS libraries for the dashboard diff --git a/package-lock.json b/package-lock.json index def787f..03e1fdd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1232,6 +1232,11 @@ "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" }, + "autonumeric": { + "version": "4.5.13", + "resolved": "https://registry.npmjs.org/autonumeric/-/autonumeric-4.5.13.tgz", + "integrity": "sha512-GHpM0cGWTzFVDQyOgpXlNoqe8fDn6minpGEauLgDnGb4k24WmF8GInLnhIGWoJXisHZuWmTeKqFcIBnf5c2DaA==" + }, "autoprefixer": { "version": "9.7.6", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.7.6.tgz", diff --git a/package.json b/package.json index 1cdc40b..392cd14 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "@babel/core": "^7.9.0", "@babel/preset-env": "^7.9.5", "@fortawesome/fontawesome-free": "^5.13.0", + "autonumeric": "^4.5.13", "autoprefixer": "^9.7.6", "babelify": "^10.0.0", "bootstrap": "4.4.1", diff --git a/readme.md b/readme.md index 2080545..1543698 100644 --- a/readme.md +++ b/readme.md @@ -179,12 +179,15 @@ These are variables that only function when the `$dashboard_type` variable is se * `$dashboard_reorder`: A boolean determining whether to render drag handles to reorder the items in the list * `$dashboard_sort_column`: A string containing the column used to sort the list (this column should be an `integer` when `$dashboard_reorder` is true) * `$dashboard_sort_direction`: When `$dashboard_reorder` is false this determines the sort direction (this can be `desc` for descending or `asc` ascending) -* `$dashboard_button`: An array containing the following items in this order: +* `$dashboard_button`: Add a dashboard button with custom functionality by populating an array containing the following items in this order: * The title * Confirmation text asking the user to confirm * A "success" message to display when the response is `success` * A "failure" message to display when the response is not `success` * The URL to send the POST request to with the respective `id` in the request variable +* `$dashboard_id_link`: Add a dashboard button linking to another list filtered by the current item by populating an array containing the following items in this order: + * The title + * The URL to link to where the id will come after the rest ##### Configuring the columns @@ -198,15 +201,20 @@ All models use the following attributes: Models with their `$dashboard_type` set to `edit` also use: * `type`: The column type which can be any of the following: - * `text`: Text input field for text data - * `mkd`: Markdown editor for text data containing markdown - * `date`: Date and time selection tool for date/time data - * `select`: Text input via option select * `hidden`: Fields that will contain values to pass to the update function but won't appear on the page (this must be used for the sort column) + * `user`: This should point to a foreign key that references the id on the users table; setting this will bind items to the user that created them + * `string`: Single-line text input field + * `text`: Multi-line text input field + * `currency`: Text input field for currency data + * `date`: Date and time selection tool for date/time data + * `mkd`: Multi-line text input field with a markdown editor + * `select`: Text input via option select + * `list`: One or more items saved to a connected table * `image`: Fields that contain image uploads * `file`: Fields that contains file uploads * `display`: Displayed information that can't be edited - * `user`: This should point to a foreign key that references the id on the users table; setting this will bind items to the user that created them +* `required`: If set an error will be displayed if the field has no value +* `unique`: If set an error will be displayed if another row in the table has the same value for a given column * `type-new`: This takes the same options as `type` and overrides it when creating new items (eg: to allow input on a field during creation but not after) * `options` (required by `select`) Takes an array of options that are either strings or arrays containing the keys `title` (for what will display with the option) and `value` (for what will be recorded) * `name`: (required by `file` and `image`) Used along with the record id to determine the filename @@ -229,9 +237,9 @@ An example of the `$dashboard_columns` array in a model with its `$dashboard_typ public static $dashboard_columns = [ [ 'name' => 'user_id', 'type' => 'user' ], [ 'name' => 'created_at', 'title' => 'Date', 'type' => 'display' ], - [ 'name' => 'title', 'type' => 'text' ], - [ 'name' => 'body', 'type' => 'mkd' ], - [ 'name' => 'tags', 'type' => 'text' ], - [ 'name' => 'header-image', 'title' => 'Header Image', 'type' => 'image', 'delete' => true ] + [ 'name' => 'title', 'required' => true, 'unique' => true, 'type' => 'string' ], + [ 'name' => 'body', 'required' => true, 'type' => 'mkd' ], + [ 'name' => 'header-image', 'title' => 'Header Image', 'type' => 'image', 'delete' => true ], + [ 'name' => 'tags', 'type' => 'list', 'model' => 'BlogTags', 'columns' => [ 'name' ], 'foreign' => 'blog_id', 'sort' => 'order' ] ]; ``` diff --git a/resources/js/dashboard.js b/resources/js/dashboard.js index 578408a..4750a64 100644 --- a/resources/js/dashboard.js +++ b/resources/js/dashboard.js @@ -135,7 +135,7 @@ function showAlert(message, command) { $acceptButton.on("click", closeAlertModal); // set the message with the supplied message - $message.text(message); + $message.html(message); // show the alert modal $alertModal.css({ @@ -151,15 +151,6 @@ function editListInit() { $token = $("#token"), model = $editList.data("model"); - // initialize new button functionality - const newButtonInit = function() { - const $newButton = $(".btn.new-button"); - - $newButton.on("click", function() { - window.location.href = "/dashboard/edit/" + model + "/new"; - }); - }; - // initialize delete button functionality const deleteButtonInit = function() { const $deleteButtons = $(".btn.delete-button"); @@ -282,11 +273,32 @@ function editListInit() { } }; - newButtonInit(); + // initialize search functionality if the search-form element exists + const searchFormInit = function() { + const $form = $("#search-form"); + + if ($form.length) { + $form.on("submit", function(e) { + const term = $form.find(".search").val(); + + let url = $form.data("url"); + + e.preventDefault(); + + if (term !== "") { + url += `?search=${term}`; + } + + window.location.href = url; + }); + } + }; + deleteButtonInit(); actionButtonInit(); sortRowInit(); filterInputInit(); + searchFormInit(); } // initialize edit item functionality @@ -295,10 +307,12 @@ function editItemInit() { $submit = $("#submit"), $backButton = $("#back"), $textInputs = $(".text-input"), + $currencyInputs = $(".currency-input"), $datePickers = $(".date-picker"), $mkdEditors = $(".mkd-editor"), $fileUploads = $(".file-upload"), $imgUploads = $(".image-upload"), + $lists = $(".list"), $token = $("#token"), model = $editItem.data("model"), id = $editItem.data("id"), @@ -315,12 +329,12 @@ function editItemInit() { // fill the formData object with data from all the form fields const getFormData = function() { // function to add a column and value to the formData object - const addFormData = function(column, value) { + const addFormData = function(type, column, value) { // add the value to a key with the column name formData[column] = value; // add the column to the array of columns - formData.columns.push(column); + formData.columns.push({ type: type, name: column }); }; // reset the formData object @@ -334,31 +348,63 @@ function editItemInit() { // create an empty array to contain the list of columns formData.columns = []; - // add values from the contents of text-input class elements + // add values from the contents of text-input elements $textInputs.each(function() { const $this = $(this), column = $this.attr("id"), value = $this.val(); - addFormData(column, value); + addFormData("text", column, value); }); - // add values from the contents of date-picker class elements + // add values from the contents of date-picker elements $datePickers.each(function() { const $this = $(this), column = $this.attr("id"), value = $this.val(); - addFormData(column, value); + addFormData("date", column, value); }); - // add values from the contents of the markdown editor for mkd-editor class elements + // add values from the contents of currency-input elements + $currencyInputs.each(function() { + const $this = $(this), + column = $this.attr("id"), + value = AutoNumeric.getNumericString(this); + + addFormData("text", column, value); + }); + + // add values from the contents of the markdown editor for mkd-editor elements $mkdEditors.each(function() { const $this = $(this), column = $this.attr("id"), value = easymde[column].value(); - addFormData(column, value); + addFormData("text", column, value); + }); + + // add values from list-items inputs + $lists.each(function() { + const $this = $(this), + column = $this.attr("id"), + value = []; + + $this.find(".list-items .list-items-row").each(function(index, row) { + const rowData = {}; + + $(row).find(".list-items-row-input-inner").each(function(index, input) { + const $input = $(input), + column = $input.data("column"), + value = $input.val(); + + rowData[column] = value; + }); + + value.push(rowData); + }); + + addFormData("list", column, value); }); }; @@ -463,6 +509,7 @@ function editItemInit() { $submit.removeClass("no-input"); }; + // initialize image deletion $(".edit-button.delete.image").on("click", function(e) { const $this = $(this), name = $this.data("name"); @@ -497,6 +544,7 @@ function editItemInit() { } }); + // initialize file deletion $(".edit-button.delete.file").on("click", function(e) { const $this = $(this), name = $this.data("name"), @@ -533,21 +581,89 @@ function editItemInit() { } }); - // allow start time selection to start on the hour and every 15 minutes after + // initialize list item functionality + $lists.each(function(index, list) { + const $list = $(list), + $template = $list.find(".list-template"), + $items = $list.find(".list-items"); + + let sortable = undefined; + + const initSort = function() { + if (typeof sortable !== "undefined") { + sortable.destroy(); + } + + sortable = Sortable.create($items[0], { + handle: ".sort-icon", + onUpdate: contentChanged + }); + }; + + const initDelete = function() { + $items.find(".list-items-row").each(function(index, row) { + const $row = $(row); + + // initialize delete button functionality + $row.find(".list-items-row-button").off("click").on("click", function() { + $row.remove(); + initSort(); + contentChanged(); + }); + }); + }; + + const initList = function() { + $list.find(".list-data-row").each(function(rowIndex, row) { + // Add the values from the current data row to the template + $(row).find(".list-data-row-item").each(function(itemIndex, item) { + const $item = $(item), + column = $item.data("column"), + value = $item.data("value"); + + $template.find(".list-items-row-input-inner").each(function(inputIndex, input) { + const $input = $(input); + + if ($input.data("column") === column) { + $input.val(value); + } + }); + }); + + // Add the populated template to the list of items then clear the template values + $template.find(".list-items-row").clone().appendTo($items); + $template.find(".list-items-row-input-inner").val(""); + }); + + initSort(); + initDelete(); + }; + + $list.find(".list-add-button").on("click", function() { + $template.find(".list-items-row").clone().appendTo($items); + initDelete(); + initSort(); + contentChanged(); + }); + + initList(); + }); + + // allow the date picker start time selection to start on the hour and every 15 minutes after for (hours = 0; hours <= 23; hours++) { for (minutes = 0; minutes <= 3; minutes++) { allowTimes.push(hours + ":" + (minutes === 0 ? "00" : minutes * 15)); } } - // enable the datepicker for each element with the date-picker class + // enable the datepicker for date-picker elements $datePickers.each(function() { $(this).flatpickr({ enableTime: true }); }); - // enable the markdown editor for each element with the mkd-editor class + // enable the markdown editor for mkd-editor elements $mkdEditors.each(function() { const $this = $(this), column = $this.attr("id"); @@ -583,6 +699,14 @@ function editItemInit() { }, 500); }); + // enable currency formatting for currency-input elements + new AutoNumeric.multiple($currencyInputs.toArray(), { + currencySymbol: "$", + rawValueDivisor: 0.01, + allowDecimalPadding: false, + modifyValueOnWheel: false + }); + // watch for changes to input and select element contents $editItem.find("input, select").on("input change", contentChanged); @@ -617,12 +741,22 @@ function editItemInit() { url: "/dashboard/update", data: formData }).always(function(response) { + let message = ""; + if ((/^id:[0-9][0-9]*$/).test(response)) { uploadImage(response.replace(/^id:/, ""), 0); } else { loadingModal("hide"); - showAlert("Failed to " + operation + " record", function() { + if ((/^not-unique:/).test(response)) { + message = `${response.replace(/'/g, "").replace(/^[^:]*:/, "").replace(/,([^,]*)$/, " and $1").replace(/,/g, ", ")} must be unique`; + } else if ((/^required:/).test(response)) { + message = `${response.replace(/'/g, "").replace(/^[^:]*:/, "").replace(/,([^,]*)$/, " and $1").replace(/,/g, ", ")} must not be empty`; + } else { + message = `Failed to ${operation} record`; + } + + showAlert(message, function() { submitting = false; }); } diff --git a/resources/sass/dashboard.scss b/resources/sass/dashboard.scss index 12e1804..3a4db9b 100644 --- a/resources/sass/dashboard.scss +++ b/resources/sass/dashboard.scss @@ -271,6 +271,23 @@ body { } } + .search-form { + display: flex; + width: 100%; + + &-input { + display: block; + width: 100%; + } + + &-submit { + margin-left: 10px; + height: 32px; + border: 1px solid fade-out($c-text, 0.75); + border-radius: 4px; + } + } + .search { margin-bottom: 10px; width: 100%; @@ -478,6 +495,20 @@ body { &.btn-link { color: $c-dashboard-light; } + + &.btn-outline { + background-color: transparent; + color: $c-text; + } + + &.btn-inactive { + pointer-events: none; + } + + &.btn-disabled { + opacity: 0.5; + pointer-events: none; + } } .view-table-container { @@ -536,6 +567,123 @@ body { } } +.pagination-navigation-bar { + margin-bottom: 10px; + margin-left: 4px; + display: flex; + width: calc(100% - 8px); + + @include media-breakpoint-down(xs) { + flex-direction: column; + } + + @include media-breakpoint-up(sm) { + justify-content: space-between; + } + + &-arrow { + position: relative; + font-size: 0px; + + @include media-breakpoint-down(xs) { + height: 35px; + } + + &.prev { + @include media-breakpoint-down(xs) { + margin-bottom: 8px; + } + + @include media-breakpoint-up(sm) { + margin-right: ($grid-gutter-width / 2); + } + } + + &.next { + transform: rotate(180deg); + + @include media-breakpoint-down(xs) { + margin-top: 8px; + } + + @include media-breakpoint-up(sm) { + margin-left: ($grid-gutter-width / 2); + } + } + + &:before, &:after { + content: ""; + position: absolute; + top: calc(50% - 1px); + left: 25%; + width: 16px; + height: 2px; + background-color: $c-text-light; + + @include media-breakpoint-down(xs) { + left: 50%; + } + } + + &:before { + transform: rotate(-45deg); + transform-origin: bottom left; + } + + &:after { + transform: rotate(45deg); + transform-origin: top left; + } + } + + &-page-count { + display: flex; + + @include media-breakpoint-down(xs) { + justify-content: center; + } + + .btn { + $spacing: 3px; + + &:not(:first-child) { + margin-left: $spacing; + } + + &:not(:last-child) { + margin-right: $spacing; + } + + &.space { + position: relative; + + &:after { + content: "..."; + position: absolute; + top: 0px; + line-height: 25px; + } + + &:first-child { + margin-right: 20px; + + &:after { + left: calc(100% + 7px); + } + } + + &:last-child { + margin-left: 20px; + + &:after { + right: calc(100% + 7px); + } + } + } + } + } +} + .list-group { margin-bottom: 0px; @@ -715,6 +863,108 @@ form { } } + .list { + margin-bottom: 10px; + padding-bottom: 10px; + border-bottom: 1px solid fade-out($c-text, 0.75); + + &-template, &-data { + display: none; + } + + &-items-row { + margin-bottom: 10px; + display: flex; + align-items: center; + + .sort-icon { + margin-right: 10px; + display: inline-block; + opacity: 1; + transition: opacity 100ms; + cursor: grab; + + &-inner { + position: relative; + top: 2px; + display: inline-block; + width: 12px; + height: 14px; + + &-bar { + position: absolute; + left: 0px; + width: 100%; + height: 2px; + background-color: $c-text; + + &:nth-child(1) { + top: 2px; + } + + &:nth-child(2) { + top: 50%; + transform: translateY(-50%); + } + + &:nth-child(3) { + bottom: 2px; + } + } + } + } + + &-input, &-button { + &:not(:first-child) { + margin-left: 5px; + } + + &:not(:last-child) { + margin-right: 5px; + } + } + + &-input { + position: relative; + + &.wide { + width: 100%; + } + + &-inner { + margin-bottom: 0px; + } + + &-overlay { + overflow: hidden; + position: absolute; + top: 0px; + left: 0px; + width: 100%; + height: 100%; + padding: 5px 8px; + background-color: $c-input-bg; + white-space: nowrap; + text-overflow: ellipsis; + pointer-events: none; + } + } + + &-button { + min-width: 70px; + height: $label-height; + border: 1px solid fade-out($c-text, 0.75); + border-radius: 4px; + } + } + + &-add-button { + height: $label-height; + border: 1px solid fade-out($c-text, 0.75); + border-radius: 4px; + } + } + .picker__holder { overflow-y: hidden; @@ -1023,6 +1273,7 @@ form { .card { position: relative; margin: 0px; + width: 100%; max-width: 500px; .btn { diff --git a/resources/views/dashboard/pages/edit-item.blade.php b/resources/views/dashboard/pages/edit-item.blade.php index b2b3275..83ed1cc 100644 --- a/resources/views/dashboard/pages/edit-item.blade.php +++ b/resources/views/dashboard/pages/edit-item.blade.php @@ -27,13 +27,17 @@ @elseif($type == 'user') @elseif($type != 'display' || $id != 'new') -
+
-
- @if($type == 'text') +
+ @if($type == 'string') + @elseif($type == 'text') + + @elseif($type == 'currency') + @elseif($type == 'date') @elseif($type == 'mkd') @@ -58,6 +62,45 @@ @endif @endforeach + @elseif($type == 'list') +
+
+
+
+
+
+
+
+
+
+ + @foreach($column['columns'] as $list_column) + @set('placeholder', $column['name'] == 'included' || $column['name'] == 'recommended' ? '' : $list_column) + +
+ +
+ @endforeach + + +
+
+ +
+ @if($id != 'new') + @foreach($value as $row) +
+ @foreach($column['columns'] as $list_column) +
+ @endforeach +
+ @endforeach + @endif +
+ +
+ +
@elseif($type == 'image') @set('current_image', "/uploads/$model/img/$id-" . $column['name'] . '.jpg') diff --git a/resources/views/dashboard/pages/edit-list.blade.php b/resources/views/dashboard/pages/edit-list.blade.php index 787cecd..7e644c9 100644 --- a/resources/views/dashboard/pages/edit-list.blade.php +++ b/resources/views/dashboard/pages/edit-list.blade.php @@ -6,7 +6,7 @@ @endif @if($create) - + New @endif @endsection @@ -14,48 +14,142 @@
- @if($filter) - + @if(count($paramdisplay)) + @foreach($paramdisplay as $param) +
Showing {{ $heading }} with a {{ $param['title'] }} of "{{ $param['value'] }}"
+ @endforeach @endif -
    - @foreach($rows as $row) -
  • -
    - @if($sortcol != false) -
    -
    -
    -
    -
    + @if($filter) + @if(!$paginate) + + @else +
    + + + + +
    + @endif + @endif + + @if($paginate && $rows->lastPage() !== 1) +
    + + +
    + @set('pages_around', 2) + @set('start_page', $rows->currentPage() - $pages_around) + + @if($start_page < 1) + @set('start_page', 1) + @elseif($start_page + $pages_around > $rows->lastPage()) + @set('start_page', $rows->lastPage() - $pages_around) + @endif + + @if($start_page > 1) + + + 1 + + @endif + + @for($page = $start_page; $page < $start_page + 1 + $pages_around * 2; $page++) + @if($page === $rows->currentPage()) +
    {{ $page }}
    + @elseif($page <= $rows->lastPage()) + + + {{ $page }} + + @endif + @endfor + + @if($start_page + $pages_around * 2 < $rows->lastPage()) + + + {{ $rows->lastPage() }} + + @endif +
    + + +
    + @endif + + @if(request()->query('search', null) != null && count($rows) == 0) +
    No Matching {{ $heading }} Found
    + @else +
      + @foreach($rows as $row) +
    • +
      + @if($sortcol != false) +
      +
      +
      +
      +
      +
      -
      - @endif - - @foreach($display as $display_column) - @if($row[$display_column] != '') -
      {{ $row[$display_column] }}
      - - @if(!$loop->last) -
      |
      - @endif @endif - @endforeach -
    -
    - @if(!empty($button)) - - @endif + @foreach($display as $display_column) + @if($row[$display_column] != '') +
    {{ $row[$display_column] }}
    - Edit + @if(!$loop->last) +
    |
    + @endif + @endif + @endforeach +
    - @if($delete) - - @endif -
    -
  • - @endforeach -
+
+ @if(!empty($button)) + + @endif + + @if(!empty($idlink)) + {{ $idlink[0] }} + @endif + + Edit + + @if($delete) + + @endif +
+ + @endforeach + + @endif
@endsection