diff --git a/.github/workflows/prod.yml b/.github/workflows/prod.yml
index d3f1ad1..20e4725 100644
--- a/.github/workflows/prod.yml
+++ b/.github/workflows/prod.yml
@@ -16,4 +16,3 @@ jobs:
environment: Production
server_image_tag: "latest"
client_image_tag: "latest"
-
\ No newline at end of file
diff --git a/client/index.html b/client/index.html
index 76db3ab..d2d0931 100644
--- a/client/index.html
+++ b/client/index.html
@@ -8,9 +8,9 @@
rel="stylesheet"
/>
-
+
-
MA Mang
+ Thaii
diff --git a/client/public/robot.png b/client/public/robot.png
new file mode 100644
index 0000000..5071dbc
Binary files /dev/null and b/client/public/robot.png differ
diff --git a/client/src/App.tsx b/client/src/App.tsx
index 6b9b9ac..8205c64 100644
--- a/client/src/App.tsx
+++ b/client/src/App.tsx
@@ -4,22 +4,22 @@ import PageLayout from "./components/layout/page-layout.component";
import Chatbot from "./components/chatbot/chatbot.component";
import Pages from "./components/pages/pages.component";
import Login from "./components/login/login.component";
-import ProtectedRoute from "./components/general/protected-route/protected-route.route";
+import ProtectedRoute from "./routes/protected-route/protected-route.route";
import Register from "./components/register/register.component";
import Activation from "./components/register/activation/activation.component";
import AdminTable from "./components/admin-table/admin-table.component";
import Statistics from "./components/statistics/statistics.component";
import NotFound from "./components/not-found/not-found.component";
import InactivityLogout from "./components/general/inactivity-logout/inactivity-logout.component";
-import AdminRoute from "./components/general/admin-route/admin-route.component";
import Documentation from "./components/documentation/documentation.component";
import { useAuthStore, useToolStore } from "./states/global.store";
import { getPagesForInsights } from "./services/pages.service";
import { getLabels } from "./services/label.service";
import { getTags } from "./services/tags.service";
-import { PageDTO, TagDTO } from "./components/pages/types/pages.types";
import { LabelDTO } from "./types/chatbot/chatbot.types";
import { QueryClient, QueryClientProvider } from "react-query";
+import AdminRoute from "./routes/admin-route/admin-route.component";
+import { PageDTO, TagDTO } from "./types/page/page.types";
function App() {
const queryClient = new QueryClient()
diff --git a/client/src/api/auth.api.ts b/client/src/api/auth.api.ts
index c754f79..4119a02 100644
--- a/client/src/api/auth.api.ts
+++ b/client/src/api/auth.api.ts
@@ -5,6 +5,9 @@ const apiRefresh = axios.create({
baseURL: import.meta.env.VITE_API_URL as string,
});
+// Authenticate a registered user into the system
+// @user: username: string, password: string
+// @response: token and refresh token
export const loginUser = async (user: UserBody) => {
try {
const response = await apiRefresh.post("api/v1/token/", user);
@@ -14,6 +17,9 @@ export const loginUser = async (user: UserBody) => {
}
};
+// Refresh expired token with the provided refresh token
+// @refresh: JWT to refresh token
+// @response: JWT to authenticate user
export const refreshToken = async (refresh: string) => {
try {
const response = await apiRefresh.post("api/v1/token/refresh/", {
@@ -25,6 +31,9 @@ export const refreshToken = async (refresh: string) => {
}
};
+// Register a new user with a username and a password. The username (email) must be whitelisted before registering
+// @user: username: string, password: string
+// @response: status 201 if created and an email is sent to the email
export const registerUser = async (user: UserBody) => {
try {
const response = await apiRefresh.post("api/v1/user/register/", user);
@@ -34,6 +43,8 @@ export const registerUser = async (user: UserBody) => {
}
};
+// Activate account by by clicking on a link
+// @activation: uique query parameters to activate account of users
export const activateUser = async (activation: ActivationBody) => {
try {
const response = await apiRefresh.post("api/v1/user/activate/", activation);
diff --git a/client/src/api/insights.api.ts b/client/src/api/insights.api.ts
index 07ad278..7108710 100644
--- a/client/src/api/insights.api.ts
+++ b/client/src/api/insights.api.ts
@@ -7,7 +7,7 @@ import api from "./interceptor.api";
// - labels: Labels included in analysis
// - tags: Tags included in analysis
-// Fetch number of total chats
+// Fetches the total number of chats based on the provided filter criteria
export const fetchTotalChats = async (filter: FilterBody) => {
try {
const response = await api.post(`/api/v1/insights/total-chats/`, filter);
@@ -17,7 +17,7 @@ export const fetchTotalChats = async (filter: FilterBody) => {
}
};
-// Fetch number of total messages
+// Fetches the total number of messages based on the provided filter criteria
export const fetchTotalMessages = async (filter: FilterBody) => {
try {
const response = await api.post(`/api/v1/insights/total-messages/`, filter);
@@ -27,6 +27,7 @@ export const fetchTotalMessages = async (filter: FilterBody) => {
}
};
+// Fetches chats and messages statistics by a specific time unit
export const fetchChatsMessagesByTime = async (
filter: FilterBody,
item: number
@@ -42,6 +43,7 @@ export const fetchChatsMessagesByTime = async (
}
};
+// Fetches chats and messages statistics by a specific item
export const fetchChatsMessagesByItem = async (
filter: FilterBody,
item: number
@@ -57,6 +59,7 @@ export const fetchChatsMessagesByItem = async (
}
};
+// Fetches the duration of conversations based on the provided filter criteria
export const fetchConversationDuration = async (filter: FilterBody) => {
try {
const response = await api.post(
@@ -69,6 +72,7 @@ export const fetchConversationDuration = async (filter: FilterBody) => {
}
};
+// Fetches conversation duration by specific items
export const fetchConversationDurationByItem = async (
filter: FilterBody,
item: number
@@ -84,6 +88,7 @@ export const fetchConversationDurationByItem = async (
}
};
+// Fetches the total emission statistics based on the filter criteria
export const fetchTotalEmission = async (filter: FilterBody) => {
try {
const response = await api.post(`/api/v1/insights/total-emission/`, filter);
@@ -93,6 +98,7 @@ export const fetchTotalEmission = async (filter: FilterBody) => {
}
};
+// Fetches total water usage statistics based on the filter criteria
export const fetchTotalWater = async (filter: FilterBody) => {
try {
const response = await api.post(`/api/v1/insights/total-water/`, filter);
@@ -102,6 +108,7 @@ export const fetchTotalWater = async (filter: FilterBody) => {
}
};
+// Fetches the total cost statistics based on the filter criteria
export const fetchTotalCost = async (filter: FilterBody) => {
try {
const response = await api.post(`/api/v1/insights/total-cost/`, filter);
@@ -111,6 +118,7 @@ export const fetchTotalCost = async (filter: FilterBody) => {
}
};
+// Fetches tradeoff indicators by a time unit
export const fetchTradeoffIndicatorsByTime = async (
filter: FilterBody,
item: number
@@ -126,6 +134,7 @@ export const fetchTradeoffIndicatorsByTime = async (
}
};
+// Fetches tradeoff indicators by a specific item
export const fetchTradeoffIndicatorsByItem = async (
filter: FilterBody,
item: number
@@ -141,6 +150,7 @@ export const fetchTradeoffIndicatorsByItem = async (
}
};
+// Fetches keywords based on the provided filter criteria
export const fetchKeywords = async (filter: FilterBody) => {
try {
const response = await api.post(`/api/v1/insights/keywords/`, filter);
@@ -150,6 +160,7 @@ export const fetchKeywords = async (filter: FilterBody) => {
}
};
+// Fetches common nouns from the API based on the filter criteria
export const fetchCommonNouns = async (filter: FilterBody) => {
try {
const response = await api.post(`/api/v1/insights/common-nouns/`, filter);
@@ -159,6 +170,7 @@ export const fetchCommonNouns = async (filter: FilterBody) => {
}
};
+// Fetches common verbs from the API based on the filter criteria
export const fetchCommonVerbs = async (filter: FilterBody) => {
try {
const response = await api.post(`/api/v1/insights/common-verbs/`, filter);
@@ -168,6 +180,7 @@ export const fetchCommonVerbs = async (filter: FilterBody) => {
}
};
+// Fetches common adjectives from the API based on the filter criteria
export const fetchCommonAdjectives = async (filter: FilterBody) => {
try {
const response = await api.post(
diff --git a/client/src/api/interaction.api.ts b/client/src/api/interaction.api.ts
index e7eed90..69e4c71 100644
--- a/client/src/api/interaction.api.ts
+++ b/client/src/api/interaction.api.ts
@@ -1,6 +1,9 @@
import { EventLogBody } from "../types/interaction/interaction.types";
import api from "./interceptor.api";
+// Creates a new event log by sending a POST request with the event log data
+// @param eventlog - An object of type EventLogBody containing event log details
+// @returns The response from the API if the event log is created successfully
export const createEventLog = async (eventlog: EventLogBody) => {
try {
const response = await api.post("/api/v1/event-logs/", eventlog);
@@ -11,6 +14,8 @@ export const createEventLog = async (eventlog: EventLogBody) => {
}
};
+// Fetches all event logs from the API as a blob (binary large object)
+// @returns The response from the API containing the event logs in binary format
export const fetchEventLogs = async () => {
try {
const response = await api.get(`/api/v1/event-logs/`, {
diff --git a/client/src/api/interceptor.api.ts b/client/src/api/interceptor.api.ts
index 0ebf928..51c1107 100644
--- a/client/src/api/interceptor.api.ts
+++ b/client/src/api/interceptor.api.ts
@@ -3,18 +3,23 @@ import { ACCESS_TOKEN } from "../helpers/auth.helpers";
import { isTokenExpired } from "../helpers/token.helpers";
import { refreshToken } from "./auth.api";
+// Creates an axios instance with the base URL set to the environment variable
const api = axios.create({
baseURL: import.meta.env.VITE_API_URL as string,
});
+// Interceptor for handling requests, particularly for token management
api.interceptors.request.use(
async (config) => {
+ // Retrieve the access token from local storage
const token = localStorage.getItem(ACCESS_TOKEN);
+ // If a token exists, attach it to the request's Authorization header
if (token) {
config.headers.Authorization = `Bearer ${token}`;
-
+ // Check if the token is expired
if (isTokenExpired(token)) {
const newToken = await refreshToken(token);
+ // Save the new token to local storage and update the Authorization header
if (newToken) {
localStorage.setItem(ACCESS_TOKEN, String(newToken));
config.headers.Authorization = `Bearer ${newToken}`;
diff --git a/client/src/api/label.api.ts b/client/src/api/label.api.ts
index af70b4a..3912cb2 100644
--- a/client/src/api/label.api.ts
+++ b/client/src/api/label.api.ts
@@ -1,6 +1,8 @@
import { LabelBody } from "../types/chatbot/chatbot.types";
import api from "./interceptor.api";
+// Fetches all labels from the API
+// @returns The response from the API containing the list of labels
export const fetchLabels = async () => {
try {
const response = await api.get(`/api/v1/labels/`);
@@ -11,12 +13,28 @@ export const fetchLabels = async () => {
}
};
+// Creates a new label by sending a POST request with the label data
+// @param label - An object of type LabelBody containing label details
+// @returns The response from the API if the label is created successfully
export const createLabel = async (label: LabelBody) => {
try {
const response = await api.post("/api/v1/labels/", label);
return response;
} catch (error) {
- console.error("Error creating user:", error);
+ console.error("Error creating label:", error);
throw error;
}
};
+
+// Delete existing label of user
+// @id: id of label to delete
+export const deleteLabel = async (id: number) => {
+ try {
+ const response = await api.delete(`/api/v1/labels/${id}/`);
+ return response;
+ } catch (error) {
+ console.error("Error deleting label:", error);
+ throw error;
+ }
+};
+
diff --git a/client/src/api/message.api.ts b/client/src/api/message.api.ts
index d2ed7b7..6c44f52 100644
--- a/client/src/api/message.api.ts
+++ b/client/src/api/message.api.ts
@@ -1,6 +1,9 @@
import { MessageBody } from "../types/chatbot/chatbot.types";
import api from "./interceptor.api";
+// Fetches all messages associated with a specific chat by its ID
+// @param chatId - The unique identifier of the chat for which messages are to be fetched
+// @returns The response from the API containing the list of messages for the specified chat
export const fetchMessagesByChatId = async (chatId: number) => {
try {
const response = await api.get(`/api/v1/messages/${chatId}/`);
@@ -10,6 +13,10 @@ export const fetchMessagesByChatId = async (chatId: number) => {
}
};
+// Creates a new message in a specific chat by sending a POST request with the message data
+// @param chatId - The unique identifier of the chat where the message will be created
+// @param message - An object of type MessageBody containing message details (e.g., content, sender)
+// @returns The response from the API if the message is created successfully
export const createMessage = async (chatId: number, message: MessageBody) => {
try {
const response = await api.post(`/api/v1/messages/${chatId}/`, message);
diff --git a/client/src/api/page.api.ts b/client/src/api/page.api.ts
index 4be7476..3351f57 100644
--- a/client/src/api/page.api.ts
+++ b/client/src/api/page.api.ts
@@ -1,6 +1,8 @@
-import { PageBody } from "../components/pages/types/pages.types";
+import { PageBody } from "../types/page/page.types";
import api from "./interceptor.api";
+// Fetches all pages from the API
+// @returns The response from the API containing the list of all pages
export const fetchPages = async () => {
try {
const response = await api.get(`/api/v1/pages/`);
@@ -10,6 +12,8 @@ export const fetchPages = async () => {
}
};
+// Fetches insight-related pages from the API
+// @returns The response from the API containing the list of pages with insights
export const fetchInsightPages = async () => {
try {
const response = await api.get(`/api/v1/pages/insights/`);
@@ -19,6 +23,9 @@ export const fetchInsightPages = async () => {
}
};
+// Fetches a specific page by its ID from the API
+// @param pageId - The unique identifier of the page to be fetched
+// @returns The response from the API containing the details of the requested page
export const fetchPageById = async (pageId: number) => {
try {
const response = await api.get(`/api/v1/pages/${pageId}/`);
@@ -28,6 +35,9 @@ export const fetchPageById = async (pageId: number) => {
}
};
+// Creates a new page by sending a POST request with the page data
+// @param page - An object of type PageBody containing page details
+// @returns The response from the API if the page is created successfully
export const createPage = async (page: PageBody) => {
try {
const response = await api.post("/api/v1/pages/", page);
@@ -37,7 +47,10 @@ export const createPage = async (page: PageBody) => {
}
};
-
+// Updates an existing page by sending a PUT request with the updated page data
+// @param id - The unique identifier of the page to be updated
+// @param page - An object of type PageBody containing updated page details
+// @returns The response from the API if the page is updated successfully
export const changePage = async (id: number, page: PageBody) => {
try {
const response = await api.put(`/api/v1/pages/${id}/`, page);
@@ -47,6 +60,9 @@ export const changePage = async (id: number, page: PageBody) => {
}
};
+// Deletes a specific page by its ID from the API
+// @param id - The unique identifier of the page to be deleted
+// @returns The response from the API if the page is deleted successfully
export const deletePage = async (id: number) => {
try {
const response = await api.delete(`/api/v1/pages/${id}/`);
diff --git a/client/src/api/tag.api.ts b/client/src/api/tag.api.ts
index 5fbb471..b886750 100644
--- a/client/src/api/tag.api.ts
+++ b/client/src/api/tag.api.ts
@@ -1,6 +1,8 @@
-import { TagBody } from "../components/pages/types/pages.types";
+import { TagBody } from "../types/page/page.types";
import api from "./interceptor.api";
+// Fetches all tags from the API
+// @returns The response from the API containing the list of all tags
export const fetchTags = async () => {
try {
const response = await api.get(`/api/v1/tags/`);
@@ -11,12 +13,27 @@ export const fetchTags = async () => {
}
};
+// Creates a new tag by sending a POST request with the tag data
+// @param tag - An object of type TagBody containing tag details
+// @returns The response from the API if the tag is created successfully
export const createTag = async (tag: TagBody) => {
try {
const response = await api.post("/api/v1/tags/", tag);
return response;
} catch (error) {
- console.error("Error creating user:", error);
+ console.error("Error creating tag:", error);
+ throw error;
+ }
+};
+
+// Delete existing tag of user
+// @id: id of tag to delete
+export const deleteTag = async (id: number) => {
+ try {
+ const response = await api.delete(`/api/v1/tags/${id}/`);
+ return response;
+ } catch (error) {
+ console.error("Error deleting tag:", error);
throw error;
}
};
diff --git a/client/src/api/user.api.ts b/client/src/api/user.api.ts
index 6b97920..4b6faf9 100644
--- a/client/src/api/user.api.ts
+++ b/client/src/api/user.api.ts
@@ -1,5 +1,7 @@
import api from "./interceptor.api";
+// Fetches all users from the API
+// @returns The response from the API containing the list of all users
export const fetchUsers = async () => {
try {
const response = await api.get(`/api/v1/users/`);
@@ -9,6 +11,11 @@ export const fetchUsers = async () => {
}
};
+// Updates a user's status and role (active and staff) by sending a PUT request
+// @param id - The unique identifier of the user to be updated
+// @param is_active - A boolean indicating whether the user account is activated
+// @param is_staff - A boolean indicating whether the user is a staff member
+// @returns The response from the API if the user is updated successfully
export const changeUser = async (id: number, is_active: boolean, is_staff: boolean) => {
try {
const response = await api.put(`/api/v1/users/${id}/`, {is_active, is_staff});
@@ -18,6 +25,8 @@ export const changeUser = async (id: number, is_active: boolean, is_staff: boole
}
}
+// Fetches the whitelist of emails from the API
+// @returns The response from the API containing the whitelist of emails
export const fetchWhitelist = async () => {
try {
const response = await api.get(`/api/v1/users/whitelist/`);
@@ -27,6 +36,9 @@ export const fetchWhitelist = async () => {
}
};
+// Adds a new email to the whitelist by sending a POST request
+// @param email - The email address to be added to the whitelist
+// @returns The response from the API if the email is added successfully
export const createWhitelistEmail = async (email: string) => {
try {
const response = await api.post(`/api/v1/users/whitelist/`, {email});
@@ -36,6 +48,9 @@ export const createWhitelistEmail = async (email: string) => {
}
};
+// Deletes an email from the whitelist by its ID
+// @param id - The unique identifier of the email to be deleted from the whitelist
+// @returns The response from the API if the email is deleted successfully
export const deleteWhitelist = async (id: number) => {
try {
const response = await api.delete(`/api/v1/users/whitelist/${id}/`);
@@ -45,6 +60,8 @@ export const deleteWhitelist = async (id: number) => {
}
};
+// Fetches the permissions of the current user from the API
+// @returns The response from the API containing the user's permissions
export const fetchUserPermission = async () => {
try {
const response = await api.get(`/api/v1/users/permissions/`);
diff --git a/client/src/components/admin-table/admin-table.component.tsx b/client/src/components/admin-table/admin-table.component.tsx
index f0e0116..8052c99 100644
--- a/client/src/components/admin-table/admin-table.component.tsx
+++ b/client/src/components/admin-table/admin-table.component.tsx
@@ -7,7 +7,7 @@ import {
Typography,
useTheme,
} from "@mui/material";
-import { SidebarParams } from "../sidebar/types/sidebar.types";
+import { SidebarParams } from "../../types/sidebar/sidebar.types";
import AdminTitle from "./admin-title/admin-title.component";
import { getUsers, getWhitelist } from "../../services/user.service";
import { UserDTO, WhitelistDTO } from "../../types/register/register.types";
diff --git a/client/src/components/chatbot/chatbot.component.tsx b/client/src/components/chatbot/chatbot.component.tsx
index caf9d03..8067f8a 100644
--- a/client/src/components/chatbot/chatbot.component.tsx
+++ b/client/src/components/chatbot/chatbot.component.tsx
@@ -9,7 +9,7 @@ import {
IconButton,
useTheme,
} from "@mui/material";
-import { SidebarParams } from "../sidebar/types/sidebar.types";
+import { SidebarParams } from "../../types/sidebar/sidebar.types";
import "../layout/styles/layout.styles.css";
import { ArrowUpCircle, Edit } from "react-feather";
import { lazy, Suspense, useEffect, useState } from "react";
@@ -21,7 +21,7 @@ import LoadingComponent from "../general/loading-component/loading.component";
import { useToolStore } from "../../states/global.store";
import { getPages } from "../../services/pages.service";
import "./styles/chatbot.styles.css";
-import EmptyChat from "../general/empty-chat/empty-chat.component";
+import EmptyChat from "./empty-chat/empty-chat.component";
import { ErrorBoundary } from "react-error-boundary";
import ErrorBoundaryFallback from "../general/error-boundary/error-boundary.component";
import CreationDialog from "../general/create-dialog/creation-dialog.component";
diff --git a/client/src/components/general/empty-chat/empty-chat.component.tsx b/client/src/components/chatbot/empty-chat/empty-chat.component.tsx
similarity index 100%
rename from client/src/components/general/empty-chat/empty-chat.component.tsx
rename to client/src/components/chatbot/empty-chat/empty-chat.component.tsx
diff --git a/client/src/components/documentation/documentation.component.tsx b/client/src/components/documentation/documentation.component.tsx
index 675d435..4a1b9b4 100644
--- a/client/src/components/documentation/documentation.component.tsx
+++ b/client/src/components/documentation/documentation.component.tsx
@@ -1,5 +1,5 @@
import { Box, Typography } from "@mui/material";
-import { SidebarParams } from "../sidebar/types/sidebar.types";
+import { SidebarParams } from "../../types/sidebar/sidebar.types";
function Documentation({ open }: SidebarParams) {
return (
diff --git a/client/src/components/general/footer/footer.component.tsx b/client/src/components/footer/footer.component.tsx
similarity index 100%
rename from client/src/components/general/footer/footer.component.tsx
rename to client/src/components/footer/footer.component.tsx
diff --git a/client/src/components/general/footer/styles/footer.styles.css b/client/src/components/footer/styles/footer.styles.css
similarity index 100%
rename from client/src/components/general/footer/styles/footer.styles.css
rename to client/src/components/footer/styles/footer.styles.css
diff --git a/client/src/components/general/anonymous-route/anonymous-route.component.tsx b/client/src/components/general/anonymous-route/anonymous-route.component.tsx
deleted file mode 100644
index df49849..0000000
--- a/client/src/components/general/anonymous-route/anonymous-route.component.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import { Navigate, Outlet } from "react-router-dom";
-import { ACCESS_TOKEN } from "../../../helpers/auth.helpers";
-
-function AnonymousRoute() {
- const token = localStorage.getItem(ACCESS_TOKEN);
- return token ? : ;
-}
-
-export default AnonymousRoute;
\ No newline at end of file
diff --git a/client/src/components/general/auth-provider/auth-provider.component.tsx b/client/src/components/general/auth-provider/auth-provider.component.tsx
deleted file mode 100644
index e69de29..0000000
diff --git a/client/src/components/general/create-dialog/creation-dialog.component.tsx b/client/src/components/general/create-dialog/creation-dialog.component.tsx
index e083633..61bf35d 100644
--- a/client/src/components/general/create-dialog/creation-dialog.component.tsx
+++ b/client/src/components/general/create-dialog/creation-dialog.component.tsx
@@ -15,10 +15,9 @@ import {
Typography,
useTheme,
} from "@mui/material";
-import { CreationDialogParams } from "./types/creation-dialog.types";
+import { CreationDialogParams } from "../../../types/create-dialog/creation-dialog.types";
import { PlusCircle, X } from "react-feather";
import React, { lazy, Suspense, useEffect, useState } from "react";
-import { TagDTO } from "../../pages/types/pages.types";
import { getTags } from "../../../services/tags.service";
import { useToolStore } from "../../../states/global.store";
import { LabelDTO } from "../../../types/chatbot/chatbot.types";
@@ -28,6 +27,7 @@ import { getLabels } from "../../../services/label.service";
import LoadingComponent from "../loading-component/loading.component";
import { ErrorBoundary } from "react-error-boundary";
import ErrorBoundaryFallback from "../error-boundary/error-boundary.component";
+import { TagDTO } from "../../../types/page/page.types";
const PageTreeView = lazy(
() => import("../../pages/page-tree-view/page-tree-view.component")
);
@@ -323,6 +323,8 @@ function CreationDialog({
setCurrentElements={setCurrentElements}
selectedTagsOrLabels={selectedTagOrLabel}
setSelectedTagsOrLabels={setSelectedTagOrLabel}
+ fetchTagOrLabelData={fetchTagOrLabelData}
+ source={source}
/>
diff --git a/client/src/components/general/create-dialog/tag-label-auto-complete/tag-label-auto-complete.component.tsx b/client/src/components/general/create-dialog/tag-label-auto-complete/tag-label-auto-complete.component.tsx
index b21ccec..31f538f 100644
--- a/client/src/components/general/create-dialog/tag-label-auto-complete/tag-label-auto-complete.component.tsx
+++ b/client/src/components/general/create-dialog/tag-label-auto-complete/tag-label-auto-complete.component.tsx
@@ -2,14 +2,20 @@ import {
Autocomplete,
Checkbox,
Chip,
+ Grid,
+ IconButton,
TextField,
useTheme,
} from "@mui/material";
-import { CheckSquare, Square } from "react-feather";
-import { SyntheticEvent } from "react";
-import { TagDTO } from "../../../pages/types/pages.types";
-import { TagLabelListParams } from "../types/creation-dialog.types";
+import { CheckSquare, Square, Trash } from "react-feather";
+import { SyntheticEvent, useState } from "react";
+import { TagLabelListParams } from "../../../../types/create-dialog/creation-dialog.types";
import { LabelDTO } from "../../../../types/chatbot/chatbot.types";
+import { TagDTO } from "../../../../types/page/page.types";
+import CustomSnackbar from "../../snackbar/snackbar.component";
+import LoadingComponent from "../../loading-component/loading.component";
+import { removeTag } from "../../../../services/tags.service";
+import { removeLabel } from "../../../../services/label.service";
function TagLabelAutoComplete({
elements,
@@ -17,8 +23,31 @@ function TagLabelAutoComplete({
setCurrentElements,
selectedTagsOrLabels,
setSelectedTagsOrLabels,
+ fetchTagOrLabelData,
+ source,
}: TagLabelListParams) {
const theme = useTheme();
+ const [snackbar, setSnackbar] = useState({
+ message: "",
+ type: "",
+ open: false,
+ });
+ const [loading, setLoading] = useState(false);
+
+ const handleClose = (
+ _event: React.SyntheticEvent | Event,
+ reason?: string
+ ) => {
+ if (reason === "clickaway") {
+ return;
+ }
+
+ setSnackbar({
+ message: snackbar.message,
+ type: snackbar.type,
+ open: false,
+ });
+ };
const handleChange = (_event: SyntheticEvent, value: any) => {
setSelectedTagsOrLabels(
@@ -27,61 +56,144 @@ function TagLabelAutoComplete({
setCurrentElements(value);
};
- return (
- option.label}
- renderTags={(value, getTagProps) =>
- value.map((option, index) => {
- const { key, ...tagProps } = getTagProps({ index });
- return (
-
- );
+ const handleDeleteElement = async (id: number) => {
+ setLoading(true);
+ if (source == "chat") {
+ removeLabel(id)
+ .then(() => {
+ setSnackbar({
+ message: "Label successfully deleted.",
+ type: "success",
+ open: true,
+ });
+ })
+ .then(() => {})
+ .catch(() => {
+ setSnackbar({
+ message: "Error: Label could not be deleted.",
+ type: "error",
+ open: true,
+ });
+ })
+ .finally(() => {
+ fetchTagOrLabelData();
+ setLoading(false);
+ });
+ } else {
+ removeTag(id)
+ .then(() => {
+ setSnackbar({
+ message: "Tag successfully deleted.",
+ type: "success",
+ open: true,
+ });
})
- }
- renderOption={(props, option, { selected }) => (
-
- }
- checkedIcon={}
- style={{ marginRight: 8 }}
- checked={selected || selectedTagsOrLabels.includes(option.id)}
+ .then(() => {})
+ .catch(() => {
+ setSnackbar({
+ message: "Error: Tag could not be deleted.",
+ type: "error",
+ open: true,
+ });
+ })
+ .finally(() => {
+ fetchTagOrLabelData();
+ setLoading(false);
+ });
+ }
+ };
+
+ if (loading) {
+ return ;
+ }
+
+ return (
+ <>
+ option.label}
+ renderTags={(value, getTagProps) =>
+ value.map((option, index) => {
+ const { key, ...tagProps } = getTagProps({ index });
+ return (
+
+ );
+ })
+ }
+ renderOption={(props, option, { selected }) => (
+
+
+
+ }
+ checkedIcon={}
+ style={{ marginRight: 8 }}
+ checked={selected || selectedTagsOrLabels.includes(option.id)}
+ />
+ {option.label}
+
+
+ handleDeleteElement(option.id)}
+ >
+
+
+
+
+
+ )}
+ renderInput={(params) => (
+
- {option.label}
-
- )}
- renderInput={(params) => (
-
- )}
- />
+ )}
+ />
+
+ >
);
}
diff --git a/client/src/components/general/create-dialog/tag-label-dialog/color-selector/color-selector.component.tsx b/client/src/components/general/create-dialog/tag-label-dialog/color-selector/color-selector.component.tsx
index f6fe8ee..0f747b9 100644
--- a/client/src/components/general/create-dialog/tag-label-dialog/color-selector/color-selector.component.tsx
+++ b/client/src/components/general/create-dialog/tag-label-dialog/color-selector/color-selector.component.tsx
@@ -1,5 +1,5 @@
import { Avatar, Stack } from "@mui/material";
-import { ColorSelectorParams } from "../../types/creation-dialog.types";
+import { ColorSelectorParams } from "../../../../../types/create-dialog/creation-dialog.types";
import { Check, Circle } from "react-feather";
const colors = [
diff --git a/client/src/components/general/create-dialog/tag-label-dialog/tag-label-dialog.component.tsx b/client/src/components/general/create-dialog/tag-label-dialog/tag-label-dialog.component.tsx
index 6ff3811..1b7e2d6 100644
--- a/client/src/components/general/create-dialog/tag-label-dialog/tag-label-dialog.component.tsx
+++ b/client/src/components/general/create-dialog/tag-label-dialog/tag-label-dialog.component.tsx
@@ -1,5 +1,5 @@
import { useEffect, useState } from "react";
-import { TagLabelDialogParams } from "../types/creation-dialog.types";
+import { TagLabelDialogParams } from "../../../../types/create-dialog/creation-dialog.types";
import { addTag } from "../../../../services/tags.service";
import { TagBody } from "../../../pages/types/pages.types";
import {
diff --git a/client/src/components/general/snackbar/snackbar.component.tsx b/client/src/components/general/snackbar/snackbar.component.tsx
index 3c45a4b..af85bd3 100644
--- a/client/src/components/general/snackbar/snackbar.component.tsx
+++ b/client/src/components/general/snackbar/snackbar.component.tsx
@@ -1,5 +1,5 @@
import { Alert, Snackbar } from "@mui/material";
-import { SnackbarParams } from "./types/snackbar.types";
+import { SnackbarParams } from "../../../types/general/general.types";
function CustomSnackbar({ message, type, open, handleClose }: SnackbarParams) {
diff --git a/client/src/components/general/snackbar/types/snackbar.types.ts b/client/src/components/general/snackbar/types/snackbar.types.ts
deleted file mode 100644
index 0b3609d..0000000
--- a/client/src/components/general/snackbar/types/snackbar.types.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-export type SnackbarParams = {
- message: string;
- type: string;
- open: boolean;
- handleClose: (event: React.SyntheticEvent | Event, reason?: string) => void;
-};
diff --git a/client/src/components/layout/page-layout.component.tsx b/client/src/components/layout/page-layout.component.tsx
index eaf1339..a61421a 100644
--- a/client/src/components/layout/page-layout.component.tsx
+++ b/client/src/components/layout/page-layout.component.tsx
@@ -1,8 +1,8 @@
import { Grid } from "@mui/material";
import Sidebar from "../sidebar/sidebar.component";
import { Outlet } from "react-router-dom";
-import { SidebarParams } from "../sidebar/types/sidebar.types";
-import Footer from "../general/footer/footer.component";
+import { SidebarParams } from "../../types/sidebar/sidebar.types";
+import Footer from "../footer/footer.component";
function PageLayout({ open, setOpen }: SidebarParams) {
return (
diff --git a/client/src/components/pages/page-tree-view/page-tree-view.component.tsx b/client/src/components/pages/page-tree-view/page-tree-view.component.tsx
index d1b51c4..084defc 100644
--- a/client/src/components/pages/page-tree-view/page-tree-view.component.tsx
+++ b/client/src/components/pages/page-tree-view/page-tree-view.component.tsx
@@ -8,8 +8,8 @@ import { useTreeViewApiRef } from "@mui/x-tree-view/hooks";
import { SyntheticEvent, useEffect, useState } from "react";
import { useToolStore } from "../../../states/global.store";
import { Alert, Typography } from "@mui/material";
-import { PageDTO } from "../types/pages.types";
import { getPageById } from "../../../services/pages.service";
+import { PageDTO } from "../../../types/page/page.types";
function PageTreeView({
currentPageId,
diff --git a/client/src/components/pages/pages.component.tsx b/client/src/components/pages/pages.component.tsx
index 97453ed..678338a 100644
--- a/client/src/components/pages/pages.component.tsx
+++ b/client/src/components/pages/pages.component.tsx
@@ -1,17 +1,17 @@
import { Button, Grid, Typography } from "@mui/material";
-import { SidebarParams } from "../sidebar/types/sidebar.types";
+import { SidebarParams } from "../../types/sidebar/sidebar.types";
import { Edit } from "react-feather";
import { lazy, Suspense, useEffect, useState } from "react";
import CustomSnackbar from "../general/snackbar/snackbar.component";
import { useToolStore } from "../../states/global.store";
-import { PageDTO } from "./types/pages.types";
import { addPage, getPageById, getPages } from "../../services/pages.service";
import CreationDialog from "../general/create-dialog/creation-dialog.component";
import LoadingComponent from "../general/loading-component/loading.component";
import { getChatsByPageId } from "../../services/chats.service";
import { ChatDTO } from "../../types/chatbot/chatbot.types";
import { useQuery } from "react-query";
-import EmptyChat from "../general/empty-chat/empty-chat.component";
+import EmptyChat from "../chatbot/empty-chat/empty-chat.component";
+import { PageDTO } from "../../types/page/page.types";
const PageTitle = lazy(() => import("./page-title/page-title.component"));
const ChatTable = lazy(() => import("./chat-table/chat-table.component"));
const PageTreeView = lazy(
diff --git a/client/src/components/pages/types/pages.types.ts b/client/src/components/pages/types/pages.types.ts
deleted file mode 100644
index 444392c..0000000
--- a/client/src/components/pages/types/pages.types.ts
+++ /dev/null
@@ -1,82 +0,0 @@
-import { Dispatch, SetStateAction } from "react";
-
-// DTOs
-export type PageDTO = {
- id: string;
- label: string;
- children: string[];
- parent_page: number | null;
- level: number;
- read_only: boolean;
- tags: TagDTO[];
-};
-
-export type TagDTO = {
- id: string;
- label: string;
- color: string;
-};
-
-// Request Bodies
-export type PageBody = {
- label: string;
- parent_page_id: number | null;
- tags: string[];
-};
-
-export type TagBody = {
- label: string;
- color: string;
-};
-
-// List Elements
-export type TagList = {
- tags: TagDTO[];
- selectedTags: TagDTO[];
- setSelectedTags: Dispatch>;
-};
-
-// Function Parameter Definitions
-export type PageDialogParams = {
- open: boolean;
- setOpen: (open: boolean) => void;
- label: string;
- setLabel: (title: string) => void;
- parentId: number;
- setParentId: (parentId: number) => void;
- createPage?: (e: any) => void;
- editPage?: (page: PageDTO) => void;
- deletePage?: (id: number) => void;
- pages: any;
- type: string;
-};
-
-export type PopperParams = {
- open: boolean;
- setOpen: (open: boolean) => void;
- anchor: HTMLButtonElement | null;
- fetchTags: () => Promise;
- hasValue: (tag_name: string) => boolean;
-};
-
-export type ColorSelectorParams = {
- color: string;
- setColor: (color: string) => void;
-};
-
-export type TreeViewParams = {
- currentPageId: string;
- setCurrentPageId: (currentPageId: string) => void;
- isNewPage: boolean;
-};
-
-export type PageTitleParams = {
- currentPage: PageDTO;
- fetchPagesData: () => void;
- fetchPageById: (id: number) => void;
- setSnackbar: (snackbar: {
- message: string;
- type: string;
- open: boolean;
- }) => void;
-};
diff --git a/client/src/components/sidebar/download-button/download-button.component.tsx b/client/src/components/sidebar/download-button/download-button.component.tsx
index 3ee9547..8ba5b28 100644
--- a/client/src/components/sidebar/download-button/download-button.component.tsx
+++ b/client/src/components/sidebar/download-button/download-button.component.tsx
@@ -1,8 +1,15 @@
-import { Button, Typography, useTheme } from "@mui/material";
+import {
+ Button,
+ IconButton,
+ Tooltip,
+ Typography,
+ useTheme,
+} from "@mui/material";
import { getEventLogs } from "../../../services/interactions.service";
import { Download } from "react-feather";
+import { DownloadButtonParams } from "../../../types/sidebar/sidebar.types";
-const DownloadButton = () => {
+const DownloadButton = ({ open }: DownloadButtonParams) => {
const theme = useTheme();
const handleDownload = async () => {
@@ -25,15 +32,25 @@ const DownloadButton = () => {
}
};
+ if (open) {
+ return (
+ }
+ sx={{ background: theme.palette.primary.dark, textTransform: "none" }}
+ onClick={handleDownload}
+ >
+ Download Data
+
+ );
+ }
+
return (
- }
- sx={{ background: theme.palette.primary.dark, textTransform: "none" }}
- onClick={handleDownload}
- >
- Download Data
-
+
+
+
+
+
);
};
diff --git a/client/src/components/sidebar/sidebar.component.tsx b/client/src/components/sidebar/sidebar.component.tsx
index 72fb5d3..f274c62 100644
--- a/client/src/components/sidebar/sidebar.component.tsx
+++ b/client/src/components/sidebar/sidebar.component.tsx
@@ -9,7 +9,7 @@ import {
} from "@mui/material";
import WavingHandIcon from "@mui/icons-material/WavingHand";
import { menu_items } from "./helpers/sidebar.helpers";
-import { MenuItem, SidebarParams } from "./types/sidebar.types";
+import { MenuItem, SidebarParams } from "../../types/sidebar/sidebar.types";
import ChatBubbleOutlineIcon from "@mui/icons-material/ChatBubbleOutline";
import {
Sidebar as SidebarIcon,
@@ -160,13 +160,11 @@ function Sidebar({ open, setOpen }: SidebarParams) {
xs={12}
sx={{
display: "flex",
- justifyContent: "flex-start",
- ml: "1rem",
- mr: "1rem",
+ justifyContent: "center",
mt: "3vh",
}}
>
-
+
import("./filter/filter.component"));
const BehavioralDashboard = lazy(
@@ -123,7 +123,9 @@ function Statistics({ open }: SidebarParams) {
className="main tabs"
onClick={() => {
setTab(tabItem.tab);
- if ((import.meta.env.VITE_ENABLE_TRACKING as string) == "true") {
+ if (
+ (import.meta.env.VITE_ENABLE_TRACKING as string) == "true"
+ ) {
addEventLog({
location: "Insights - " + tabItem.name,
});
diff --git a/client/src/components/general/admin-route/admin-route.component.tsx b/client/src/routes/admin-route/admin-route.component.tsx
similarity index 79%
rename from client/src/components/general/admin-route/admin-route.component.tsx
rename to client/src/routes/admin-route/admin-route.component.tsx
index 16f2bbf..01e2320 100644
--- a/client/src/components/general/admin-route/admin-route.component.tsx
+++ b/client/src/routes/admin-route/admin-route.component.tsx
@@ -1,7 +1,7 @@
import { useEffect, useState } from "react";
-import { getUserPermissions } from "../../../services/user.service";
-import { ACCESS_TOKEN } from "../../../helpers/auth.helpers";
import { Navigate, Outlet } from "react-router-dom";
+import { ACCESS_TOKEN } from "../../helpers/auth.helpers";
+import { getUserPermissions } from "../../services/user.service";
function AdminRoute() {
const [isAdmin, setIsAdmin] = useState(null);
diff --git a/client/src/components/general/protected-route/protected-route.route.tsx b/client/src/routes/protected-route/protected-route.route.tsx
similarity index 88%
rename from client/src/components/general/protected-route/protected-route.route.tsx
rename to client/src/routes/protected-route/protected-route.route.tsx
index 2b2991f..f00e397 100644
--- a/client/src/components/general/protected-route/protected-route.route.tsx
+++ b/client/src/routes/protected-route/protected-route.route.tsx
@@ -1,9 +1,9 @@
import { Navigate, Outlet } from "react-router-dom";
import { jwtDecode } from "jwt-decode";
-import { REFRESH_TOKEN, ACCESS_TOKEN } from "../../../helpers/auth.helpers";
+import { REFRESH_TOKEN, ACCESS_TOKEN } from "../../helpers/auth.helpers";
import { useState, useEffect } from "react";
-import { refreshToken } from "../../../api/auth.api";
-import { useAuthStore } from "../../../states/global.store";
+import { refreshToken } from "../../api/auth.api";
+import { useAuthStore } from "../../states/global.store";
function ProtectedRoute() {
const [loading, setLoading] = useState(true);
diff --git a/client/src/services/label.service.ts b/client/src/services/label.service.ts
index 6896a89..411698a 100644
--- a/client/src/services/label.service.ts
+++ b/client/src/services/label.service.ts
@@ -1,4 +1,4 @@
-import { createLabel, fetchLabels } from "../api/label.api";
+import { createLabel, deleteLabel, fetchLabels } from "../api/label.api";
import { LabelBody } from "../types/chatbot/chatbot.types";
export const getLabels = async () => {
@@ -18,3 +18,13 @@ export const addLabel = async (label: LabelBody) => {
throw error;
}
};
+
+
+export const removeLabel = async (id: number) => {
+ try {
+ const response = await deleteLabel(id)
+ return response.data;
+ } catch (error: any) {
+ throw error;
+ }
+};
diff --git a/client/src/services/tags.service.ts b/client/src/services/tags.service.ts
index 7764021..6ed63db 100644
--- a/client/src/services/tags.service.ts
+++ b/client/src/services/tags.service.ts
@@ -1,5 +1,5 @@
-import { createTag, fetchTags } from "../api/tag.api";
-import { TagBody } from "../components/pages/types/pages.types";
+import { createTag, deleteTag, fetchTags } from "../api/tag.api";
+import { TagBody } from "../types/page/page.types";
export const getTags = async () => {
try {
@@ -18,3 +18,12 @@ export const addTag = async (tag: TagBody) => {
throw error;
}
};
+
+export const removeTag = async (id: number) => {
+ try {
+ const response = await deleteTag(id)
+ return response.data;
+ } catch (error: any) {
+ throw error;
+ }
+};
diff --git a/client/src/types/chatbot/chatbot.types.ts b/client/src/types/chatbot/chatbot.types.ts
index 974dc46..244641a 100644
--- a/client/src/types/chatbot/chatbot.types.ts
+++ b/client/src/types/chatbot/chatbot.types.ts
@@ -1,4 +1,4 @@
-import { PageDTO } from "../../components/pages/types/pages.types";
+import { PageDTO } from "../page/page.types";
export type LabelDTO = {
id: string;
diff --git a/client/src/components/general/create-dialog/types/creation-dialog.types.ts b/client/src/types/create-dialog/creation-dialog.types.ts
similarity index 87%
rename from client/src/components/general/create-dialog/types/creation-dialog.types.ts
rename to client/src/types/create-dialog/creation-dialog.types.ts
index fdaea5f..6d393e6 100644
--- a/client/src/components/general/create-dialog/types/creation-dialog.types.ts
+++ b/client/src/types/create-dialog/creation-dialog.types.ts
@@ -1,5 +1,5 @@
-import { LabelDTO } from "../../../../types/chatbot/chatbot.types";
-import { TagDTO } from "../../../pages/types/pages.types";
+import { LabelDTO } from "../chatbot/chatbot.types";
+import { TagDTO } from "../page/page.types";
export type CreationDialogParams = {
open: boolean;
@@ -25,6 +25,8 @@ export type TagLabelListParams = {
setCurrentElements: (currentElement: TagDTO[] | LabelDTO[]) => void;
selectedTagsOrLabels: string[];
setSelectedTagsOrLabels: (selectedTagOrLabel: string[]) => void;
+ fetchTagOrLabelData: () => void;
+ source: string;
};
export type TagLabelDialogParams = {
diff --git a/client/src/types/general/general.types.ts b/client/src/types/general/general.types.ts
index 64ff67e..2d6f5af 100644
--- a/client/src/types/general/general.types.ts
+++ b/client/src/types/general/general.types.ts
@@ -1,4 +1,12 @@
export type InputError = {
error: boolean;
errorMessage: string;
-}
\ No newline at end of file
+}
+
+export type SnackbarParams = {
+ message: string;
+ type: string;
+ open: boolean;
+ handleClose: (event: React.SyntheticEvent | Event, reason?: string) => void;
+ };
+
\ No newline at end of file
diff --git a/client/src/types/page/page.types.ts b/client/src/types/page/page.types.ts
index e69de29..444392c 100644
--- a/client/src/types/page/page.types.ts
+++ b/client/src/types/page/page.types.ts
@@ -0,0 +1,82 @@
+import { Dispatch, SetStateAction } from "react";
+
+// DTOs
+export type PageDTO = {
+ id: string;
+ label: string;
+ children: string[];
+ parent_page: number | null;
+ level: number;
+ read_only: boolean;
+ tags: TagDTO[];
+};
+
+export type TagDTO = {
+ id: string;
+ label: string;
+ color: string;
+};
+
+// Request Bodies
+export type PageBody = {
+ label: string;
+ parent_page_id: number | null;
+ tags: string[];
+};
+
+export type TagBody = {
+ label: string;
+ color: string;
+};
+
+// List Elements
+export type TagList = {
+ tags: TagDTO[];
+ selectedTags: TagDTO[];
+ setSelectedTags: Dispatch>;
+};
+
+// Function Parameter Definitions
+export type PageDialogParams = {
+ open: boolean;
+ setOpen: (open: boolean) => void;
+ label: string;
+ setLabel: (title: string) => void;
+ parentId: number;
+ setParentId: (parentId: number) => void;
+ createPage?: (e: any) => void;
+ editPage?: (page: PageDTO) => void;
+ deletePage?: (id: number) => void;
+ pages: any;
+ type: string;
+};
+
+export type PopperParams = {
+ open: boolean;
+ setOpen: (open: boolean) => void;
+ anchor: HTMLButtonElement | null;
+ fetchTags: () => Promise;
+ hasValue: (tag_name: string) => boolean;
+};
+
+export type ColorSelectorParams = {
+ color: string;
+ setColor: (color: string) => void;
+};
+
+export type TreeViewParams = {
+ currentPageId: string;
+ setCurrentPageId: (currentPageId: string) => void;
+ isNewPage: boolean;
+};
+
+export type PageTitleParams = {
+ currentPage: PageDTO;
+ fetchPagesData: () => void;
+ fetchPageById: (id: number) => void;
+ setSnackbar: (snackbar: {
+ message: string;
+ type: string;
+ open: boolean;
+ }) => void;
+};
diff --git a/client/src/components/sidebar/types/sidebar.types.ts b/client/src/types/sidebar/sidebar.types.ts
similarity index 80%
rename from client/src/components/sidebar/types/sidebar.types.ts
rename to client/src/types/sidebar/sidebar.types.ts
index 37832ec..341c6d4 100644
--- a/client/src/components/sidebar/types/sidebar.types.ts
+++ b/client/src/types/sidebar/sidebar.types.ts
@@ -14,3 +14,6 @@ export type StatisticParams = {
messages: any
}
+export type DownloadButtonParams = {
+ open: boolean;
+}
diff --git a/server/chat/urls.py b/server/chat/urls.py
index 51fa9f4..0937c92 100644
--- a/server/chat/urls.py
+++ b/server/chat/urls.py
@@ -15,5 +15,6 @@
name="chats",
),
path("messages//", views.MessageDetailView.as_view(), name="message"),
- path("labels/", views.LabelApiView.as_view(), name="label-create")
+ path("labels/", views.LabelApiView.as_view(), name="label-create"),
+ path("labels//", views.LabelApiView.as_view(), name="label-delete")
]
\ No newline at end of file
diff --git a/server/chat/views.py b/server/chat/views.py
index 5c0a51f..3c830c0 100644
--- a/server/chat/views.py
+++ b/server/chat/views.py
@@ -131,6 +131,7 @@ def post(self, request, *args, **kwargs):
def delete(self, request, pk, format=None):
try:
label = Label.objects.get(id=pk)
+ label.chats.clear()
except Label.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
label.delete()
diff --git a/server/insights/services/arrayMerge.py b/server/insights/services/arrayMerge.py
index 49ccb8d..0430d5a 100644
--- a/server/insights/services/arrayMerge.py
+++ b/server/insights/services/arrayMerge.py
@@ -1,3 +1,4 @@
+
def mergeByDate(array1, array2, key):
combined_dict = {}
for obj in array1:
diff --git a/server/pages/urls.py b/server/pages/urls.py
index 326fecf..de0edee 100644
--- a/server/pages/urls.py
+++ b/server/pages/urls.py
@@ -9,5 +9,6 @@
),
path("pages/", views.PageListView.as_view(), name="page-list"),
path("pages/insights/", views.PageListFilterView.as_view(), name="page-list"),
- path("tags/", views.TagApiView.as_view(), name="tag-create")
+ path("tags/", views.TagApiView.as_view(), name="tag-create"),
+ path("tags//", views.TagApiView.as_view(), name="tag-delete")
]
\ No newline at end of file
diff --git a/server/pages/views.py b/server/pages/views.py
index c152ca0..6920420 100644
--- a/server/pages/views.py
+++ b/server/pages/views.py
@@ -116,6 +116,7 @@ def post(self, request, *args, **kwargs):
def delete(self, request, pk, format=None):
try:
tag = Tag.objects.get(id=pk)
+ tag.pages.clear()
except Tag.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
tag.delete()