Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Allow Users to Edit and Delete Their Own Comments in shifting a… #8724

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions src/Components/Common/components/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import { useIsAuthorized } from "../../../Common/hooks/useIsAuthorized";

interface DropdownMenuProps {
id?: string;
title: string;
title?: string;
dropdownIconVisible?: boolean;
variant?: ButtonVariant;
size?: ButtonSize;
icon?: JSX.Element | undefined;
Expand All @@ -23,6 +24,7 @@ interface DropdownMenuProps {
export default function DropdownMenu({
variant = "primary",
size = "default",
dropdownIconVisible = true,
...props
}: DropdownMenuProps) {
return (
Expand All @@ -42,12 +44,14 @@ export default function DropdownMenu({
)}
>
{props.icon}
{props.title || "Dropdown"}
{props.title}
</div>
<CareIcon
icon="l-angle-down"
className={size === "small" ? "text-base" : "text-lg"}
/>
{dropdownIconVisible && (
<CareIcon
icon="l-angle-down"
className={size === "small" ? "text-base" : "text-lg"}
/>
)}
</MenuButton>

<MenuItems
Expand Down
128 changes: 110 additions & 18 deletions src/Components/Resource/CommentSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ import routes from "../../Redux/api";
import PaginatedList from "../../CAREUI/misc/PaginatedList";
import { IComment } from "./models";
import request from "../../Utils/request/request";
import { useAuthContext } from "../../Common/hooks/useAuthUser.js";
import DropdownMenu, { DropdownItem } from "../Common/components/Menu.js";
import CareIcon from "../../CAREUI/icons/CareIcon.js";
import ConfirmDialog from "../Common/ConfirmDialog.js";

const CommentSection = (props: { id: string }) => {
const [commentBox, setCommentBox] = useState("");
Expand Down Expand Up @@ -80,26 +84,114 @@ const CommentSection = (props: { id: string }) => {
export default CommentSection;

export const Comment = ({
id,
comment,
created_by_object,
modified_date,
}: IComment) => (
<div className="mt-4 flex w-full flex-col rounded-lg border border-secondary-300 bg-white p-4 text-secondary-800">
<div className="flex w-full">
<p className="text-justify">{comment}</p>
</div>
<div className="mt-3">
<span className="text-xs text-secondary-500">
{formatDateTime(modified_date) || "-"}
</span>
</div>
<div className="mr-auto flex items-center rounded-md border bg-secondary-100 py-1 pl-2 pr-3">
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-primary-700 p-1 uppercase text-white">
{created_by_object?.first_name?.charAt(0) || "U"}
}: IComment) => {
const [isCommentEditable, setIsCommentEditable] = useState(false);
const [openDeleteCommentDialog, setOpenDeleteCommentDialog] = useState(false);
const [commentBox, setCommentBox] = useState(comment);
const { user } = useAuthContext();
const handleResourceDelete = async () => {
const { res, error } = await request(routes.deleteResourceComments, {
pathParams: { id },
});
if (res?.status === 204) {
Notification.Success({
msg: "Comment deleted successfully",
});
} else {
Notification.Error({
msg: "Error while deleting comment: " + (error || ""),
});
}
};
const handleCommentUpdate = async () => {
const payload = {
comment: commentBox,
};
if (!/\S+/.test(commentBox)) {
Notification.Error({
msg: "Comment Should Contain At Least 1 Character",
});
return;
}
const { res } = await request(routes.updateResourceComments, {
pathParams: { id },
body: payload,
});
if (res?.ok) {
Notification.Success({ msg: "Comment updated successfully" });
}
setCommentBox("");
setIsCommentEditable((prev) => !prev);
};
return (
<div className="mt-4 flex w-full flex-col rounded-lg border border-secondary-300 bg-white py-4 pl-4 text-secondary-800">
<div className="flex w-full justify-between gap-2">
{isCommentEditable ? (
<TextAreaFormField
name="comment"
value={commentBox}
onChange={(e) => setCommentBox(e.value)}
className="w-full"
/>
) : (
<p className="break-all text-justify">{comment}</p>
)}
{created_by_object.id === user?.id && (
<DropdownMenu
variant="secondary"
icon={<CareIcon icon="l-ellipsis-v" className="size-6" />}
dropdownIconVisible={false}
className="w-fit bg-white px-4 hover:bg-transparent"
containerClassName="mr-4"
>
<DropdownItem
className="pl-0 text-black hover:bg-gray-200"
onClick={() => setIsCommentEditable((prev) => !prev)}
>
Edit
</DropdownItem>
<DropdownItem
className="pl-0 text-red-700 hover:bg-red-100"
onClick={() => setOpenDeleteCommentDialog(true)}
>
Delete
</DropdownItem>
</DropdownMenu>
)}
</div>
<span className="pl-2 text-sm text-secondary-700">
{formatName(created_by_object)}
</span>
<div className="mt-3">
<span className="text-xs text-secondary-500">
{formatDateTime(modified_date) || "-"}
</span>
</div>
<div className="flex justify-between">
<div className="mr-auto flex items-center rounded-md border bg-secondary-100 py-1 pl-2 pr-3">
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-primary-700 p-1 uppercase text-white">
{created_by_object?.first_name?.charAt(0) || "U"}
</div>
<span className="pl-2 text-sm text-secondary-700">
{formatName(created_by_object)}
</span>
</div>
{isCommentEditable && (
<ButtonV2 className="mr-4" onClick={handleCommentUpdate}>
Update
</ButtonV2>
)}
</div>
<ConfirmDialog
title="Delete Comment"
description="Are you sure you want to delete this comment?"
action="Delete"
variant="danger"
show={openDeleteCommentDialog}
onClose={() => setOpenDeleteCommentDialog(false)}
onConfirm={handleResourceDelete}
/>
</div>
</div>
);
);
};
107 changes: 98 additions & 9 deletions src/Components/Shifting/CommentsSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ import routes from "../../Redux/api";
import { IComment } from "../Resource/models";
import PaginatedList from "../../CAREUI/misc/PaginatedList";
import request from "../../Utils/request/request";
import { useAuthContext } from "../../Common/hooks/useAuthUser";
import DropdownMenu, { DropdownItem } from "../Common/components/Menu";
import CareIcon from "../../CAREUI/icons/CareIcon";
import TextAreaFormField from "../Form/FormFields/TextAreaFormField";
import ConfirmDialog from "../Common/ConfirmDialog";

interface CommentSectionProps {
id: string;
Expand Down Expand Up @@ -88,28 +93,112 @@ export const Comment = ({
created_by_object,
modified_date,
}: IComment) => {
const { t } = useTranslation();
const [isCommentEditable, setIsCommentEditable] = useState(false);
const [openDeleteCommentDialog, setOpenDeleteCommentDialog] = useState(false);
const [commentBox, setCommentBox] = useState(comment);
const { user } = useAuthContext();
const handleResourceDelete = async () => {
const { res, error } = await request(routes.deleteShiftComments, {
pathParams: { id },
});
if (res?.status === 204) {
Notification.Success({
msg: "Comment deleted successfully",
});
} else {
Notification.Error({
msg: "Error while deleting comment: " + (error || ""),
});
}
};
const handleCommentUpdate = async () => {
const payload = {
comment: commentBox,
};
if (!/\S+/.test(commentBox)) {
Notification.Error({
msg: "Comment Should Contain At Least 1 Character",
});
return;
}
const { res } = await request(routes.updateShiftComments, {
pathParams: { id },
body: payload,
});
if (res?.ok) {
Notification.Success({ msg: "Comment updated successfully" });
}
setCommentBox("");
setIsCommentEditable((prev) => !prev);
};
return (
<div
key={id}
className="mt-4 flex w-full flex-col rounded-lg border border-secondary-300 bg-white p-4 text-secondary-800"
>
<div className="flex w-full">
<p className="text-justify">{comment}</p>
<div className="flex w-full justify-between gap-2">
{isCommentEditable ? (
<TextAreaFormField
name="comment"
value={commentBox}
onChange={(e) => setCommentBox(e.value)}
className="w-full"
/>
) : (
<p className="break-all text-justify">{comment}</p>
)}
{created_by_object.id === user?.id && (
<DropdownMenu
variant="secondary"
icon={<CareIcon icon="l-ellipsis-v" className="size-6" />}
dropdownIconVisible={false}
className="w-fit bg-white px-4 hover:bg-transparent"
containerClassName="mr-4"
>
<DropdownItem
className="pl-0 text-black hover:bg-gray-200"
onClick={() => setIsCommentEditable((prev) => !prev)}
>
Edit
</DropdownItem>
<DropdownItem
className="pl-0 text-red-700 hover:bg-red-100"
onClick={() => setOpenDeleteCommentDialog(true)}
>
Delete
</DropdownItem>
</DropdownMenu>
)}
</div>
<div className="mt-3">
<span className="text-xs text-secondary-500">
{modified_date ? formatDateTime(modified_date) : "-"}
</span>
</div>
<div className="mr-auto flex items-center rounded-md border bg-secondary-100 py-1 pl-2 pr-3">
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-primary-700 p-1 uppercase text-white">
{created_by_object?.first_name?.charAt(0) || t("unknown")}
<div className="flex justify-between">
<div className="mr-auto flex items-center rounded-md border bg-secondary-100 py-1 pl-2 pr-3">
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-primary-700 p-1 uppercase text-white">
{created_by_object?.first_name?.charAt(0) || "U"}
</div>
<span className="pl-2 text-sm text-secondary-700">
{formatName(created_by_object)}
</span>
</div>
<span className="pl-2 text-sm text-secondary-700">
{formatName(created_by_object)}
</span>
{isCommentEditable && (
<ButtonV2 className="mr-4" onClick={handleCommentUpdate}>
Update
</ButtonV2>
)}
</div>
<ConfirmDialog
title="Delete Comment"
description="Are you sure you want to delete this comment?"
action="Delete"
variant="danger"
show={openDeleteCommentDialog}
onClose={() => setOpenDeleteCommentDialog(false)}
onConfirm={handleResourceDelete}
/>
</div>
);
};
22 changes: 22 additions & 0 deletions src/Redux/api.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1138,6 +1138,17 @@ const routes = {
TBody: Type<Partial<IComment>>(),
TRes: Type<IComment>(),
},
updateShiftComments: {
path: "/api/v1/resource/{id}/comment/",
method: "PUT",
TRes: Type<IComment>(),
TBody: Type<Partial<IComment>>(),
},
deleteShiftComments: {
path: "/api/v1/resource/{id}/comment/",
method: "DELETE",
TRes: Type<Record<string, never>>(),
},

// Notifications
getNotifications: {
Expand Down Expand Up @@ -1310,6 +1321,17 @@ const routes = {
TRes: Type<IComment>(),
TBody: Type<Partial<IComment>>(),
},
updateResourceComments: {
path: "/api/v1/resource/{id}/comment/",
method: "PUT",
TRes: Type<IComment>(),
TBody: Type<Partial<IComment>>(),
},
deleteResourceComments: {
path: "/api/v1/resource/{id}/comment/",
method: "DELETE",
TRes: Type<Record<string, never>>(),
},

// Assets endpoints

Expand Down
Loading