diff --git a/.env.example b/.env.example index 1329598..365288c 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,6 @@ DEFAULT_LANGUAGE=en -APP_NAME='Hypothetical Template' +APP_NAME='Hypothetical' APP_DESC='A website template' APP_ENV=local APP_KEY= diff --git a/app/Http/Controllers/DashboardController.php b/app/Http/Controllers/DashboardController.php index 3a39707..b7a6258 100644 --- a/app/Http/Controllers/DashboardController.php +++ b/app/Http/Controllers/DashboardController.php @@ -6,9 +6,7 @@ use File; use Image; use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Writer\Xlsx; - -use App\Models\Contact; -use App\Models\Subscriptions; +use App\Models\Dashboard; class DashboardController extends Controller { @@ -37,61 +35,177 @@ class DashboardController extends Controller { /** * Dashboard View */ - public function getContact() + public function getView($model) { - return view('dashboard.view', [ - 'heading' => 'Contact Form Submissions', - 'model' => 'contact', - 'rows' => Contact::getContactSubmissions(), - 'columns' => Contact::$dashboard_columns - ]); - } + $model_class = Dashboard::getModel($model, 'view'); - public function getSubscriptions() - { - return view('dashboard.view', [ - 'heading' => 'Subscriptions', - 'model' => 'subscriptions', - 'rows' => Subscriptions::getSubscriptions(), - 'columns' => Subscriptions::$dashboard_columns - ]); + if ($model_class != null) { + return view('dashboard.view', [ + 'heading' => $model_class::getDashboardHeading($model), + 'column_headings' => $model_class::getDashboardColumnData('headings'), + 'model' => $model, + 'rows' => $model_class::getDashboardData(), + 'columns' => $model_class::$dashboard_columns + ]); + } else { + abort(404); + } } /** - * Dashboard Edit + * Dashboard Edit List */ + public function getEditList($model) + { + $model_class = Dashboard::getModel($model, 'edit'); + + if ($model_class != null) { + return view('dashboard.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 + ]); + } else { + abort(404); + } + } + + /** + * Dashboard Edit Item + */ + public function getEditItem($model, $id = 'new') + { + $model_class = Dashboard::getModel($model, 'edit'); + + if ($model_class != null) { + if ($id == 'new') { + $item = null; + } else { + if ($model_class::where('id', $id)->exists()) { + $item = $model_class::find($id); + + if (is_null($item) || !$item->userCheck()) { + return view('errors.no-such-record'); + } + } else { + return view('errors.no-such-record'); + } + } + + return view('dashboard.edit-item', [ + 'heading' => $model_class::getDashboardHeading($model), + 'model' => $model, + 'id' => $id, + 'item' => $item, + 'help_text' => $model_class::$dashboard_help_text, + 'columns' => $model_class::$dashboard_columns + ]); + } else { + abort(404); + } + } /** * Dashboard Export: Export data as a spreadsheet */ public function getExport($model) { - // set the filename of the spreadsheet - $filename = preg_replace([ '/\ /', '/[^a-z0-9\-]/' ], [ '-', '' ], strtolower(env('APP_NAME'))) . '-' . $model . '-' . date('m-d-Y'); + $model_class = Dashboard::getModel($model); - // set the model using the 'model' request argument - switch ($model) { - case 'contact': - $headings = [ 'Date', 'Name', 'Email', 'Message' ]; - $items = Contact::select('created_at', 'name', 'email', 'message')->get()->toArray(); - break; - case 'subscriptions': - $headings = [ 'Date', 'Email', 'Name' ]; - $items = Subscriptions::select('created_at', 'email', 'name')->get()->toArray(); - break; - default: - abort(404); + if ($model_class != null && $model_class::$export) { + $filename = preg_replace([ '/\ /', '/[^a-z0-9\-]/' ], [ '-', '' ], strtolower(env('APP_NAME'))) . '-' . $model . '-' . date('m-d-Y'); + $headings = $model_class::getDashboardColumnData('headings', false); + $items = $model_class::select($model_class::getDashboardColumnData('names', false))->get()->toArray(); + array_unshift($items, $headings); + $spreadsheet = new Spreadsheet(); + $spreadsheet->getActiveSheet()->getDefaultColumnDimension()->setWidth(25); + $spreadsheet->getActiveSheet()->fromArray($items, NULL, 'A1'); + $writer = new Xlsx($spreadsheet); + header('Content-Type: application/vnd.ms-excel'); + header('Content-Disposition: attachment;filename="' . $filename . '"'); + header('Cache-Control: max-age=0'); + $writer->save('php://output'); + } else { + abort(404); } + } - array_unshift($items, $headings); - $spreadsheet = new Spreadsheet(); - $spreadsheet->getActiveSheet()->getDefaultColumnDimension()->setWidth(25); - $spreadsheet->getActiveSheet()->fromArray($items, NULL, 'A1'); - $writer = new Xlsx($spreadsheet); - header('Content-Type: application/vnd.ms-excel'); - header('Content-Disposition: attachment;filename="' . $filename . '"'); - header('Cache-Control: max-age=0'); - $writer->save('php://output'); + /** + * Dashboard Reorder: Reorder rows + */ + public function postReorder(Request $request) + { + $this->validate($request, [ + 'order' => 'required', + 'column' => 'required', + 'model' => 'required' + ]); + + $model_class = Dashboard::getModel($request['model'], 'edit'); + + if ($model_class != null) { + $order = $request['order']; + $column = $request['column']; + + // update each row with the new order + foreach (array_keys($order) as $order_id) { + $item = $model_class::find($order_id); + $item->$column = $order[$order_id]; + $item->save(); + } + + return 'success'; + } else { + return 'model-access-fail'; + } + } + + /** + * Dashboard Update: Create and update rows + */ + public function postUpdate(Request $request) + { + $this->validate($request, [ + 'id' => 'required', + 'model' => 'required', + 'columns' => 'required' + ]); + + $model_class = Dashboard::getModel($request['model'], 'edit'); + + if ($model_class != null) { + if ($request['id'] == 'new') { + $item = new $model_class; + } else { + $item = $model_class::find($request['id']); + + if (is_null($item)) { + return 'record-access-fail'; + } else if (!$item->userCheck()) { + return 'permission-fail'; + } + } + + // populate the eloquent object with the remaining items in $request + foreach ($request['columns'] as $column) { + $item->$column = $request[$column]; + } + + // save the new or updated item + $item->save(); + + // return the id number in the format '^id:[0-9][0-9]*$' on success + return 'id:' . $item->id; + } else { + return 'model-access-fail'; + } } /** @@ -105,16 +219,28 @@ class DashboardController extends Controller { 'name' => 'required' ]); - if ($request->hasFile('file')) { - $directory = base_path() . '/public/uploads/' . $request['model'] . '/img/'; - file::makeDirectory($directory, 0755, true, true); - $image = Image::make($request->file('file')); - $image->save($directory . $request['id'] . '-' . $request['name'] . '.jpg'); - } else { - return 'file-upload-fail'; - } + $model_class = Dashboard::getModel($request['model'], 'edit'); - return 'success'; + if ($model_class != null) { + $item = $model_class::find($request['id']); + + if (is_null($item)) { + return 'record-access-fail'; + } else if (!$item->userCheck()) { + return 'permission-fail'; + } else if ($request->hasFile('file')) { + $directory = base_path() . '/public/uploads/' . $request['model'] . '/img/'; + file::makeDirectory($directory, 0755, true, true); + $image = Image::make($request->file('file')); + $image->save($directory . $request['id'] . '-' . $request['name'] . '.jpg'); + } else { + return 'file-upload-fail'; + } + + return 'success'; + } else { + return 'model-access-fail'; + } } /** @@ -129,77 +255,27 @@ class DashboardController extends Controller { 'ext' => 'required' ]); - if ($request->hasFile('file')) { - $directory = base_path() . '/public/uploads/' . $request['model'] . '/files/'; - file::makeDirectory($directory, 0755, true, true); - $request->file('file')->move($directory, $request['id'] . '-' . $request['name'] . '.' . $request['ext']); + $model_class = Dashboard::getModel($request['model'], 'edit'); + + if ($model_class != null) { + $item = $model_class::find($request['id']); + + if (is_null($item)) { + return 'record-access-fail'; + } else if (!$item->userCheck()) { + return 'permission-fail'; + } else if ($request->hasFile('file')) { + $directory = base_path() . '/public/uploads/' . $request['model'] . '/files/'; + file::makeDirectory($directory, 0755, true, true); + $request->file('file')->move($directory, $request['id'] . '-' . $request['name'] . '.' . $request['ext']); + } else { + return 'file-upload-fail'; + } + + return 'success'; } else { - return 'file-upload-fail'; + return 'model-access-fail'; } - - return 'success'; - } - - /** - * Dashboard Edit: Create and edit rows - */ - public function postEdit(Request $request) - { - $this->validate($request, [ - 'id' => 'required', - 'model' => 'required', - 'columns' => 'required' - ]); - - // store the id request variable for easy access - $id = $request['id']; - - // set the model using the 'model' request argument - switch ($request['model']) { - default: - return 'model-access-fail'; - } - - // populate the eloquent object with the remaining items in $request - foreach ($request['columns'] as $column) { - $item->$column = $request[$column]; - } - - // save the new or updated item - $item->save(); - - // return the id number in the format '^id:[0-9][0-9]*$' on success - return 'id:' . $item->id; - } - - /** - * Dashboard Reorder: Reorder rows - */ - public function postReorder(Request $request) - { - $this->validate($request, [ - 'order' => 'required', - 'column' => 'required', - 'model' => 'required' - ]); - - $order = $request['order']; - $column = $request['column']; - - // set the model using the 'model' request argument - switch ($request['model']) { - default: - return 'model-access-fail'; - } - - // update each row with the new order - foreach (array_keys($order) as $order_id) { - $item = $items::find($order_id); - $item->$column = $order[$order_id]; - $item->save(); - } - - return 'success'; } /** @@ -212,38 +288,42 @@ class DashboardController extends Controller { 'model' => 'required' ]); - // set the model using the 'model' request argument - switch ($request['model']) { - default: - return 'model-access-fail'; - } + $model_class = Dashboard::getModel($request['model'], 'edit'); - // delete the row with the id using the 'id' request argument - if ($items::where('id', $request['id'])->exists()) { - $items::where('id', $request['id'])->delete(); - } else { - return 'row-delete-fail'; - } + if ($model_class != null) { + $item = $model_class::find($request['id']); - // delete associated files if they exist - foreach ($items::$dashboard_columns as $column) { - if ($column['type'] == 'image') { - $image = base_path() . '/public/uploads/' . $request['model'] . '/img/' . $request['id'] . '-' . $column['name'] . '.jpg'; + if (is_null($item)) { + return 'record-access-fail'; + } else if (!$item->userCheck()) { + return 'permission-fail'; + } - if (file_exists($image) && !unlink($image)) { - return 'image-delete-fail'; - } - } else if ($column['type'] == 'file') { - $file = base_path() . '/public/uploads/' . $request['model'] . '/files/' . $request['id'] . '-' . $column['name'] . '.' . $column['ext']; + // delete the row + $item->delete(); - if (file_exists($file) && !unlink($file)) { - return 'file-delete-fail'; + // delete associated files if they exist + foreach ($model_class::$dashboard_columns as $column) { + if ($column['type'] == 'image') { + $image = base_path() . '/public/uploads/' . $request['model'] . '/img/' . $request['id'] . '-' . $column['name'] . '.jpg'; + + if (file_exists($image) && !unlink($image)) { + return 'image-delete-fail'; + } + } else if ($column['type'] == 'file') { + $file = base_path() . '/public/uploads/' . $request['model'] . '/files/' . $request['id'] . '-' . $column['name'] . '.' . $column['ext']; + + if (file_exists($file) && !unlink($file)) { + return 'file-delete-fail'; + } } } - } - // Return a success - return 'success'; + // Return a success + return 'success'; + } else { + return 'model-access-fail'; + } } /** @@ -257,15 +337,26 @@ class DashboardController extends Controller { 'name' => 'required' ]); - $image = base_path() . '/public/uploads/' . $request['model'] . '/img/' . $request['id'] . '-' . $request['name'] . '.jpg'; + $model_class = Dashboard::getModel($request['model'], 'edit'); - if (!file_exists($image)) { - return 'image-not-exists-fail'; - } else if (!unlink($image)) { - return 'image-delete-fail'; + if ($model_class != null) { + $image = base_path() . '/public/uploads/' . $request['model'] . '/img/' . $request['id'] . '-' . $request['name'] . '.jpg'; + $item = $model_class::find($request['id']); + + if (is_null($item)) { + return 'record-access-fail'; + } else if (!$item->userCheck()) { + return 'permission-fail'; + } else if (!file_exists($image)) { + return 'image-not-exists-fail'; + } else if (!unlink($image)) { + return 'image-delete-fail'; + } + + return 'success'; + } else { + return 'model-access-fail'; } - - return 'success'; } /** @@ -280,15 +371,26 @@ class DashboardController extends Controller { 'ext' => 'required' ]); - $file = base_path() . '/public/uploads/' . $request['model'] . '/files/' . $request['id'] . '-' . $request['name'] . '.' . $request['ext']; + $model_class = Dashboard::getModel($request['model'], 'edit'); - if (!file_exists($file)) { - return 'file-not-exists-fail'; - } else if (!unlink($file)) { - return 'file-delete-fail'; + if ($model_class != null) { + $file = base_path() . '/public/uploads/' . $request['model'] . '/files/' . $request['id'] . '-' . $request['name'] . '.' . $request['ext']; + $item = $model_class::find($request['id']); + + if (is_null($item)) { + return 'record-access-fail'; + } else if (!$item->userCheck()) { + return 'permission-fail'; + } else if (!file_exists($file)) { + return 'file-not-exists-fail'; + } else if (!unlink($file)) { + return 'file-delete-fail'; + } + + return 'success'; + } else { + return 'model-access-fail'; } - - return 'success'; } } diff --git a/app/Models/Blog.php b/app/Models/Blog.php new file mode 100644 index 0000000..34831fa --- /dev/null +++ b/app/Models/Blog.php @@ -0,0 +1,25 @@ +NOTE: Tags should be separated by semicolons'; + + public static $dashboard_display = [ 'title', 'created_at' ]; + + 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 ] + ]; +} diff --git a/app/Models/Contact.php b/app/Models/Contact.php index 898a793..fd3c57a 100644 --- a/app/Models/Contact.php +++ b/app/Models/Contact.php @@ -4,34 +4,18 @@ namespace App\Models; use Illuminate\Database\Eloquent\Model; -class Contact extends Model +class Contact extends DashboardModel { - /** - * The contact table - * - * @var string - */ protected $table = 'contact'; - /** - * Dashboard columns - * - * @var array - */ - public static $dashboard_columns = [ - [ 'Date', 'created_at' ], - [ 'Name', 'name' ], - [ 'Email', 'email' ], - [ 'Message', 'message' ] - ]; + public static $dashboard_heading = 'Contact Form Submissions'; - /** - * Returns the list of all contact submissions - * - * @return array - */ - public static function getContactSubmissions() - { - return self::orderBy('created_at', 'desc')->get(); - } + public static $export = true; + + public static $dashboard_columns = [ + [ 'name' => 'created_at', 'title' => 'Date' ], + [ 'name' => 'name' ], + [ 'name' => 'email' ], + [ 'name' => 'message' ] + ]; } diff --git a/app/Models/Dashboard.php b/app/Models/Dashboard.php new file mode 100644 index 0000000..ebd6940 --- /dev/null +++ b/app/Models/Dashboard.php @@ -0,0 +1,58 @@ + 'Blog', + 'type' => 'edit', + 'model' => 'blog' + ], + + [ + 'title' => 'Form Submissions', + + 'submenu' => [ + [ + 'title' => 'Contact', + 'type' => 'view', + 'model' => 'contact' + ], + [ + 'title' => 'Subscriptions', + 'type' => 'view', + 'model' => 'subscriptions' + ] + ] + ] + ]; + + /** + * Retrieve a Dashboard Model + * + * @return model + */ + public static function getModel($model, $type = null) + { + $model_name = ucfirst($model); + + if (file_exists(app_path() . '/Models/' . $model_name . '.php')) { + $model_class = 'App\\Models\\' . ucfirst($model); + + if ($type != null && $type != $model_class::$dashboard_type) { + return null; + } + + return $model_class; + } else { + return null; + } + } +} diff --git a/app/Models/DashboardMenu.php b/app/Models/DashboardMenu.php deleted file mode 100644 index cf1deb1..0000000 --- a/app/Models/DashboardMenu.php +++ /dev/null @@ -1,30 +0,0 @@ - 'Submissions', - - 'submenu' => [ - [ - 'title' => 'Contact', - 'type' => 'view', - 'model' => 'contact' - ], - [ - 'title' => 'Subscriptions', - 'type' => 'view', - 'model' => 'subscriptions' - ] - ] - ] - ]; -} diff --git a/app/Models/DashboardModel.php b/app/Models/DashboardModel.php new file mode 100644 index 0000000..2392c3e --- /dev/null +++ b/app/Models/DashboardModel.php @@ -0,0 +1,171 @@ +where($column['name'], Auth::id()); + break; + } + } + + + return $query->get(); + } + + /** + * Determines whether a user column exists and whether it matches the current user if it does + * + * @return boolean + */ + public function userCheck() { + $user_check = true; + + foreach (static::$dashboard_columns as $column) { + if (array_key_exists('type', $column) && $column['type'] == 'user') { + if ($this->{$column['name']} != Auth::id()) { + $user_check = false; + } + + break; + } + } + + return $user_check; + } +} diff --git a/app/Models/Subscriptions.php b/app/Models/Subscriptions.php index d20fbc3..5fb81fd 100644 --- a/app/Models/Subscriptions.php +++ b/app/Models/Subscriptions.php @@ -4,33 +4,15 @@ namespace App\Models; use Illuminate\Database\Eloquent\Model; -class Subscriptions extends Model +class Subscriptions extends DashboardModel { - /** - * The subscriptions table - * - * @var string - */ protected $table = 'subscriptions'; - /** - * Dashboard columns - * - * @var array - */ - public static $dashboard_columns = [ - [ 'Date', 'created_at' ], - [ 'Email', 'email' ], - [ 'Name', 'name' ] - ]; + public static $export = true; - /** - * Returns the list of all subscriptions - * - * @return array - */ - public static function getSubscriptions() - { - return self::orderBy('created_at', 'desc')->get(); - } + public static $dashboard_columns = [ + [ 'title' => 'Date', 'name' => 'created_at' ], + [ 'name' => 'email' ], + [ 'name' => 'name' ] + ]; } diff --git a/database/migrations/2018_04_17_222542_add_blog_table.php b/database/migrations/2018_04_17_222542_add_blog_table.php new file mode 100644 index 0000000..1f56df9 --- /dev/null +++ b/database/migrations/2018_04_17_222542_add_blog_table.php @@ -0,0 +1,36 @@ +increments('id'); + $table->integer('user_id')->unsigned(); + $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); + $table->string('title')->nullable(); + $table->text('body')->nullable(); + $table->text('tags')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::drop('blog'); + } +} diff --git a/readme.md b/readme.md index 06c596b..d170641 100644 --- a/readme.md +++ b/readme.md @@ -54,283 +54,96 @@ Other information about database interaction, routing, controllers, etc can be v ## Dashboard -Unless otherwise stated all examples in this section are to be added to `app/Http/Controllers/DashboardController.php`. +### Updating the dashboard menu -### Adding a Viewable Model to the Dashboard +The dashboard menu can be edited by changing the `$menu` array in `app/Models/Dashboard.php`. -#### Viewable List of Rows +The each item in the array is itself an array, containing either a menu item or a dropdown of menu items. -First add a function to generate the page: +Dropdowns should contain the following keys: -```php - public function getContact() - { - return view('dashboard.view', [ - 'heading' => 'Contact Form Submissions', - 'model' => 'contact', - 'rows' => Contact::getContactSubmissions(), - 'columns' => Contact::$dashboard_columns - ]); - } -``` +* `title`: The text that appears on the dropdown item +* `submenu`: This is an array of menu items. -* `heading`: The title that will appear for this page -* `model`: The model that will be accessed on this page -* `rows`: A function returning an array containing the data to be shown on this page -* `columns`: Expects a variable called `$dashboard_columns` in the respective model that contains an array: +Menu items should contain the following keys: + +* `title`: The text that appears on the menu item +* `type`: The dashboard type (this can be `view` for a viewable table or `edit` for an editable list) +* `model`: The lowercase name of the database model + +### Adding a new model to the dashboard + +Create a model that extends the `DashboardModel` class and override variables that don't fit the defaults. + +#### DashboardModel variables + +* `$dashboard_type`: The dashboard type (this can be `view` for a viewable table or `edit` for an editable list) +* `$dashboard_heading`: This sets the heading that appears on the dashboard page; not setting this will use the model name +* `$export`: This enables a button that allows the table to be exported as a spreadsheet + +##### Edit variables + +These are variables that only function when the `$dashboard_type` variable is set to `edit`. + +* `$create`: A boolean determining whether to enable a button that allows new records to be created +* `$delete`: A boolean determining whether to enable a button that allows records to be deleted +* `$filter`: A boolean determining whether to enable an input field that allows records to be searched +* `$dashboard_help_text`: An html string that will add a help box to the top of the edit-item page +* `$dashboard_display`: An array to configure what column data to show on each item in the edit-list +* `$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 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: + * 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 + +##### Configuring the columns + +All `DashboardModel` models require a `$dashboard_columns` array that declares which columns to show and how to treat them. + +All models use the following attributes: + +* `name`: The name of the model +* `title`: (optional) The title that should be associated with the model; when unset this becomes the model name with its first letter capitalized + +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 with possible options in an `options` array + * `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) + * `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 +* `name`: (required by `file` and `image`) Used along with the record id to determine the filename +* `delete`: (optional for `file` and `image`) Enables a delete button for the upload when set to true +* `ext`: (required by `file`) Configures the file extension of the upload + +An example of the `$dashboard_columns` array in a model with its `$dashboard_type` set to `view`: ```php public static $dashboard_columns = [ - [ 'Date', 'created_at' ], - [ 'Name', 'name' ], - [ 'Email', 'email' ], - [ 'Message', 'message' ] + [ 'title' => 'Date', 'name' => 'created_at' ], + [ 'name' => 'email' ], + [ 'name' => 'name' ] ]; ``` -### Adding an Editable Model to the Dashboard - -#### Editable List of Rows - -##### Editable List for Unsortable Model - -```php - public function getShows() - { - return view('dashboard.edit-list', [ - 'heading' => 'Shows', - 'model' => 'shows', - 'path' => 'shows-page', - 'rows' => Shows::getShowsList(), - 'column' => 'title', - 'button' => [ 'Email Show', 'Are you sure you want to send an email?', 'Email successfully sent', 'Failed to send email', '/email-show' ], - 'sortcol' => false, - 'delete' => true, - 'create' => true, - 'export' => true, - 'filter' => true - ]); - } -``` - -##### Editable List for Sortable Model - -**NOTE**: Sortable models must have an entry configured in the `postReorder` function (details below) - -```php - public function getNews() - { - return view('dashboard.edit-list', [ - 'heading' => 'News', - 'model' => 'news', - 'rows' => News::getNewsList(), - 'column' => 'title', - 'button' => [ 'Email Show', 'Are you sure you want to send an email?', 'Email successfully sent', 'Failed to send email', '/email-show' ], - 'sortcol' => 'order', - 'delete' => false, - 'create' => true, - 'export' => true, - 'filter' => true - ]); - } -``` - -* `heading`: The title that will appear for this page -* `model`: The model that will be accessed on this page -* `path`: (optional) This can be used to set a different URL path than the default of the model name -* `rows`: A function returning an array containing the data to be shown on this page -* `column`: The column name in the array that contains the data to display in each row (an array can be used to specify multiple columns) -* `button`: Add a button with a title, confirmation, success and error messages, and a post request path that takes an id and returns `success` on success -* `sortcol`: The name of the column containing the sort order or `false` to disable -* `delete`: A `delete` button will appear in the list if this is set to `true` -* `create`: A `new` button will appear in the heading if this is set to `true` -* `export`: An `export` button will appear in the heading if this is set to `true` -* `filter`: An input box will appear below the heading that can filter rows by input if this is set to `true` - -#### Editable Item - -This function should be named the same as the one above except with `Edit` at the end - -##### Editable Item for Unsortable Model - -```php - public function getShowsEdit($id = 'new') - { - if ($id != 'new') { - if (Shows::where('id', $id)->exists()) { - $item = Shows::where('id', $id)->first(); - } else { - return view('errors.no-such-record'); - } - } else { - $item = null; - } - - return view('dashboard.edit-item', [ - 'heading' => 'Shows', - 'model' => 'shows', - 'id' => $id, - 'item' => $item, - 'help_text' => 'NOTE: This is some help text for the current page', - 'columns' => $dashboard_columns - ]); - } -``` - -##### Editable Item for Sortable Model - -```php - public function getNewsEdit($id = 'new') - { - if ($id != 'new') { - if (News::where('id', $id)->exists()) { - $item = News::where('id', $id)->first(); - } else { - return view('errors.no-such-record'); - } - } else { - $item = new News(); - $item['order'] = News::count(); - } - - return view('dashboard.edit-item', [ - 'heading' => 'News', - 'model' => 'news', - 'id' => $id, - 'item' => $item, - 'columns' => News::$dashboard_columns - ]); - } -``` - -* `heading`: The title that will appear for this page -* `model`: The model that will be accessed on this page -* `id`: Always set this to `$id` -* `item`: Always set this to `$item` -* `help_text`: An optional value that will add a box containing help text above the form if set -* `columns`: Expects a variable called `$dashboard_columns` in the respective model that contains an array: - * `name` is the name of the column to be edited - * `type` is the type of column (details below) - * `label` is an optional value that overrides the visible column name +An example of the `$dashboard_columns` array in a model with its `$dashboard_type` set to `edit`: ```php public static $dashboard_columns = [ - [ 'name' => 'title', 'type' => 'text', 'label' => 'The Title' ], - [ 'name' => 'iframe', 'type' => 'text' ], - [ 'name' => 'halign', 'type' => 'select', 'options' => [ 'left', 'center', 'right' ] ], - [ 'name' => 'story', 'type' => 'mkd' ], - [ 'label' => 'Header Image', 'name' => 'headerimage', 'type' => 'image' ], - [ 'name' => 'order', 'type' => 'hidden' ], - [ 'label' => 'PDF File', 'name' => 'pdf', 'type' => 'file', 'ext' => 'pdf' ] + [ '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 ] ]; ``` - -###### Editable Column Types - -The following is a list of possible `types` in the `columns` array for Editable Items: - -* `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 with possible options in an `options` array -* `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) -* `image`: Fields that contain image uploads - * `name`: not part of the database and is instead used in the filename - * `delete`: (optional) if true then uploaded images can be deleted -* `file`: Fields that contains file uploads - * `name`: not part of the database and is instead used in the filename - * `ext` required key containing the file extension - * `delete`: (optional) if true then uploaded files can be deleted -* `display`: Displayed information that can't be edited - -#### Edit Item Functionality - -Editable models must have an entry in the switch statement of the `postEdit` function to make create and edit functionality work: - -```php - switch ($request['model']) { - case 'shows': - $item = $id == 'new' ? new Shows : Shows::find($id); - break; - case 'news': - $item = $id == 'new' ? new News : News::find($id); - break; - default: - return 'model-access-fail'; - } -``` - -#### Additional Requirement for Sortable Models - -Sortable models must have an entry in the switch statement of the `postReorder` function to make sorting functionality work: - -```php - switch ($request['model']) { - case 'news': - $items = new News(); - break; - default: - return 'model-access-fail'; - } -``` - -#### Additional Requirements for Image Upload - -If the value of `imgup` has been set to `true`, ensure `public/uploads/model_name` exists (where `model_name` is the name of the given model) and contains a `.gitkeep` that exists in version control. - -By default, uploaded images are saved in JPEG format with the value of the `id` column of the respective row as its name and `.jpg` as its file extension. - -When a row is deleted, its respective image will be deleted as well if it exists. - -### Adding to the Dashboard Menu - -Edit the `$menu` array in `app/Models/DashboardMenu.php` where the first column of each item is the title and the second is either a path, or an array of submenu items. - -```php - public static $menu = [ - [ 'Contact', 'contact' ], - [ 'Subscriptions', 'subscriptions' ], - - [ - 'Projects', [ - [ 'Residential', 'projects-residential' ], - [ 'Commercial', 'projects-commercial' ] - ] - ] - ]; -``` - -#### Additional Requirement for Delete Functionality - -Editable models with `delete` set to `true` must have an entry in the switch statement of the `deleteDelete` function to make deletion functionality work: - -```php - switch ($request['model']) { - case 'shows': - $items = new Shows(); - break; - case 'news': - $items = new News(); - break; - default: - return 'model-access-fail'; - } -``` - -#### Additional Requirement for Export Functionality - -Viewable models and editable models with `export` set to `true` must have an entry in the switch statement of the `getExport` function to make the export button work: - -```php - switch ($model) { - case 'contact': - $headings = [ 'Date', 'Name', 'Email', 'Message' ]; - $items = Contact::select('created_at', 'name', 'email', 'message')->get()->toArray(); - break; - default: - abort(404); - } -``` - -* `$headings`: The visible column names in the same order as the array containing the items to be exported -* `$items`: A function returning an array containing the data to be exported diff --git a/resources/assets/js/dashboard.js b/resources/assets/js/dashboard.js index 64ad163..ce36fbb 100644 --- a/resources/assets/js/dashboard.js +++ b/resources/assets/js/dashboard.js @@ -113,15 +113,14 @@ function editListInit() { const editList = document.getElementById("edit-list"), $editList = $(editList), $token = $("#token"), - model = $editList.data("model"), - path = $editList.data("path"); + model = $editList.data("model"); // initialize new button functionality const newButtonInit = function() { const $newButton = $(".btn.new-button"); $newButton.on("click", function() { - window.location.href = "/dashboard/" + path + "-edit/new"; + window.location.href = "/dashboard/edit-item/" + model + "/new"; }); }; @@ -135,7 +134,7 @@ function editListInit() { itemId = $listItem.data("id"); // go to the edit page - window.location.href = "/dashboard/" + path + "-edit/" + itemId; + window.location.href = "/dashboard/edit-item/" + model + "/" + itemId; }); }; @@ -281,7 +280,6 @@ function editItemInit() { $spinner = $("#loading-modal"), fadeTime = 250, model = $editItem.data("model"), - path = $editItem.data("path"), id = $editItem.data("id"), operation = id === "new" ? "create" : "update"; @@ -363,7 +361,7 @@ function editItemInit() { // functionality to run on success const returnSuccess = function() { hideLoadingModal(); - window.location.href = "/dashboard/" + path; + window.location.href = "/dashboard/edit/" + model; }; // add the file from the file upload box for file-upload class elements @@ -582,10 +580,10 @@ function editItemInit() { if (!submitting) { if (changes) { askConfirmation("Cancel changes and return to the list?", function() { - window.location.href = "/dashboard/" + path; + window.location.href = "/dashboard/edit/" + model; }); } else { - window.location.href = "/dashboard/" + path; + window.location.href = "/dashboard/edit/" + model; } } }); @@ -604,7 +602,7 @@ function editItemInit() { // submit the update $.ajax({ type: "POST", - url: "/dashboard/edit", + url: "/dashboard/update", data: formData }).always(function(response) { if (/^id:[0-9][0-9]*$/.test(response)) { diff --git a/resources/assets/sass/dashboard.scss b/resources/assets/sass/dashboard.scss index 954a213..e1bf5b5 100644 --- a/resources/assets/sass/dashboard.scss +++ b/resources/assets/sass/dashboard.scss @@ -37,6 +37,7 @@ body { margin-bottom: $grid-gutter-width; border: 0; background-color: $c-dashboard-dark; + user-select: none; @include media-breakpoint-down(md) { padding-right: 8px; @@ -51,8 +52,13 @@ body { @include font-sans-bold; overflow: hidden; max-width: calc(100vw - 60px); + color: $c-text-light; white-space: nowrap; + &:hover, &:focus, &:active { + color: $c-text-light; + } + @include media-breakpoint-down(sm) { font-size: 12px; } @@ -67,6 +73,45 @@ body { @include media-breakpoint-up(md) { top: 10px; } + + &-icon { + position: relative; + + &-bar { + $v-inset: 6px; + $h-inset: 3px; + position: absolute; + left: $h-inset; + width: calc(100% - #{$h-inset * 2}); + height: 2px; + background-color: $c-text-light; + + &:nth-child(1) { + top: $v-inset; + } + + &:nth-child(2) { + top: 50%; + transform: translateY(-50%); + } + + &:nth-child(3) { + bottom: $v-inset; + } + } + } + } + + .nav-link { + color: fade-out($c-text-light, 0.25); + + &.active { + color: $c-text-light; + } + + &.dropdown-toggle { + cursor: pointer; + } } .dropdown-menu { @@ -84,10 +129,18 @@ body { } .dropdown-item { + background-color: transparent; + transition: background-color 150ms; + @include media-breakpoint-down(md) { padding-right: 8px; padding-left: 8px; } + + &:hover, &:focus, &:active { + background-color: fade-out(#000, 0.95); + color: $c-text; + } } } } diff --git a/resources/views/dashboard/edit-item.blade.php b/resources/views/dashboard/edit-item.blade.php index c373fc9..5ba554b 100644 --- a/resources/views/dashboard/edit-item.blade.php +++ b/resources/views/dashboard/edit-item.blade.php @@ -1,7 +1,7 @@ @extends('dashboard.core') @section('dashboard-body') - @if(!empty($help_text)) + @if($help_text != '')
@@ -13,7 +13,7 @@
@endif -
+
@@ -23,9 +23,11 @@ @if($column['type'] == 'hidden') + @elseif($column['type'] == 'user') + @elseif($column['type'] != 'display' || $id != 'new')
- +
@@ -46,13 +48,12 @@ @endforeach @elseif($column['type'] == 'image') - - @set('current_image', "/uploads/$model/img/$id-" . $column['name'] . '.jpg') + @if(file_exists(base_path() . '/public' . $current_image))
- + @if(array_key_exists('delete', $column) && $column['delete']) diff --git a/resources/views/dashboard/edit-list.blade.php b/resources/views/dashboard/edit-list.blade.php index f9553e7..c6e3234 100644 --- a/resources/views/dashboard/edit-list.blade.php +++ b/resources/views/dashboard/edit-list.blade.php @@ -18,7 +18,7 @@ @endif -
    +
      @foreach($rows as $row)
    • @@ -32,23 +32,19 @@
      @endif - @if(is_array($column)) - @foreach($column as $col) - @if($row[$col] != '') -
      {{ $row[$col] }}
      + @foreach($display as $display_column) + @if($row[$display_column] != '') +
      {{ $row[$display_column] }}
      - @if(!$loop->last) -
      |
      - @endif + @if(!$loop->last) +
      |
      @endif - @endforeach - @else - {{ $row[$column] }} - @endif + @endif + @endforeach
- @if(isset($button) && is_array($button)) + @if(!empty($button)) @endif diff --git a/resources/views/dashboard/home.blade.php b/resources/views/dashboard/home.blade.php index 79e5a1c..546cd3b 100644 --- a/resources/views/dashboard/home.blade.php +++ b/resources/views/dashboard/home.blade.php @@ -2,7 +2,7 @@ @section('dashboard-body')