145 lines
4.1 KiB
TypeScript
145 lines
4.1 KiB
TypeScript
![]() |
import { questionApi } from "@api/question";
|
|||
|
import { devlog } from "@frontend/kitui";
|
|||
|
import { produce } from "immer";
|
|||
|
import { Question } from "model/question/question";
|
|||
|
import { enqueueSnackbar } from "notistack";
|
|||
|
import { isAxiosCanceledError } from "utils/isAxiosCanceledError";
|
|||
|
import { create } from "zustand";
|
|||
|
import { devtools } from "zustand/middleware";
|
|||
|
|
|||
|
|
|||
|
type QuestionsStore = {
|
|||
|
questionsById: Record<number, Question | undefined>;
|
|||
|
};
|
|||
|
|
|||
|
const initialState: QuestionsStore = {
|
|||
|
questionsById: {},
|
|||
|
};
|
|||
|
|
|||
|
export const useQuestionsStore = create<QuestionsStore>()(
|
|||
|
devtools(
|
|||
|
() => initialState,
|
|||
|
{
|
|||
|
name: "QuestionsStore",
|
|||
|
enabled: process.env.NODE_ENV === "development",
|
|||
|
}
|
|||
|
)
|
|||
|
);
|
|||
|
|
|||
|
export const setQuestions = (questions: QuestionsStore["questionsById"]) => useQuestionsStore.setState({ questionsById: questions });
|
|||
|
|
|||
|
export const setQuestion = (question: Question) => setProducedState(state => {
|
|||
|
state.questionsById[question.id] = question;
|
|||
|
}, {
|
|||
|
type: "setQuestion",
|
|||
|
question,
|
|||
|
});
|
|||
|
|
|||
|
export const setQuestionField = <T extends keyof Question>(
|
|||
|
questionId: number,
|
|||
|
field: T,
|
|||
|
value: Question[T],
|
|||
|
) => setProducedState(state => {
|
|||
|
const question = state.questionsById[questionId];
|
|||
|
if (!question) return;
|
|||
|
|
|||
|
question[field] = value;
|
|||
|
}, {
|
|||
|
type: "setQuestionField",
|
|||
|
questionId,
|
|||
|
field,
|
|||
|
value,
|
|||
|
});
|
|||
|
|
|||
|
let savedOriginalQuestion: Question | null = null;
|
|||
|
let controller: AbortController | null = null;
|
|||
|
|
|||
|
export const setQuestionFieldOptimistic = async <T extends keyof Question>(
|
|||
|
questionId: number,
|
|||
|
field: T,
|
|||
|
value: Question[T],
|
|||
|
) => {
|
|||
|
const question = useQuestionsStore.getState().questionsById[questionId] ?? null;
|
|||
|
if (!question) return;
|
|||
|
|
|||
|
const currentUpdatedQuestion = produce(question, draft => {
|
|||
|
draft[field] = value;
|
|||
|
});
|
|||
|
controller?.abort();
|
|||
|
controller = new AbortController();
|
|||
|
savedOriginalQuestion ??= question;
|
|||
|
|
|||
|
setQuestion(currentUpdatedQuestion);
|
|||
|
try {
|
|||
|
const { updated } = await questionApi.edit(currentUpdatedQuestion, controller.signal);
|
|||
|
// await new Promise((resolve, reject) => setTimeout(reject, 2000, new Error("Api rejected")));
|
|||
|
|
|||
|
setQuestionField(question.id, "version", updated);
|
|||
|
controller = null;
|
|||
|
savedOriginalQuestion = null;
|
|||
|
} catch (error) {
|
|||
|
if (isAxiosCanceledError(error)) return;
|
|||
|
|
|||
|
devlog("Error editing question", { error, question: question, currentUpdatedQuestion });
|
|||
|
enqueueSnackbar("Не удалось сохранить вопрос");
|
|||
|
if (!savedOriginalQuestion) {
|
|||
|
devlog("Cannot rollback question");
|
|||
|
throw new Error("Cannot rollback question");
|
|||
|
}
|
|||
|
|
|||
|
setQuestion(savedOriginalQuestion);
|
|||
|
controller = null;
|
|||
|
savedOriginalQuestion = null;
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
export const updateQuestionWithFn = (
|
|||
|
questionId: number,
|
|||
|
updateFn: (question: Question) => void,
|
|||
|
) => setProducedState(state => {
|
|||
|
const question = state.questionsById[questionId];
|
|||
|
if (!question) return;
|
|||
|
|
|||
|
updateFn(question);
|
|||
|
}, {
|
|||
|
type: "updateQuestion",
|
|||
|
questionId,
|
|||
|
updateFn: updateFn.toString(),
|
|||
|
});
|
|||
|
|
|||
|
export const createQuestion = async () => {
|
|||
|
try {
|
|||
|
const question = await questionApi.create();
|
|||
|
|
|||
|
setQuestion(question);
|
|||
|
} catch (error) {
|
|||
|
devlog("Error creating question", error);
|
|||
|
enqueueSnackbar("Не удалось создать вопрос");
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
export const deleteQuestion = async (questionId: number) => {
|
|||
|
try {
|
|||
|
await questionApi.delete(questionId);
|
|||
|
|
|||
|
removeQuestion(questionId);
|
|||
|
} catch (error) {
|
|||
|
devlog("Error deleting question", error);
|
|||
|
enqueueSnackbar("Не удалось удалить вопрос");
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
export const removeQuestion = (questionId: number) => setProducedState(state => {
|
|||
|
delete state.questionsById[questionId];
|
|||
|
}, {
|
|||
|
type: "removeQuestion",
|
|||
|
questionId,
|
|||
|
});
|
|||
|
|
|||
|
function setProducedState<A extends string | { type: unknown; }>(
|
|||
|
recipe: (state: QuestionsStore) => void,
|
|||
|
action?: A,
|
|||
|
) {
|
|||
|
useQuestionsStore.setState(state => produce(state, recipe), false, action);
|
|||
|
}
|