Skip to content

Commit 6e0b94f

Browse files
committed
Create dedicated page for product and add ckeditor for description
1 parent 4f0efed commit 6e0b94f

File tree

9 files changed

+1038
-19
lines changed

9 files changed

+1038
-19
lines changed

backend/package-lock.json

Lines changed: 876 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

backend/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
"preview": "vite preview"
99
},
1010
"dependencies": {
11+
"@ckeditor/ckeditor5-build-classic": "^39.0.1",
12+
"@ckeditor/ckeditor5-vue": "^5.1.0",
1113
"axios": "^0.27.2",
1214
"chart.js": "^3.9.1",
1315
"vue": "^3.2.25",

backend/src/components/core/CustomInput.vue

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
:value="props.modelValue"
1313
:class="inputClasses"
1414
@change="onChange($event.target.value)">
15-
<option v-for="option of selectOptions" :value="option.key">{{option.text}}</option>
15+
<option v-for="option of selectOptions" :value="option.key">{{ option.text }}</option>
1616
</select>
1717
</template>
1818
<template v-else-if="type === 'textarea'">
@@ -23,6 +23,15 @@
2323
:class="inputClasses"
2424
:placeholder="label"></textarea>
2525
</template>
26+
<template v-else-if="type === 'richtext'">
27+
<ckeditor :name="name"
28+
:required="required"
29+
:editor="editor"
30+
:model-value="props.modelValue"
31+
@input="onChange"
32+
:class="inputClasses"
33+
:config="props.editorConfig"></ckeditor>
34+
</template>
2635
<template v-else-if="type === 'file'">
2736
<input :type="type"
2837
:name="name"
@@ -57,13 +66,16 @@
5766
{{ append }}
5867
</span>
5968
</div>
60-
<small v-if="errors && errors[0]" class="text-red-600">{{errors[0]}}</small>
69+
<small v-if="errors && errors[0]" class="text-red-600">{{ errors[0] }}</small>
6170
</div>
6271
</template>
6372

6473
<script setup>
6574
6675
import {computed, ref} from "vue";
76+
import ClassicEditor from "@ckeditor/ckeditor5-build-classic";
77+
78+
const editor = ClassicEditor
6779
6880
const props = defineProps({
6981
modelValue: [String, Number, File],
@@ -86,7 +98,14 @@ const props = defineProps({
8698
errors: {
8799
type: Array,
88100
required: false
89-
}
101+
},
102+
editorConfig: {
103+
type: Object,
104+
default: () => ({
105+
// toolbar: ['bold', 'italic', 'link', 'bulletedList', 'numberedList'],
106+
})
107+
},
108+
90109
})
91110
92111
const id = computed(() => {
@@ -121,5 +140,11 @@ function onChange(value) {
121140
</script>
122141

123142
<style scoped>
143+
/deep/ .ck.ck-editor {
144+
width: 100%;
145+
}
124146
147+
/deep/ .ck-content {
148+
min-height: 200px;
149+
}
125150
</style>

backend/src/components/core/Spinner.vue

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<template>
2-
<div class="flex flex-col items-center" :class="class">
2+
<div class="spinner flex flex-col items-center" :class="class">
33
<svg
44
class="animate-spin -ml-1 h-8 w-8 text-gray-700"
55
xmlns="http://www.w3.org/2000/svg"
@@ -40,5 +40,7 @@ const {text} = defineProps({
4040
</script>
4141

4242
<style scoped>
43-
43+
.spinner {
44+
min-height: 140px;
45+
}
4446
</style>

backend/src/main.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import {createApp} from 'vue'
2+
import CKEditor from '@ckeditor/ckeditor5-vue';
3+
24
import store from './store'
35
import router from './router'
46
import './index.css';
@@ -11,6 +13,7 @@ const app = createApp(App);
1113
app
1214
.use(store)
1315
.use(router)
16+
.use( CKEditor )
1417
.mount('#app')
1518
;
1619

backend/src/router/index.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import store from "../store";
1515
import Report from "../views/Reports/Report.vue";
1616
import OrdersReport from "../views/Reports/OrdersReport.vue";
1717
import CustomersReport from "../views/Reports/CustomersReport.vue";
18+
import ProductForm from "../views/Products/ProductForm.vue";
1819

1920
const routes = [
2021
{
@@ -40,6 +41,16 @@ const routes = [
4041
name: 'app.products',
4142
component: Products
4243
},
44+
{
45+
path: 'products/create',
46+
name: 'app.products.create',
47+
component: ProductForm
48+
},
49+
{
50+
path: 'products/:id',
51+
name: 'app.products.edit',
52+
component: ProductForm
53+
},
4354
{
4455
path: 'users',
4556
name: 'app.users',
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
<!-- This example requires Tailwind CSS v2.0+ -->
2+
<template>
3+
<div id="product-form">
4+
<div class="flex items-center justify-between mb-3">
5+
<h1 class="text-3xl font-semibold">{{ product.id ? `Update product: "${product.title}"` : 'Create new Product' }}</h1>
6+
</div>
7+
<Spinner v-if="loading"
8+
class="p-5 z-[100] absolute left-0 top-0 bg-white right-0 bottom-0 flex items-center justify-center"/>
9+
<form @submit.prevent="onSubmit">
10+
<div class="bg-white px-4 pt-5 pb-4 z-50">
11+
<CustomInput class="mb-2" v-model="product.title" label="Product Title"/>
12+
<CustomInput type="file" class="mb-2" label="Product Image" @change="file => product.image = file"/>
13+
<CustomInput type="richtext" class="mb-2" v-model="product.description" label="Description"/>
14+
<CustomInput type="number" class="mb-2" v-model="product.price" label="Price" prepend="$"/>
15+
<CustomInput type="checkbox" class="mb-2" v-model="product.published" label="Published"/>
16+
17+
18+
</div>
19+
<footer class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
20+
<button type="submit"
21+
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm
22+
text-white bg-indigo-600 hover:bg-indigo-700 focus:ring-indigo-500">
23+
Save
24+
</button>
25+
<button type="button"
26+
@click="onSubmit($event, true)"
27+
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm
28+
text-white bg-indigo-600 hover:bg-indigo-700 focus:ring-indigo-500">
29+
Save and Close
30+
</button>
31+
<router-link :to="{name: 'app.products'}"
32+
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm">
33+
Cancel
34+
</router-link>
35+
</footer>
36+
</form>
37+
</div>
38+
</template>
39+
40+
<script setup>
41+
import {computed, onMounted, ref} from 'vue'
42+
import CustomInput from "../../components/core/CustomInput.vue";
43+
import store from "../../store/index.js";
44+
import Spinner from "../../components/core/Spinner.vue";
45+
import {useRoute, useRouter} from "vue-router";
46+
47+
const product = ref({
48+
id: null,
49+
title: null,
50+
image: null,
51+
description: '',
52+
price: null,
53+
published: null
54+
})
55+
56+
const loading = ref(false)
57+
58+
const router = useRouter();
59+
const route = useRoute();
60+
61+
onMounted(() => {
62+
if (route.params.id) {
63+
loading.value = true
64+
store.dispatch('getProduct', route.params.id)
65+
.then(({data}) => {
66+
loading.value = false
67+
product.value = data
68+
})
69+
}
70+
})
71+
72+
function onSubmit(event, close = false) {
73+
loading.value = true
74+
if (product.value.id) {
75+
store.dispatch('updateProduct', product.value)
76+
.then(response => {
77+
loading.value = false;
78+
if (response.status === 200) {
79+
store.commit('showToast', 'Product was successfully updated')
80+
store.dispatch('getProducts')
81+
if (close) {
82+
router.push({name: 'app.products'})
83+
}
84+
}
85+
})
86+
} else {
87+
store.dispatch('createProduct', product.value)
88+
.then(response => {
89+
loading.value = false;
90+
if (response.status === 201) {
91+
store.commit('showToast', 'Product was successfully created')
92+
store.dispatch('getProducts')
93+
if (close) {
94+
router.push({name: 'app.products'})
95+
}
96+
}
97+
})
98+
.catch(err => {
99+
loading.value = false;
100+
})
101+
}
102+
}
103+
</script>
104+
<style>
105+
#product-form {
106+
position: relative;
107+
}
108+
</style>

backend/src/views/Products/Products.vue

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
<template>
22
<div class="flex items-center justify-between mb-3">
33
<h1 class="text-3xl font-semibold">Products</h1>
4-
<button type="button"
5-
@click="showAddNewModal()"
4+
<router-link :to="{name: 'app.products.create'}"
65
class="py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
76
>
87
Add new Product
9-
</button>
8+
</router-link>
109
</div>
11-
<ProductsTable @clickEdit="editProduct"/>
10+
<ProductsTable/>
1211
<ProductModal v-model="showProductModal" :product="productModel" @close="onModalClose"/>
1312
</template>
1413

backend/src/views/Products/ProductsTable.vue

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -96,20 +96,20 @@
9696
>
9797
<div class="px-1 py-1">
9898
<MenuItem v-slot="{ active }">
99-
<button
99+
<router-link
100+
:to="{name: 'app.products.edit', params: {id: product.id}}"
100101
:class="[
101102
active ? 'bg-indigo-600 text-white' : 'text-gray-900',
102103
'group flex w-full items-center rounded-md px-2 py-2 text-sm',
103104
]"
104-
@click="editProduct(product)"
105105
>
106106
<PencilIcon
107107
:active="active"
108108
class="mr-2 h-5 w-5 text-indigo-400"
109109
aria-hidden="true"
110110
/>
111111
Edit
112-
</button>
112+
</router-link>
113113
</MenuItem>
114114
<MenuItem v-slot="{ active }">
115115
<button
@@ -244,9 +244,6 @@ function deleteProduct(product) {
244244
})
245245
}
246246
247-
function editProduct(p) {
248-
emit('clickEdit', p)
249-
}
250247
</script>
251248

252249
<style scoped>

0 commit comments

Comments
 (0)