diff --git a/frontend/components/product/ChatBox/ChatButton.vue b/frontend/components/product/ChatBox/ChatButton.vue new file mode 100644 index 0000000..0ad5dcc --- /dev/null +++ b/frontend/components/product/ChatBox/ChatButton.vue @@ -0,0 +1,45 @@ + + + \ No newline at end of file diff --git a/frontend/composables/api/branch/useCreateBranch.ts b/frontend/composables/api/branch/useCreateBranch.ts new file mode 100644 index 0000000..1d891b2 --- /dev/null +++ b/frontend/composables/api/branch/useCreateBranch.ts @@ -0,0 +1,33 @@ +// imports + +import { useMutation } from "@tanstack/vue-query"; +import axios from "~/configs/axios.config"; +import { API_ENDPOINTS } from "~/constants"; + +// types + +export type CreateBranchRequest = { + name: string; + description: string; +}; + +// methods + +export const handleCreateBranch = async ({ name, description }: CreateBranchRequest) => { + const payload: CreateBranchRequest = { + name, + description, + }; + + await axios.post(API_ENDPOINTS.branch.createBranch, payload); +}; + +// composable + +const useCreateBranch = () => { + return useMutation({ + mutationFn: (data: CreateBranchRequest) => handleCreateBranch(data), + }); +}; + +export default useCreateBranch; diff --git a/frontend/composables/api/branch/useGetBranch.ts b/frontend/composables/api/branch/useGetBranch.ts new file mode 100644 index 0000000..50228b2 --- /dev/null +++ b/frontend/composables/api/branch/useGetBranch.ts @@ -0,0 +1,54 @@ +// imports + +import { useQuery } from "@tanstack/vue-query"; +import axios from "~/configs/axios.config"; +import { API_ENDPOINTS, QUERY_KEYS } from "~/constants"; +import type { ComputedRef } from "vue"; + +// types + +export type GetBranchResponse = Branch; + +// methods + +export const handleGetBranch = async ( + branchId: string, + page: string | undefined, + folderId: string | undefined, + sort: string | undefined, + signal: AbortSignal +) => { + + const { data } = await axios.get(`${API_ENDPOINTS.branch.get}/${branchId}`, { + signal, + params: { + sort_by: sort, + folder_id: folderId, + offset: ((!!page ? Number(page) : 1) * 20) - 20, + limit: 20 + } + }); + return data; +}; + +// composable + +const useGetBranch = ( + branchId: ComputedRef, + page: ComputedRef, + folderId: ComputedRef, + sort: ComputedRef +) => { + return useQuery({ + queryKey: [QUERY_KEYS.branch, branchId, page, folderId, sort], + queryFn: ({ signal }) => handleGetBranch( + branchId.value, + page.value, + folderId.value, + sort.value, + signal + ) + }); +}; + +export default useGetBranch; diff --git a/frontend/composables/api/branch/useGetBranches.ts b/frontend/composables/api/branch/useGetBranches.ts new file mode 100644 index 0000000..d665d7a --- /dev/null +++ b/frontend/composables/api/branch/useGetBranches.ts @@ -0,0 +1,29 @@ +// imports + +import { useQuery } from "@tanstack/vue-query"; +import axios from "~/configs/axios.config"; +import { API_ENDPOINTS, QUERY_KEYS } from "~/constants"; + +// types + +export type GetBranchesResponse = Branch[]; + +// methods + +export const handleGetBranches = async () => { + const { data } = await axios.get(`${API_ENDPOINTS.branch.getAll}`); + + return data; +}; + +// composable + +const useGetBranches = () => { + return useQuery({ + staleTime: 60 * 1000, + queryKey: [QUERY_KEYS.branches], + queryFn: () => handleGetBranches() + }); +}; + +export default useGetBranches; diff --git a/frontend/composables/api/branch/useGetUserBranches.ts b/frontend/composables/api/branch/useGetUserBranches.ts new file mode 100644 index 0000000..42d928e --- /dev/null +++ b/frontend/composables/api/branch/useGetUserBranches.ts @@ -0,0 +1,31 @@ +// imports + +import { useQuery } from "@tanstack/vue-query"; +import axios from "~/configs/axios.config"; +import { API_ENDPOINTS, QUERY_KEYS } from "~/constants"; + +// types + +export type GetUserBranchesResponse = Branch[]; + +// methods + +export const handleGetUserBranches = async () => { + const { data } = await axios.get( + `${API_ENDPOINTS.branch.getUserBranches}` + ); + + return data; +}; + +// composable + +const useGetUserBranches = () => { + return useQuery({ + staleTime: 60 * 1000, + queryKey: [QUERY_KEYS.userBranches], + queryFn: () => handleGetUserBranches(), + }); +}; + +export default useGetUserBranches; diff --git a/frontend/composables/api/chat/useGetChat.ts b/frontend/composables/api/chat/useGetChat.ts new file mode 100644 index 0000000..10bfc34 --- /dev/null +++ b/frontend/composables/api/chat/useGetChat.ts @@ -0,0 +1,60 @@ +// imports + +import { useInfiniteQuery, useQuery } from "@tanstack/vue-query"; +import axios from "~/configs/axios.config"; +import { API_ENDPOINTS, QUERY_KEYS } from "~/constants"; +import type { ComputedRef } from "vue"; + +// types + +export type GetBranchResponse = ApiPaginated; + +// methods + +export const handleGetChat = async ({ productId, limit, offset }: { + productId: number | string, + limit: number, + offset: number +}) => { + const { data } = await axios.get(`${API_ENDPOINTS.chat.messages}/${productId}`, { + params: { + offset, + limit + }, + headers: { + Authorization: `Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoyMTY3ODE2OTAwLCJpYXQiOjE3MzU4MTY5MDAsImp0aSI6ImQwN2E2Y2Y2NzgwZjRlNTE5NWIzOGQxMTAzYzU4NDQ3IiwidXNlcl9pZCI6NX0.slwd7ZSV7nUXEuDTYwwHUOo9ekCefwEEL4kVv2vSTFo` + } + }); + return data; +}; + +// composable + +const useGetBranch = ( + productId: Ref, + enabled: Ref +) => { + return useInfiniteQuery({ + enabled, + queryKey: [QUERY_KEYS.chat], + initialPageParam: { + limit: 10, + offset: 0 + }, + queryFn: ({ pageParam }) => handleGetChat({ + limit: pageParam.limit, + offset: pageParam.offset, + productId: productId.value + }), + getNextPageParam: (lastPage, pages) => { + if (!lastPage.next) return undefined; + + return { + limit: 10, + offset: pages.length * 10 + }; + } + }); +}; + +export default useGetBranch; diff --git a/frontend/composables/api/documents/useAddDoc.ts b/frontend/composables/api/documents/useAddDoc.ts new file mode 100644 index 0000000..ed1675f --- /dev/null +++ b/frontend/composables/api/documents/useAddDoc.ts @@ -0,0 +1,45 @@ +// imports + +import { useMutation } from "@tanstack/vue-query"; +import axios from "~/configs/axios.config"; +import { API_ENDPOINTS } from "~/constants"; + +// types + +export type AddDocRequest = { + name: string, + parent?: string, + branch: string | undefined, + type: { + title: "File" | "Folder", + icon: "bi:folder" | "bi:file-earmark" + }, + content: File | undefined +}; + +// methods + +export const handleAddDoc = async (variables: AddDocRequest & { updateUploadProgress: (p: number) => void }) => { + const { data } = await axios.post(`${API_ENDPOINTS.branch.getDoc}/`, { + ...variables, + type: variables.type.title.toLocaleLowerCase() + }, { + onUploadProgress: (progressEvent) => { + variables.updateUploadProgress(progressEvent.progress!); + }, + headers: { + "Content-Type": "multipart/form-data" + } + }); + return data; +}; + +// composable + +const useAddDoc = () => { + return useMutation({ + mutationFn: (variables: AddDocRequest & { updateUploadProgress: (p: number) => void }) => handleAddDoc(variables) + }); +}; + +export default useAddDoc; diff --git a/frontend/composables/api/documents/useDeleteDoc.ts b/frontend/composables/api/documents/useDeleteDoc.ts new file mode 100644 index 0000000..d241169 --- /dev/null +++ b/frontend/composables/api/documents/useDeleteDoc.ts @@ -0,0 +1,27 @@ +// imports + +import { useMutation } from "@tanstack/vue-query"; +import axios from "~/configs/axios.config"; +import { API_ENDPOINTS } from "~/constants"; + +// types + +export type DeleteDocRequest = { + id: number +}; + +// methods + +export const handleDeleteDoc = async ({ id }: { id: string | undefined }) => { + await axios.delete(`${API_ENDPOINTS.branch.getDoc}/${id}`); +}; + +// composable + +const useDeleteDoc = (id: Ref) => { + return useMutation({ + mutationFn: () => handleDeleteDoc({ id: id.value }) + }); +}; + +export default useDeleteDoc; diff --git a/frontend/composables/api/documents/useEditDoc.ts b/frontend/composables/api/documents/useEditDoc.ts new file mode 100644 index 0000000..786962e --- /dev/null +++ b/frontend/composables/api/documents/useEditDoc.ts @@ -0,0 +1,29 @@ +// imports + +import { useMutation } from "@tanstack/vue-query"; +import axios from "~/configs/axios.config"; +import { API_ENDPOINTS } from "~/constants"; + +// types + +export type EditDocRequest = { + id: number +}; + +// methods + +export const handleEditDoc = async ({ id, name }: { id: string | undefined, name: string | undefined }) => { + await axios.patch(`${API_ENDPOINTS.branch.getDoc}/${id}`, { name }); +}; + +// composable + +const useEditDoc = (id: Ref) => { + return useMutation({ + mutationFn: ({ name }: { name: Ref }) => { + return handleEditDoc({ id: id.value, name: name.value }); + } + }); +}; + +export default useEditDoc; diff --git a/frontend/composables/api/documents/useGetDoc.ts b/frontend/composables/api/documents/useGetDoc.ts new file mode 100644 index 0000000..582ab02 --- /dev/null +++ b/frontend/composables/api/documents/useGetDoc.ts @@ -0,0 +1,33 @@ +// imports + +import { useQuery } from "@tanstack/vue-query"; +import axios from "~/configs/axios.config"; +import { API_ENDPOINTS, QUERY_KEYS } from "~/constants"; + +// types + +export type GetDocResponse = DocumentStructure; + +// methods + +export const handleGetDoc = async (id : string | undefined) => { + const { data } = await axios.get(`${API_ENDPOINTS.branch.getDoc}/${id}`); + return data; +}; + +// composable + +const useGetDoc = (id: ComputedRef) => { + + const enabled = computed(() => { + return !!id.value + }); + + return useQuery({ + enabled, + queryKey: [QUERY_KEYS.document, id], + queryFn: () => handleGetDoc(id.value) + }); +}; + +export default useGetDoc; diff --git a/frontend/composables/api/documents/useMoveDoc.ts b/frontend/composables/api/documents/useMoveDoc.ts new file mode 100644 index 0000000..91d26ce --- /dev/null +++ b/frontend/composables/api/documents/useMoveDoc.ts @@ -0,0 +1,32 @@ +// imports + +import { useMutation } from "@tanstack/vue-query"; +import axios from "~/configs/axios.config"; +import { API_ENDPOINTS } from "~/constants"; + +// types + +export type MoveDocRequest = { + itemsToMove: number[] | string[], + parent: number | string +}; + +// methods + +export const handleMoveDoc = async ({ itemsToMove, parent }: MoveDocRequest) => { + const apiUrl = `${API_ENDPOINTS.branch.moveDoc}?new_parent_id=${parent}&${itemsToMove.map(i => `patch_list=${i}&`)}` + const splittedUrl = apiUrl.split(""); + splittedUrl.pop() + + await axios.patch(splittedUrl.join("").replaceAll(",", "")); +}; + +// composable + +const useMoveDoc = () => { + return useMutation({ + mutationFn: (variables: MoveDocRequest) => handleMoveDoc(variables) + }); +}; + +export default useMoveDoc; diff --git a/frontend/composables/api/documents/useReplyDoc.ts b/frontend/composables/api/documents/useReplyDoc.ts new file mode 100644 index 0000000..774077d --- /dev/null +++ b/frontend/composables/api/documents/useReplyDoc.ts @@ -0,0 +1,40 @@ +// imports + +import { useMutation } from "@tanstack/vue-query"; +import axios from "~/configs/axios.config"; +import { API_ENDPOINTS } from "~/constants"; + +// types + +export type ReplyDocRequest = { + user_id: number; + message: string; + reply_id: number; +}; + +export type ReplyDocResponse = { + chat_id: number; +}; + +// methods + +export const handleReplyDoc = async ({ user_id, message, reply_id }: ReplyDocRequest) => { + const payload = { + user_id, + message, + item_id: reply_id, + }; + + const { data } = await axios.post(API_ENDPOINTS.branch.replyDoc, payload); + return data; +}; + +// composable + +const useReplyDoc = () => { + return useMutation({ + mutationFn: (data: ReplyDocRequest) => handleReplyDoc(data), + }); +}; + +export default useReplyDoc; diff --git a/frontend/composables/api/documents/useSearchFile.ts b/frontend/composables/api/documents/useSearchFile.ts new file mode 100644 index 0000000..4563efe --- /dev/null +++ b/frontend/composables/api/documents/useSearchFile.ts @@ -0,0 +1,78 @@ +// imports + +import { useInfiniteQuery } from "@tanstack/vue-query"; +import axios from "~/configs/axios.config"; +import { API_ENDPOINTS, QUERY_KEYS } from "~/constants"; +import type { ComputedRef } from "vue"; + +// types + +export type SearchFileResponse = Branch; + +type HandlerProps = typeof initialPageParam & { + signal: AbortSignal, + search: string, + id: number | undefined; + sort: string | undefined; +} + +// state + +const initialPageParam = { + limit: 10, + offset: 0 +}; + +// methods + +export const handleSearchFile = async ({ search, offset, limit, id, signal, sort }: HandlerProps) => { + const { data } = await axios.get(`${API_ENDPOINTS.branch.get}/${id}`, { + params: { + offset, + limit, + search, + sort_by: sort + }, + signal + }); + return data; +}; + +// composable + +const useSearchFile = (search: Ref, id: Ref, sort: ComputedRef) => { + + const enabled = computed(() => { + return search.value.trim() != "" && !!id.value; + }); + + return useInfiniteQuery({ + enabled, + retry: false, + refetchOnMount: false, + refetchOnWindowFocus: false, + queryKey: [QUERY_KEYS.searchFile, search, id, sort], + queryFn: ({ pageParam, signal }) => handleSearchFile({ + ...pageParam, + signal, + search: search.value, + sort: sort.value, + id: id.value + }), + initialPageParam, + getNextPageParam: (lastPage, pages) => { + const page = pages.length + 1; + + const limit = initialPageParam.limit; + + const nextPageParams = { + offset: page * limit - limit, + limit + }; + + return lastPage?.structure.next ? nextPageParams : undefined; + } + }); +}; + +export default useSearchFile; \ No newline at end of file