Skip to content

Commit

Permalink
Resources details update and delete
Browse files Browse the repository at this point in the history
  • Loading branch information
akmatoff committed Jan 30, 2024
1 parent 686c190 commit 8df00cb
Show file tree
Hide file tree
Showing 22 changed files with 733 additions and 247 deletions.
5 changes: 3 additions & 2 deletions src/components/shared/Button/Button.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
min-width: 64px;
min-height: 46px;
width: 100%;
padding: 8px 12px;
border-radius: var(--secondary-radius);
padding: 8px 16px;
border-radius: var(--primary-radius);
cursor: pointer;
}

Expand All @@ -21,4 +21,5 @@
font-size: 16px;
margin-right: 6px;
margin-top: 4px;
color: var(--text-color);
}
8 changes: 7 additions & 1 deletion src/components/shared/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ interface Props {
text?: string;
disabled?: boolean;
isLoading?: boolean;
isTextButton?: boolean;
height?: string;
type?: "button" | "submit";
color?: string;
Expand All @@ -21,6 +22,7 @@ export default function Button({
color = "var(--accent-color)",
isLoading,
icon,
isTextButton,
onClick,
}: Props) {
const handleClick = () => {
Expand All @@ -32,7 +34,11 @@ export default function Button({
return (
<button
className={cn(styles.primary, disabled ? styles.disabled : "")}
style={{ minHeight: height, background: color }}
style={{
minHeight: height,
background: !isTextButton ? (!disabled ? color : "") : "transparent",
color: isTextButton ? color : "",
}}
onClick={handleClick}
type={type}
disabled={disabled}
Expand Down
5 changes: 5 additions & 0 deletions src/components/shared/Modal/Modal.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,8 @@
.subtitle {
margin-bottom: 16px;
}

.buttons {
display: flex;
gap: 24px;
}
23 changes: 20 additions & 3 deletions src/components/shared/Modal/Modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ interface Props {
title: string;
subtitle: string;
show: boolean;
children: ReactNode;
children?: ReactNode;
isLoading?: boolean;
primaryButtonText?: string;
onPrimaryClick: () => void;
onCancelClick?: () => void;
onClose: () => void;
}

Expand All @@ -17,7 +20,10 @@ function Modal({
subtitle,
show,
children,
primaryButtonText = "Сохранить",
isLoading,
onPrimaryClick,
onCancelClick,
onClose,
}: Props) {
return show ? (
Expand All @@ -34,8 +40,19 @@ function Modal({
<Typography variant="body2">{subtitle}</Typography>
</div>
<div>{children}</div>
<div>
<Button text="Сохранить" onClick={onPrimaryClick} />
<div className={styles.buttons}>
<Button
text={primaryButtonText || "Сохранить"}
isLoading={isLoading}
onClick={onPrimaryClick}
/>
{onCancelClick && (
<Button
text="Отмена"
onClick={onCancelClick}
color="var(--text-color)"
/>
)}
</div>
</div>
</div>
Expand Down
11 changes: 11 additions & 0 deletions src/components/shared/Resource/Resource.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,20 @@
align-items: center;
}

.buttons {
display: flex;
gap: 16px;
}

.content {
margin-top: 20px;
padding: 16px;
background: var(--secondary-color);
border-radius: var(--primary-radius);
}

.spinner-wrapper {
width: 100%;
display: grid;
place-content: center;
}
73 changes: 68 additions & 5 deletions src/components/shared/Resource/Resource.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,85 @@
"use client";

import styles from "./Resource.module.scss";
import Typography from "../Typography/Typography";
import { ReactNode } from "react";
import { ReactNode, useState } from "react";
import LoadingSpinner from "../LoadingSpinner/LoadingSpinner";
import Button from "../Button/Button";
import Modal from "../Modal/Modal";

interface Props {
title: string;
children?: ReactNode;
isLoading?: boolean;
isSaveButtonLoading: boolean;
isSaveDisabled?: boolean;
isExisting?: boolean;
isDeleting?: boolean;
onSaveClick: () => void;
onDeleteClick: () => void;
}

export default function Resource({ title, children }: Props) {
export default function Resource({
title,
children,
isLoading,
isSaveButtonLoading,
isSaveDisabled,
isExisting,
isDeleting,
onSaveClick,
onDeleteClick,
}: Props) {
const [showConfirmModal, setShowConfirmModal] = useState(false);

const handleDeleteClick = () => {
setShowConfirmModal(true);
};

return (
<div className={styles.wrapper}>
<div className={styles.header}>
<Typography>{title}</Typography>

<div className={styles.buttons}>
{isExisting && (
<Button
text="Удалить"
onClick={handleDeleteClick}
height="40px"
color="var(--danger-color)"
isLoading={isDeleting}
isTextButton
/>
)}

<Button
onClick={onSaveClick}
text="Сохранить"
height="40px"
disabled={isSaveDisabled}
isLoading={isSaveButtonLoading}
/>
</div>
</div>

<div className={styles.content}>
{!isLoading && <div>{children}</div>}
{isLoading && (
<div className={styles["spinner-wrapper"]}>
<LoadingSpinner light size="l" />
</div>
)}
</div>

<div className={styles.content}>{children}</div>
<Modal
show={showConfirmModal}
title="Удаление"
subtitle="Вы уверене что хотите удалить?"
primaryButtonText="Удалить"
isLoading={isDeleting}
onPrimaryClick={onDeleteClick}
onClose={() => setShowConfirmModal(false)}
onCancelClick={() => setShowConfirmModal(false)}
/>
</div>
);
}
136 changes: 89 additions & 47 deletions src/pages/groups/GroupDetails.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import Button from "@/components/shared/Button/Button";
import CustomInput from "@/components/shared/CustomInput/CustomInput";
import RelationInput from "@/components/shared/RelationInput/RelationInput";
import Resource from "@/components/shared/Resource/Resource";
Expand All @@ -7,7 +6,11 @@ import { ROUTES } from "@/constants/routes";
import { useNotification } from "@/hooks/useNotification";
import { IOption } from "@/interfaces/common";
import { IGroupCreate } from "@/interfaces/group";
import { useGroupDetailsQuery, useGroupMutation } from "@/queries/groups";
import {
useGroupDeletion,
useGroupDetailsQuery,
useGroupMutation,
} from "@/queries/groups";
import { useSubjectsQuery } from "@/queries/subjects";
import { yupResolver } from "@hookform/resolvers/yup";
import { useQueryClient } from "@tanstack/react-query";
Expand Down Expand Up @@ -44,7 +47,11 @@ function GroupDetails() {
const queryClient = useQueryClient();
const navigate = useNavigate();

const { data, isLoading } = useGroupDetailsQuery(+id!);
const {
data: existingGroup,
isLoading,
isSuccess,
} = useGroupDetailsQuery(+id!, { enabled: !!id });

const {
data: subjects,
Expand All @@ -54,7 +61,7 @@ function GroupDetails() {
params: { search },
});

const { mutate: createGroup, isPending } = useGroupMutation({
const { mutate, isPending } = useGroupMutation({
onSuccess: () => {
queryClient.invalidateQueries({
refetchType: "all",
Expand All @@ -68,8 +75,28 @@ function GroupDetails() {
onError: () => {
showErrorNotification();
},
id: +id!,
});

const { mutate: deleteGroup, isPending: isDeleting } = useGroupDeletion(
+id!,
{
onSuccess: () => {
queryClient.invalidateQueries({
refetchType: "all",
queryKey: [QUERY_KEYS.GROUPS],
});

navigate(ROUTES.GROUPS);

showSuccessNotification();
},
onError: () => {
showErrorNotification();
},
}
);

const [activeValue, setActiveValue] = useState<IOption>({
label: subjects?.[0].title,
value: subjects?.[0].id.toString(),
Expand All @@ -93,56 +120,71 @@ function GroupDetails() {
const isValid = Object.values(groupForm.formState.errors).length === 0;

const onSubmit: SubmitHandler<GroupForm> = (data: IGroupCreate) => {
createGroup(data);
mutate(data);
};

useEffect(() => {
setActiveValue({
label: subjects?.[0].title,
value: subjects?.[0].id.toString(),
});
if (existingGroup && id) {
groupForm.reset({
title: existingGroup.title,
subjectId: existingGroup.subject.id,
});
setActiveValue({
label: existingGroup.subject.title,
value: existingGroup.subject.id.toString(),
});
}
}, [isSuccess, existingGroup, groupForm, id]);

useEffect(() => {
if (!existingGroup || !id) {
setActiveValue({
label: subjects?.[0].title,
value: subjects?.[0].id.toString(),
});

if (subjects) {
groupForm.setValue("subjectId", subjects[0].id);
if (subjects) {
groupForm.setValue("subjectId", subjects[0].id);
}
}
}, [subjects, groupForm]);
}, [subjects, groupForm, id, existingGroup]);

return (
<Resource title="Группа">
<form onSubmit={groupForm.handleSubmit(onSubmit)}>
<CustomInput
name="title"
label="Название"
placeholder="Введите название..."
errorMessage={groupForm.formState.errors.title?.message}
onChangeCallback={(value) => {
groupForm.setValue("title", value);
}}
/>
<RelationInput
name="subject"
options={subjectOptions}
activeValue={activeValue}
setActiveValue={(value) => {
groupForm.setValue("subjectId", +value.value);
setActiveValue(value);
}}
label="Предмет"
placeholder="Выберите предмет..."
isLoading={isFetching}
onSearch={(value) => {
setSearch(value);
refetch();
}}
/>

<Button
type="submit"
text="Создать"
disabled={!isValid}
isLoading={isPending}
/>
</form>
<Resource
title={existingGroup?.title || "Группа"}
isExisting={!!id}
isLoading={isLoading}
isSaveDisabled={!isValid || !groupForm.formState.isDirty}
isSaveButtonLoading={isPending}
isDeleting={isDeleting}
onDeleteClick={deleteGroup}
onSaveClick={() => onSubmit(groupForm.getValues())}
>
<CustomInput
{...groupForm.register("title")}
label="Название"
placeholder="Введите название..."
errorMessage={groupForm.formState.errors.title?.message}
onChangeCallback={(value) => {
groupForm.setValue("title", value);
}}
/>
<RelationInput
name="subject"
options={subjectOptions}
activeValue={activeValue}
setActiveValue={(value) => {
groupForm.setValue("subjectId", +value.value);
setActiveValue(value);
}}
label="Предмет"
placeholder="Выберите предмет..."
isLoading={isFetching}
onSearch={(value) => {
setSearch(value);
refetch();
}}
/>
</Resource>
);
}
Expand Down
Loading

0 comments on commit 8df00cb

Please sign in to comment.