From 7d28d165f31c34a727eae5077725da9cfd5e5352 Mon Sep 17 00:00:00 2001 From: Saeid Doroudi Date: Sun, 19 Nov 2023 10:30:52 +0330 Subject: [PATCH 1/2] As a product manager I want to be able to define Brands and manage them Fixes #27 --- locales/en.yml | 8 ++ src/auto-imports.d.ts | 3 + src/components.d.ts | 2 + src/components/Brand/BrandManagement.vue | 133 ++++++++++++++++++ src/components/Brand/CreateBrand.vue | 88 ++++++++++++ .../Category/CategoryManagement.vue | 7 +- src/mocks/handlers/brand.handler.ts | 59 ++++++++ src/models/Brand.ts | 8 ++ src/pages/Brands/index.vue | 15 ++ src/services/brand.service.ts | 11 ++ src/services/category.service.ts | 21 +-- src/services/generic.service.ts | 32 +++++ src/store/brand.store.ts | 65 +++++++++ src/store/category.store.ts | 8 +- 14 files changed, 435 insertions(+), 25 deletions(-) create mode 100644 src/components/Brand/BrandManagement.vue create mode 100644 src/components/Brand/CreateBrand.vue create mode 100644 src/mocks/handlers/brand.handler.ts create mode 100644 src/models/Brand.ts create mode 100644 src/pages/Brands/index.vue create mode 100644 src/services/brand.service.ts create mode 100644 src/services/generic.service.ts create mode 100644 src/store/brand.store.ts diff --git a/locales/en.yml b/locales/en.yml index 254ee0c..b47a21d 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -81,4 +81,12 @@ categories: validations: nameRequired: Name is required parentRequired: Parent Not Selected + +brands: + title: Brands + createButton: Create + create: + buttonTitle: Create + title: Create New Brand + not-found: Not found diff --git a/src/auto-imports.d.ts b/src/auto-imports.d.ts index 279f76f..8c42384 100644 --- a/src/auto-imports.d.ts +++ b/src/auto-imports.d.ts @@ -135,6 +135,7 @@ declare global { const useBase64: typeof import('@vueuse/core')['useBase64'] const useBattery: typeof import('@vueuse/core')['useBattery'] const useBluetooth: typeof import('@vueuse/core')['useBluetooth'] + const useBrandStore: typeof import('./store/brand.store')['useBrandStore'] const useBreakpoints: typeof import('@vueuse/core')['useBreakpoints'] const useBroadcastChannel: typeof import('@vueuse/core')['useBroadcastChannel'] const useBrowserLocation: typeof import('@vueuse/core')['useBrowserLocation'] @@ -444,6 +445,7 @@ declare module 'vue' { readonly useBase64: UnwrapRef readonly useBattery: UnwrapRef readonly useBluetooth: UnwrapRef + readonly useBrandStore: UnwrapRef readonly useBreakpoints: UnwrapRef readonly useBroadcastChannel: UnwrapRef readonly useBrowserLocation: UnwrapRef @@ -747,6 +749,7 @@ declare module '@vue/runtime-core' { readonly useBase64: UnwrapRef readonly useBattery: UnwrapRef readonly useBluetooth: UnwrapRef + readonly useBrandStore: UnwrapRef readonly useBreakpoints: UnwrapRef readonly useBroadcastChannel: UnwrapRef readonly useBrowserLocation: UnwrapRef diff --git a/src/components.d.ts b/src/components.d.ts index f658ef1..ee18eb1 100644 --- a/src/components.d.ts +++ b/src/components.d.ts @@ -8,9 +8,11 @@ export {} declare module 'vue' { export interface GlobalComponents { BarChart: typeof import('./components/BarChart.vue')['default'] + BrandManagement: typeof import('./components/Brand/BrandManagement.vue')['default'] Card: typeof import('./components/Card.vue')['default'] CategoryManagement: typeof import('./components/Category/CategoryManagement.vue')['default'] CategoryStatics: typeof import('./components/Category/CategoryStatics.vue')['default'] + CreateBrand: typeof import('./components/Brand/CreateBrand.vue')['default'] CreateCategory: typeof import('./components/Category/CreateCategory.vue')['default'] DashboardCard: typeof import('./components/DashboardCard.vue')['default'] DonutChart: typeof import('./components/DonutChart.vue')['default'] diff --git a/src/components/Brand/BrandManagement.vue b/src/components/Brand/BrandManagement.vue new file mode 100644 index 0000000..da546b1 --- /dev/null +++ b/src/components/Brand/BrandManagement.vue @@ -0,0 +1,133 @@ + + + + + diff --git a/src/components/Brand/CreateBrand.vue b/src/components/Brand/CreateBrand.vue new file mode 100644 index 0000000..6d2eca9 --- /dev/null +++ b/src/components/Brand/CreateBrand.vue @@ -0,0 +1,88 @@ + + + + + diff --git a/src/components/Category/CategoryManagement.vue b/src/components/Category/CategoryManagement.vue index 272a076..995d43d 100644 --- a/src/components/Category/CategoryManagement.vue +++ b/src/components/Category/CategoryManagement.vue @@ -38,7 +38,7 @@ const columns: DataTableColumns = [ renderIcon: renderIcon(EditIcon), ghost: true, class: 'mr-2', - onClick: () => edit(row), + onClick: () => {}, }, { default: () => 'Edit' }, ), @@ -59,14 +59,11 @@ const columns: DataTableColumns = [ ] const { options } = storeToRefs(store) const showAddDialog = ref(false) + function renderIcon(icon: any) { return () => h(NIcon, null, { default: () => h(icon) }) } -function edit(row: RowData) { - -} - function handleDeleteItem(row: RowData) { dialog.error({ title: 'Confirm', diff --git a/src/mocks/handlers/brand.handler.ts b/src/mocks/handlers/brand.handler.ts new file mode 100644 index 0000000..e1baacf --- /dev/null +++ b/src/mocks/handlers/brand.handler.ts @@ -0,0 +1,59 @@ +import { rest } from 'msw' +import _ from 'lodash' +import { faker } from '@faker-js/faker' +import { CreatePagedResponse } from '../handlers.utility' +import type { Brand, BrandCreateModel } from '~/models/Brand' + +const brands = _.times(7, createFakeBrand) +const handlers = [ + rest.get('/api/Brand', (req, res, ctx) => { + const response = CreatePagedResponse(req, brands) + return res( + ctx.status(200), + ctx.delay(200), + ctx.json(response), + ) + }), + rest.post('/api/brand', async (req, res, ctx) => { + const newItem = await req.json() + const brand: Brand = { + id: faker.datatype.number({ max: 2000 }).toString(), + name: newItem.name, + url: newItem.url, + image: newItem.image, + } + brands.push(brand) + return res( + ctx.status(200), + ctx.delay(200), + ctx.json(brand), + ) + }), + rest.delete('/api/Brand/:id', (req, res, ctx) => { + const id = req.params.id.toString() + const itemIndex = brands.findIndex(x => x.id === id) + brands.splice(itemIndex, 1) + return res( + ctx.delay(1000), + ctx.status(200), + ctx.json(true), + ) + }), + +] + +function createFakeBrand(): Brand { + const name = faker.company.name() + return { + id: faker.datatype.number().toString(), + name, + image: '', + url: toKebabCase(name), + } +} + +function toKebabCase(str: string) { + return str.replaceAll(' ', '').replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase() +} + +export default handlers diff --git a/src/models/Brand.ts b/src/models/Brand.ts new file mode 100644 index 0000000..248014c --- /dev/null +++ b/src/models/Brand.ts @@ -0,0 +1,8 @@ +export interface Brand { + id: string + name: string + image: string + url: string +} + +export interface BrandCreateModel extends Brand {} diff --git a/src/pages/Brands/index.vue b/src/pages/Brands/index.vue new file mode 100644 index 0000000..0f5ae70 --- /dev/null +++ b/src/pages/Brands/index.vue @@ -0,0 +1,15 @@ + + + +meta: + title: Brands + + + + + diff --git a/src/services/brand.service.ts b/src/services/brand.service.ts new file mode 100644 index 0000000..d91b74a --- /dev/null +++ b/src/services/brand.service.ts @@ -0,0 +1,11 @@ +import GenericService from './generic.service' +import type { Brand } from '~/models/Brand' +import { ApiService } from '~/common/api/api-service' + +const apiService = new ApiService('Brand') +class BrandService extends GenericService { + constructor() { + super(apiService) + } +} +export default new BrandService() diff --git a/src/services/category.service.ts b/src/services/category.service.ts index b0aaa2e..36e1083 100644 --- a/src/services/category.service.ts +++ b/src/services/category.service.ts @@ -1,22 +1,11 @@ +import GenericService from './generic.service' +import type { Category } from '~/models/Category' import { ApiService } from '~/common/api/api-service' -import type { Category, CategoryCreateModel } from '~/models/Category' -import type { PagedAndSortedRequest } from '~/models/PagedAndSortedRequest' -import type { PagedListResult } from '~/models/PagedListResult' const apiService = new ApiService('Category') -class CategoryService { - constructor() { } - async getCategoryList(options: PagedAndSortedRequest): Promise> { - const response = await apiService.getPagedList('', options) - return response - } - - async createCategory(categoryItem: CategoryCreateModel): Promise { - return apiService.post('', categoryItem) - } - - async deleteCategory(id: string | number): Promise { - return apiService.delete(id) +class CategoryService extends GenericService { + constructor() { + super(apiService) } } export default new CategoryService() diff --git a/src/services/generic.service.ts b/src/services/generic.service.ts new file mode 100644 index 0000000..08e8844 --- /dev/null +++ b/src/services/generic.service.ts @@ -0,0 +1,32 @@ +import type { ApiService } from '~/common/api/api-service' +import type { PagedAndSortedRequest } from '~/models/PagedAndSortedRequest' +import type { PagedListResult } from '~/models/PagedListResult' + +class GenericService { + private apiService: ApiService + constructor(service: ApiService) { + this.apiService = service + } + + async getList(options: PagedAndSortedRequest): Promise> { + const response = await this.apiService.getPagedList('', options) + return response + } + + async getSingle(id: TKey): Promise { + return await this.apiService.get(`${id}`) + } + + async create(item: TModel): Promise { + return await this.apiService.post('', item) + } + + async edit(id: TKey, item: T): Promise { + return await this.apiService.put(`${id}`, item) + } + + async delete(id: TKey): Promise { + return await this.apiService.delete(`${id}`) + } +} +export default GenericService diff --git a/src/store/brand.store.ts b/src/store/brand.store.ts new file mode 100644 index 0000000..02f6440 --- /dev/null +++ b/src/store/brand.store.ts @@ -0,0 +1,65 @@ +import { acceptHMRUpdate, defineStore } from 'pinia' +import type { Brand, BrandCreateModel } from '~/models/Brand' +import type { PagedAndSortedRequest } from '~/models/PagedAndSortedRequest' +import brandService from '~/services/brand.service' + +export interface BrandState { +} +export const useBrandStore = defineStore('Brand', () => { + const brands = ref([]) + const brandItem = ref() + const isLoading = ref(false) + const isSaving = ref(false) + const { options } = useOptions() + + async function getBrands(options: PagedAndSortedRequest) { + isLoading.value = true + try { + const response = await brandService.getList(options) + brands.value = response.items + options.pageSize = Number.parseInt(response.totalCount / options.itemsPerPage) + } + finally { + isLoading.value = false + } + } + + function getBrand() { + + } + + async function createBrand(brandItem: BrandCreateModel) { + isLoading.value = true + try { + await brandService.create(brandItem) + getBrands(options.value) + } + finally { + isLoading.value = false + } + } + + async function deleteBrand(id: string) { + await brandService.delete(id) + getBrands(options.value) + } + + function editBrand() { + + } + + return { + isLoading, + isSaving, + brands, + options, + brandItem, + getBrands, + getBrand, + createBrand, + deleteBrand, + editBrand, + } +}) +if (import.meta.hot) + import.meta.hot.accept(acceptHMRUpdate(useBrandStore, import.meta.hot)) diff --git a/src/store/category.store.ts b/src/store/category.store.ts index f30c88e..fb35a0a 100644 --- a/src/store/category.store.ts +++ b/src/store/category.store.ts @@ -16,7 +16,7 @@ export const useCategoryStore = defineStore('Category', () => { async function getCategories(options: PagedAndSortedRequest) { isLoading.value = true try { - const response = await categoryService.getCategoryList(options) + const response = await categoryService.getList(options) categories.value = response.items options.pageSize = Number.parseInt(response.totalCount / options.itemsPerPage) } @@ -32,7 +32,7 @@ export const useCategoryStore = defineStore('Category', () => { async function createCategory(categoryItem: CategoryCreateModel) { isLoading.value = true try { - await categoryService.createCategory(categoryItem) + await categoryService.create(categoryItem) getCategories(options.value) } finally { @@ -40,8 +40,8 @@ export const useCategoryStore = defineStore('Category', () => { } } - async function deleteCategory(id: string | number) { - await categoryService.deleteCategory(id) + async function deleteCategory(id: number) { + await categoryService.delete(id) getCategories(options.value) } From 3d0d7d1598eb4bb5bb391944facb9e1e4eae3953 Mon Sep 17 00:00:00 2001 From: Saeid Doroudi Date: Sun, 19 Nov 2023 23:24:53 +0330 Subject: [PATCH 2/2] implement create brand add image selector --- locales/en.yml | 7 +++ src/components.d.ts | 2 + src/components/Brand/CreateBrand.vue | 79 +++++++++++++++++----------- 3 files changed, 56 insertions(+), 32 deletions(-) diff --git a/locales/en.yml b/locales/en.yml index b47a21d..5942ec0 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -88,5 +88,12 @@ brands: create: buttonTitle: Create title: Create New Brand + image: Image + brandName: Name + url: Url + validations: + name: Name is required + url: Url is required + image: Image is required not-found: Not found diff --git a/src/components.d.ts b/src/components.d.ts index ee18eb1..ecadbd5 100644 --- a/src/components.d.ts +++ b/src/components.d.ts @@ -35,10 +35,12 @@ declare module 'vue' { NLayoutSider: typeof import('naive-ui')['NLayoutSider'] NMenu: typeof import('naive-ui')['NMenu'] NMessageProvider: typeof import('naive-ui')['NMessageProvider'] + NModal: typeof import('naive-ui')['NModal'] NNotificationProvider: typeof import('naive-ui')['NNotificationProvider'] NPageHeader: typeof import('naive-ui')['NPageHeader'] NPopselect: typeof import('naive-ui')['NPopselect'] NTreeSelect: typeof import('naive-ui')['NTreeSelect'] + NUpload: typeof import('naive-ui')['NUpload'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] Sidebar: typeof import('./components/Sidebar.vue')['default'] diff --git a/src/components/Brand/CreateBrand.vue b/src/components/Brand/CreateBrand.vue index 6d2eca9..4863724 100644 --- a/src/components/Brand/CreateBrand.vue +++ b/src/components/Brand/CreateBrand.vue @@ -1,34 +1,23 @@