feat: Add bulletin board system

- Add BoardCategory and BoardPost models with multi-language support
- Add bulletin API endpoints (CRUD, notice toggle, pin toggle)
- Add board_enabled setting to control menu visibility
- Create frontend board pages (list, detail, write, edit)
- Create admin board management and category management pages
- Update Header.tsx with conditional Board menu between Inquiry and Contact Us
- Update admin settings with board_enabled toggle
- Add Board menu to admin sidebar

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
AutonetSellCar Deploy
2026-01-10 01:34:41 +09:00
parent 04bec0d2c7
commit e0c1f4540b
17 changed files with 2630 additions and 2 deletions

View File

@@ -1765,4 +1765,174 @@ export const snsShareApi = {
},
};
// Board (Bulletin) API Types
export interface BoardCategory {
id: number;
name: string;
name_en?: string;
name_mn?: string;
name_ru?: string;
slug: string;
description?: string;
sort_order: number;
is_active: boolean;
created_at: string;
updated_at?: string;
post_count: number;
}
export interface BoardPostListItem {
id: number;
title: string;
category_id: number;
category_name?: string;
author_id: number;
author_name?: string;
is_notice: boolean;
is_pinned: boolean;
view_count: number;
created_at: string;
}
export interface BoardPost {
id: number;
title: string;
content: string;
category_id: number;
category?: BoardCategory;
author_id: number;
author?: {
id: number;
name?: string;
email: string;
is_admin: boolean;
};
is_notice: boolean;
is_pinned: boolean;
is_published: boolean;
view_count: number;
created_at: string;
updated_at?: string;
}
export interface BoardPostListResponse {
posts: BoardPostListItem[];
notices: BoardPostListItem[];
total: number;
page: number;
page_size: number;
total_pages: number;
}
export interface BoardCategoryListResponse {
categories: BoardCategory[];
total: number;
}
// Board API
export const boardApi = {
// Categories
getCategories: async (includeInactive: boolean = false): Promise<BoardCategoryListResponse> => {
const { data } = await api.get('/board/categories', { params: { include_inactive: includeInactive } });
return data;
},
createCategory: async (category: {
name: string;
name_en?: string;
name_mn?: string;
name_ru?: string;
slug: string;
description?: string;
sort_order?: number;
is_active?: boolean;
}): Promise<BoardCategory> => {
const { data } = await api.post('/board/categories', category);
return data;
},
updateCategory: async (categoryId: number, category: {
name?: string;
name_en?: string;
name_mn?: string;
name_ru?: string;
slug?: string;
description?: string;
sort_order?: number;
is_active?: boolean;
}): Promise<BoardCategory> => {
const { data } = await api.put(`/board/categories/${categoryId}`, category);
return data;
},
deleteCategory: async (categoryId: number): Promise<void> => {
await api.delete(`/board/categories/${categoryId}`);
},
// Posts
getPosts: async (params: {
page?: number;
page_size?: number;
category_id?: number;
search?: string;
}): Promise<BoardPostListResponse> => {
const { data } = await api.get('/board/posts', { params });
return data;
},
getPost: async (postId: number): Promise<BoardPost> => {
const { data } = await api.get(`/board/posts/${postId}`);
return data;
},
createPost: async (post: {
title: string;
content: string;
category_id: number;
is_notice?: boolean;
}): Promise<BoardPost> => {
const { data } = await api.post('/board/posts', post);
return data;
},
updatePost: async (postId: number, post: {
title?: string;
content?: string;
category_id?: number;
is_notice?: boolean;
is_pinned?: boolean;
is_published?: boolean;
}): Promise<BoardPost> => {
const { data } = await api.put(`/board/posts/${postId}`, post);
return data;
},
deletePost: async (postId: number): Promise<void> => {
await api.delete(`/board/posts/${postId}`);
},
// Admin
getAdminPosts: async (params: {
page?: number;
page_size?: number;
category_id?: number;
is_notice?: boolean;
is_published?: boolean;
search?: string;
}): Promise<BoardPostListResponse> => {
const { data } = await api.get('/board/admin/posts', { params });
return data;
},
toggleNotice: async (postId: number): Promise<{ is_notice: boolean; is_pinned: boolean }> => {
const { data } = await api.put(`/board/admin/posts/${postId}/toggle-notice`);
return data;
},
togglePin: async (postId: number): Promise<{ is_pinned: boolean }> => {
const { data } = await api.put(`/board/admin/posts/${postId}/toggle-pin`);
return data;
},
};
export default api;