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