From fd8047855b6a09c2de19437a117e21775a17d1fe Mon Sep 17 00:00:00 2001 From: xGooddevilx Date: Sat, 10 May 2025 09:31:29 +0330 Subject: [PATCH 01/20] feat(Views):Initialize comments page and add its route in router file --- src/router/index.js | 58 +++++++++++++++++++++----------------- src/views/CommentsView.vue | 9 ++++++ 2 files changed, 41 insertions(+), 26 deletions(-) create mode 100644 src/views/CommentsView.vue diff --git a/src/router/index.js b/src/router/index.js index 452ceea..8ea09b9 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -1,51 +1,57 @@ -import { createRouter, createWebHistory } from 'vue-router'; +import { createRouter, createWebHistory } from "vue-router"; -import AuthenticateRoute from './middleware/AuthenticateRoute'; -import AuthorizeRoute from './middleware/AuthorizeRoute'; -import RedirectIfAuthenticated from './middleware/RedirectIfAuthenticated'; +import AuthenticateRoute from "./middleware/AuthenticateRoute"; +import AuthorizeRoute from "./middleware/AuthorizeRoute"; +import RedirectIfAuthenticated from "./middleware/RedirectIfAuthenticated"; -import PanelView from '@/views/PanelView.vue'; +import PanelView from "@/views/PanelView.vue"; const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes: [ { - path: '/', - name: 'Panel', + path: "/", + name: "Panel", component: PanelView, beforeEnter: [AuthenticateRoute], children: [ { - path: 'dashboard', - name: 'Dashboard', - component: () => import('@/views/DashboardView.vue'), + path: "dashboard", + name: "Dashboard", + component: () => import("@/views/DashboardView.vue"), beforeEnter: [AuthorizeRoute], meta: { - permissions: ['dashboard'] - } + permissions: ["dashboard"], + }, }, { - path: 'users', - name: 'Users', - component: () => import('@/views/UsersView.vue') + path: "users", + name: "Users", + component: () => import("@/views/UsersView.vue"), }, { - path: 'todos', - name: 'Todos', - component: () => import('@/views/TodosView.vue') - } - ] + path: "todos", + name: "Todos", + component: () => import("@/views/TodosView.vue"), + }, + { + path:'comments', + name:"Comments", + component: () => import('@/views/CommentsView.vue'), + }, + + ], }, { - path: '/login', - name: 'Login', + path: "/login", + name: "Login", // route level code-splitting // this generates a separate chunk (Login.[hash].js) for this route // which is lazy-loaded when the route is visited. - component: () => import('@/views/LoginView.vue'), - beforeEnter: [RedirectIfAuthenticated] - } - ] + component: () => import("@/views/LoginView.vue"), + beforeEnter: [RedirectIfAuthenticated], + }, + ], }); export default router; diff --git a/src/views/CommentsView.vue b/src/views/CommentsView.vue new file mode 100644 index 0000000..a61142c --- /dev/null +++ b/src/views/CommentsView.vue @@ -0,0 +1,9 @@ + + + From 0d165c621331b0fc89f5df06e4b45a55f8b3dbe7 Mon Sep 17 00:00:00 2001 From: xGooddevilx Date: Sat, 10 May 2025 09:56:37 +0330 Subject: [PATCH 02/20] feat:(Components):Add component page link to the sidebar menu component --- src/components/layout/side-menu/VSideMenu.vue | 20 ++++++++++++------- src/locales/en/messages.js | 1 + src/locales/fa/messages.js | 1 + src/views/CommentsView.vue | 10 ++++++---- 4 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/components/layout/side-menu/VSideMenu.vue b/src/components/layout/side-menu/VSideMenu.vue index e6b252c..d80be8c 100644 --- a/src/components/layout/side-menu/VSideMenu.vue +++ b/src/components/layout/side-menu/VSideMenu.vue @@ -24,19 +24,25 @@ {{ $t('Todos') }} + + + {{ $t('Comments') }} + diff --git a/src/locales/en/messages.js b/src/locales/en/messages.js index c8da1c7..32ac351 100644 --- a/src/locales/en/messages.js +++ b/src/locales/en/messages.js @@ -4,6 +4,7 @@ export default { "Password": "Password", "Submit": "Submit", "Cancel": "Cancel", + "Comments": "Comments", "Dashboard": "Dashboard", "Users": "Users", "Authorization Error": "Authorization Error", diff --git a/src/locales/fa/messages.js b/src/locales/fa/messages.js index 54e9ff3..009b3d0 100644 --- a/src/locales/fa/messages.js +++ b/src/locales/fa/messages.js @@ -6,6 +6,7 @@ export default { "Cancel": "انصراف", "Dashboard": "داشبورد", "Users": "کاربران", + "Comments": "نظرات", "Authorization Error": "خطای دسترسی", "No data available": "داده\u200Cای موجود نیست", "You are successfully logged in": "شما با موفقیت وارد سیستم شدید", diff --git a/src/views/CommentsView.vue b/src/views/CommentsView.vue index a61142c..5d76c30 100644 --- a/src/views/CommentsView.vue +++ b/src/views/CommentsView.vue @@ -1,9 +1,11 @@ - - + + From 495a0eb2c0ff367ad50d30633068517d94d88366 Mon Sep 17 00:00:00 2001 From: xGooddevilx Date: Sat, 10 May 2025 10:39:15 +0330 Subject: [PATCH 03/20] feat(Services):Implement comments services & its composable --- src/composables/comments.composable.js | 32 ++++++++++++++++++++++++++ src/services/comments.service.js | 29 +++++++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 src/composables/comments.composable.js create mode 100644 src/services/comments.service.js diff --git a/src/composables/comments.composable.js b/src/composables/comments.composable.js new file mode 100644 index 0000000..5e779fa --- /dev/null +++ b/src/composables/comments.composable.js @@ -0,0 +1,32 @@ +import { computed, ref } from "vue"; +import { useLoading } from "./loading.composable"; +import { keyBy } from "@/utils"; +import CommentsService from "@/services/comments.service"; + +export const useFetchComments = () => { + const { isLoading, startLoading, endLoading } = useLoading(); + const comments = ref([]); + const commentsKeyById = computed(() => keyBy(comments.value, "id")); + + /** + * @param {AxiosRequestConfig} [config] + */ + const fetchComments = (config) => { + startLoading(); + return CommentsService.getAll(config) + .then((response) => { + comments.value = response.data; + return response; + }) + .finally(() => { + endLoading(); + }); + }; + + return { + comments, + commentsIsLoading: isLoading, + commentsKeyById, + fetchComments, + }; +}; diff --git a/src/services/comments.service.js b/src/services/comments.service.js new file mode 100644 index 0000000..32fe258 --- /dev/null +++ b/src/services/comments.service.js @@ -0,0 +1,29 @@ +import CrudService from "./crud.service"; + +class CommentsService extends CrudService { + /** + * Service url + * + * @returns {String} + */ + static get URL() { + return "comments"; + } + + /** + * Get default item + * + * @returns {Object} + */ + static getDefault() { + return { + postId: undefined, + id: undefined, + name: undefined, + body: undefined, + email: undefined, + }; + } +} + +export default CommentsService; From 058a793580ffa92e7ef4a8b8520c1b0172226ab9 Mon Sep 17 00:00:00 2001 From: xGooddevilx Date: Sat, 10 May 2025 12:44:35 +0330 Subject: [PATCH 04/20] feat(View):Implement comments table & add status field to the data --- src/composables/comments.composable.js | 2 +- src/locales/en/messages.js | 3 ++ src/locales/fa/messages.js | 3 ++ src/views/CommentsView.vue | 70 ++++++++++++++++++++++++-- 4 files changed, 74 insertions(+), 4 deletions(-) diff --git a/src/composables/comments.composable.js b/src/composables/comments.composable.js index 5e779fa..1277e26 100644 --- a/src/composables/comments.composable.js +++ b/src/composables/comments.composable.js @@ -15,7 +15,7 @@ export const useFetchComments = () => { startLoading(); return CommentsService.getAll(config) .then((response) => { - comments.value = response.data; + comments.value = response.data.map(item=>({...item,status:"PENDING"})); return response; }) .finally(() => { diff --git a/src/locales/en/messages.js b/src/locales/en/messages.js index 32ac351..e9c91af 100644 --- a/src/locales/en/messages.js +++ b/src/locales/en/messages.js @@ -5,6 +5,9 @@ export default { "Submit": "Submit", "Cancel": "Cancel", "Comments": "Comments", + "Confirm":"Confirm", + "Rejected":"Rejected", + "Pending":"Pending", "Dashboard": "Dashboard", "Users": "Users", "Authorization Error": "Authorization Error", diff --git a/src/locales/fa/messages.js b/src/locales/fa/messages.js index 009b3d0..4a70401 100644 --- a/src/locales/fa/messages.js +++ b/src/locales/fa/messages.js @@ -7,6 +7,9 @@ export default { "Dashboard": "داشبورد", "Users": "کاربران", "Comments": "نظرات", + "Confirm":"تایید شده", + "Rejected":"رد شده", + "Pending":"در انتظار تایید", "Authorization Error": "خطای دسترسی", "No data available": "داده\u200Cای موجود نیست", "You are successfully logged in": "شما با موفقیت وارد سیستم شدید", diff --git a/src/views/CommentsView.vue b/src/views/CommentsView.vue index 5d76c30..2aecfae 100644 --- a/src/views/CommentsView.vue +++ b/src/views/CommentsView.vue @@ -1,11 +1,75 @@ From 6f7b508b8e731da35f61f733a0947fe8033c75d4 Mon Sep 17 00:00:00 2001 From: xGooddevilx Date: Sat, 10 May 2025 16:16:51 +0330 Subject: [PATCH 05/20] feat(Component):Initialize comments filter form --- .../comments/CommentsFilterForm.vue | 48 ++++++++++++++ src/locales/en/messages.js | 4 ++ src/locales/fa/messages.js | 65 ++++++++++--------- src/views/CommentsView.vue | 20 ++++-- 4 files changed, 102 insertions(+), 35 deletions(-) create mode 100644 src/components/comments/CommentsFilterForm.vue diff --git a/src/components/comments/CommentsFilterForm.vue b/src/components/comments/CommentsFilterForm.vue new file mode 100644 index 0000000..f9ef85b --- /dev/null +++ b/src/components/comments/CommentsFilterForm.vue @@ -0,0 +1,48 @@ + + + diff --git a/src/locales/en/messages.js b/src/locales/en/messages.js index e9c91af..fa18855 100644 --- a/src/locales/en/messages.js +++ b/src/locales/en/messages.js @@ -20,6 +20,8 @@ export default { "Email": "Email", "Loading": "Loading", "Previous": "Previous", + "SearchInputPlaceholder":'Search...', + "Text":"Text", "Next": "Next", "From": "From", "Size": "Size", @@ -38,4 +40,6 @@ export default { "Dark": "Dark", "System": "System", "Settings": "Settings", + "Actions":"Actions", + "EmailPlaceholder":"Email" }; diff --git a/src/locales/fa/messages.js b/src/locales/fa/messages.js index 4a70401..117dfa1 100644 --- a/src/locales/fa/messages.js +++ b/src/locales/fa/messages.js @@ -1,41 +1,46 @@ export default { - "Login": "ورود", - "Username": "نام کاریری", - "Password": "رمز عبور", - "Submit": "ارسال", - "Cancel": "انصراف", - "Dashboard": "داشبورد", - "Users": "کاربران", - "Comments": "نظرات", - "Confirm":"تایید شده", - "Rejected":"رد شده", - "Pending":"در انتظار تایید", + Login: "ورود", + Username: "نام کاریری", + Password: "رمز عبور", + Submit: "ارسال", + Cancel: "انصراف", + Dashboard: "داشبورد", + Users: "کاربران", + Comments: "نظرات", + Confirm: "تایید شده", + Rejected: "رد شده", + Pending: "در انتظار تایید", "Authorization Error": "خطای دسترسی", "No data available": "داده\u200Cای موجود نیست", "You are successfully logged in": "شما با موفقیت وارد سیستم شدید", "Incorrect username or password": "نام کاربری یا گذرواژه اشتباه است", "System error": "خطای سیستم", - "Id": "شناسه", - "Name": "نام", - "Email": "یارانامه", - "Loading": "درحال بارگذاری", - "Previous": "قبلی", - "Next": "بعدی", - "From": "از", - "Size": "اندازه", - "Todos": "کارها", - "Title": "عنوان", - "User": "کاربر", - "Status": "وضعیت", - "Completed": "تکمیل شده", + Id: "شناسه", + Post: "پست", + Name: "نام", + Email: "یارانامه", + Loading: "درحال بارگذاری", + Previous: "قبلی", + Next: "بعدی", + From: "از", + Size: "اندازه", + SearchInputPlaceholder: "جست و جو...", + Text: "متن", + Todos: "کارها", + Title: "عنوان", + User: "کاربر", + Status: "وضعیت", + Completed: "تکمیل شده", "Not completed": "تکمیل نشده", "Filter by title": "فیلتر بر اساس عنوان", "Filter by user": "فیلتر بر اساس کاربر", "Filter by status": "فیلتر بر اساس وضعیت", - "Language": "زبان", - "Theme": "زمینه", - "Light": "روشن", - "Dark": "تیره", - "System": "سیستم", - "Settings": "تنظیمات", + Language: "زبان", + Theme: "زمینه", + Light: "روشن", + Dark: "تیره", + System: "سیستم", + Settings: "تنظیمات", + Actions: "عملیات", + EmailPlaceholder: "Email", }; diff --git a/src/views/CommentsView.vue b/src/views/CommentsView.vue index 2aecfae..205a687 100644 --- a/src/views/CommentsView.vue +++ b/src/views/CommentsView.vue @@ -1,6 +1,6 @@ - + - From 2cf204ce63a234f7a675a4eada2463e01d676196 Mon Sep 17 00:00:00 2001 From: xGooddevilx Date: Sun, 11 May 2025 08:18:00 +0330 Subject: [PATCH 06/20] feat(View):Implement client-side filtering for comments --- .../comments/CommentsFilterForm.vue | 33 +++-- src/views/CommentsView.vue | 116 ++++++++++-------- 2 files changed, 86 insertions(+), 63 deletions(-) diff --git a/src/components/comments/CommentsFilterForm.vue b/src/components/comments/CommentsFilterForm.vue index f9ef85b..27bfc98 100644 --- a/src/components/comments/CommentsFilterForm.vue +++ b/src/components/comments/CommentsFilterForm.vue @@ -3,25 +3,26 @@ import { useModelRef } from '@/composables/model.composable'; import VForm from '../form/VForm.vue'; import VInput from '../form/VInput.vue'; import { t } from '@/services/language.service'; +import VSelect from '../form/VSelect.vue'; export default { name: 'CommentsFilterForm', setup() { - const filters = useModelRef('modelValue'); + const filters = useModelRef('modelValue'); const statusItems = [ { - key:'PENDING', - text:t('Pending') + key: 'PENDING', + text: t('Pending') }, { - key:'REJECTED', - text:t('Rejected') + key: 'REJECTED', + text: t('Rejected') }, { - key:'CONFIRMED', - text:t('Confirmed') + key: 'CONFIRMED', + text: t('Confirmed') } ] @@ -30,9 +31,16 @@ export default { statusItems } }, + props: { + modelValue: { + type: Object, + required: true + } + }, components: { VForm, - VInput + VInput, + VSelect }, emits: ['update:modelValue'], @@ -40,9 +48,16 @@ export default { diff --git a/src/views/CommentsView.vue b/src/views/CommentsView.vue index 205a687..c55440e 100644 --- a/src/views/CommentsView.vue +++ b/src/views/CommentsView.vue @@ -1,45 +1,45 @@ From ac384732923b70624612da730f900d033ae9adf7 Mon Sep 17 00:00:00 2001 From: xGooddevilx Date: Sun, 11 May 2025 09:11:21 +0330 Subject: [PATCH 11/20] feat(Composables): Implement caching post logic --- src/composables/posts.composable.js | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/composables/posts.composable.js b/src/composables/posts.composable.js index e421c07..a5ae93e 100644 --- a/src/composables/posts.composable.js +++ b/src/composables/posts.composable.js @@ -1,6 +1,7 @@ import { ref } from "vue"; import { useLoading } from "./loading.composable"; import PostsServices from "@/services/posts.services"; +import StorageService from "@/services/storage.service"; export default function useFetchPost() { const { endLoading, isLoading, startLoading } = useLoading(); @@ -8,18 +9,23 @@ export default function useFetchPost() { const post = ref(null); function fetchPost(id) { - startLoading() - return PostsServices.getOneById(id).then((response) => { - post.value = response.data - return response - }).finally(()=>{ - endLoading() - }); + const cachedPosts = StorageService.get("cached-posts") || {}; + if (cachedPosts[`post-${id}`]) return cachedPosts[`post-${id}`]; + startLoading(); + return PostsServices.getOneById(id) + .then((response) => { + post.value = response.data; + StorageService.set('cached-posts', { ...cachedPosts, [`post-${id}`]: response.data }) + return response; + }) + .finally(() => { + endLoading(); + }); } return { postIsLoading: isLoading, post, - fetchPost + fetchPost, }; } From 78ea6faca9224c43f751289c695c9602ad5452ee Mon Sep 17 00:00:00 2001 From: xGooddevilx Date: Sun, 11 May 2025 09:57:08 +0330 Subject: [PATCH 12/20] fix(Composable): resolve issue with not showing cached data in post fetch function & modal --- src/composables/posts.composable.js | 12 +++++++++--- src/locales/en/messages.js | 4 +++- src/locales/fa/messages.js | 1 + 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/composables/posts.composable.js b/src/composables/posts.composable.js index a5ae93e..4908d24 100644 --- a/src/composables/posts.composable.js +++ b/src/composables/posts.composable.js @@ -10,9 +10,14 @@ export default function useFetchPost() { function fetchPost(id) { const cachedPosts = StorageService.get("cached-posts") || {}; - if (cachedPosts[`post-${id}`]) return cachedPosts[`post-${id}`]; - startLoading(); - return PostsServices.getOneById(id) + + if (cachedPosts[`post-${id}`]) { + post.value = cachedPosts[`post-${id}`]; + return + } + else{ + startLoading(); + return PostsServices.getOneById(id) .then((response) => { post.value = response.data; StorageService.set('cached-posts', { ...cachedPosts, [`post-${id}`]: response.data }) @@ -21,6 +26,7 @@ export default function useFetchPost() { .finally(() => { endLoading(); }); + } } return { diff --git a/src/locales/en/messages.js b/src/locales/en/messages.js index fa18855..b4bd6f2 100644 --- a/src/locales/en/messages.js +++ b/src/locales/en/messages.js @@ -41,5 +41,7 @@ export default { "System": "System", "Settings": "Settings", "Actions":"Actions", - "EmailPlaceholder":"Email" + "EmailPlaceholder":"Email", + "Post":"Post", + "Confirmed":"Confirmed" }; diff --git a/src/locales/fa/messages.js b/src/locales/fa/messages.js index 117dfa1..c30239c 100644 --- a/src/locales/fa/messages.js +++ b/src/locales/fa/messages.js @@ -43,4 +43,5 @@ export default { Settings: "تنظیمات", Actions: "عملیات", EmailPlaceholder: "Email", + "Confirmed":"تایید شده" }; From 927cf658505f94fd155bd0dee64e1afcfac244d0 Mon Sep 17 00:00:00 2001 From: xGooddevilx Date: Sun, 11 May 2025 10:34:55 +0330 Subject: [PATCH 13/20] feat:Implement sync logic between tabs with localestorage --- src/composables/posts.composable.js | 2 +- src/services/storage.service.js | 8 +++ src/views/CommentsView.vue | 93 ++++++++++++++++------------- 3 files changed, 60 insertions(+), 43 deletions(-) diff --git a/src/composables/posts.composable.js b/src/composables/posts.composable.js index 4908d24..f098764 100644 --- a/src/composables/posts.composable.js +++ b/src/composables/posts.composable.js @@ -8,7 +8,7 @@ export default function useFetchPost() { const post = ref(null); - function fetchPost(id) { + async function fetchPost (id) { const cachedPosts = StorageService.get("cached-posts") || {}; if (cachedPosts[`post-${id}`]) { diff --git a/src/services/storage.service.js b/src/services/storage.service.js index a784bf7..1bc4dce 100644 --- a/src/services/storage.service.js +++ b/src/services/storage.service.js @@ -41,6 +41,14 @@ class StorageService { static has(name) { return Boolean(this.get(name)); } + + static observe(eventKey , callback){ + window.addEventListener('storage',(event)=>{ + if(event.key === eventKey && event.newValue !==null) { + callback(JSON.parse(event.newValue)) + } + }) + } } export default StorageService; diff --git a/src/views/CommentsView.vue b/src/views/CommentsView.vue index eadae66..a3b39ec 100644 --- a/src/views/CommentsView.vue +++ b/src/views/CommentsView.vue @@ -1,5 +1,5 @@ From 2d54240804c5b29a210575e0268b2f7f659e9880 Mon Sep 17 00:00:00 2001 From: xGooddevilx Date: Sun, 11 May 2025 10:44:50 +0330 Subject: [PATCH 14/20] refactor(Composables): Implement cache until function and replace it in posts manually caching --- src/composables/cache.composable.js | 16 ++++++++++++++++ src/composables/posts.composable.js | 28 ++++++++++++++-------------- 2 files changed, 30 insertions(+), 14 deletions(-) create mode 100644 src/composables/cache.composable.js diff --git a/src/composables/cache.composable.js b/src/composables/cache.composable.js new file mode 100644 index 0000000..b068584 --- /dev/null +++ b/src/composables/cache.composable.js @@ -0,0 +1,16 @@ +import StorageService from "@/services/storage.service"; + +export default function useLocaleStorageCache() { + const getCache = (key) => { + return StorageService.get(key); + }; + const clearCache = (key) => StorageService.delete(key); + + const setCache = (key, value) => StorageService.set(key, value); + + return { + getCache, + clearCache, + setCache, + }; +} diff --git a/src/composables/posts.composable.js b/src/composables/posts.composable.js index f098764..a52982f 100644 --- a/src/composables/posts.composable.js +++ b/src/composables/posts.composable.js @@ -1,31 +1,31 @@ import { ref } from "vue"; import { useLoading } from "./loading.composable"; import PostsServices from "@/services/posts.services"; -import StorageService from "@/services/storage.service"; +import useLocaleStorageCache from "./cache.composable"; export default function useFetchPost() { const { endLoading, isLoading, startLoading } = useLoading(); + const { getCache, setCache } = useLocaleStorageCache(); const post = ref(null); - async function fetchPost (id) { - const cachedPosts = StorageService.get("cached-posts") || {}; + async function fetchPost(id) { + const cachedPosts = getCache("cached-posts") || {}; if (cachedPosts[`post-${id}`]) { post.value = cachedPosts[`post-${id}`]; - return - } - else{ + return; + } else { startLoading(); return PostsServices.getOneById(id) - .then((response) => { - post.value = response.data; - StorageService.set('cached-posts', { ...cachedPosts, [`post-${id}`]: response.data }) - return response; - }) - .finally(() => { - endLoading(); - }); + .then((response) => { + post.value = response.data; + setCache('cached-posts', { ...cachedPosts, [`post-${id}`]: response.data }) + return response; + }) + .finally(() => { + endLoading(); + }); } } From 218b4eac2b1ba1e8fc49a3e8f9ff8d28d0fefb39 Mon Sep 17 00:00:00 2001 From: xGooddevilx Date: Sun, 11 May 2025 11:56:22 +0330 Subject: [PATCH 15/20] feat(View): Implement confirmatin modal --- src/views/CommentsView.vue | 60 +++++++++++++++++++++++++++++++++----- 1 file changed, 53 insertions(+), 7 deletions(-) diff --git a/src/views/CommentsView.vue b/src/views/CommentsView.vue index a3b39ec..0b78c2f 100644 --- a/src/views/CommentsView.vue +++ b/src/views/CommentsView.vue @@ -40,9 +40,9 @@ -