feat: 게시판 댓글/답글 시스템 및 대시보드 개선
주요 변경사항: - 게시판 계층형 댓글/답글 시스템 구현 - DB: root_idx, depth, thread_path, is_comment, reply_count 컬럼 추가 - 트리거: 댓글 개수 자동 업데이트 - 답글(is_comment=false)은 목록에 표시, 댓글(is_comment=true)은 뷰어에만 표시 - ESC 키로 모달 닫기 기능 - 업무일지 개선 - 프로젝트 선택 시 최종 설정 자동 불러오기 - 복사 시 jobgrp, tag 포함 - 완료(보고) 상태 프로젝트도 검색 가능하도록 수정 - 대시보드 개선 - 할일 목록 페이징 추가 (6개씩) - 할일에 요청자 정보 표시 (제목 좌측에 괄호로)
This commit is contained in:
@@ -29,21 +29,27 @@ namespace Project.Web
|
||||
|
||||
var sql = @"
|
||||
SELECT idx, bidx, header, cate, title, contents, [file], guid, url, wuid, wdate, project, pidx, gcode, [close], remark,
|
||||
root_idx, depth, sort_order, thread_path, is_comment, reply_count,
|
||||
dbo.getUserName(wuid) AS wuid_name
|
||||
FROM Board WITH (nolock)
|
||||
WHERE gcode = @gcode AND bidx = @bidx
|
||||
FROM EETGW_Board WITH (nolock)
|
||||
WHERE gcode = @gcode AND bidx = @bidx AND (is_comment IS NULL OR is_comment = 0)
|
||||
AND (ISNULL(title,'') LIKE @search OR ISNULL(contents,'') LIKE @search OR ISNULL(wuid,'') LIKE @search)
|
||||
ORDER BY wdate DESC";
|
||||
ORDER BY
|
||||
CASE WHEN root_idx IS NULL THEN idx ELSE root_idx END DESC,
|
||||
thread_path ASC";
|
||||
|
||||
if(bidx == 5) //패치내역은 모두가 다 확인할 수있도록 그룹코드를 제한하지 않는다
|
||||
{
|
||||
sql = @"
|
||||
SELECT idx, bidx, header, cate, title, contents, [file], guid, url, wuid, wdate, project, pidx, gcode, [close], remark,
|
||||
root_idx, depth, sort_order, thread_path, is_comment, reply_count,
|
||||
dbo.getUserName(wuid) AS wuid_name
|
||||
FROM Board WITH (nolock)
|
||||
WHERE bidx = @bidx
|
||||
FROM EETGW_Board WITH (nolock)
|
||||
WHERE bidx = @bidx AND (is_comment IS NULL OR is_comment = 0)
|
||||
AND (ISNULL(title,'') LIKE @search OR ISNULL(contents,'') LIKE @search OR ISNULL(wuid,'') LIKE @search)
|
||||
ORDER BY wdate DESC";
|
||||
ORDER BY
|
||||
CASE WHEN root_idx IS NULL THEN idx ELSE root_idx END DESC,
|
||||
thread_path ASC";
|
||||
}
|
||||
|
||||
var cmd = new SqlCommand(sql, conn);
|
||||
@@ -75,7 +81,13 @@ namespace Project.Web
|
||||
gcode = reader.IsDBNull(13) ? "" : reader.GetString(13),
|
||||
close = reader.IsDBNull(14) ? false : reader.GetBoolean(14),
|
||||
remark = reader.IsDBNull(15) ? "" : reader.GetString(15),
|
||||
wuid_name = reader.IsDBNull(16) ? "" : reader.GetString(16)
|
||||
root_idx = reader.IsDBNull(16) ? (int?)null : reader.GetInt32(16),
|
||||
depth = reader.IsDBNull(17) ? 0 : reader.GetInt32(17),
|
||||
sort_order = reader.IsDBNull(18) ? 0 : reader.GetInt32(18),
|
||||
thread_path = reader.IsDBNull(19) ? "" : reader.GetString(19),
|
||||
is_comment = reader.IsDBNull(20) ? false : reader.GetBoolean(20),
|
||||
reply_count = reader.IsDBNull(21) ? 0 : reader.GetInt32(21),
|
||||
wuid_name = reader.IsDBNull(22) ? "" : reader.GetString(22)
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -108,8 +120,9 @@ namespace Project.Web
|
||||
|
||||
var cmd = new SqlCommand(@"
|
||||
SELECT idx, bidx, header, cate, title, contents, [file], guid, url, wuid, wdate, project, pidx, gcode, [close], remark,
|
||||
root_idx, depth, sort_order, thread_path, is_comment, reply_count,
|
||||
dbo.getUserName(wuid) AS wuid_name
|
||||
FROM Board WITH (nolock)
|
||||
FROM EETGW_Board WITH (nolock)
|
||||
WHERE idx = @idx AND gcode = @gcode", conn);
|
||||
|
||||
cmd.Parameters.Add("@idx", SqlDbType.Int).Value = idx;
|
||||
@@ -137,7 +150,13 @@ namespace Project.Web
|
||||
gcode = reader.IsDBNull(13) ? "" : reader.GetString(13),
|
||||
close = reader.IsDBNull(14) ? false : reader.GetBoolean(14),
|
||||
remark = reader.IsDBNull(15) ? "" : reader.GetString(15),
|
||||
wuid_name = reader.IsDBNull(16) ? "" : reader.GetString(16)
|
||||
root_idx = reader.IsDBNull(16) ? (int?)null : reader.GetInt32(16),
|
||||
depth = reader.IsDBNull(17) ? 0 : reader.GetInt32(17),
|
||||
sort_order = reader.IsDBNull(18) ? 0 : reader.GetInt32(18),
|
||||
thread_path = reader.IsDBNull(19) ? "" : reader.GetString(19),
|
||||
is_comment = reader.IsDBNull(20) ? false : reader.GetBoolean(20),
|
||||
reply_count = reader.IsDBNull(21) ? 0 : reader.GetInt32(21),
|
||||
wuid_name = reader.IsDBNull(22) ? "" : reader.GetString(22)
|
||||
};
|
||||
|
||||
return JsonConvert.SerializeObject(new { Success = true, Data = data });
|
||||
@@ -173,9 +192,12 @@ namespace Project.Web
|
||||
conn.Open();
|
||||
|
||||
var cmd = new SqlCommand(@"
|
||||
INSERT INTO Board (bidx, header, cate, title, contents, wuid, wdate, gcode)
|
||||
VALUES (@bidx, @header, @cate, @title, @contents, @wuid, GETDATE(), @gcode);
|
||||
SELECT SCOPE_IDENTITY();", conn);
|
||||
DECLARE @newIdx INT;
|
||||
INSERT INTO EETGW_Board (bidx, header, cate, title, contents, wuid, wdate, gcode, depth, root_idx, thread_path, is_comment)
|
||||
VALUES (@bidx, @header, @cate, @title, @contents, @wuid, GETDATE(), @gcode, 0, NULL, NULL, 0);
|
||||
SET @newIdx = SCOPE_IDENTITY();
|
||||
UPDATE EETGW_Board SET root_idx = @newIdx, thread_path = CAST(@newIdx AS VARCHAR(20)) WHERE idx = @newIdx;
|
||||
SELECT @newIdx;", conn);
|
||||
|
||||
cmd.Parameters.Add("@bidx", SqlDbType.Int).Value = bidx;
|
||||
cmd.Parameters.Add("@header", SqlDbType.NVarChar).Value = string.IsNullOrEmpty(header) ? (object)DBNull.Value : header;
|
||||
@@ -214,7 +236,7 @@ namespace Project.Web
|
||||
conn.Open();
|
||||
|
||||
// 권한 확인: 작성자 본인이거나 레벨 9 이상만 수정 가능
|
||||
var checkCmd = new SqlCommand("SELECT wuid FROM Board WHERE idx = @idx", conn);
|
||||
var checkCmd = new SqlCommand("SELECT wuid FROM EETGW_Board WHERE idx = @idx", conn);
|
||||
checkCmd.Parameters.Add("@idx", SqlDbType.Int).Value = idx;
|
||||
var originalWuid = checkCmd.ExecuteScalar()?.ToString();
|
||||
|
||||
@@ -224,7 +246,7 @@ namespace Project.Web
|
||||
}
|
||||
|
||||
var cmd = new SqlCommand(@"
|
||||
UPDATE Board
|
||||
UPDATE EETGW_Board
|
||||
SET header = @header, cate = @cate, title = @title, contents = @contents
|
||||
WHERE idx = @idx", conn);
|
||||
|
||||
@@ -270,7 +292,7 @@ namespace Project.Web
|
||||
conn.Open();
|
||||
|
||||
// 권한 확인: 작성자 본인이거나 레벨 9 이상만 삭제 가능
|
||||
var checkCmd = new SqlCommand("SELECT wuid FROM Board WHERE idx = @idx", conn);
|
||||
var checkCmd = new SqlCommand("SELECT wuid FROM EETGW_Board WHERE idx = @idx", conn);
|
||||
checkCmd.Parameters.Add("@idx", SqlDbType.Int).Value = idx;
|
||||
var originalWuid = checkCmd.ExecuteScalar()?.ToString();
|
||||
|
||||
@@ -279,7 +301,7 @@ namespace Project.Web
|
||||
return JsonConvert.SerializeObject(new { Success = false, Message = "삭제 권한이 없습니다." });
|
||||
}
|
||||
|
||||
var cmd = new SqlCommand("DELETE FROM Board WHERE idx = @idx", conn);
|
||||
var cmd = new SqlCommand("DELETE FROM EETGW_Board WHERE idx = @idx", conn);
|
||||
cmd.Parameters.Add("@idx", SqlDbType.Int).Value = idx;
|
||||
|
||||
var affected = cmd.ExecuteNonQuery();
|
||||
@@ -299,5 +321,150 @@ namespace Project.Web
|
||||
return JsonConvert.SerializeObject(new { Success = false, Message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 댓글 목록 조회 (is_comment=true만)
|
||||
/// </summary>
|
||||
public string Board_GetReplies(int rootIdx)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(info.Login.no) || string.IsNullOrEmpty(info.Login.gcode))
|
||||
{
|
||||
return JsonConvert.SerializeObject(new { Success = false, Message = "로그인이 필요합니다." });
|
||||
}
|
||||
|
||||
var connStr = Project.Properties.Settings.Default.CS;
|
||||
using (var conn = new SqlConnection(connStr))
|
||||
{
|
||||
conn.Open();
|
||||
|
||||
var cmd = new SqlCommand(@"
|
||||
SELECT idx, bidx, header, cate, title, contents, [file], guid, url, wuid, wdate, project, pidx, gcode, [close], remark,
|
||||
root_idx, depth, sort_order, thread_path, is_comment, reply_count,
|
||||
dbo.getUserName(wuid) AS wuid_name
|
||||
FROM EETGW_Board WITH (nolock)
|
||||
WHERE root_idx = @rootIdx AND depth > 0 AND is_comment = 1
|
||||
ORDER BY thread_path, wdate", conn);
|
||||
|
||||
cmd.Parameters.Add("@rootIdx", SqlDbType.Int).Value = rootIdx;
|
||||
|
||||
var list = new List<object>();
|
||||
using (var reader = cmd.ExecuteReader())
|
||||
{
|
||||
while (reader.Read())
|
||||
{
|
||||
list.Add(new
|
||||
{
|
||||
idx = reader.GetInt32(0),
|
||||
bidx = reader.GetInt32(1),
|
||||
header = reader.IsDBNull(2) ? "" : (reader.GetBoolean(2) ? "공지" : ""),
|
||||
cate = reader.IsDBNull(3) ? "" : reader.GetString(3),
|
||||
title = reader.IsDBNull(4) ? "" : reader.GetString(4),
|
||||
contents = reader.IsDBNull(5) ? "" : reader.GetString(5),
|
||||
file = reader.IsDBNull(6) ? "" : reader.GetString(6),
|
||||
guid = reader.IsDBNull(7) ? "" : reader.GetString(7),
|
||||
url = reader.IsDBNull(8) ? "" : reader.GetString(8),
|
||||
wuid = reader.IsDBNull(9) ? "" : reader.GetString(9),
|
||||
wdate = reader.IsDBNull(10) ? (DateTime?)null : reader.GetDateTime(10),
|
||||
project = reader.IsDBNull(11) ? "" : reader.GetInt32(11).ToString(),
|
||||
pidx = reader.IsDBNull(12) ? -1 : reader.GetInt32(12),
|
||||
gcode = reader.IsDBNull(13) ? "" : reader.GetString(13),
|
||||
close = reader.IsDBNull(14) ? false : reader.GetBoolean(14),
|
||||
remark = reader.IsDBNull(15) ? "" : reader.GetString(15),
|
||||
root_idx = reader.IsDBNull(16) ? (int?)null : reader.GetInt32(16),
|
||||
depth = reader.IsDBNull(17) ? 0 : reader.GetInt32(17),
|
||||
sort_order = reader.IsDBNull(18) ? 0 : reader.GetInt32(18),
|
||||
thread_path = reader.IsDBNull(19) ? "" : reader.GetString(19),
|
||||
is_comment = reader.IsDBNull(20) ? false : reader.GetBoolean(20),
|
||||
reply_count = reader.IsDBNull(21) ? 0 : reader.GetInt32(21),
|
||||
wuid_name = reader.IsDBNull(22) ? "" : reader.GetString(22)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return JsonConvert.SerializeObject(new { Success = true, Data = list });
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return JsonConvert.SerializeObject(new { Success = false, Message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 댓글/답글 추가
|
||||
/// </summary>
|
||||
public string Board_AddReply(int rootIdx, int pidx, string title, string contents, bool isComment)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(info.Login.no) || string.IsNullOrEmpty(info.Login.gcode))
|
||||
{
|
||||
return JsonConvert.SerializeObject(new { Success = false, Message = "로그인이 필요합니다." });
|
||||
}
|
||||
|
||||
var connStr = Project.Properties.Settings.Default.CS;
|
||||
using (var conn = new SqlConnection(connStr))
|
||||
{
|
||||
conn.Open();
|
||||
|
||||
// 부모 글 정보 조회
|
||||
var parentCmd = new SqlCommand(@"
|
||||
SELECT bidx, gcode, depth, thread_path
|
||||
FROM EETGW_Board
|
||||
WHERE idx = @pidx", conn);
|
||||
parentCmd.Parameters.Add("@pidx", SqlDbType.Int).Value = pidx;
|
||||
|
||||
int bidx = 0;
|
||||
string gcode = "";
|
||||
int parentDepth = 0;
|
||||
string parentThreadPath = "";
|
||||
|
||||
using (var reader = parentCmd.ExecuteReader())
|
||||
{
|
||||
if (reader.Read())
|
||||
{
|
||||
bidx = reader.GetInt32(0);
|
||||
gcode = reader.IsDBNull(1) ? "" : reader.GetString(1);
|
||||
parentDepth = reader.IsDBNull(2) ? 0 : reader.GetInt32(2);
|
||||
parentThreadPath = reader.IsDBNull(3) ? "" : reader.GetString(3);
|
||||
}
|
||||
else
|
||||
{
|
||||
return JsonConvert.SerializeObject(new { Success = false, Message = "부모 글을 찾을 수 없습니다." });
|
||||
}
|
||||
}
|
||||
|
||||
// 댓글/답글 삽입
|
||||
var cmd = new SqlCommand(@"
|
||||
DECLARE @newIdx INT;
|
||||
INSERT INTO EETGW_Board (bidx, cate, title, contents, wuid, wdate, gcode, pidx, root_idx, depth, is_comment)
|
||||
VALUES (@bidx, NULL, @title, @contents, @wuid, GETDATE(), @gcode, @pidx, @rootIdx, @depth, @isComment);
|
||||
SET @newIdx = SCOPE_IDENTITY();
|
||||
UPDATE EETGW_Board SET thread_path = @parentThreadPath + '/' + CAST(@newIdx AS VARCHAR(20)) WHERE idx = @newIdx;
|
||||
SELECT @newIdx;", conn);
|
||||
|
||||
cmd.Parameters.Add("@bidx", SqlDbType.Int).Value = bidx;
|
||||
cmd.Parameters.Add("@title", SqlDbType.NVarChar).Value = string.IsNullOrEmpty(title) ? (object)DBNull.Value : title;
|
||||
cmd.Parameters.Add("@contents", SqlDbType.NVarChar).Value = contents;
|
||||
cmd.Parameters.Add("@wuid", SqlDbType.VarChar).Value = info.Login.no;
|
||||
cmd.Parameters.Add("@gcode", SqlDbType.VarChar).Value = string.IsNullOrEmpty(gcode) ? info.Login.gcode : gcode;
|
||||
cmd.Parameters.Add("@pidx", SqlDbType.Int).Value = pidx;
|
||||
cmd.Parameters.Add("@rootIdx", SqlDbType.Int).Value = rootIdx;
|
||||
cmd.Parameters.Add("@depth", SqlDbType.Int).Value = parentDepth + 1;
|
||||
cmd.Parameters.Add("@isComment", SqlDbType.Bit).Value = isComment;
|
||||
cmd.Parameters.Add("@parentThreadPath", SqlDbType.VarChar).Value = parentThreadPath;
|
||||
|
||||
var newIdx = Convert.ToInt32(cmd.ExecuteScalar());
|
||||
|
||||
return JsonConvert.SerializeObject(new { Success = true, Message = "댓글이 등록되었습니다.", Data = new { idx = newIdx } });
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return JsonConvert.SerializeObject(new { Success = false, Message = ex.Message });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user