Forum değişiklikleri
This commit is contained in:
parent
b0311425ce
commit
4252cbf0f5
9 changed files with 212 additions and 140 deletions
|
|
@ -3,7 +3,6 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Volo.Abp.Application.Dtos;
|
using Volo.Abp.Application.Dtos;
|
||||||
using Volo.Abp.Authorization;
|
using Volo.Abp.Authorization;
|
||||||
using Volo.Abp.Domain.Entities;
|
using Volo.Abp.Domain.Entities;
|
||||||
|
|
@ -159,7 +158,8 @@ public class ForumAppService : PlatformAppService, IForumAppService
|
||||||
input.Slug,
|
input.Slug,
|
||||||
input.Description,
|
input.Description,
|
||||||
input.Icon,
|
input.Icon,
|
||||||
input.DisplayOrder
|
input.DisplayOrder,
|
||||||
|
input.TenantId
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
IsActive = input.IsActive,
|
IsActive = input.IsActive,
|
||||||
|
|
@ -192,15 +192,17 @@ public class ForumAppService : PlatformAppService, IForumAppService
|
||||||
{
|
{
|
||||||
var category = await _categoryRepository.GetAsync(id);
|
var category = await _categoryRepository.GetAsync(id);
|
||||||
category.IsLocked = !category.IsLocked;
|
category.IsLocked = !category.IsLocked;
|
||||||
|
|
||||||
await _categoryRepository.UpdateAsync(category);
|
await _categoryRepository.UpdateAsync(category);
|
||||||
return ObjectMapper.Map<ForumCategory, ForumCategoryDto>(category);
|
return ObjectMapper.Map<ForumCategory, ForumCategoryDto>(category);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize("App.ForumManagement.Update")]
|
[Authorize("App.ForumManagement.Update")]
|
||||||
public async Task<ForumCategoryDto> UpdateCategoryActiveAsync(Guid id)
|
public async Task<ForumCategoryDto> UpdateCategoryActiveAsync(Guid id)
|
||||||
{
|
{
|
||||||
var category = await _categoryRepository.GetAsync(id);
|
var category = await _categoryRepository.GetAsync(id);
|
||||||
category.IsActive = !category.IsActive;
|
category.IsActive = !category.IsActive;
|
||||||
|
|
||||||
await _categoryRepository.UpdateAsync(category);
|
await _categoryRepository.UpdateAsync(category);
|
||||||
return ObjectMapper.Map<ForumCategory, ForumCategoryDto>(category);
|
return ObjectMapper.Map<ForumCategory, ForumCategoryDto>(category);
|
||||||
}
|
}
|
||||||
|
|
@ -281,7 +283,8 @@ public class ForumAppService : PlatformAppService, IForumAppService
|
||||||
input.Content,
|
input.Content,
|
||||||
input.CategoryId,
|
input.CategoryId,
|
||||||
CurrentUser.Id.Value,
|
CurrentUser.Id.Value,
|
||||||
CurrentUser.Name
|
CurrentUser.Name,
|
||||||
|
input.TenantId
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
IsPinned = input.IsPinned,
|
IsPinned = input.IsPinned,
|
||||||
|
|
@ -380,7 +383,8 @@ public class ForumAppService : PlatformAppService, IForumAppService
|
||||||
input.Content,
|
input.Content,
|
||||||
CurrentUser.Id.Value,
|
CurrentUser.Id.Value,
|
||||||
CurrentUser.Name,
|
CurrentUser.Name,
|
||||||
input.ParentPostId
|
input.ParentPostId,
|
||||||
|
input.TenantId
|
||||||
);
|
);
|
||||||
|
|
||||||
await _postRepository.InsertAsync(post, autoSave: true);
|
await _postRepository.InsertAsync(post, autoSave: true);
|
||||||
|
|
@ -473,24 +477,6 @@ public class ForumAppService : PlatformAppService, IForumAppService
|
||||||
await _categoryRepository.UpdateAsync(category);
|
await _categoryRepository.UpdateAsync(category);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Like/Unlike topic
|
|
||||||
public async Task<ForumTopicDto> LikeTopicAsync(Guid id)
|
|
||||||
{
|
|
||||||
var topic = await _topicRepository.GetAsync(id);
|
|
||||||
topic.LikeCount++;
|
|
||||||
await _topicRepository.UpdateAsync(topic);
|
|
||||||
return ObjectMapper.Map<ForumTopic, ForumTopicDto>(topic);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<ForumTopicDto> UnlikeTopicAsync(Guid id)
|
|
||||||
{
|
|
||||||
var topic = await _topicRepository.GetAsync(id);
|
|
||||||
topic.LikeCount = Math.Max(0, topic.LikeCount - 1);
|
|
||||||
await _topicRepository.UpdateAsync(topic);
|
|
||||||
return ObjectMapper.Map<ForumTopic, ForumTopicDto>(topic);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Like/Unlike posts
|
|
||||||
public async Task<ForumPostDto> LikePostAsync(Guid id)
|
public async Task<ForumPostDto> LikePostAsync(Guid id)
|
||||||
{
|
{
|
||||||
var post = await _postRepository.GetAsync(id);
|
var post = await _postRepository.GetAsync(id);
|
||||||
|
|
@ -523,6 +509,96 @@ public class ForumAppService : PlatformAppService, IForumAppService
|
||||||
return ObjectMapper.Map<ForumPost, ForumPostDto>(post);
|
return ObjectMapper.Map<ForumPost, ForumPostDto>(post);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<ForumPostDto> MarkPostAsync(Guid id)
|
||||||
|
{
|
||||||
|
var post = await _postRepository.GetAsync(id);
|
||||||
|
post.IsAcceptedAnswer = true;
|
||||||
|
|
||||||
|
await _postRepository.UpdateAsync(post);
|
||||||
|
|
||||||
|
return ObjectMapper.Map<ForumPost, ForumPostDto>(post);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ForumPostDto> UnmarkPostAsync(Guid id)
|
||||||
|
{
|
||||||
|
var post = await _postRepository.GetAsync(id);
|
||||||
|
post.IsAcceptedAnswer = false;
|
||||||
|
await _postRepository.UpdateAsync(post);
|
||||||
|
|
||||||
|
return ObjectMapper.Map<ForumPost, ForumPostDto>(post);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Like/Unlike topic
|
||||||
|
public async Task<ForumTopicDto> LikeTopicAsync(Guid id)
|
||||||
|
{
|
||||||
|
var topic = await _topicRepository.GetAsync(id);
|
||||||
|
topic.LikeCount++;
|
||||||
|
|
||||||
|
await _topicRepository.UpdateAsync(topic);
|
||||||
|
return ObjectMapper.Map<ForumTopic, ForumTopicDto>(topic);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ForumTopicDto> UnlikeTopicAsync(Guid id)
|
||||||
|
{
|
||||||
|
var topic = await _topicRepository.GetAsync(id);
|
||||||
|
topic.LikeCount = Math.Max(0, topic.LikeCount - 1);
|
||||||
|
|
||||||
|
await _topicRepository.UpdateAsync(topic);
|
||||||
|
return ObjectMapper.Map<ForumTopic, ForumTopicDto>(topic);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ForumTopicDto> PinTopicAsync(Guid id)
|
||||||
|
{
|
||||||
|
var topic = await _topicRepository.GetAsync(id);
|
||||||
|
topic.IsPinned = true;
|
||||||
|
|
||||||
|
await _topicRepository.UpdateAsync(topic);
|
||||||
|
return ObjectMapper.Map<ForumTopic, ForumTopicDto>(topic);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ForumTopicDto> UnpinTopicAsync(Guid id)
|
||||||
|
{
|
||||||
|
var topic = await _topicRepository.GetAsync(id);
|
||||||
|
topic.IsPinned = false;
|
||||||
|
await _topicRepository.UpdateAsync(topic);
|
||||||
|
return ObjectMapper.Map<ForumTopic, ForumTopicDto>(topic);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ForumTopicDto> LockTopicAsync(Guid id)
|
||||||
|
{
|
||||||
|
var topic = await _topicRepository.GetAsync(id);
|
||||||
|
topic.IsLocked = true;
|
||||||
|
|
||||||
|
await _topicRepository.UpdateAsync(topic);
|
||||||
|
return ObjectMapper.Map<ForumTopic, ForumTopicDto>(topic);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ForumTopicDto> UnlockTopicAsync(Guid id)
|
||||||
|
{
|
||||||
|
var topic = await _topicRepository.GetAsync(id);
|
||||||
|
topic.IsLocked = false;
|
||||||
|
await _topicRepository.UpdateAsync(topic);
|
||||||
|
return ObjectMapper.Map<ForumTopic, ForumTopicDto>(topic);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ForumTopicDto> SolvedTopicAsync(Guid id)
|
||||||
|
{
|
||||||
|
var topic = await _topicRepository.GetAsync(id);
|
||||||
|
topic.IsSolved = true;
|
||||||
|
|
||||||
|
await _topicRepository.UpdateAsync(topic);
|
||||||
|
return ObjectMapper.Map<ForumTopic, ForumTopicDto>(topic);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ForumTopicDto> UnsolvedTopicAsync(Guid id)
|
||||||
|
{
|
||||||
|
var topic = await _topicRepository.GetAsync(id);
|
||||||
|
topic.IsSolved = false;
|
||||||
|
|
||||||
|
await _topicRepository.UpdateAsync(topic);
|
||||||
|
return ObjectMapper.Map<ForumTopic, ForumTopicDto>(topic);
|
||||||
|
}
|
||||||
|
|
||||||
// Statistics
|
// Statistics
|
||||||
public async Task<ForumStatsDto> GetForumStatsAsync()
|
public async Task<ForumStatsDto> GetForumStatsAsync()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
|
<UserSecretsId>467bbc0f-83d0-40d0-a9f2-230c8620bdad</UserSecretsId>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@ define(['./workbox-54d0af47'], (function (workbox) { 'use strict';
|
||||||
"revision": "3ca0b8505b4bec776b69afdba2768812"
|
"revision": "3ca0b8505b4bec776b69afdba2768812"
|
||||||
}, {
|
}, {
|
||||||
"url": "index.html",
|
"url": "index.html",
|
||||||
"revision": "0.mvu82hb2mqg"
|
"revision": "0.m83v8d1s4bo"
|
||||||
}], {});
|
}], {});
|
||||||
workbox.cleanupOutdatedCaches();
|
workbox.cleanupOutdatedCaches();
|
||||||
workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {
|
workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {
|
||||||
|
|
|
||||||
|
|
@ -159,14 +159,6 @@ class ForumService {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// async toggleCategoryStatus(id: string): Promise<ForumCategory> {
|
|
||||||
// const response = await apiService.fetchData<ForumCategory>({
|
|
||||||
// url: `/api/app/forum/categories/${id}/toggle-status`,
|
|
||||||
// method: 'POST',
|
|
||||||
// })
|
|
||||||
// return response.data
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Topics
|
// Topics
|
||||||
async getTopics(params: TopicListParams = {}): Promise<PaginatedResponse<ForumTopic>> {
|
async getTopics(params: TopicListParams = {}): Promise<PaginatedResponse<ForumTopic>> {
|
||||||
const response = await apiService.fetchData<PaginatedResponse<ForumTopic>>({
|
const response = await apiService.fetchData<PaginatedResponse<ForumTopic>>({
|
||||||
|
|
@ -210,9 +202,25 @@ class ForumService {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async likeTopic(id: string): Promise<ForumTopic> {
|
||||||
|
const response = await apiService.fetchData<ForumTopic>({
|
||||||
|
url: `/api/app/forum/${id}/like-topic`,
|
||||||
|
method: 'POST',
|
||||||
|
})
|
||||||
|
return response.data
|
||||||
|
}
|
||||||
|
|
||||||
|
async unlikeTopic(id: string): Promise<ForumTopic> {
|
||||||
|
const response = await apiService.fetchData<ForumTopic>({
|
||||||
|
url: `/api/app/forum/${id}/unlike-topic`,
|
||||||
|
method: 'POST',
|
||||||
|
})
|
||||||
|
return response.data
|
||||||
|
}
|
||||||
|
|
||||||
async pinTopic(id: string): Promise<ForumTopic> {
|
async pinTopic(id: string): Promise<ForumTopic> {
|
||||||
const response = await apiService.fetchData<ForumTopic>({
|
const response = await apiService.fetchData<ForumTopic>({
|
||||||
url: `/api/app/forum/topics/${id}/pin`,
|
url: `/api/app/forum/${id}/pin-topic`,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
})
|
})
|
||||||
return response.data
|
return response.data
|
||||||
|
|
@ -220,7 +228,7 @@ class ForumService {
|
||||||
|
|
||||||
async unpinTopic(id: string): Promise<ForumTopic> {
|
async unpinTopic(id: string): Promise<ForumTopic> {
|
||||||
const response = await apiService.fetchData<ForumTopic>({
|
const response = await apiService.fetchData<ForumTopic>({
|
||||||
url: `/api/app/forum/topics/${id}/unpin`,
|
url: `/api/app/forum/${id}/unpin-topic`,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
})
|
})
|
||||||
return response.data
|
return response.data
|
||||||
|
|
@ -228,7 +236,7 @@ class ForumService {
|
||||||
|
|
||||||
async lockTopic(id: string): Promise<ForumTopic> {
|
async lockTopic(id: string): Promise<ForumTopic> {
|
||||||
const response = await apiService.fetchData<ForumTopic>({
|
const response = await apiService.fetchData<ForumTopic>({
|
||||||
url: `/api/app/forum/topics/${id}/lock`,
|
url: `/api/app/forum/${id}/lock-topic`,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
})
|
})
|
||||||
return response.data
|
return response.data
|
||||||
|
|
@ -236,23 +244,23 @@ class ForumService {
|
||||||
|
|
||||||
async unlockTopic(id: string): Promise<ForumTopic> {
|
async unlockTopic(id: string): Promise<ForumTopic> {
|
||||||
const response = await apiService.fetchData<ForumTopic>({
|
const response = await apiService.fetchData<ForumTopic>({
|
||||||
url: `/api/app/forum/topics/${id}/unlock`,
|
url: `/api/app/forum/${id}/unlock-topic`,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
})
|
})
|
||||||
return response.data
|
return response.data
|
||||||
}
|
}
|
||||||
|
|
||||||
async markTopicAsSolved(id: string): Promise<ForumTopic> {
|
async solvedTopic(id: string): Promise<ForumTopic> {
|
||||||
const response = await apiService.fetchData<ForumTopic>({
|
const response = await apiService.fetchData<ForumTopic>({
|
||||||
url: `/api/app/forum/topics/${id}/mark-solved`,
|
url: `/api/app/forum/${id}/solved-topic`,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
})
|
})
|
||||||
return response.data
|
return response.data
|
||||||
}
|
}
|
||||||
|
|
||||||
async markTopicAsUnsolved(id: string): Promise<ForumTopic> {
|
async unsolvedTopic(id: string): Promise<ForumTopic> {
|
||||||
const response = await apiService.fetchData<ForumTopic>({
|
const response = await apiService.fetchData<ForumTopic>({
|
||||||
url: `/api/app/forum/topics/${id}/mark-unsolved`,
|
url: `/api/app/forum/${id}/unsolved-topic`,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
})
|
})
|
||||||
return response.data
|
return response.data
|
||||||
|
|
@ -287,7 +295,7 @@ class ForumService {
|
||||||
|
|
||||||
async updatePost(id: string, data: Partial<CreatePostRequest>): Promise<ForumPost> {
|
async updatePost(id: string, data: Partial<CreatePostRequest>): Promise<ForumPost> {
|
||||||
const response = await apiService.fetchData<ForumPost>({
|
const response = await apiService.fetchData<ForumPost>({
|
||||||
url: `/api/app/forum/posts/${id}`,
|
url: `/api/app/forum/${id}/post`,
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
data,
|
data,
|
||||||
})
|
})
|
||||||
|
|
@ -296,7 +304,7 @@ class ForumService {
|
||||||
|
|
||||||
async deletePost(id: string): Promise<void> {
|
async deletePost(id: string): Promise<void> {
|
||||||
await apiService.fetchData({
|
await apiService.fetchData({
|
||||||
url: `/api/app/forum/posts/${id}`,
|
url: `/api/app/forum/${id}/post`,
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -317,25 +325,9 @@ class ForumService {
|
||||||
return response.data
|
return response.data
|
||||||
}
|
}
|
||||||
|
|
||||||
async likeTopic(id: string): Promise<ForumTopic> {
|
|
||||||
const response = await apiService.fetchData<ForumTopic>({
|
|
||||||
url: `/api/app/forum/${id}/like-topic`,
|
|
||||||
method: 'POST',
|
|
||||||
})
|
|
||||||
return response.data
|
|
||||||
}
|
|
||||||
|
|
||||||
async unlikeTopic(id: string): Promise<ForumTopic> {
|
|
||||||
const response = await apiService.fetchData<ForumTopic>({
|
|
||||||
url: `/api/app/forum/${id}/unlike-topic`,
|
|
||||||
method: 'POST',
|
|
||||||
})
|
|
||||||
return response.data
|
|
||||||
}
|
|
||||||
|
|
||||||
async markPostAsAcceptedAnswer(id: string): Promise<ForumPost> {
|
async markPostAsAcceptedAnswer(id: string): Promise<ForumPost> {
|
||||||
const response = await apiService.fetchData<ForumPost>({
|
const response = await apiService.fetchData<ForumPost>({
|
||||||
url: `/api/app/forum/posts/${id}/mark-accepted`,
|
url: `/api/app/forum/${id}/mark-post`,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
})
|
})
|
||||||
return response.data
|
return response.data
|
||||||
|
|
@ -343,7 +335,7 @@ class ForumService {
|
||||||
|
|
||||||
async unmarkPostAsAcceptedAnswer(id: string): Promise<ForumPost> {
|
async unmarkPostAsAcceptedAnswer(id: string): Promise<ForumPost> {
|
||||||
const response = await apiService.fetchData<ForumPost>({
|
const response = await apiService.fetchData<ForumPost>({
|
||||||
url: `/api/app/forum/posts/${id}/unmark-accepted`,
|
url: `/api/app/forum/${id}/unmark-post`,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
})
|
})
|
||||||
return response.data
|
return response.data
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,8 @@ export function Management() {
|
||||||
unpinTopic,
|
unpinTopic,
|
||||||
lockTopic,
|
lockTopic,
|
||||||
unlockTopic,
|
unlockTopic,
|
||||||
markTopicAsSolved,
|
solvedTopic,
|
||||||
markTopicAsUnsolved,
|
unsolvedTopic,
|
||||||
createPost,
|
createPost,
|
||||||
updatePost,
|
updatePost,
|
||||||
deletePost,
|
deletePost,
|
||||||
|
|
@ -91,8 +91,8 @@ export function Management() {
|
||||||
onUnpinTopic={(id) => unpinTopic(id).then(() => {})}
|
onUnpinTopic={(id) => unpinTopic(id).then(() => {})}
|
||||||
onLockTopic={(id) => lockTopic(id).then(() => {})}
|
onLockTopic={(id) => lockTopic(id).then(() => {})}
|
||||||
onUnlockTopic={(id) => unlockTopic(id).then(() => {})}
|
onUnlockTopic={(id) => unlockTopic(id).then(() => {})}
|
||||||
onMarkTopicAsSolved={(id) => markTopicAsSolved(id).then(() => {})}
|
onMarkTopicAsSolved={(id) => solvedTopic(id).then(() => {})}
|
||||||
onMarkTopicAsUnsolved={(id) => markTopicAsUnsolved(id).then(() => {})}
|
onMarkTopicAsUnsolved={(id) => unsolvedTopic(id).then(() => {})}
|
||||||
onCreatePost={(data) => createPost(data).then(() => {})}
|
onCreatePost={(data) => createPost(data).then(() => {})}
|
||||||
onUpdatePost={(id, data) => updatePost(id, data).then(() => {})}
|
onUpdatePost={(id, data) => updatePost(id, data).then(() => {})}
|
||||||
onDeletePost={(id) => deletePost(id).then(() => {})}
|
onDeletePost={(id) => deletePost(id).then(() => {})}
|
||||||
|
|
|
||||||
|
|
@ -1,56 +1,52 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react'
|
||||||
import { Folder, MessageSquare, FileText, Plus, BarChart3 } from 'lucide-react';
|
import { Folder, MessageSquare, FileText, Plus, BarChart3 } from 'lucide-react'
|
||||||
import { CategoryManagement } from './CategoryManagement';
|
import { CategoryManagement } from './CategoryManagement'
|
||||||
import { TopicManagement } from './TopicManagement';
|
import { TopicManagement } from './TopicManagement'
|
||||||
import { PostManagement } from './PostManagement';
|
import { PostManagement } from './PostManagement'
|
||||||
import { AdminStats } from './AdminStats';
|
import { ForumCategory, ForumPost, ForumTopic } from '@/proxy/forum/forum'
|
||||||
import { ForumCategory, ForumPost, ForumTopic } from '@/proxy/forum/forum';
|
import { AdminStats } from './Dashboard'
|
||||||
|
|
||||||
interface AdminViewProps {
|
interface AdminViewProps {
|
||||||
categories: ForumCategory[];
|
categories: ForumCategory[]
|
||||||
topics: ForumTopic[];
|
topics: ForumTopic[]
|
||||||
posts: ForumPost[];
|
posts: ForumPost[]
|
||||||
loading: boolean;
|
loading: boolean
|
||||||
onCreateCategory: (category: {
|
onCreateCategory: (category: {
|
||||||
name: string;
|
name: string
|
||||||
slug: string;
|
slug: string
|
||||||
description: string;
|
description: string
|
||||||
icon: string;
|
icon: string
|
||||||
displayOrder: number;
|
displayOrder: number
|
||||||
isActive: boolean;
|
isActive: boolean
|
||||||
isLocked: boolean;
|
isLocked: boolean
|
||||||
}) => Promise<void>;
|
}) => Promise<void>
|
||||||
onUpdateCategory: (id: string, category: Partial<ForumCategory>) => Promise<void>;
|
onUpdateCategory: (id: string, category: Partial<ForumCategory>) => Promise<void>
|
||||||
onUpdateCategoryLockState: (id: string) => Promise<void>;
|
onUpdateCategoryLockState: (id: string) => Promise<void>
|
||||||
onUpdateCategoryActiveState: (id: string) => Promise<void>;
|
onUpdateCategoryActiveState: (id: string) => Promise<void>
|
||||||
onDeleteCategory: (id: string) => Promise<void>;
|
onDeleteCategory: (id: string) => Promise<void>
|
||||||
onCreateTopic: (topic: {
|
onCreateTopic: (topic: {
|
||||||
title: string;
|
title: string
|
||||||
content: string;
|
content: string
|
||||||
categoryId: string;
|
categoryId: string
|
||||||
isPinned?: boolean;
|
isPinned?: boolean
|
||||||
isLocked?: boolean;
|
isLocked?: boolean
|
||||||
}) => Promise<void>;
|
}) => Promise<void>
|
||||||
onUpdateTopic: (id: string, topic: Partial<ForumTopic>) => Promise<void>;
|
onUpdateTopic: (id: string, topic: Partial<ForumTopic>) => Promise<void>
|
||||||
onDeleteTopic: (id: string) => Promise<void>;
|
onDeleteTopic: (id: string) => Promise<void>
|
||||||
onPinTopic: (id: string) => Promise<void>;
|
onPinTopic: (id: string) => Promise<void>
|
||||||
onUnpinTopic: (id: string) => Promise<void>;
|
onUnpinTopic: (id: string) => Promise<void>
|
||||||
onLockTopic: (id: string) => Promise<void>;
|
onLockTopic: (id: string) => Promise<void>
|
||||||
onUnlockTopic: (id: string) => Promise<void>;
|
onUnlockTopic: (id: string) => Promise<void>
|
||||||
onMarkTopicAsSolved: (id: string) => Promise<void>;
|
onMarkTopicAsSolved: (id: string) => Promise<void>
|
||||||
onMarkTopicAsUnsolved: (id: string) => Promise<void>;
|
onMarkTopicAsUnsolved: (id: string) => Promise<void>
|
||||||
onCreatePost: (post: {
|
onCreatePost: (post: { topicId: string; content: string; parentPostId?: string }) => Promise<void>
|
||||||
topicId: string;
|
onUpdatePost: (id: string, post: Partial<ForumPost>) => Promise<void>
|
||||||
content: string;
|
onDeletePost: (id: string) => Promise<void>
|
||||||
parentPostId?: string;
|
onMarkPostAsAcceptedAnswer: (id: string) => Promise<void>
|
||||||
}) => Promise<void>;
|
onUnmarkPostAsAcceptedAnswer: (id: string) => Promise<void>
|
||||||
onUpdatePost: (id: string, post: Partial<ForumPost>) => Promise<void>;
|
|
||||||
onDeletePost: (id: string) => Promise<void>;
|
|
||||||
onMarkPostAsAcceptedAnswer: (id: string) => Promise<void>;
|
|
||||||
onUnmarkPostAsAcceptedAnswer: (id: string) => Promise<void>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type AdminSection = 'stats' | 'categories' | 'topics' | 'posts';
|
type AdminSection = 'stats' | 'categories' | 'topics' | 'posts'
|
||||||
|
|
||||||
export function AdminView({
|
export function AdminView({
|
||||||
categories,
|
categories,
|
||||||
|
|
@ -75,16 +71,16 @@ export function AdminView({
|
||||||
onUpdatePost,
|
onUpdatePost,
|
||||||
onDeletePost,
|
onDeletePost,
|
||||||
onMarkPostAsAcceptedAnswer,
|
onMarkPostAsAcceptedAnswer,
|
||||||
onUnmarkPostAsAcceptedAnswer
|
onUnmarkPostAsAcceptedAnswer,
|
||||||
}: AdminViewProps) {
|
}: AdminViewProps) {
|
||||||
const [activeSection, setActiveSection] = useState<AdminSection>('stats');
|
const [activeSection, setActiveSection] = useState<AdminSection>('stats')
|
||||||
|
|
||||||
const navigationItems = [
|
const navigationItems = [
|
||||||
{ id: 'stats' as AdminSection, label: 'Dashboard', icon: BarChart3 },
|
{ id: 'stats' as AdminSection, label: 'Dashboard', icon: BarChart3 },
|
||||||
{ id: 'categories' as AdminSection, label: 'Categories', icon: Folder },
|
{ id: 'categories' as AdminSection, label: 'Categories', icon: Folder },
|
||||||
{ id: 'topics' as AdminSection, label: 'Topics', icon: MessageSquare },
|
{ id: 'topics' as AdminSection, label: 'Topics', icon: MessageSquare },
|
||||||
{ id: 'posts' as AdminSection, label: 'Posts', icon: FileText },
|
{ id: 'posts' as AdminSection, label: 'Posts', icon: FileText },
|
||||||
];
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
<div className="mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||||
|
|
@ -93,7 +89,7 @@ export function AdminView({
|
||||||
<div className="lg:w-64 flex-shrink-0">
|
<div className="lg:w-64 flex-shrink-0">
|
||||||
<nav className="space-y-2">
|
<nav className="space-y-2">
|
||||||
{navigationItems.map((item) => {
|
{navigationItems.map((item) => {
|
||||||
const Icon = item.icon;
|
const Icon = item.icon
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
key={item.id}
|
key={item.id}
|
||||||
|
|
@ -107,7 +103,7 @@ export function AdminView({
|
||||||
<Icon className="w-5 h-5" />
|
<Icon className="w-5 h-5" />
|
||||||
<span className="font-medium">{item.label}</span>
|
<span className="font-medium">{item.label}</span>
|
||||||
</button>
|
</button>
|
||||||
);
|
)
|
||||||
})}
|
})}
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -162,5 +158,5 @@ export function AdminView({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import { Plus, Edit2, Trash2, CheckCircle, Circle, Heart, Loader2 } from 'lucide-react'
|
import { Plus, Edit2, Trash2, CheckCircle, Circle, Heart, Loader2 } from 'lucide-react'
|
||||||
import { ForumPost, ForumTopic } from '@/proxy/forum/forum'
|
import { ForumPost, ForumTopic } from '@/proxy/forum/forum'
|
||||||
|
import ReactQuill from 'react-quill'
|
||||||
|
import 'react-quill/dist/quill.snow.css'
|
||||||
|
|
||||||
interface PostManagementProps {
|
interface PostManagementProps {
|
||||||
posts: ForumPost[]
|
posts: ForumPost[]
|
||||||
|
|
@ -23,11 +25,11 @@ export function PostManagement({
|
||||||
onMarkPostAsAcceptedAnswer,
|
onMarkPostAsAcceptedAnswer,
|
||||||
onUnmarkPostAsAcceptedAnswer,
|
onUnmarkPostAsAcceptedAnswer,
|
||||||
}: PostManagementProps) {
|
}: PostManagementProps) {
|
||||||
|
const [content, setContent] = useState('')
|
||||||
const [showCreateForm, setShowCreateForm] = useState(false)
|
const [showCreateForm, setShowCreateForm] = useState(false)
|
||||||
const [editingPost, setEditingPost] = useState<ForumPost | null>(null)
|
const [editingPost, setEditingPost] = useState<ForumPost | null>(null)
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
topicId: '',
|
topicId: '',
|
||||||
content: '',
|
|
||||||
isAcceptedAnswer: false,
|
isAcceptedAnswer: false,
|
||||||
})
|
})
|
||||||
const [submitting, setSubmitting] = useState(false)
|
const [submitting, setSubmitting] = useState(false)
|
||||||
|
|
@ -35,7 +37,6 @@ export function PostManagement({
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
setFormData({
|
setFormData({
|
||||||
topicId: '',
|
topicId: '',
|
||||||
content: '',
|
|
||||||
isAcceptedAnswer: false,
|
isAcceptedAnswer: false,
|
||||||
})
|
})
|
||||||
setShowCreateForm(false)
|
setShowCreateForm(false)
|
||||||
|
|
@ -44,15 +45,18 @@ export function PostManagement({
|
||||||
|
|
||||||
const handleSubmit = async (e: React.FormEvent) => {
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (submitting) return
|
if (submitting || !content.trim()) return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setSubmitting(true)
|
setSubmitting(true)
|
||||||
|
const data = { ...formData, content: content.trim() }
|
||||||
|
|
||||||
if (editingPost) {
|
if (editingPost) {
|
||||||
await onUpdatePost(editingPost.id, formData)
|
await onUpdatePost(editingPost.id, data)
|
||||||
} else {
|
} else {
|
||||||
await onCreatePost(formData)
|
await onCreatePost(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
resetForm()
|
resetForm()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error submitting form:', error)
|
console.error('Error submitting form:', error)
|
||||||
|
|
@ -65,9 +69,9 @@ export function PostManagement({
|
||||||
setEditingPost(post)
|
setEditingPost(post)
|
||||||
setFormData({
|
setFormData({
|
||||||
topicId: post.topicId,
|
topicId: post.topicId,
|
||||||
content: post.content,
|
|
||||||
isAcceptedAnswer: post.isAcceptedAnswer,
|
isAcceptedAnswer: post.isAcceptedAnswer,
|
||||||
})
|
})
|
||||||
|
setContent(post.content)
|
||||||
setShowCreateForm(true)
|
setShowCreateForm(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -151,12 +155,12 @@ export function PostManagement({
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">Content</label>
|
<label className="block text-sm font-medium text-gray-700 mb-1">Content</label>
|
||||||
<textarea
|
<ReactQuill
|
||||||
value={formData.content}
|
theme="snow"
|
||||||
onChange={(e) => setFormData({ ...formData, content: e.target.value })}
|
value={content}
|
||||||
rows={6}
|
onChange={setContent}
|
||||||
className="w-full border border-gray-300 rounded-lg px-3 py-2 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
style={{ height: '400px', marginBottom: '50px' }}
|
||||||
required
|
placeholder={'Write your message...'}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -230,7 +234,10 @@ export function PostManagement({
|
||||||
Reply to:{' '}
|
Reply to:{' '}
|
||||||
<span className="font-medium">{getTopicTitle(post.topicId)}</span>
|
<span className="font-medium">{getTopicTitle(post.topicId)}</span>
|
||||||
</p>
|
</p>
|
||||||
<p className="text-gray-700 line-clamp-3">{post.content}</p>
|
<p
|
||||||
|
className="text-gray-700 line-clamp-3"
|
||||||
|
dangerouslySetInnerHTML={{ __html: post.content }}
|
||||||
|
></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center justify-between text-sm text-gray-500">
|
<div className="flex items-center justify-between text-sm text-gray-500">
|
||||||
|
|
|
||||||
|
|
@ -248,9 +248,9 @@ export function useForumData() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const markTopicAsSolved = async (id: string) => {
|
const solvedTopic = async (id: string) => {
|
||||||
try {
|
try {
|
||||||
const updatedTopic = await forumService.markTopicAsSolved(id)
|
const updatedTopic = await forumService.solvedTopic(id)
|
||||||
setTopics((prev) => prev.map((topic) => (topic.id === id ? updatedTopic : topic)))
|
setTopics((prev) => prev.map((topic) => (topic.id === id ? updatedTopic : topic)))
|
||||||
return updatedTopic
|
return updatedTopic
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
@ -260,9 +260,9 @@ export function useForumData() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const markTopicAsUnsolved = async (id: string) => {
|
const unsolvedTopic = async (id: string) => {
|
||||||
try {
|
try {
|
||||||
const updatedTopic = await forumService.markTopicAsUnsolved(id)
|
const updatedTopic = await forumService.unsolvedTopic(id)
|
||||||
setTopics((prev) => prev.map((topic) => (topic.id === id ? updatedTopic : topic)))
|
setTopics((prev) => prev.map((topic) => (topic.id === id ? updatedTopic : topic)))
|
||||||
return updatedTopic
|
return updatedTopic
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
@ -432,8 +432,8 @@ export function useForumData() {
|
||||||
unpinTopic,
|
unpinTopic,
|
||||||
lockTopic,
|
lockTopic,
|
||||||
unlockTopic,
|
unlockTopic,
|
||||||
markTopicAsSolved,
|
solvedTopic,
|
||||||
markTopicAsUnsolved,
|
unsolvedTopic,
|
||||||
|
|
||||||
// Post operations
|
// Post operations
|
||||||
createPost,
|
createPost,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue