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:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user