diff --git a/package.json b/package.json index 7a464c2..ce7b0fa 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@vueuse/head": "^1.3.1", "apexcharts": "^3.44.2", "axios": "^0.27.2", + "moment": "^2.30.1", "nprogress": "^0.2.0", "pinia": "^2.1.7", "vue": "^3.3.11", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9cfef91..5a0d18d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,6 +26,9 @@ dependencies: axios: specifier: ^0.27.2 version: 0.27.2 + moment: + specifier: ^2.30.1 + version: 2.30.1 nprogress: specifier: ^0.2.0 version: 0.2.0 @@ -7912,6 +7915,10 @@ packages: ufo: 1.3.2 dev: true + /moment@2.30.1: + resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==} + dev: false + /mrmime@1.0.1: resolution: {integrity: sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==} engines: {node: '>=10'} diff --git a/src/auto-imports.d.ts b/src/auto-imports.d.ts index 07be639..03da606 100644 --- a/src/auto-imports.d.ts +++ b/src/auto-imports.d.ts @@ -154,6 +154,7 @@ declare global { const useCssVar: typeof import('@vueuse/core')['useCssVar'] const useCssVars: typeof import('vue')['useCssVars'] const useCurrentElement: typeof import('@vueuse/core')['useCurrentElement'] + const useCustomerStore: typeof import('./store/customer')['useCustomerStore'] const useCycleList: typeof import('@vueuse/core')['useCycleList'] const useDark: typeof import('@vueuse/core')['useDark'] const useDateFormat: typeof import('@vueuse/core')['useDateFormat'] @@ -470,6 +471,7 @@ declare module 'vue' { readonly useCssVar: UnwrapRef readonly useCssVars: UnwrapRef readonly useCurrentElement: UnwrapRef + readonly useCustomerStore: UnwrapRef readonly useCycleList: UnwrapRef readonly useDark: UnwrapRef readonly useDateFormat: UnwrapRef @@ -779,6 +781,7 @@ declare module '@vue/runtime-core' { readonly useCssVar: UnwrapRef readonly useCssVars: UnwrapRef readonly useCurrentElement: UnwrapRef + readonly useCustomerStore: UnwrapRef readonly useCycleList: UnwrapRef readonly useDark: UnwrapRef readonly useDateFormat: UnwrapRef diff --git a/src/common/api/api-service.ts b/src/common/api/api-service.ts index cbd4661..a0e0219 100644 --- a/src/common/api/api-service.ts +++ b/src/common/api/api-service.ts @@ -36,7 +36,7 @@ export class ApiService { } async post(url: string, data: any): Promise { - return this.httpClient.post(`${this.apiBase}/${url}`, data) + const response = this.httpClient.post(`${this.apiBase}/${url}`, data) return response.data } diff --git a/src/common/filters/date.filter.ts b/src/common/filters/date.filter.ts new file mode 100644 index 0000000..f9dbb2c --- /dev/null +++ b/src/common/filters/date.filter.ts @@ -0,0 +1,19 @@ +import moment from 'moment' + +const formattedDate = function (value: string, format = 'DD, MMM-YYYY, HH:mm') { + if (value === null) + return value + moment.locale('en') + return moment.utc(value).local().format(format) +} +const shortDate = function (value: string) { + moment.locale('en') + return moment.utc(value).local().format('YYYY/MM') +} +const friendlyTime = function (value: string) { + if (value === null) + return value + return moment(value).local().fromNow() +} + +export default { formattedDate, shortDate, friendlyTime } diff --git a/src/components.d.ts b/src/components.d.ts index 8f41a70..a31b312 100644 --- a/src/components.d.ts +++ b/src/components.d.ts @@ -17,6 +17,7 @@ declare module 'vue' { CreateCategory: typeof import('./components/Category/CreateCategory.vue')['default'] CreateColor: typeof import('./components/Color/CreateColor.vue')['default'] CreateProduct: typeof import('./components/Products/CreateProduct.vue')['default'] + CustomerManagement: typeof import('./components/Customers/CustomerManagement.vue')['default'] DashboardCard: typeof import('./components/DashboardCard.vue')['default'] DonutChart: typeof import('./components/DonutChart.vue')['default'] Editor: typeof import('./components/Editor.vue')['default'] diff --git a/src/components/Customers/CustomerManagement.vue b/src/components/Customers/CustomerManagement.vue new file mode 100644 index 0000000..215f5bd --- /dev/null +++ b/src/components/Customers/CustomerManagement.vue @@ -0,0 +1,166 @@ + + + + + diff --git a/src/components/Sidebar.vue b/src/components/Sidebar.vue index 96177d5..701f04e 100644 --- a/src/components/Sidebar.vue +++ b/src/components/Sidebar.vue @@ -106,22 +106,22 @@ const menuOptions: MenuOption[] = [ ], }, { - label: () => renderLabel(t('menu.customers'), 'customers'), + label: () => renderLabel(t('menu.customers'), '/customers'), key: 'customers', icon: renderIcon(CustomersIcon), }, { - label: () => renderLabel(t('menu.announcement'), 'announcement'), + label: () => renderLabel(t('menu.announcement'), '/announcement'), key: 'notify', icon: renderIcon(NewsIcon), children: [ { - label: () => renderLabel(t('menu.news'), 'news'), + label: () => renderLabel(t('menu.news'), '/news'), key: 'news', icon: renderIcon(NewsIcon), }, { - label: () => renderLabel(t('menu.notifications'), 'notify'), + label: () => renderLabel(t('menu.notifications'), '/notify'), key: 'notifications', icon: renderIcon(NotifyIcon), }, @@ -138,12 +138,12 @@ const menuOptions: MenuOption[] = [ icon: renderIcon(SettingsIcon), children: [ { - label: () => renderLabel(t('menu.accountSettings'), 'account'), + label: () => renderLabel(t('menu.accountSettings'), '/account'), key: 'account-settings', icon: renderIcon(AccountSettingsIcon), }, { - label: () => renderLabel(t('menu.websiteSettings'), 'website-settings'), + label: () => renderLabel(t('menu.websiteSettings'), '/website-settings'), key: 'website-settings', icon: renderIcon(WebsiteSettingsIcon), }, diff --git a/src/main.ts b/src/main.ts index 5affb44..e18a585 100644 --- a/src/main.ts +++ b/src/main.ts @@ -9,7 +9,11 @@ import 'uno.css' import './styles/main.scss' const routes = setupLayouts(generatedRoutes) - +declare module '@vue/runtime-core' { + export interface ComponentCustomProperties { + $filters: any + } +} async function enableMocking() { const isMocking = import.meta.env.VITE_API_MOCKING_ENABLED if (!isMocking) @@ -28,6 +32,11 @@ app.use(router) Object.values(import.meta.glob<{ install: AppModule }>('./modules/*.ts', { eager: true })) .forEach(i => i.install?.(app, router)) +// register filters +app.config.globalProperties.$filters = {} +Object.values(import.meta.glob('./common/filters/*.filter.ts', { eager: true, import: 'default' })) + .forEach(filters => Object.keys(filters).forEach(func => app.config.globalProperties.$filters[func] = filters[func])) + router.beforeEach((to, from, next) => { // @ts-expect-error "Type instantiation is excessively deep and possibly infinite.ts(2589)" const { t } = i18n.global diff --git a/src/mocks/handlers/account.handlers.ts b/src/mocks/handlers/account.handler.ts similarity index 90% rename from src/mocks/handlers/account.handlers.ts rename to src/mocks/handlers/account.handler.ts index 5bf166e..b5cd64c 100644 --- a/src/mocks/handlers/account.handlers.ts +++ b/src/mocks/handlers/account.handler.ts @@ -1,5 +1,5 @@ import { HttpResponse, delay, http } from 'msw' -import type { LoginResponse, LoginViewModel } from '~/models/Login' +import type { LoginResponse, LoginViewModel } from '@/models/Login' const handlers = [ http.post('/api/account/login', async ({ request }) => { diff --git a/src/mocks/handlers/customer.handler.ts b/src/mocks/handlers/customer.handler.ts new file mode 100644 index 0000000..cb30133 --- /dev/null +++ b/src/mocks/handlers/customer.handler.ts @@ -0,0 +1,52 @@ +import { HttpResponse, http } from 'msw' +import _ from 'lodash' +import { faker } from '@faker-js/faker' +import { CreatePagedResponse } from '../handlers.utility' + +import type { Customer, CustomerCreateModel } from '~/models/Customer' + +const customers = _.times(65, createFakeCustomer) +const handlers = [ + http.get('/api/customer', ({ request }) => { + const response = CreatePagedResponse(request, customers) + return HttpResponse.json(response, { status: 200 }) + }), + http.post('/api/customer', async ({ request }) => { + const newItem = await request.json() as CustomerCreateModel + const customer: CustomerCreateModel = { + id: faker.number.int({ max: 2000 }).toString(), + firstName: newItem.firstName, + lastName: newItem.lastName, + address: [], + mobile: newItem.mobile, + joinDate: new Date(), + birthDate: newItem.birthDate, + email: newItem.email, + } + customers.push(customer) + return HttpResponse.json(customer, { status: 201 }) + }), + http.delete('/api/customer/:id', ({ params }) => { + const { id } = params + const itemIndex = customers.findIndex(x => x.id === id) + customers.splice(itemIndex, 1) + return HttpResponse.json(true, { status: 200 }) + }), + +] + +function createFakeCustomer(): Customer { + return { + id: faker.number.int().toString(), + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + address: [], + mobile: faker.phone.number(), + joinDate: faker.date.past(), + birthDate: faker.date.birthdate(), + email: faker.internet.email(), + ordersCount: faker.number.int({ max: 50 }), + } +} + +export default handlers diff --git a/src/models/Customer.ts b/src/models/Customer.ts new file mode 100644 index 0000000..0da5af9 --- /dev/null +++ b/src/models/Customer.ts @@ -0,0 +1,19 @@ +export interface Customer { + id: string + firstName: string + lastName: string + email: string + mobile: string + address: Address[] + joinDate: Date + birthDate: Date + ordersCount?: number +} + +export interface Address { + +} + +export interface CustomerCreateModel extends Customer { + +} diff --git a/src/pages/Customers/index.vue b/src/pages/Customers/index.vue index 5f6ea40..9599806 100644 --- a/src/pages/Customers/index.vue +++ b/src/pages/Customers/index.vue @@ -2,8 +2,13 @@ + +meta: + title: Customers + +