673 lines
20 KiB
TypeScript
673 lines
20 KiB
TypeScript
import {
|
|
articlesStore,
|
|
Language,
|
|
authInstance,
|
|
languageInstance,
|
|
mediaStore,
|
|
selectedCityStore,
|
|
} from "@shared";
|
|
import { makeAutoObservable, runInAction } from "mobx";
|
|
|
|
type MediaItem = {
|
|
id: string;
|
|
filename: string;
|
|
media_name?: string;
|
|
media_type: number;
|
|
};
|
|
|
|
type SightLanguageInfo = {
|
|
name: string;
|
|
short_name: string;
|
|
address: string;
|
|
left: {
|
|
heading: string;
|
|
body: string;
|
|
media: MediaItem[];
|
|
};
|
|
right: { id: number; heading: string; body: string; media: MediaItem[] }[];
|
|
};
|
|
|
|
type SightCommonInfo = {
|
|
city_id: number;
|
|
city: string;
|
|
latitude: number;
|
|
longitude: number;
|
|
is_default_icon: boolean;
|
|
thumbnail: string | null;
|
|
icon: string | null;
|
|
alt_icon: string | null;
|
|
watermark_lu: string | null;
|
|
watermark_rd: string | null;
|
|
left_article: number;
|
|
preview_media: string | null;
|
|
video_preview: string | null;
|
|
preview_font_size?: number;
|
|
};
|
|
|
|
type SightBaseInfo = SightCommonInfo & {
|
|
[key in Language]: SightLanguageInfo;
|
|
};
|
|
|
|
const initialSightState: SightBaseInfo = {
|
|
city_id: 0,
|
|
city: "",
|
|
latitude: 0,
|
|
longitude: 0,
|
|
is_default_icon: false,
|
|
thumbnail: null,
|
|
icon: null,
|
|
alt_icon: null,
|
|
watermark_lu: null,
|
|
watermark_rd: null,
|
|
left_article: 0,
|
|
preview_media: null,
|
|
video_preview: null,
|
|
ru: {
|
|
name: "",
|
|
short_name: "",
|
|
address: "",
|
|
left: { heading: "", body: "", media: [] },
|
|
right: [],
|
|
},
|
|
en: {
|
|
name: "",
|
|
short_name: "",
|
|
address: "",
|
|
left: { heading: "", body: "", media: [] },
|
|
right: [],
|
|
},
|
|
zh: {
|
|
name: "",
|
|
short_name: "",
|
|
address: "",
|
|
left: { heading: "", body: "", media: [] },
|
|
right: [],
|
|
},
|
|
};
|
|
|
|
class CreateSightStore {
|
|
sight: SightBaseInfo = JSON.parse(JSON.stringify(initialSightState));
|
|
|
|
uploadMediaOpen = false;
|
|
setUploadMediaOpen = (open: boolean) => {
|
|
this.uploadMediaOpen = open;
|
|
};
|
|
fileToUpload: File | null = null;
|
|
setFileToUpload = (file: File | null) => {
|
|
this.fileToUpload = file;
|
|
};
|
|
|
|
constructor() {
|
|
makeAutoObservable(this);
|
|
}
|
|
|
|
createNewRightArticle = async () => {
|
|
const articleRuData = {
|
|
heading: "Новый заголовок (RU)",
|
|
body: "Новый текст (RU)",
|
|
};
|
|
const articleEnData = {
|
|
heading: "New Heading (EN)",
|
|
body: "New Text (EN)",
|
|
};
|
|
const articleZhData = {
|
|
heading: "Новый заголовок (ZH)",
|
|
body: "Новый текст (ZH)",
|
|
};
|
|
|
|
try {
|
|
this.needLeaveAgree = true;
|
|
const articleRes = await authInstance.post("/article", {
|
|
translations: {
|
|
heading: {
|
|
ru: articleRuData.heading,
|
|
en: articleEnData.heading,
|
|
zh: articleZhData.heading,
|
|
},
|
|
body: {
|
|
ru: articleRuData.body,
|
|
en: articleEnData.body,
|
|
zh: articleZhData.body,
|
|
},
|
|
},
|
|
...(selectedCityStore.selectedCityId ? { city_id: selectedCityStore.selectedCityId } : {}),
|
|
});
|
|
const { id } = articleRes.data;
|
|
|
|
runInAction(() => {
|
|
const newArticleEntry = { id, media: [] };
|
|
this.sight.ru.right.push({ ...newArticleEntry, ...articleRuData });
|
|
this.sight.en.right.push({ ...newArticleEntry, ...articleEnData });
|
|
this.sight.zh.right.push({ ...newArticleEntry, ...articleZhData });
|
|
});
|
|
return id;
|
|
} catch (error) {
|
|
console.error("Error creating new right article:", error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
linkExistingRightArticle = async (articleId: number) => {
|
|
try {
|
|
const ruData = await languageInstance("ru").get(`/article/${articleId}`);
|
|
const enData = await languageInstance("en").get(`/article/${articleId}`);
|
|
const zhData = await languageInstance("zh").get(`/article/${articleId}`);
|
|
const mediaRes = await authInstance.get(`/article/${articleId}/media`);
|
|
const mediaData: MediaItem[] = mediaRes.data || [];
|
|
|
|
runInAction(() => {
|
|
this.sight.ru.right.push({
|
|
id: articleId,
|
|
heading: ruData.data.heading,
|
|
body: ruData.data.body,
|
|
media: mediaData,
|
|
});
|
|
this.sight.en.right.push({
|
|
id: articleId,
|
|
heading: enData.data.heading,
|
|
body: enData.data.body,
|
|
media: mediaData,
|
|
});
|
|
this.sight.zh.right.push({
|
|
id: articleId,
|
|
heading: zhData.data.heading,
|
|
body: zhData.data.body,
|
|
media: mediaData,
|
|
});
|
|
});
|
|
|
|
return articleId;
|
|
} catch (error) {
|
|
console.error("Error linking existing right article:", error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
updateRightArticleInfo = (
|
|
index: number,
|
|
language: Language,
|
|
heading: string,
|
|
body: string,
|
|
) => {
|
|
if (this.sight[language].right[index]) {
|
|
this.sight[language].right[index].heading = heading;
|
|
this.sight[language].right[index].body = body;
|
|
}
|
|
};
|
|
|
|
unlinkRightAritcle = (articleId: number) => {
|
|
runInAction(() => {
|
|
this.sight.ru.right = this.sight.ru.right.filter(
|
|
(article) => article.id !== articleId,
|
|
);
|
|
this.sight.en.right = this.sight.en.right.filter(
|
|
(article) => article.id !== articleId,
|
|
);
|
|
this.sight.zh.right = this.sight.zh.right.filter(
|
|
(article) => article.id !== articleId,
|
|
);
|
|
});
|
|
};
|
|
|
|
deleteRightArticle = async (articleId: number) => {
|
|
try {
|
|
await authInstance.delete(`/article/${articleId}`);
|
|
runInAction(() => {
|
|
this.sight.ru.right = this.sight.ru.right.filter(
|
|
(article) => article.id !== articleId,
|
|
);
|
|
this.sight.en.right = this.sight.en.right.filter(
|
|
(article) => article.id !== articleId,
|
|
);
|
|
this.sight.zh.right = this.sight.zh.right.filter(
|
|
(article) => article.id !== articleId,
|
|
);
|
|
});
|
|
} catch (error) {
|
|
console.error("Error deleting right article:", error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
createLinkWithRightArticle = async (media: MediaItem, articleId: number) => {
|
|
try {
|
|
await authInstance.post(`/article/${articleId}/media`, {
|
|
media_id: media.id,
|
|
media_order: 1,
|
|
});
|
|
runInAction(() => {
|
|
(["ru", "en", "zh"] as Language[]).forEach((lang) => {
|
|
const article = this.sight[lang].right.find(
|
|
(a) => a.id === articleId,
|
|
);
|
|
if (article) {
|
|
if (!article.media) article.media = [];
|
|
article.media.unshift(media);
|
|
}
|
|
});
|
|
});
|
|
} catch (error) {
|
|
console.error("Error linking media to right article:", error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
deleteRightArticleMedia = async (articleId: number, mediaId: string) => {
|
|
try {
|
|
await authInstance.delete(`/article/${articleId}/media`, {
|
|
data: { media_id: mediaId },
|
|
});
|
|
runInAction(() => {
|
|
(["ru", "en", "zh"] as Language[]).forEach((lang) => {
|
|
const article = this.sight[lang].right.find(
|
|
(a) => a.id === articleId,
|
|
);
|
|
if (article && article.media) {
|
|
article.media = article.media.filter((m) => m.id !== mediaId);
|
|
}
|
|
});
|
|
});
|
|
} catch (error) {
|
|
console.error("Error deleting media from right article:", error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
updateLeftInfo = (language: Language, heading: string, body: string) => {
|
|
this.sight[language].left.heading = heading;
|
|
this.sight[language].left.body = body;
|
|
};
|
|
|
|
unlinkLeftArticle = () => {
|
|
/* ... your existing logic ... */
|
|
this.sight.left_article = 0;
|
|
this.sight.ru.left = { heading: "", body: "", media: [] };
|
|
this.sight.en.left = { heading: "", body: "", media: [] };
|
|
this.sight.zh.left = { heading: "", body: "", media: [] };
|
|
};
|
|
|
|
updateLeftArticle = async (articleId: number) => {
|
|
/* ... your existing logic ... */
|
|
this.sight.left_article = articleId;
|
|
if (articleId) {
|
|
const [ruArticleData, enArticleData, zhArticleData, mediaData] =
|
|
await Promise.all([
|
|
languageInstance("ru").get(`/article/${articleId}`),
|
|
languageInstance("en").get(`/article/${articleId}`),
|
|
languageInstance("zh").get(`/article/${articleId}`),
|
|
authInstance.get(`/article/${articleId}/media`),
|
|
]);
|
|
runInAction(() => {
|
|
this.sight.ru.left = {
|
|
heading: ruArticleData.data.heading,
|
|
body: ruArticleData.data.body,
|
|
media: mediaData.data || [],
|
|
};
|
|
this.sight.en.left = {
|
|
heading: enArticleData.data.heading,
|
|
body: enArticleData.data.body,
|
|
media: mediaData.data || [],
|
|
};
|
|
this.sight.zh.left = {
|
|
heading: zhArticleData.data.heading,
|
|
body: zhArticleData.data.body,
|
|
media: mediaData.data || [],
|
|
};
|
|
});
|
|
} else {
|
|
this.unlinkLeftArticle();
|
|
}
|
|
};
|
|
|
|
deleteLeftArticle = async (articleId: number) => {
|
|
/* ... your existing logic ... */
|
|
await authInstance.delete(`/article/${articleId}`);
|
|
|
|
runInAction(() => {
|
|
articlesStore.articles.ru = articlesStore.articles.ru.filter(
|
|
(article) => article.id !== articleId,
|
|
);
|
|
articlesStore.articles.en = articlesStore.articles.en.filter(
|
|
(article) => article.id !== articleId,
|
|
);
|
|
articlesStore.articles.zh = articlesStore.articles.zh.filter(
|
|
(article) => article.id !== articleId,
|
|
);
|
|
});
|
|
this.unlinkLeftArticle();
|
|
};
|
|
|
|
createLeftArticle = async () => {
|
|
/* ... your existing logic to create a new left article (placeholder or DB) ... */
|
|
const ruName = (this.sight.ru.name || "").trim();
|
|
const enName = (this.sight.en.name || "").trim();
|
|
const zhName = (this.sight.zh.name || "").trim();
|
|
|
|
const hasAnyName = !!(ruName || enName || zhName);
|
|
|
|
const response = await languageInstance("ru").post("/article", {
|
|
heading: hasAnyName ? ruName : "",
|
|
body: "",
|
|
...(selectedCityStore.selectedCityId ? { city_id: selectedCityStore.selectedCityId } : {}),
|
|
});
|
|
const newLeftArticleId = response.data.id;
|
|
|
|
await languageInstance("en").patch(`/article/${newLeftArticleId}`, {
|
|
heading: hasAnyName ? enName : "",
|
|
body: "",
|
|
});
|
|
await languageInstance("zh").patch(`/article/${newLeftArticleId}`, {
|
|
heading: hasAnyName ? zhName : "",
|
|
body: "",
|
|
});
|
|
|
|
runInAction(() => {
|
|
this.sight.left_article = newLeftArticleId;
|
|
this.sight.ru.left = {
|
|
heading: hasAnyName ? ruName : "",
|
|
body: "",
|
|
media: [],
|
|
};
|
|
this.sight.en.left = {
|
|
heading: hasAnyName ? enName : "",
|
|
body: "",
|
|
media: [],
|
|
};
|
|
this.sight.zh.left = {
|
|
heading: hasAnyName ? zhName : "",
|
|
body: "",
|
|
media: [],
|
|
};
|
|
|
|
articlesStore.articles.ru.push({
|
|
id: newLeftArticleId,
|
|
heading: hasAnyName ? ruName : "",
|
|
body: "",
|
|
service_name: hasAnyName ? ruName : "",
|
|
});
|
|
articlesStore.articles.en.push({
|
|
id: newLeftArticleId,
|
|
heading: hasAnyName ? enName : "",
|
|
body: "",
|
|
service_name: hasAnyName ? enName : "",
|
|
});
|
|
articlesStore.articles.zh.push({
|
|
id: newLeftArticleId,
|
|
heading: hasAnyName ? zhName : "",
|
|
body: "",
|
|
service_name: hasAnyName ? zhName : "",
|
|
});
|
|
});
|
|
return newLeftArticleId;
|
|
};
|
|
|
|
setNewLeftArticlePlaceholder = () => {
|
|
this.sight.left_article = 10000000;
|
|
this.sight.ru.left = {
|
|
heading: "Новая левая статья",
|
|
body: "Заполните контентом",
|
|
media: [],
|
|
};
|
|
this.sight.en.left = {
|
|
heading: "New Left Article",
|
|
body: "Fill with content",
|
|
media: [],
|
|
};
|
|
this.sight.zh.left = {
|
|
heading: "新的左侧文章",
|
|
body: "填写内容",
|
|
media: [],
|
|
};
|
|
};
|
|
|
|
linkPreviewMedia = (mediaId: string) => {
|
|
this.sight.preview_media = mediaId;
|
|
};
|
|
|
|
unlinkPreviewMedia = () => {
|
|
this.sight.preview_media = null;
|
|
};
|
|
|
|
clearCreateSight = () => {
|
|
this.needLeaveAgree = false;
|
|
this.sight = JSON.parse(JSON.stringify(initialSightState));
|
|
};
|
|
|
|
updateSightInfo = (
|
|
content: Partial<SightLanguageInfo | SightCommonInfo>,
|
|
language?: Language,
|
|
) => {
|
|
this.needLeaveAgree = true;
|
|
if (language) {
|
|
this.sight[language] = { ...this.sight[language], ...content };
|
|
} else {
|
|
this.sight = { ...this.sight, ...(content as Partial<SightCommonInfo>) };
|
|
}
|
|
};
|
|
|
|
createSight = async (primaryLanguage: Language) => {
|
|
let finalLeftArticleId = this.sight.left_article;
|
|
|
|
if (this.sight.left_article === 10000000) {
|
|
const res = await languageInstance("ru").post("/article", {
|
|
heading: this.sight.ru.left.heading,
|
|
body: this.sight.ru.left.body,
|
|
...(selectedCityStore.selectedCityId ? { city_id: selectedCityStore.selectedCityId } : {}),
|
|
});
|
|
finalLeftArticleId = res.data.id;
|
|
await languageInstance("en").patch(`/article/${finalLeftArticleId}`, {
|
|
heading: this.sight.en.left.heading,
|
|
body: this.sight.en.left.body,
|
|
});
|
|
await languageInstance("zh").patch(`/article/${finalLeftArticleId}`, {
|
|
heading: this.sight.zh.left.heading,
|
|
body: this.sight.zh.left.body,
|
|
});
|
|
} else if (
|
|
this.sight.left_article !== 0 &&
|
|
this.sight.left_article !== null
|
|
) {
|
|
await languageInstance("ru").patch(
|
|
`/article/${this.sight.left_article}`,
|
|
{ heading: this.sight.ru.left.heading, body: this.sight.ru.left.body },
|
|
);
|
|
await languageInstance("en").patch(
|
|
`/article/${this.sight.left_article}`,
|
|
{ heading: this.sight.en.left.heading, body: this.sight.en.left.body },
|
|
);
|
|
await languageInstance("zh").patch(
|
|
`/article/${this.sight.left_article}`,
|
|
{ heading: this.sight.zh.left.heading, body: this.sight.zh.left.body },
|
|
);
|
|
}
|
|
|
|
for (const lang of ["ru", "en", "zh"] as Language[]) {
|
|
for (const article of this.sight[lang].right) {
|
|
if (article.id == 0 || article.id == null) {
|
|
continue;
|
|
}
|
|
await languageInstance(lang).patch(`/article/${article.id}`, {
|
|
heading: article.heading,
|
|
body: article.body,
|
|
});
|
|
}
|
|
}
|
|
const rightArticleIdsForLink = this.sight[primaryLanguage].right.map(
|
|
(a) => a.id,
|
|
);
|
|
|
|
const sightPayload = {
|
|
city_id: this.sight.city_id,
|
|
city: this.sight.city,
|
|
latitude: this.sight.latitude,
|
|
longitude: this.sight.longitude,
|
|
is_default_icon: this.sight.is_default_icon,
|
|
name: (this.sight[primaryLanguage].name || "").trim(),
|
|
short_name: (this.sight[primaryLanguage].short_name || "").trim(),
|
|
address: this.sight[primaryLanguage].address,
|
|
thumbnail: this.sight.thumbnail,
|
|
icon: this.sight.icon,
|
|
alt_icon: this.sight.alt_icon,
|
|
watermark_lu: this.sight.watermark_lu,
|
|
watermark_rd: this.sight.watermark_rd,
|
|
left_article: finalLeftArticleId === 0 ? null : finalLeftArticleId,
|
|
preview_media: this.sight.preview_media,
|
|
video_preview: this.sight.video_preview,
|
|
preview_font_size: this.sight.preview_font_size,
|
|
};
|
|
|
|
const response = await languageInstance(primaryLanguage).post(
|
|
"/sight",
|
|
sightPayload,
|
|
);
|
|
const newSightId = response.data.id;
|
|
|
|
const otherLanguages = (["ru", "en", "zh"] as Language[]).filter(
|
|
(l) => l !== primaryLanguage,
|
|
);
|
|
for (const lang of otherLanguages) {
|
|
await languageInstance(lang).patch(`/sight/${newSightId}`, {
|
|
city_id: this.sight.city_id,
|
|
city: this.sight.city,
|
|
latitude: this.sight.latitude,
|
|
longitude: this.sight.longitude,
|
|
is_default_icon: this.sight.is_default_icon,
|
|
name: (this.sight[lang].name || "").trim(),
|
|
short_name: (this.sight[lang].short_name || "").trim(),
|
|
address: this.sight[lang].address,
|
|
thumbnail: this.sight.thumbnail,
|
|
icon: this.sight.icon,
|
|
alt_icon: this.sight.alt_icon,
|
|
watermark_lu: this.sight.watermark_lu,
|
|
watermark_rd: this.sight.watermark_rd,
|
|
left_article: finalLeftArticleId === 0 ? null : finalLeftArticleId,
|
|
preview_media: this.sight.preview_media,
|
|
video_preview: this.sight.video_preview,
|
|
});
|
|
}
|
|
|
|
for (let i = 0; i < rightArticleIdsForLink.length; i++) {
|
|
await authInstance.post(`/sight/${newSightId}/article`, {
|
|
article_id: rightArticleIdsForLink[i],
|
|
page_num: i + 1,
|
|
});
|
|
}
|
|
|
|
runInAction(() => {
|
|
this.needLeaveAgree = false;
|
|
});
|
|
|
|
return newSightId;
|
|
};
|
|
|
|
uploadMedia = async (
|
|
filename: string,
|
|
type: number,
|
|
file: File,
|
|
media_name?: string,
|
|
): Promise<MediaItem> => {
|
|
const formData = new FormData();
|
|
formData.append("file", file);
|
|
formData.append("filename", filename);
|
|
if (media_name) formData.append("media_name", media_name);
|
|
formData.append("type", type.toString());
|
|
if (selectedCityStore.selectedCityId) {
|
|
formData.append("city_id", selectedCityStore.selectedCityId.toString());
|
|
}
|
|
|
|
try {
|
|
const response = await authInstance.post(`/media`, formData);
|
|
runInAction(() => {
|
|
this.fileToUpload = null;
|
|
this.uploadMediaOpen = false;
|
|
});
|
|
mediaStore.getMedia();
|
|
return {
|
|
id: response.data.id,
|
|
filename: filename,
|
|
media_name: media_name,
|
|
media_type: type,
|
|
};
|
|
} catch (error) {
|
|
console.error("Error uploading media:", error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
createLinkWithLeftArticle = async (media: MediaItem) => {
|
|
if (!this.sight.left_article || this.sight.left_article === 10000000) {
|
|
console.warn(
|
|
"Left article not selected or is a placeholder. Cannot link media yet.",
|
|
);
|
|
|
|
return;
|
|
}
|
|
try {
|
|
await authInstance.post(`/article/${this.sight.left_article}/media`, {
|
|
media_id: media.id,
|
|
media_order: (this.sight.ru.left.media?.length || 0) + 1,
|
|
});
|
|
runInAction(() => {
|
|
(["ru", "en", "zh"] as Language[]).forEach((lang) => {
|
|
if (!this.sight[lang].left.media) this.sight[lang].left.media = [];
|
|
this.sight[lang].left.media.unshift(media);
|
|
});
|
|
});
|
|
} catch (error) {
|
|
console.error("Error linking media to left article:", error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
deleteLeftArticleMedia = async (mediaId: string) => {
|
|
if (!this.sight.left_article || this.sight.left_article === 10000000)
|
|
return;
|
|
try {
|
|
await authInstance.delete(`/article/${this.sight.left_article}/media`, {
|
|
data: { media_id: mediaId },
|
|
});
|
|
runInAction(() => {
|
|
(["ru", "en", "zh"] as Language[]).forEach((lang) => {
|
|
if (this.sight[lang].left.media) {
|
|
this.sight[lang].left.media = this.sight[lang].left.media.filter(
|
|
(m) => m.id !== mediaId,
|
|
);
|
|
}
|
|
});
|
|
});
|
|
} catch (error) {
|
|
console.error("Error deleting media from left article:", error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
updateRightArticles = async (articles: any[]) => {
|
|
const articlesIds = articles.map((article) => article.id);
|
|
|
|
const sortArticles = (existing: any[]) => {
|
|
const articleMap = new Map(
|
|
existing.map((article) => [article.id, article]),
|
|
);
|
|
return articlesIds
|
|
.map((id) => articleMap.get(id))
|
|
.filter(
|
|
(article): article is (typeof existing)[number] =>
|
|
article !== undefined,
|
|
);
|
|
};
|
|
|
|
this.sight.ru.right = sortArticles(this.sight.ru.right);
|
|
this.sight.en.right = sortArticles(this.sight.en.right);
|
|
this.sight.zh.right = sortArticles(this.sight.zh.right);
|
|
|
|
this.needLeaveAgree = true;
|
|
};
|
|
|
|
needLeaveAgree = false;
|
|
setNeedLeaveAgree = (need: boolean) => {
|
|
this.needLeaveAgree = need;
|
|
};
|
|
}
|
|
|
|
export const createSightStore = new CreateSightStore();
|