Commit 6f198a23 authored by Le Dinh Trung's avatar Le Dinh Trung

Merge branch 'feature/UI-vuetify' into 'dev'

Feature/ui vuetify

See merge request !19
parents 68c8f625 4b0a2154
......@@ -9,28 +9,28 @@
<v-app-bar-nav-icon />
<v-toolbar-title>
<v-btn href="/users">
<v-btn to="/users">
<span>USER</span>
<v-icon dense>
mdi-account
</v-icon>
</v-btn>
<v-btn href="/categories">
<v-btn to="/categories">
<span>CATEGORY</span>
<v-icon dense>
mdi-heart
</v-icon>
</v-btn>
<v-btn href="/products">
<v-btn to="/products">
<span>PRODUCT</span>
<v-icon dense>
mdi-briefcase
</v-icon>
</v-btn>
<v-btn href="/posts">
<v-btn to="/posts">
<span>POST</span>
<v-icon dense>
mdi-newspaper
......@@ -86,7 +86,7 @@ export default {
const resp = await this.$axios.post('/logout', {
token: this.$auth.$storage.getUniversal('token')
})
if (resp.status == '200') {
if (resp.status === 200) {
this.$toast.success('Logout!', {
duration: 2000
})
......
export default ({ redirect, store }) => {
if (typeof localStorage !== 'undefined' && !localStorage.getItem('token')) {
export default ({ redirect, store, $auth }) => {
if (!$auth.$storage.getLocalStorage('token')) {
return redirect('/login')
}
}
......@@ -10,119 +10,87 @@
</v-breadcrumbs>
</div>
<!-- modal-create -->
<v-dialog
v-model="dialog3"
persistent
max-width="600px"
>
<v-card>
<v-card-title>
<span class="text-h5">Category</span>
</v-card-title>
<v-card-text>
<v-container>
<v-row>
<v-col cols="12">
<v-text-field
v-model="eName"
label="Name"
readonly
/>
</v-col>
<v-col cols="12">
<v-text-field
v-model="eOrdering"
label="Ordering"
type="number"
readonly
/>
</v-col>
<v-col cols="12">
<v-select
v-model="eParentId"
:items="categories.filter(category => category.id !== eID)"
item-text="name"
item-value="id"
label="Parent"
readonly
/>
</v-col>
<v-col cols="12">
<v-img :src="eImage" />
</v-col>
</v-row>
</v-container>
<small>*indicates required field</small>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn
color="blue darken-1"
text
@click="dialog3 = false"
>
Close
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-dialog
v-model="dialog2"
persistent
max-width="600px"
>
<v-dialog v-model="dialog2" persistent max-width="600px">
<v-card>
<v-card-title>
<span class="text-h5">Category Edit</span>
</v-card-title>
<v-card-text>
<v-container>
<v-row>
<v-col cols="12">
<v-text-field
v-model="eName"
label="Name"
:rules="nameRules"
required
/>
</v-col>
<v-col cols="12">
<v-text-field
v-model="eOrdering"
label="Ordering"
type="number"
:rules="requiredRules"
required
/>
</v-col>
<v-col cols="12">
<v-select
v-model="eParentId"
:items="categories.filter(category =>category.id !== eID)"
item-text="name"
item-value="id"
label="Parent"
/>
</v-col>
<v-col cols="12">
<v-file-input
v-model="eImage"
accept="image/*"
label="Image"
prepend-icon="mdi-camera"
/>
</v-col>
<v-img v-if="typeof eImage === 'string'" :src="eImage" />
</v-row>
<v-form ref="formEdit">
<v-row>
<v-col cols="12">
<v-text-field
v-model="eName"
label="Name"
:rules="nameRules"
required
/>
</v-col>
<v-col cols="12">
<v-text-field
v-model="eOrdering"
label="Ordering"
type="number"
:rules="requiredRules"
required
/>
</v-col>
<v-col cols="12">
<v-autocomplete
v-model="eParentId"
:items="
categories.filter((category) => category.id !== eID)
"
item-text="name"
item-value="id"
label="Parent"
>
<template #item="{item}">
<div :class="`category-${item.depth}`">
{{ item.name }}
</div>
</template>
</v-autocomplete>
</v-col>
<v-col cols="12">
<v-file-input
v-model="eImage"
accept="image/*"
label="Image"
prepend-icon="mdi-camera"
@change="fileSelected"
/>
</v-col>
<v-col cols="12">
<img
v-if="file"
:src="file"
>
</v-col>
<v-col cols="12" text-align=" left">
<v-img
v-if="typeof eImage === 'string'"
contain
height="100px"
width="150px"
:src="eImage"
/>
</v-col>
</v-row>
</v-form>
</v-container>
<small>*indicates required field</small>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn
color="blue darken-1"
text
@click="dialog2 = false"
@click="
dialog2 = false;
clearFile();
clearEditData();
"
>
Close
</v-btn>
......@@ -130,18 +98,33 @@
color="blue darken-1"
text
type="submit"
@click="dialog2 = false; updateCategory();"
@click="
dialog2 = false;
updateCategory();
"
>
Save
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-data-table :headers="headers" :items="categories" :item-class="indentation" sort-by="calories" class="elevation-1">
<v-data-table
:headers="headers"
:items="categories"
:item-class="indentation"
:search="searchTable"
sort-by="calories"
class="elevation-1"
>
<template #top>
<v-toolbar flat>
<v-toolbar-title>Category Manage</v-toolbar-title>
<v-divider class="mx-4" inset vertical />
<v-text-field
v-model="searchTable"
append-icon="mdi-magnify"
label="Search"
single-line
hide-details
/>
<v-spacer />
<v-toolbar-title style="float: right">
<v-dialog
......@@ -151,13 +134,8 @@
lazy-validation
>
<template #activator="{ on, attrs }">
<v-btn
color="primary"
dark
v-bind="attrs"
v-on="on"
>
New Category
<v-btn color="primary" dark v-bind="attrs" v-on="on">
CREATE NEW
</v-btn>
</template>
<v-card>
......@@ -167,11 +145,7 @@
<v-card-text>
<v-container>
<v-row>
<v-col
cols="12"
sm="6"
md="4"
>
<v-col cols="12">
<v-text-field
v-model="name"
label="Name"
......@@ -190,32 +164,46 @@
/>
</v-col>
<v-col cols="12">
<v-select
<v-autocomplete
v-model="parent_id"
:items="categories"
:search="search"
:filter="filter"
item-text="name"
item-value="id"
label="Parent*"
:rules="requiredRules"
/>
>
<template #item="{item}">
<div :class="`category-${item.depth}`">
{{ item.name }}
</div>
</template>
</v-autocomplete>
</v-col>
<v-col cols="12">
<v-file-input
v-model="image"
label="Image"
prepend-icon="mdi-camera"
@change="fileSelected"
/>
</v-col>
<v-col cols="12">
<img v-if="file" :src="file" contain>
</v-col>
</v-row>
</v-container>
<small>*indicates required field</small>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn
color="blue darken-1"
text
@click="dialog1 = false"
@click="
dialog1 = false;
clearFile();
"
>
Close
</v-btn>
......@@ -223,7 +211,10 @@
color="blue darken-1"
text
type="submit"
@click="dialog1 = false; createCategory();"
@click="
dialog1 = false;
createCategory();
"
>
Save
</v-btn>
......@@ -246,13 +237,22 @@
<v-text-field v-model="editedItem.id" label="id" />
</v-col>
<v-col cols="12" sm="6" md="4">
<v-text-field v-model="editedItem.ordering" label="ordering" />
<v-text-field
v-model="editedItem.ordering"
label="ordering"
/>
</v-col>
<v-col cols="12" sm="6" md="4">
<v-text-field v-model="editedItem.created_at" label="created_at" />
<v-text-field
v-model="editedItem.created_at"
label="created_at"
/>
</v-col>
<v-col cols="12" sm="6" md="4">
<v-text-field v-model="editedItem.updated_at" label="updated_at" />
<v-text-field
v-model="editedItem.updated_at"
label="updated_at"
/>
</v-col>
</v-row>
</v-container>
......@@ -278,7 +278,14 @@
<v-btn color="blue darken-1" text @click="closeDelete">
Cancel
</v-btn>
<v-btn color="blue darken-1" text @click="deleteCategory(); dialogDelete = false">
<v-btn
color="blue darken-1"
text
@click="
deleteCategory();
dialogDelete = false;
"
>
OK
</v-btn>
<v-spacer />
......@@ -290,22 +297,33 @@
<template #[`item.index`]="{ index }">
{{ index + 1 }}
</template>
<template #item.created_at="{ item }">
<template #[`item.created_at`]="{ item }">
<span>{{ formatDate(item.created_at) }}</span>
</template>
<template #item.updated_at="{ item }">
<template #[`item.updated_at`]="{ item }">
<span>{{ formatDate(item.updated_at) }}</span>
</template>
<template #item.actions="{ item }">
<v-icon :id="item.id" small @click="dialog2 = true; editCategory(item)">
<template #[`item.actions`]="{ item }">
<v-icon
:id="item.id"
small
@click="
dialog2 = true;
editCategory(item);
"
>
mdi-pencil
</v-icon>
<v-icon :id="item.id" small @click="dialogDelete = true; getID(item)">
<v-icon
:id="item.id"
small
@click="
dialogDelete = true;
getID(item);
"
>
mdi-delete
</v-icon>
<v-icon :id="item.id" small @click="dialog3 = true; showCategory(item)">
mdi-account-details
</v-icon>
</template>
<template #no-data>
<v-btn color="primary" @click="initialize">
......@@ -317,12 +335,14 @@
</v-app>
</template>
<script>
export default {
layout: 'admin',
middleware: ['web'],
data: () => {
return {
search: '',
searchTable: '',
file: null,
parent_id: '',
name: '',
ordering: '',
......@@ -331,23 +351,26 @@ export default {
dialog: false,
dialog1: false,
dialog2: false,
dialog3: false,
dialogDelete: false,
options: [],
headers: [
{
text: '#',
align: 'start',
value: 'index',
groupable: false
value: 'index'
},
{ text: 'Name', value: 'name', groupable: false },
{ text: 'Odering', value: 'ordering', groupable: false },
{ text: 'Created', value: 'created_at', groupable: false },
{ text: 'Updated', value: 'updated_at', groupable: false },
{ text: 'Actions', value: 'actions', sortable: false, groupable: false }
{ text: 'Ordering', value: 'ordering', filterable: true },
{ text: 'Created', value: 'created_at' },
{ text: 'Updated', value: 'updated_at' },
{
text: 'Actions',
value: 'actions',
sortable: false
}
],
categories: [],
categoryTrees: [],
eID: '',
eName: '',
eOrdering: '',
......@@ -359,7 +382,7 @@ export default {
name: '',
id: '',
ordering: '',
parent_id: '',
parent_id: [],
created_at: '',
updated_at: ''
},
......@@ -388,9 +411,7 @@ export default {
v => !!v || 'Name is required',
v => (v && v.length <= 255) || 'Name must be less than 255 characters'
],
requiredRules: [
v => !!v || 'This field is required',
],
requiredRules: [v => !!v || 'This field is required'],
numberRules: [
v => !!v || 'This field is required',
v => v > 0 || 'value must be a positive integer'
......@@ -400,6 +421,11 @@ export default {
computed: {
formTitle () {
return this.editedIndex === -1 ? 'New Item' : 'Edit Item'
},
filter () {
return this.caseSensitive
? (item, search, textKey) => item[textKey].includes(search)
: undefined
}
},
watch: {
......@@ -413,6 +439,7 @@ export default {
created () {
this.initialize()
this.getCategories()
this.getCategoryTrees()
},
methods: {
indentation (item) {
......@@ -458,11 +485,29 @@ export default {
}
this.close()
},
getCategoryTrees () {
this.$axios
.get('/categories-tree/', {
headers: {
Authorization: `Bearer ${this.$auth.$storage.getUniversal(
'token'
)}`
}
})
.then((response) => {
this.categoryTrees = response.data.data
})
.catch(function (error) {
console.log(error)
})
},
getCategories () {
this.$axios
.get('/categories/', {
headers: {
Authorization: `Bearer ${this.$auth.$storage.getUniversal('token')}`
Authorization: `Bearer ${this.$auth.$storage.getUniversal(
'token'
)}`
}
})
.then((response) => {
......@@ -482,14 +527,11 @@ export default {
fd.append('image', this.image)
}
this.$axios
.post('/categories/',
fd,
{
headers: {
Authorization: `Bearer ${this.$auth.$storage.getUniversal('token')}`
}
.post('/categories/', fd, {
headers: {
Authorization: `Bearer ${this.$auth.$storage.getUniversal('token')}`
}
)
})
.then((response) => {
self.$toast.success('Category created successfully!', {
duration: 3000
......@@ -505,6 +547,8 @@ export default {
})
})
this.getCategories()
this.getCategoryTrees()
this.clearFile()
this.clearData()
},
deleteCategory () {
......@@ -512,12 +556,13 @@ export default {
const currentPostIndex = this.editedIndex
try {
this.$axios
.delete(`/categories/${this.eID}`,
{
headers: {
Authorization: `Bearer ${this.$auth.$storage.getUniversal('token')}`
}
})
.delete(`/categories/${this.eID}`, {
headers: {
Authorization: `Bearer ${this.$auth.$storage.getUniversal(
'token'
)}`
}
})
.then((response) => {
this.categories.splice(currentPostIndex, 1)
self.$toast.success('Category deleted successfully!', {
......@@ -544,12 +589,6 @@ export default {
console.log(this.eID)
console.log(this.eImage)
},
showCategory (item) {
this.eName = item.name
this.eOrdering = item.ordering
this.eParentId = item.parent_id
this.eImage = item.image
},
updateCategory (userID) {
const self = this
const fd = new FormData()
......@@ -565,32 +604,63 @@ export default {
}
try {
this.$axios
.post(`categories/update/${this?.eID}`,
fd,
{
headers: {
Authorization: `Bearer ${this.$auth.$storage.getUniversal('token')}`
}
.post(`categories/update/${this?.eID}`, fd, {
headers: {
Authorization: `Bearer ${this.$auth.$storage.getUniversal(
'token'
)}`
}
)
})
.then((response) => {
self.$toast.success('User updated successfully!', {
duration: 3000
})
this.editedItem = response.data.data
Object.assign(this.categories[this.editedIndex], this.editedItem)
this.clearFile()
})
} catch (error) {
console.log(error)
}
},
clearData () {
// eslint-disable-next-line no-unused-expressions
this.parent_id = '',
this.name = '',
this.ordering = '',
this.parent_id = ''
this.name = ''
this.ordering = ''
this.image = null
},
clearEditData () {
this.eParentId = ''
this.eName = ''
this.eOrdering = ''
this.eImage = null
},
fileSelected (event) {
if (event) {
this.file = URL.createObjectURL(event)
console.log(this.file)
} else {
this.file = null
}
},
clearFile () {
this.file = null
}
// onOpen (e) {
// if (!this.__initial) {
// this.__initial = true
// return
// }
// // this.eParentId.length = 0
// console.log('toggle arrow clicked', e)
// },
// selectCategory (e) {
// console.log(e[0])
// console.log(this.eParentId)
// // this.eParentId.length = 0
// // this.eParentId.push(e[0])
// }
}
}
</script>
......@@ -599,9 +669,9 @@ export default {
.depth-0 {
background-color: rgba(211, 211, 211, 0.555);
}
.depth-1 {
.depth-1 {
padding-left: 30px !important;
background-color: rgba(211, 211, 211, 0.133);
background-color: rgba(211, 211, 211, 0.133);
}
.depth-1 > td:nth-child(2) {
padding-left: 30px !important;
......@@ -618,4 +688,17 @@ export default {
.depth-5 > td:nth-child(2) {
padding-left: 150px !important;
}
.category-1 {
padding-left: 30px !important;
}
.category-2 {
padding-left: 60px !important;
}
.category-3 {
padding-left: 90px !important;
}
img {
width: 100px;
height: 150px;
}
</style>
......@@ -79,6 +79,6 @@
<script>
export default {
layout: 'admin',
middleware: ['web'],
middleware: ['web']
}
</script>
......@@ -109,10 +109,15 @@ export default {
this.password = ''
this.checkbox = false
},
async login() {
async login () {
try {
const resp = await this.$axios.post('/login',
{
const resp = await fetch('http://127.0.0.1:8000/api/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json'
},
body: JSON.stringify({
email: this.email,
password: this.password,
status: this.status
......@@ -121,7 +126,7 @@ export default {
return response.json()
})
console.log(resp.status)
localStorage.setItem('token', resp.data.bearer_token)
localStorage.setItem('token', resp.data.bearer_token)
this.$auth.$storage.setUniversal('token', resp.data.bearer_token)
this.$auth.$storage.setUniversal('userName', resp.data.name)
this.$auth.$storage.setUniversal('loggedIn', 'true')
......@@ -132,13 +137,13 @@ export default {
this.$router.push('home')
}
} catch (e) {
this.$toast.error("Username or Password not valid", {
duration: 2000,
});
this.$router.push("/login");
this.$toast.error('Username or Password not valid', {
duration: 2000
})
this.$router.push('/login')
}
}
}
}
}
</script>
<style scoped>
......
<!-- eslint-disable vue/require-v-for-key -->
<!-- eslint-disable-next-line no-console -->
<template>
<div>
<div>
......@@ -10,91 +11,12 @@
</v-breadcrumbs>
</div>
<div style="float: right" />
<!-- modal-show -->
<v-dialog
v-model="dialog2"
persistent
max-width="600px"
>
<v-card>
<v-card-title>
<span class="text-h5">POST</span>
</v-card-title>
<v-card-text>
<v-container>
<v-row>
<v-col
cols="12"
sm="6"
md="4"
>
<v-text-field
v-model="sTitle"
label="Title"
readonly
/>
</v-col>
<v-col
cols="12"
>
<v-select
v-model="sCategoryId"
:items="categories"
item-text="name"
item-value="id"
label="Category"
readonly
/>
</v-col>
<v-col cols="12">
<v-textarea
v-model="sContent"
label="Content"
word-break="break-word"
readonly
/>
</v-col>
<v-col
cols="12"
>
<v-select
v-model="sStatus"
:items="statusDefaul"
item-text="name"
item-value="id"
label="Status"
readonly
/>
</v-col>
<v-col cols="12">
<!-- <v-col v-for="(image, index) in sImages" :key="index" cols="12">
<v-img :src="image"/> -->
<v-img
v-if="typeof sImages === 'string'"
:src="sImages"
/>
</v-col>
</v-row>
</v-container>
<small>*indicates required field</small>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn
color="blue darken-1"
text
@click="dialog2 = false"
>
Close
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<!-- modal-edit -->
<v-dialog
v-model="dialog"
persistent
max-width="600px"
lazy-validation
>
<v-card>
<v-card-title>
......@@ -111,25 +33,43 @@
<v-text-field
v-model="eTitle"
label="Title*"
:rules="requiredRules"
required
/>
</v-col>
<v-col
cols="12"
>
<v-select
<!-- <v-select
v-model="eCategoryId"
:items="categories"
item-text="name"
item-value="id"
label="Category"
/>
:rules="requiredRules"
/> -->
<v-autocomplete
v-model="eCategoryId"
:items="
categories.filter((category) => category.id !== eId)
"
item-text="name"
item-value="id"
label="Category"
>
<template #item="{item}">
<div :class="`category-${item.depth}`">
{{ item.name }}
</div>
</template>
</v-autocomplete>
</v-col>
<v-col cols="12">
<v-textarea
v-model="eContent"
label="Content"
required
:rules="requiredRules"
/>
</v-col>
<v-col
......@@ -137,10 +77,11 @@
>
<v-select
v-model="eStatus"
:items="statusDefaul"
:items="statusDefault"
item-text="name"
item-value="id"
label="Status"
:rules="requiredRules"
/>
</v-col>
<v-col cols="12">
......@@ -151,18 +92,37 @@
small-chips
dense
prepend-icon="mdi-camera"
@change="fileSelected"
/>
</v-col>
<v-col cols="12" class="v-image v-responsive theme--dark">
<img
v-if="file"
contain
max-height="300"
max-width="500"
:src="file"
>
</v-col>
<v-col cols="12" text-align=" left">
<v-img
v-for="(imageEdit, index) in eImages"
:key="index"
contain
height="100px"
width="150px"
:src="imageEdit"
/>
</v-col>
</v-row>
</v-container>
<small>*indicates required field</small>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn
color="blue darken-1"
text
@click="dialog = false"
@click="dialog = false; clearFile()"
>
Close
</v-btn>
......@@ -197,18 +157,25 @@
</v-dialog>
<!-- table -->
<div>
<v-data-table :headers="headers" :items="posts" sort-by="calories" class="elevation-1">
<v-data-table :headers="headers" :items="posts" :search="searchTable" sort-by="calories" class="elevation-1">
<template #top>
<v-toolbar flat>
<v-toolbar-title>Post Manage</v-toolbar-title>
<v-divider class="mx-4" inset vertical />
<v-text-field
v-model="searchTable"
append-icon="mdi-magnify"
label="Search"
single-line
hide-details
/>
<v-spacer />
<v-toolbar-title>
<!-- modal-create -->
<v-dialog
v-model="dialog1"
persistent
max-width="600px"
enctype="multipart/form-data"
lazy-validation
>
<template #activator="{ on, attrs }">
<v-btn
......@@ -217,7 +184,7 @@
v-bind="attrs"
v-on="on"
>
New Post
create new
</v-btn>
</template>
<v-card>
......@@ -235,24 +202,35 @@
<v-text-field
v-model="title"
label="Title*"
:rules="requiredRules"
required
/>
</v-col>
<v-col
cols="12"
>
<v-select
<v-autocomplete
v-model="category_id"
:items="categories"
:search="search"
:filter="filter"
item-text="name"
item-value="id"
label="Category"
/>
:rules="requiredRules"
>
<template #item="{item}">
<div :class="`category-${item.depth}`">
{{ item.name }}
</div>
</template>
</v-autocomplete>
</v-col>
<v-col cols="12">
<v-textarea
v-model="content"
label="Content"
:rules="requiredRules"
required
/>
</v-col>
......@@ -261,10 +239,11 @@
>
<v-select
v-model="status"
:items="statusDefaul"
:items="statusDefault"
item-text="name"
item-value="id"
label="Status"
:rules="requiredRules"
/>
</v-col>
<v-col cols="12">
......@@ -275,19 +254,28 @@
dense
accept="image/*"
prepend-icon="mdi-camera"
:rules="requiredRules"
@change="fileSelected"
/>
</v-col>
<v-col cols="12" class="v-image v-responsive theme--dark">
<img
v-if="file"
contain
max-height="300"
max-width="500"
:src="file"
>
</v-col>
</v-row>
</v-container>
<small>*indicates required field</small>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn
color="blue darken-1"
text
@click="dialog1 = false"
@click="dialog1 = false; clearFile()"
>
Close
</v-btn>
......@@ -312,7 +300,7 @@
{{ categories.find(x => x.id === item.category_id)?.name }}
</template>
<template #[`item.status`]="{ item }">
{{ statusDefaul.find(x => x.id == item.status)?.name }}
{{ statusDefault.find(x => x.id == item.status)?.name }}
</template>
<template #[`item.user_id`]="{ item }">
{{ users.find(x => x.id === item.user_id)?.name }}
......@@ -324,9 +312,6 @@
<v-icon :id="item.id" small @click="dialogDelete = true; getID(item)">
mdi-delete
</v-icon>
<v-icon :id="item.id" small @click="showPost(item)">
mdi-account-details
</v-icon>
</template>
<template #no-data>
<v-btn color="primary" @click="initialize">
......@@ -346,15 +331,17 @@ export default {
middleware: ['web'],
data: () => {
return {
filter: '',
search: '',
searchTable: '',
file: null,
dialog1: false,
dialog2: false,
title: '',
category_id: null,
content: '',
user_id: '',
status: null,
images: [],
images: null,
dialog: false,
dialogDelete: false,
headers: [
......@@ -377,41 +364,35 @@ export default {
href: '/home'
},
{
text: 'Product',
text: 'Post',
disabled: false,
href: '/products'
href: '/posts'
}
],
statusDefaul: [
statusDefault: [
{
name: 'Draft',
id: '1'
id: 1
},
{
name: 'Publish',
id: '2'
id: 2
},
{
name: 'Unpublish',
id: '3'
id: 3
}
],
posts: [],
categories: [],
users: [],
sTitle: '',
sCategoryId: '',
sContent: '',
sUserId: '',
sStatus: [],
sImages: [],
eId: '',
eTitle: '',
eCategoryId: [],
eContent: '',
eUserId: '',
eStatus: '',
eImages: [],
eImages: null,
message: [],
editedIndex: -1,
editedItem: {
......@@ -428,7 +409,18 @@ export default {
status: '',
created_at: '',
updated_at: ''
}
},
requiredRules: [
v => !!v || 'This field is required'
],
numberRules: [
v => !!v || 'This field is required',
v => v > 0 || 'value must be a positive integer'
],
imageRules: [
v => !!v || 'Images is required',
v => (v && v.length > 0) || 'Images is required'
]
}
},
computed: {
......@@ -561,6 +553,8 @@ export default {
this.editedItem = response.data.data
console.log(this.editedItem)
this.posts.push(this.editedItem)
this.clearData()
this.clearFile()
})
.catch((errors) => {
self.$toast.error('something went wrong while trying create!', {
......@@ -569,14 +563,14 @@ export default {
})
},
getID (item) {
this.eID = item.id
this.eId = item.id
this.editedIndex = this.categories.indexOf(item)
},
deletePost () {
const self = this
const currentPostIndex = this.editedIndex
this.$axios
.delete(`/posts/${this.eID}`, {
.delete(`/posts/${this.eId}`, {
headers: {
Authorization: `Bearer ${this.$auth.$storage.getUniversal('token')}`
}
......@@ -594,30 +588,13 @@ export default {
})
})
},
async showPost (item) {
const ID = item.id
try {
const resp = await this.$axios.get(`/posts/${ID}`, {
headers: {
Authorization: `Bearer ${this.$auth.$storage.getUniversal('token')}`
}
})
this.sTitle = resp.data.data.title
this.sContent = resp.data.data.content
this.sCategoryId = resp.data.data.category_id
this.sStatus = resp.data.data.status
this.sImages = resp.data.data.images
} catch (error) {
console.log(error)
}
this.dialog2 = true
},
editPost (item) {
this.eId = item.id
this.eTitle = item.title
this.eCategoryId = item.category_id
this.eContent = item.content
this.eStatus = item.status
this.eImages = item.images
this.editedIndex = this.posts.indexOf(item)
console.log(this.editedIndex)
this.dialog = true
......@@ -628,9 +605,10 @@ export default {
fd.append('title', this.eTitle)
fd.append('category_id', this.eCategoryId)
fd.append('content', this.eContent)
fd.append('images', this.eImages)
fd.append('status', this.eStatus)
if (typeof this.eImages !== 'string' && this.eImages != null) {
fd.append('images', this.eImages)
}
const currentPostIndex = this.editedIndex
this.$axios
.post(
......@@ -648,6 +626,7 @@ export default {
})
this.editedItem = response.data.data
Object.assign(this.posts[currentPostIndex], this.editedItem)
this.clearFile()
})
.catch((error) => {
console.log(error)
......@@ -657,13 +636,39 @@ export default {
})
},
fileSelected (event) {
console.log(event)
console.log(this.images)
if (event) {
this.file = URL.createObjectURL(event)
console.log(this.file)
} else {
this.file = null
}
},
clearData () {
this.title = ''
this.category_id = null
this.content = ''
this.status = null
this.images = []
},
clearFile () {
this.file = null
}
}
}
</script>
<style>
.category-1 {
padding-left: 30px !important;
}
.category-2 {
padding-left: 60px !important;
}
.category-3 {
padding-left: 90px !important;
}
img {
width: 100%;
height: 100%;
}
</style>
......@@ -7,112 +7,6 @@
</template>
</v-breadcrumbs>
</div>
<!-- show-modal -->
<v-dialog
v-model="dialog2"
persistent
max-width="600px"
>
<v-card>
<v-card-title>
<span class="text-h5">PRODUCT</span>
</v-card-title>
<v-card-text>
<v-container>
<v-row>
<v-col
cols="12"
sm="6"
md="4"
>
<v-text-field
v-model="sName"
label="Name"
readonly
/>
</v-col>
<v-col
cols="12"
>
<v-select
v-model="sCategoryId"
:items="categories"
item-text="name"
item-value="id"
label="Category"
readonly
/>
</v-col>
<v-col cols="12">
<v-text-field
v-model="sPrice"
label="Price"
readonly
/>
</v-col>
<v-col cols="12">
<v-text-field
v-model="sDescription"
label="Description"
readonly
/>
</v-col>
<v-col v-for="(image, index) in sImages" :key="index" cols="12">
<v-img :src="image" contain />
</v-col>
<v-col cols="12" />
<v-row v-for="(variant, index) in sVariants" :key="'A' +index">
<v-col
cols="12"
sm="6"
md="4"
>
<v-text-field
v-model="variant.color"
label="Color"
readonly
/>
</v-col>
<v-col
cols="12"
sm="6"
md="4"
>
<v-text-field
v-model="variant.size"
label="Size"
readonly
/>
</v-col>
<v-col
cols="12"
sm="6"
md="4"
>
<v-text-field
v-model="variant.quantity"
label="Quantity"
readonly
/>
</v-col>
<v-col />
</v-row>
</v-row>
</v-container>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn
color="blue darken-1"
text
type="submit"
@click="dialog2 = false"
>
Close
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<!-- edit-modal -->
<v-dialog
v-model="dialog3"
......@@ -128,8 +22,6 @@
<v-row>
<v-col
cols="12"
sm="6"
md="4"
>
<v-text-field
v-model="eName"
......@@ -141,14 +33,21 @@
<v-col
cols="12"
>
<v-select
<v-autocomplete
v-model="eCategoryId"
class="select--menu my-select"
:items="categories"
item-text="name"
item-value="id"
label="Category"
:rules="requiredRules"
/>
>
<template #item="{item}">
<div :class="`category-${item.depth}`">
{{ item.name }}
</div>
</template>
</v-autocomplete>
</v-col>
<v-col cols="12">
<v-text-field
......@@ -172,9 +71,30 @@
small-chips
dense
multiple
@change="fileSelected"
@click:clear="clearImage"
/>
</v-col>
<v-col v-if="files" cols="12" style="display:flex; text-align:left">
<v-img
v-for="(image, index) in files"
:key="index"
:src="image"
contain
height="100px"
width="150px"
/>
</v-col>
<v-col cols="12" style="display:flex; text-align:left">
<v-img
v-for="(imageEdit, index) in eImages"
:key="index"
contain
height="100px"
width="150px"
:src="imageEdit"
/>
</v-col>
<v-img v-if="typeof eImages === 'string'" :src="eImages" />
<v-col cols="12">
<v-btn
class="mx-2"
......@@ -244,14 +164,13 @@
</v-row>
</v-row>
</v-container>
<small>*indicates required field</small>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn
color="blue darken-1"
text
@click="dialog3 = false"
@click="dialog3 = false; clearImage();"
>
Close
</v-btn>
......@@ -306,13 +225,19 @@
<v-data-table
:headers="headers"
:items="products"
:search="searchTable"
sort-by="calories"
class="elevation-1"
>
<template #top>
<v-toolbar flat>
<v-toolbar-title>Product Manage</v-toolbar-title>
<v-divider class="mx-4" inset vertical />
<v-text-field
v-model="searchTable"
append-icon="mdi-magnify"
label="Search"
single-line
hide-details
/>
<v-spacer />
<v-toolbar-title>
<v-dialog
......@@ -328,7 +253,7 @@
v-bind="attrs"
v-on="on"
>
New Product
create new
</v-btn>
</template>
<v-card>
......@@ -340,8 +265,6 @@
<v-row>
<v-col
cols="12"
sm="6"
md="4"
>
<v-text-field
v-model="name"
......@@ -354,14 +277,20 @@
<v-col
cols="12"
>
<v-select
<v-autocomplete
v-model="category_id"
:items="categories"
item-text="name"
item-value="id"
label="Category"
:rules="requiredRules"
/>
>
<template #item="{item}">
<div :class="`category-${item.depth}`">
{{ item.name }}
</div>
</template>
</v-autocomplete>
</v-col>
<v-col cols="12">
<v-text-field
......@@ -388,8 +317,20 @@
:rules="imageRules"
lazy-validation
prepend-icon="mdi-camera"
@change="fileSelected"
@click:clear="clearImage"
/>
</v-col>
<v-col v-if="files" cols="12" style="display:flex; justify-content: space-around;">
<img
v-for="(image, index) in files"
:key="index"
:src="image"
contain
height="100px"
width="150px"
>
</v-col>
<v-col cols="12">
<v-btn
class="mx-2"
......@@ -459,14 +400,13 @@
</v-row>
</v-row>
</v-container>
<small>*indicates required field</small>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn
color="blue darken-1"
text
@click="dialog1 = false"
@click="dialog1 = false; clearData ();"
>
Close
</v-btn>
......@@ -487,6 +427,15 @@
<template #[`item.index`]="{ index }">
{{ index + 1 }}
</template>
<template #[`item.variants`]="{ item }">
{{ item.variants_count }}
</template>
<template #[`item.status`]="{ item }">
<v-switch
v-model="item.status"
@click="switchStatus(item)"
/>
</template>
<template #[`item.actions`]="{ item }">
<v-icon :id="item.id" small @click="editProduct(item)">
mdi-pencil
......@@ -494,9 +443,6 @@
<v-icon :id="item.id" small @click="dialogDelete = true; getID(item)">
mdi-delete
</v-icon>
<v-icon :id="item.id" small @click="showProduct(item)">
mdi-account-details
</v-icon>
</template>
<template #no-data>
<v-btn color="primary" @click="initialize">
......@@ -514,6 +460,8 @@ export default {
middleware: ['web'],
data: () => {
return {
searchTable: '',
files: [],
name: '',
id: '',
category_id: '',
......@@ -521,6 +469,7 @@ export default {
stock: '',
description: '',
images: [],
status: '',
variants: [
{
color: '',
......@@ -533,7 +482,6 @@ export default {
quantity: '',
dialog: false,
dialog1: false,
dialog2: false,
dialog3: false,
dialogDelete: false,
dialogDeleteVariant: false,
......@@ -549,6 +497,8 @@ export default {
{ text: 'Category', value: 'category.name' },
{ text: 'Price', value: 'price' },
{ text: 'Stock', value: 'stock' },
{ text: 'Variants', value: 'variants' },
{ text: 'Status', value: 'status' },
{ text: 'Actions', value: 'actions', sortable: false }
],
items: [
......@@ -568,19 +518,7 @@ export default {
products: [],
product: [],
categories: [],
sName: '',
sCategoryId: '',
sPrice: '',
sDescription: '',
sStock: '',
sImages: [],
sVariants: [
{
color: '',
size: '',
quantity: ''
}
],
categoryTrees: [],
idVariant: '',
editedVariantIndex: '',
eId: '',
......@@ -604,7 +542,8 @@ export default {
category_id: '',
price: '',
description: '',
stock: ''
stock: '',
variants_count: ''
},
defaultItem: {
name: '',
......@@ -647,6 +586,7 @@ export default {
this.initialize()
this.getProducts()
this.getCategories()
this.getCategoryTrees()
},
methods: {
initialize () {
......@@ -704,6 +644,22 @@ export default {
}
this.close()
},
getCategoryTrees () {
this.$axios
.get('/categories-tree/', {
headers: {
Authorization: `Bearer ${this.$auth.$storage.getUniversal(
'token'
)}`
}
})
.then((response) => {
this.categoryTrees = response.data.data
})
.catch(function (error) {
console.log(error)
})
},
getCategories () {
this.$axios
.get('/categories/', {
......@@ -757,8 +713,10 @@ export default {
duration: 3000
})
this.editedItem = response.data.data
this.editedItem.variants_count = response.data.data.variants.length
console.log(this.editedItem)
this.products.push(this.editedItem)
this.clearData()
})
.catch((errors) => {
console.log(errors.response.data.message)
......@@ -792,27 +750,6 @@ export default {
console.log(error)
}
},
async showProduct (item) {
const ID = item.id
try {
const resp = await this.$axios.get(`/products/${ID}`, {
method: 'GET',
headers: {
Authorization: `Bearer ${this.$auth.$storage.getUniversal('token')}`
}
})
this.sName = resp.data.data.name
this.sPrice = resp.data.data.price
this.sCategoryId = resp.data.data.category_id
this.sDescription = resp.data.data.description
this.sStock = resp.data.data.stock
this.sVariants = resp.data.data.variants
this.sImages = resp.data.data.images
} catch (error) {
console.log(error)
}
this.dialog2 = true
},
editProduct (item) {
this.eId = item.id
this.eName = item.name
......@@ -821,6 +758,7 @@ export default {
this.eStock = item.stock
this.eDescription = item.description
this.eVariants = item.variants
this.eImages = item.images
this.editedIndex = this.products.indexOf(item)
console.log(item.variants)
this.dialog3 = true
......@@ -856,7 +794,7 @@ export default {
}
)
.then((response) => {
self.$toast.success('User updated successfully!', {
self.$toast.success('Product updated successfully!', {
duration: 3000
})
console.log(response)
......@@ -889,28 +827,77 @@ export default {
this.idVariant = item.id
},
removeVariant () {
const self = this
const currentVariantIndex = this.editedVariantIndex
if (this.idVariant !== null) {
try {
this.$axios.delete(`/products/delete-variant/${this.idVariant}`,
{
headers: {
Authorization: `Bearer ${this.$auth.$storage.getUniversal('token')}`
}
this.eVariants.splice(currentVariantIndex, 1)
},
fileSelected (event) {
if (event) {
for (let i = 0; i < event.length; i++) {
console.log(event[i])
this.files.push(URL.createObjectURL(event[i]))
}
}
console.log(this.files)
},
clearData () {
this.name = ''
this.category_id = ''
this.price = ''
this.description = ''
this.images = []
this.variants = [
{
color: '',
size: '',
quantity: ''
}
]
this.files.length = 0
},
clearImage () {
this.files.length = 0
},
switchStatus (item) {
const status = item.status ? 1 : 0
const self = this
const fd = new FormData()
fd.append('status', status)
this.$axios
.post(`/products/update-status/${item.id}`,
fd, {
headers: {
Authorization: `Bearer ${this.$auth.$storage.getUniversal('token')}`
}
).then((response) => {
self.$toast.success('Remove variant successfully!', {
duration: 3000
})
}
)
.then((response) => {
console.log(response)
self.$toast.success('Updated status successfully!', {
duration: 3000
})
this.getProducts()
} catch (error) {
})
.catch((error) => {
console.log(error)
}
this.eVariants.splice(currentVariantIndex, 1)
}
self.$toast.error('ERR!', {
duration: 3000
})
})
}
}
}
</script>
<style>
.category-1 {
padding-left: 30px !important;
}
.category-2 {
padding-left: 60px !important;
}
.category-3 {
padding-left: 90px !important;
}
img {
width: 100px;
height: 150px;
}
</style>
......@@ -18,29 +18,30 @@
</v-card-title>
<v-card-text>
<v-container>
<v-row>
<v-col cols="12" sm="6" md="4">
<v-text-field v-model="eName" label="Legal name*" :rules="nameRules" required />
</v-col>
<v-col cols="12">
<v-text-field v-model="eEmail" label="Email*" :rules="emailRules" required />
</v-col>
<v-col cols="12">
<v-text-field
v-model="ePassword"
label="Password*"
type="password"
:rules="passwordRules"
required
/>
</v-col>
</v-row>
<v-form ref="formEdit">
<v-row>
<v-col cols="12" sm="6" md="4">
<v-text-field v-model="eName" label="Legal name*" :rules="nameRules" required />
</v-col>
<v-col cols="12">
<v-text-field v-model="eEmail" label="Email*" :rules="emailRules" required />
</v-col>
<v-col cols="12">
<v-text-field
v-model="ePassword"
label="Password*"
type="password"
:rules="passwordRules"
required
/>
</v-col>
</v-row>
</v-form>
</v-container>
<small>*indicates required field</small>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn color="blue darken-1" text @click="dialog2 = false">
<v-btn color="blue darken-1" text @click="dialog2 = false; clearData();">
Close
</v-btn>
<v-btn
......@@ -48,8 +49,9 @@
text
type="submit"
@click="
dialog2 = false;
// dialog2 = false;
updateUser();
validateForm();
"
>
Save
......@@ -85,25 +87,30 @@
<v-data-table
:headers="headers"
:items="users"
:search="searchTable"
sort-by="calories"
class="elevation-1"
>
<template #top>
<v-toolbar flat>
<v-toolbar-title>User Manage</v-toolbar-title>
<v-divider class="mx-4" inset vertical />
<v-text-field
v-model="searchTable"
append-icon="mdi-magnify"
label="Search"
single-line
hide-details
/>
<v-spacer />
<v-toolbar-title style="float: right">
<v-dialog
v-model="dialog1"
persistent
max-width="600px"
@submit.prevent="createUser"
lazy-validation
>
<template #activator="{ on, attrs }">
<v-btn color="primary" dark v-bind="attrs" v-on="on">
New USER
CREATE NEW
</v-btn>
</template>
<v-card>
......@@ -112,35 +119,36 @@
</v-card-title>
<v-card-text>
<v-container>
<v-row>
<v-col cols="12" sm="6" md="4">
<v-text-field
v-model="name"
label="Legal name*"
:rules="nameRules"
required
/>
</v-col>
<v-col cols="12">
<v-text-field
v-model="email"
label="Email*"
:rules="emailRules"
required
/>
</v-col>
<v-col cols="12">
<v-text-field
v-model="password"
label="Password*"
type="password"
:rules="passwordRules"
required
/>
</v-col>
</v-row>
<v-form ref="form">
<v-row>
<v-col cols="12" sm="6" md="4">
<v-text-field
v-model="name"
label="Legal name*"
:rules="nameRules"
required
/>
</v-col>
<v-col cols="12">
<v-text-field
v-model="email"
label="Email*"
:rules="emailRules"
required
/>
</v-col>
<v-col cols="12">
<v-text-field
v-model="password"
label="Password*"
type="password"
:rules="passwordRules"
required
/>
</v-col>
</v-row>
</v-form>
</v-container>
<small>*indicates required field</small>
</v-card-text>
<v-card-actions>
<v-spacer />
......@@ -152,8 +160,9 @@
text
type="submit"
@click="
dialog1 = false;
// dialog1 = false;
createUser();
validateForm()
"
>
Save
......@@ -167,10 +176,10 @@
<template #[`item.index`]="{ index }">
{{ index + 1 }}
</template>
<template #item.created_at="{ item }">
<template #[`item.created_at`]="{ item }">
<span>{{ formatDate(item.created_at) }}</span>
</template>
<template #item.updated_at="{ item }">
<template #[`item.updated_at`]="{ item }">
<span>{{ formatDate(item.updated_at) }}</span>
</template>
<template #[`item.actions`]="{ item }">
......@@ -211,6 +220,8 @@ export default {
middleware: ['web'],
data: () => {
return {
searchTable: '',
valid: true,
email: '',
name: '',
password: '',
......@@ -224,9 +235,8 @@ export default {
align: 'start',
value: 'index'
},
{ text: 'Name', value: 'name' },
{ text: 'Name', value: 'name', sortable: false },
{ text: 'email', value: 'email' },
{ text: 'status', value: 'id', sortable: false },
{ text: 'created', value: 'created_at' },
{ text: 'updated', value: 'updated_at' },
{ text: 'Actions', value: 'actions', sortable: false }
......@@ -378,6 +388,7 @@ export default {
}
)
.then((response) => {
this.dialog1 = false
self.$toast.success('User created successfully!', {
duration: 3000
})
......@@ -385,8 +396,11 @@ export default {
this.editedItem = response.data.data
console.log(this.editedItem)
this.users.push(this.editedItem)
this.clearData()
})
.catch((errors) => {
this.dialog1 = true
this.validate()
console.log(errors.response.data.message)
this.message = errors.response.data.message
self.$toast.error('something went wrong while trying create!', {
......@@ -443,6 +457,7 @@ export default {
}
)
.then((response) => {
this.dialog2 = false
self.$toast.success('User updated successfully!', {
duration: 3000
})
......@@ -460,6 +475,21 @@ export default {
getID (item) {
this.eID = item.id
this.editedIndex = this.users.indexOf(item)
},
clearData () {
this.name = ''
this.email = ''
this.password = ''
},
async validate (name) {
const { valid } = await this.$refs.form.validate()
if (valid) { alert('Form is valid') }
},
async validateForm (name) {
const { valid } = await this.$refs.formEdit.validate()
if (valid) { alert('Form is valid') }
}
}
}
......
......@@ -12,6 +12,13 @@ export default function ({ $axios }, inject, redirect) {
}
})
$axios.onError((error) => {
const code = parseInt(error.response && error.response.status)
if (code === 403) {
redirect('/login')
}
})
const api = $axios.create({
headers: {
common: {
......
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