Commit eb2f5644 authored by Le Dinh Trung's avatar Le Dinh Trung

Merge branch 'feature/post' into 'dev'

Feature/post

See merge request !7
parents ba56bc93 28c31dc7
......@@ -3,23 +3,58 @@
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
use App\Models\Post;
use App\Models\Category;
class PostController extends Controller
{
public function index(Request $request) {
return view('post.index');
$posts = Post::latest()->paginate();
$categories = Category::orderBy('title', 'asc')->get();
return view('post.index', compact('posts', 'categories'));
}
public function create(Request $request) {
return view('post.index');
public function ajax(Request $request) {
$query = Post::latest();
if (strlen($request->search_input)) {
$query->where('title', 'like', '%'.$request->search_input.'%');
}
$posts = $query->paginate();
return view('post.ajax', compact('posts'));
}
public function edit($id, Request $request) {
return view('post.index');
public function create(Request $request) {
$categories = Category::orderBy('title', 'asc')->whereNull('parent_id')->get();
$post = new Post();
return view('post.form', compact('post','categories'));
}
public function store(Request $request) {
return view('post.index');
$request->merge([
'user_id' => Auth::user()->id,
]);
$post = Post::create($request->only([
'title',
'user_id',
'keywords',
'description',
'content'
]));
$post->categories()->sync($request->category_ids);
return redirect()->route('posts.index')->with('message', 'Tạo mới thành công');
}
public function update($id, Request $request) {
return view('post.index');
public function edit(Post $post, Request $request) {
$categories = Category::orderBy('title', 'asc')->whereNull('parent_id')->get();
return view('post.form', compact('post', 'categories'));
}
public function update(Post $post, Request $request) {
$post->update($request->only([
'title',
'keywords',
'description',
'content'
]));
$post->categories()->sync($request->category_ids);
return redirect()->route('posts.index')->with('message', 'Cập nhật thành công');
}
public function destroy($id, Request $request) {
return view('post.index');
......
......@@ -12,6 +12,9 @@ class Category extends Model
public function parent() {
return $this->belongsTo(Category::class);
}
public function children() {
return $this->hasMany(Category::class, 'parent_id');
}
public function posts() {
return $this->belongsToMany(Post::class);
}
......
......@@ -15,6 +15,9 @@ class Post extends Model
'view_count' => 'integer',
'status' => 'integer',
];
protected $attributes = [
'status' => true,
];
public function user() {
return $this->belongsTo(User::class);
}
......
......@@ -3,6 +3,7 @@
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Pagination\Paginator;
class AppServiceProvider extends ServiceProvider
{
......@@ -23,6 +24,6 @@ public function register()
*/
public function boot()
{
//
Paginator::useBootstrap();
}
}
......@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "cf14086919a839fe740e00554e4baff6",
"content-hash": "4131a5c03b502adbf91a68a0b8d73c71",
"packages": [
{
"name": "asm89/stack-cors",
......@@ -5162,6 +5162,90 @@
}
],
"packages-dev": [
{
"name": "barryvdh/laravel-debugbar",
"version": "v3.7.0",
"source": {
"type": "git",
"url": "https://github.com/barryvdh/laravel-debugbar.git",
"reference": "3372ed65e6d2039d663ed19aa699956f9d346271"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/3372ed65e6d2039d663ed19aa699956f9d346271",
"reference": "3372ed65e6d2039d663ed19aa699956f9d346271",
"shasum": ""
},
"require": {
"illuminate/routing": "^7|^8|^9",
"illuminate/session": "^7|^8|^9",
"illuminate/support": "^7|^8|^9",
"maximebf/debugbar": "^1.17.2",
"php": ">=7.2.5",
"symfony/finder": "^5|^6"
},
"require-dev": {
"mockery/mockery": "^1.3.3",
"orchestra/testbench-dusk": "^5|^6|^7",
"phpunit/phpunit": "^8.5|^9.0",
"squizlabs/php_codesniffer": "^3.5"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.6-dev"
},
"laravel": {
"providers": [
"Barryvdh\\Debugbar\\ServiceProvider"
],
"aliases": {
"Debugbar": "Barryvdh\\Debugbar\\Facades\\Debugbar"
}
}
},
"autoload": {
"files": [
"src/helpers.php"
],
"psr-4": {
"Barryvdh\\Debugbar\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Barry vd. Heuvel",
"email": "barryvdh@gmail.com"
}
],
"description": "PHP Debugbar integration for Laravel",
"keywords": [
"debug",
"debugbar",
"laravel",
"profiler",
"webprofiler"
],
"support": {
"issues": "https://github.com/barryvdh/laravel-debugbar/issues",
"source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.7.0"
},
"funding": [
{
"url": "https://fruitcake.nl",
"type": "custom"
},
{
"url": "https://github.com/barryvdh",
"type": "github"
}
],
"time": "2022-07-11T09:26:42+00:00"
},
{
"name": "doctrine/instantiator",
"version": "1.4.1",
......@@ -5677,6 +5761,72 @@
},
"time": "2022-08-31T16:38:14+00:00"
},
{
"name": "maximebf/debugbar",
"version": "v1.18.1",
"source": {
"type": "git",
"url": "https://github.com/maximebf/php-debugbar.git",
"reference": "ba0af68dd4316834701ecb30a00ce9604ced3ee9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/ba0af68dd4316834701ecb30a00ce9604ced3ee9",
"reference": "ba0af68dd4316834701ecb30a00ce9604ced3ee9",
"shasum": ""
},
"require": {
"php": "^7.1|^8",
"psr/log": "^1|^2|^3",
"symfony/var-dumper": "^2.6|^3|^4|^5|^6"
},
"require-dev": {
"phpunit/phpunit": "^7.5.20 || ^9.4.2",
"twig/twig": "^1.38|^2.7|^3.0"
},
"suggest": {
"kriswallsmith/assetic": "The best way to manage assets",
"monolog/monolog": "Log using Monolog",
"predis/predis": "Redis storage"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.18-dev"
}
},
"autoload": {
"psr-4": {
"DebugBar\\": "src/DebugBar/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Maxime Bouroumeau-Fuseau",
"email": "maxime.bouroumeau@gmail.com",
"homepage": "http://maximebf.com"
},
{
"name": "Barry vd. Heuvel",
"email": "barryvdh@gmail.com"
}
],
"description": "Debug bar in the browser for php application",
"homepage": "https://github.com/maximebf/php-debugbar",
"keywords": [
"debug",
"debugbar"
],
"support": {
"issues": "https://github.com/maximebf/php-debugbar/issues",
"source": "https://github.com/maximebf/php-debugbar/tree/v1.18.1"
},
"time": "2022-03-31T14:55:54+00:00"
},
{
"name": "mockery/mockery",
"version": "1.5.1",
......
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;
class CategoryFactory extends Factory
{
public function definition()
{
return [
'title' => $this->faker->name(),
'parent_id' => rand(0, 1) ? null : rand(1, 10),
];
}
}
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;
use App\Models\Post;
class PostFactory extends Factory
{
public function definition()
{
return [
'title' => $this->faker->name(),
'status' => Post::IS_PUBLISHED,
];
}
}
......@@ -3,6 +3,8 @@
namespace Database\Seeders;
use App\Models\User;
use App\Models\Category;
use App\Models\Post;
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
......@@ -17,6 +19,7 @@ public function run()
User::factory(1)->create([
'role' => User::ROLE_ADMIN
]);
User::factory(9)->create();
User::factory(9)->has(Post::factory()->hasCategories(1)->count(30), 'posts')->create();
Category::factory(10)->create();
}
}
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -2,6 +2,7 @@
<html lang="en">
<head>
@include('partials.head')
<meta name="csrf-token" content="{{ csrf_token() }}" />
</head>
<body class="navbar-top">
......@@ -9,6 +10,7 @@
<!-- Page content -->
<div class="page-content">
@include('partials.sidebar')
@include('partials.messages')
<!-- Main content -->
<div class="content-wrapper">
......
......@@ -5,31 +5,37 @@
<!-- Global stylesheets -->
<link href="https://fonts.googleapis.com/css?family=Roboto:400,300,100,500,700,900" rel="stylesheet" type="text/css">
<link href="../../../../global_assets/css/icons/icomoon/styles.min.css" rel="stylesheet" type="text/css">
<link href="assets/css/bootstrap.min.css" rel="stylesheet" type="text/css">
<link href="assets/css/bootstrap_limitless.min.css" rel="stylesheet" type="text/css">
<link href="assets/css/layout.min.css" rel="stylesheet" type="text/css">
<link href="assets/css/components.min.css" rel="stylesheet" type="text/css">
<link href="assets/css/colors.min.css" rel="stylesheet" type="text/css">
<link href="/global_assets/css/icons/icomoon/styles.min.css" rel="stylesheet" type="text/css">
<link href="/assets/css/bootstrap.min.css" rel="stylesheet" type="text/css">
<link href="/assets/css/bootstrap_limitless.min.css" rel="stylesheet" type="text/css">
<link href="/assets/css/layout.min.css" rel="stylesheet" type="text/css">
<link href="/assets/css/components.min.css" rel="stylesheet" type="text/css">
<link href="/assets/css/colors.min.css" rel="stylesheet" type="text/css">
<link href="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.css" rel="stylesheet" type="text/css">
<!-- /global stylesheets -->
<!-- Core JS files -->
<script src="../../../../global_assets/js/main/jquery.min.js"></script>
<script src="../../../../global_assets/js/main/bootstrap.bundle.min.js"></script>
<script src="../../../../global_assets/js/plugins/loaders/blockui.min.js"></script>
<script src="../../../../global_assets/js/plugins/ui/ripple.min.js"></script>
<script src="/global_assets/js/main/jquery.min.js"></script>
<script src="/global_assets/js/main/bootstrap.bundle.min.js"></script>
<script src="/global_assets/js/plugins/loaders/blockui.min.js"></script>
<script src="/global_assets/js/plugins/ui/ripple.min.js"></script>
<!-- /core JS files -->
<!-- Theme JS files -->
<script src="../../../../global_assets/js/plugins/visualization/d3/d3.min.js"></script>
<script src="../../../../global_assets/js/plugins/visualization/d3/d3_tooltip.js"></script>
<script src="../../../../global_assets/js/plugins/forms/styling/switchery.min.js"></script>
<script src="../../../../global_assets/js/plugins/forms/selects/bootstrap_multiselect.js"></script>
<script src="../../../../global_assets/js/plugins/ui/moment/moment.min.js"></script>
<script src="../../../../global_assets/js/plugins/pickers/daterangepicker.js"></script>
<script src="../../../../global_assets/js/plugins/ui/perfect_scrollbar.min.js"></script>
<script src="/global_assets/js/demo_pages/editor_ckeditor.js"></script>
<script src="/global_assets/js/plugins/editors/ckeditor/ckeditor.js"></script>
<script src="/global_assets/js/plugins/forms/styling/uniform.min.js"></script>
<script src="/global_assets/js/plugins/forms/validation/validate.min.js"></script>
<script src="/global_assets/js/plugins/forms/selects/select2.min.js"></script>
<script src="/global_assets/js/plugins/visualization/d3/d3.min.js"></script>
<script src="/global_assets/js/plugins/visualization/d3/d3_tooltip.js"></script>
<script src="/global_assets/js/plugins/forms/styling/switchery.min.js"></script>
<script src="/global_assets/js/plugins/forms/selects/bootstrap_multiselect.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.js"></script>
<script src="/global_assets/js/plugins/ui/moment/moment.min.js"></script>
<script src="/global_assets/js/plugins/pickers/daterangepicker.js"></script>
<script src="/global_assets/js/plugins/ui/perfect_scrollbar.min.js"></script>
<script src="/global_assets/js/plugins/forms/tags/tagsinput.min.js"></script>
<script src="assets/js/app.js"></script>
<script src="../../../../global_assets/js/demo_pages/dashboard.js"></script>
<script src="../../../../global_assets/js/demo_pages/layout_fixed_sidebar_custom.js"></script>
<script src="/assets/js/app.js"></script>
<!-- /theme JS files -->
@if(session('message'))
<script>
toastr.success('{{ session('message') }}')
</script>
@endif
\ No newline at end of file
......@@ -81,7 +81,7 @@
<!-- Main -->
<li class="nav-item-header"><div class="text-uppercase font-size-xs line-height-xs">Main</div> <i class="icon-menu" title="Main"></i></li>
<li class="nav-item">
<a href="index.html" class="nav-link">
<a href="/" class="nav-link">
<i class="icon-home4"></i>
<span>
Dashboard
......@@ -90,15 +90,10 @@
</a>
</li>
<li class="nav-item nav-item-submenu">
<a href="#" class="nav-link"><i class="icon-copy"></i> <span>Layouts</span></a>
<a href="#" class="nav-link"><i class="icon-copy"></i> <span>Bài viết</span></a>
<ul class="nav nav-group-sub" data-submenu-title="Layouts">
<li class="nav-item"><a href="index.html" class="nav-link active">Default layout</a></li>
<li class="nav-item"><a href="../../../../layout_2/LTR/material/full/index.html" class="nav-link">Layout 2</a></li>
<li class="nav-item"><a href="../../../../layout_3/LTR/material/full/index.html" class="nav-link">Layout 3</a></li>
<li class="nav-item"><a href="../../../../layout_4/LTR/material/full/index.html" class="nav-link">Layout 4</a></li>
<li class="nav-item"><a href="../../../../layout_5/LTR/material/full/index.html" class="nav-link">Layout 5</a></li>
<li class="nav-item"><a href="../../../../layout_6/LTR/material/full/index.html" class="nav-link disabled">Layout 6 <span class="badge bg-transparent align-self-center ml-auto">Coming soon</span></a></li>
<ul class="nav nav-group-sub" data-submenu-title="Bài viết">
<li class="nav-item"><a href="{{ route('posts.index') }}" class="nav-link active">Tất cả bài viết</a></li>
</ul>
</li>
<li class="nav-item nav-item-submenu">
......
<div class="my-2">
{{ $posts->links() }}
</div>
<table class="table">
<thead>
<tr>
<th>#</th>
<th>Ảnh</th>
<th>Tiêu đề</th>
<th>Danh mục</th>
<th>Người tạo</th>
<th>Tác vụ</th>
</tr>
</thead>
<tbody>
@foreach($posts as $post)
<tr>
<td>
<input type="checkbox" class="form-input-styled">
</td>
<td>
<img src="{{ $post->image }}" height="30">
</td>
<td>{{ $post->title }}</td>
<td>{{ $post->categories()->first()->title ?? '' }}</td>
<td>{{ $post->user->name }}</td>
<td>
<div class="list-icons">
<a href="{{ route('posts.edit', $post->id) }}" class="list-icons-item" title="Sửa">
<i class="icon-pencil7"></i>
</a>
<a href="#" class="text-danger list-icons-item" title="Xóa">
<i class="icon-trash"></i>
</a>
</div>
</td>
</tr>
@endforeach
</tbody>
</table>
<div class="my-2">
{{ $posts->links() }}
</div>
\ No newline at end of file
@extends('layouts.app')
@section('content')
<form method="POST" id="form" action="{{ $post->id ? route('posts.update', $post->id) : route('posts.store') }}">
@csrf
@if ($post->id)
@method('PUT')
@endif
<!-- Page header -->
<div class="page-header page-header-light">
<div class="page-header-content header-elements-md-inline">
<div class="page-title d-flex">
<h4>
<i class="icon-arrow-left52 mr-2"></i> <span class="font-weight-semibold">Bài viết</span> -
@if($post->id)
Sửa
@else
Thêm mới
@endif
</h4>
<a href="#" class="header-elements-toggle text-default d-md-none"><i class="icon-more"></i></a>
</div>
<div class="header-elements d-none">
<div class="d-flex justify-content-center">
<button type="submit" class="btn btn-link btn-float font-size-sm font-weight-semibold text-default">
<i class="icon-calendar5 text-pink-300"></i>
<span>Lưu</span>
</button>
</div>
</div>
</div>
</div>
<!-- /page header -->
<!-- Content area -->
<div class="content">
<!-- Horizontal form options -->
<div class="row">
<div class="col-md-12">
<!-- Basic layout-->
<div class="card" >
<div class="card-body">
<div class="form-group row">
<label class="col-lg-3 col-form-label">Tiêu đề <span class="text-danger">*</span></label>
<div class="col-lg-9">
<input type="text" class="form-control" value="{{ $post->id ? $post->title : ''}}" placeholder="Tiêu đề" name="title">
</div>
</div>
<div class="form-group row">
<label class="col-lg-3 col-form-label">Ảnh </label>
<div class="col-lg-9">
<input type="file" class="form-control form-input-styled" name="image">
</div>
</div>
<div class="form-group row">
<label class="col-lg-3 col-form-label">Từ khóa <span class="text-danger">*</span></label>
<div class="col-lg-9">
<input type="text" data-role="tagsinput" class="form-control tagsinput" name="keywords" value="{{ $post->id ? $post->keywords : ''}}">
</div>
</div>
<div class="form-group row">
<label class="col-lg-3 col-form-label"> tả <span class="text-danger">*</span></label>
<div class="col-lg-9">
<input type="text" class="form-control" name= "description" value="{{ $post->id ? $post->description : ''}}">
</div>
</div>
<div class="form-group row">
<label class="col-lg-3 col-form-label">Trạng thái </label>
<div class="col-lg-9">
<div class="form-check form-check-inline">
<label class="form-check-label">
<input type="radio" class="form-input-styled" name="status" {{ $post->status ? '' : 'checked' }} type="checkbox">
Ẩn
</label>
</div>
<div class="form-check form-check-inline">
<label class="form-check-label">
<input type="radio" class="form-input-styled" name="status" {{ $post->status ? 'checked' : '' }} type="checkbox" >
Hiện
</label>
</div>
</div>
</div>
<div class="form-group row">
<label class="col-lg-3 col-form-label">Danh mục <span class="text-danger">*</span></label>
<div class="col-lg-9">
<select class="form-control select-multiple-drag select2 col-lg-9" multiple="multiple" data-fouc name="category_ids[]">
@foreach($categories as $category)
<option {{ in_array($category->id, $post->categories->pluck('id')->all()) ? 'selected' : '' }} value="{{$category->id}}" >{{$category->title }}</option>
@endforeach
</select>
</div>
</div>
<div class="form-group row">
<label class="col-lg-3 col-form-label">Nội dung <span class="text-danger">*</span></label>
</div>
<div class="form-group row">
<textarea name="content" id="content" rows="4" cols="4">{{ $post->content }}</textarea>
</div>
<div class="text-right">
<button type="submit" class="btn btn-primary">Lưu <i class="icon-paperplane ml-2"></i></button>
</div>
</div>
</div>
<!-- /basic layout -->
</div>
</div>
</div>
</form>
<script>
$(function(){
CKEDITOR.replace('content', {
height: 400,
extraPlugins: 'forms'
})
$('.select2').select2()
$('.form-input-styled').uniform()
$('#form').validate({
ignore: 'input[type=hidden], .select2-search__field', // ignore hidden fields
errorClass: 'validation-invalid-label',
successClass: 'validation-valid-label',
validClass: 'validation-valid-label',
highlight: function(element, errorClass) {
$(element).removeClass(errorClass);
},
unhighlight: function(element, errorClass) {
$(element).removeClass(errorClass);
},
success: function(label) {
label.addClass('validation-valid-label')
},
// Different components require proper error label placement
errorPlacement: function(error, element) {
// Unstyled checkboxes, radios
if (element.parents().hasClass('form-check')) {
error.appendTo( element.parents('.form-check').parent() );
}
// Input with icons and Select2
else if (element.parents().hasClass('form-group-feedback') || element.hasClass('select2-hidden-accessible')) {
error.appendTo( element.parent() );
}
// Input group, styled file input
else if (element.parent().is('.uniform-uploader, .uniform-select') || element.parents().hasClass('input-group')) {
error.appendTo( element.parent().parent() );
}
// Other elements
else {
error.insertAfter(element);
}
},
rules: {
title :{
required: true
},
description: {
required: true
},
content: {
required: true
},
},
messages: {
title: {
required: "Bạn bắt buộc phải nhập tiêu đề."
},
description: {
required: "Bạn bắt buộc phải nhập mô tả."
},
content: {
required: "Nội dung không được bỏ trống"
},
}
})
})
</script>
@endsection
@extends('layouts.app')
@section('content')
<!-- Page header -->
<div class="page-header page-header-light">
<div class="page-header-content header-elements-md-inline">
<div class="page-title d-flex">
<h4>Tất cả bài viết</h4>
<a href="#" class="header-elements-toggle text-default d-md-none"><i class="icon-more"></i></a>
</div>
<div class="header-elements d-none">
<div class="d-flex justify-content-center">
<a href="#" class="btn btn-link btn-float font-size-sm font-weight-semibold text-default">
<i class="icon-trash text-pink-300"></i>
<span>Xóa</span>
</a>
<a href="{{ route('posts.create') }}" class="btn btn-link btn-float font-size-sm font-weight-semibold text-default">
<i class="icon-calendar5 text-pink-300"></i>
<span>Thêm mới</span>
</a>
</div>
</div>
</div>
</div>
<!-- /page header -->
<!-- Content area -->
<div class="content">
<!-- Basic table -->
<div class="card">
<div class="card-body">
<form id="form" class="row">
<div class="col-md-3">
<input name="keyword" placeholder="Từ khóa" class="form-control" id="search_input">
</div>
<div class="col-md-3">
<select name="category_id" class="form-control select2">
<option value="">Chọn</option>
@foreach($categories as $category)
<option value="{{ $category->id }}">{{ $category->title }}</option>
@endforeach
</select>
</div>
<div class="col-md-3">
<button type="button" class="btn btn-primary" id="filter">Lọc</button>
</div>
</form>
</div>
<div class="table-responsive" id="content">
@include('post.ajax')
</div>
</div>
<!-- /basic table -->
</div>
<!-- /content area -->
<script>
function search() {
var search_content = $('#search_input').val()
$.ajax({
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
},
type: 'post',
url: '/posts/ajax',
data: {
'search_input': search_content
},
success:function(html){
$('#content').html(html)
}
})
}
$(function(){
$('.select2').select2()
$('#form').submit(function(e){
e.preventDefault()
search()
});
$('#filter').click(function(){
search()
});
})
</script>
@endsection
......@@ -18,6 +18,11 @@
*/
Auth::routes();
Route::resource('posts', PostController::class);
Route::get('/home', [App\Http\Controllers\HomeController::class, 'index'])->name('home');
Route::middleware(['auth'])->group(function () {
Route::resource('posts', PostController::class);
Route::prefix('posts')->name('posts.')->group(function () {
Route::post('ajax', [PostController::class, 'ajax'])->name('ajax');
});
Route::get('/', [App\Http\Controllers\HomeController::class, 'index'])->name('home');
Route::get('/home', [App\Http\Controllers\HomeController::class, 'index'])->name('home');
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment