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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,8 +227,15 @@ namespace Project.Web
|
||||
FROM Projects WITH (nolock)
|
||||
WHERE gcode = @gcode
|
||||
AND (name LIKE @keyword OR CAST(idx AS VARCHAR) LIKE @keyword)
|
||||
AND status NOT IN ('보류', '취소', '완료(보고)')
|
||||
ORDER BY status DESC, pdate DESC, name";
|
||||
AND status NOT IN ('보류', '취소')
|
||||
ORDER BY
|
||||
CASE
|
||||
WHEN status = '진행' THEN 1
|
||||
WHEN status = '준비' THEN 2
|
||||
WHEN status = '완료(보고)' THEN 3
|
||||
ELSE 4
|
||||
END,
|
||||
pdate DESC, name";
|
||||
|
||||
using (var cn = new SqlConnection(cs))
|
||||
using (var cmd = new SqlCommand(sqlProjects, cn))
|
||||
|
||||
@@ -952,6 +952,28 @@ namespace Project.Web
|
||||
}
|
||||
break;
|
||||
|
||||
case "BOARD_GET_REPLIES":
|
||||
{
|
||||
int rootIdx = json.rootIdx ?? 0;
|
||||
string result = _bridge.Board_GetReplies(rootIdx);
|
||||
var response = new { type = "BOARD_REPLIES_DATA", data = JsonConvert.DeserializeObject(result) };
|
||||
await Send(socket, JsonConvert.SerializeObject(response));
|
||||
}
|
||||
break;
|
||||
|
||||
case "BOARD_ADD_REPLY":
|
||||
{
|
||||
int rootIdx = json.rootIdx ?? 0;
|
||||
int pidx = json.pidx ?? 0;
|
||||
string title = json.title ?? "";
|
||||
string contents = json.contents ?? "";
|
||||
bool isComment = json.isComment ?? false;
|
||||
string result = _bridge.Board_AddReply(rootIdx, pidx, title, contents, isComment);
|
||||
var response = new { type = "BOARD_REPLY_ADDED", data = JsonConvert.DeserializeObject(result) };
|
||||
await Send(socket, JsonConvert.SerializeObject(response));
|
||||
}
|
||||
break;
|
||||
|
||||
// ===== Mail API (메일 발신 내역) =====
|
||||
case "MAIL_GET_LIST":
|
||||
{
|
||||
|
||||
6451
Project/dsMSSQL.Designer.cs
generated
6451
Project/dsMSSQL.Designer.cs
generated
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -4,29 +4,28 @@
|
||||
Changes to this file may cause incorrect behavior and will be lost if
|
||||
the code is regenerated.
|
||||
</autogenerated>-->
|
||||
<DiagramLayout xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ex:showrelationlabel="False" ViewPortX="137" ViewPortY="22" xmlns:ex="urn:schemas-microsoft-com:xml-msdatasource-layout-extended" xmlns="urn:schemas-microsoft-com:xml-msdatasource-layout">
|
||||
<DiagramLayout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" ex:showrelationlabel="False" ViewPortX="137" ViewPortY="32" xmlns:ex="urn:schemas-microsoft-com:xml-msdatasource-layout-extended" xmlns="urn:schemas-microsoft-com:xml-msdatasource-layout">
|
||||
<Shapes>
|
||||
<Shape ID="DesignTable:Users" ZOrder="13" X="997" Y="61" Height="381" Width="300" AdapterExpanded="true" DataTableExpanded="true" OldAdapterHeight="0" OldDataTableHeight="0" SplitterPosition="254" />
|
||||
<Shape ID="DesignTable:Projects" ZOrder="11" X="208" Y="1" Height="286" Width="191" AdapterExpanded="true" DataTableExpanded="true" OldAdapterHeight="0" OldDataTableHeight="0" SplitterPosition="235" />
|
||||
<Shape ID="DesignTable:Items" ZOrder="12" X="74" Y="218" Height="267" Width="177" AdapterExpanded="true" DataTableExpanded="true" OldAdapterHeight="0" OldDataTableHeight="0" SplitterPosition="216" />
|
||||
<Shape ID="DesignTable:Users" ZOrder="12" X="997" Y="61" Height="381" Width="300" AdapterExpanded="true" DataTableExpanded="true" OldAdapterHeight="0" OldDataTableHeight="0" SplitterPosition="254" />
|
||||
<Shape ID="DesignTable:Projects" ZOrder="10" X="208" Y="1" Height="286" Width="191" AdapterExpanded="true" DataTableExpanded="true" OldAdapterHeight="0" OldDataTableHeight="0" SplitterPosition="235" />
|
||||
<Shape ID="DesignTable:Items" ZOrder="11" X="74" Y="218" Height="267" Width="177" AdapterExpanded="true" DataTableExpanded="true" OldAdapterHeight="0" OldDataTableHeight="0" SplitterPosition="216" />
|
||||
<Shape ID="DesignTable:Inventory" ZOrder="2" X="643" Y="66" Height="324" Width="234" AdapterExpanded="true" DataTableExpanded="true" OldAdapterHeight="0" OldDataTableHeight="0" SplitterPosition="254" />
|
||||
<Shape ID="DesignTable:LineCode" ZOrder="3" X="586" Y="429" Height="267" Width="199" AdapterExpanded="true" DataTableExpanded="true" OldAdapterHeight="0" OldDataTableHeight="0" SplitterPosition="216" />
|
||||
<Shape ID="DesignTable:UserGroup" ZOrder="4" X="430" Y="385" Height="229" Width="208" AdapterExpanded="true" DataTableExpanded="true" OldAdapterHeight="0" OldDataTableHeight="0" SplitterPosition="178" />
|
||||
<Shape ID="DesignTable:EETGW_GroupUser" ZOrder="8" X="12" Y="283" Height="324" Width="255" AdapterExpanded="true" DataTableExpanded="true" OldAdapterHeight="0" OldDataTableHeight="0" SplitterPosition="235" />
|
||||
<Shape ID="DesignTable:vGroupUser" ZOrder="14" X="938" Y="-5" Height="343" Width="227" AdapterExpanded="true" DataTableExpanded="true" OldAdapterHeight="0" OldDataTableHeight="0" SplitterPosition="254" />
|
||||
<Shape ID="DesignTable:JobReport" ZOrder="9" X="1243" Y="724" Height="400" Width="292" AdapterExpanded="true" DataTableExpanded="true" OldAdapterHeight="0" OldDataTableHeight="0" SplitterPosition="254" />
|
||||
<Shape ID="DesignTable:MailData" ZOrder="6" X="434" Y="28" Height="381" Width="300" AdapterExpanded="true" DataTableExpanded="true" OldAdapterHeight="0" OldDataTableHeight="0" SplitterPosition="254" />
|
||||
<Shape ID="DesignTable:MailAuto" ZOrder="7" X="799" Y="334" Height="324" Width="208" AdapterExpanded="true" DataTableExpanded="true" OldAdapterHeight="0" OldDataTableHeight="0" SplitterPosition="140" />
|
||||
<Shape ID="DesignTable:BoardFAQ" ZOrder="5" X="403" Y="247" Height="305" Width="204" AdapterExpanded="true" DataTableExpanded="true" OldAdapterHeight="0" OldDataTableHeight="0" SplitterPosition="254" />
|
||||
<Shape ID="DesignTable:EETGW_LoginInfo" ZOrder="15" X="1152" Y="566" Height="210" Width="248" AdapterExpanded="true" DataTableExpanded="true" OldAdapterHeight="0" OldDataTableHeight="0" SplitterPosition="159" />
|
||||
<Shape ID="DesignTable:EETGW_GroupUser" ZOrder="7" X="12" Y="283" Height="324" Width="255" AdapterExpanded="true" DataTableExpanded="true" OldAdapterHeight="0" OldDataTableHeight="0" SplitterPosition="235" />
|
||||
<Shape ID="DesignTable:vGroupUser" ZOrder="13" X="938" Y="-5" Height="343" Width="227" AdapterExpanded="true" DataTableExpanded="true" OldAdapterHeight="0" OldDataTableHeight="0" SplitterPosition="254" />
|
||||
<Shape ID="DesignTable:JobReport" ZOrder="8" X="1243" Y="724" Height="400" Width="292" AdapterExpanded="true" DataTableExpanded="true" OldAdapterHeight="0" OldDataTableHeight="0" SplitterPosition="254" />
|
||||
<Shape ID="DesignTable:MailData" ZOrder="5" X="434" Y="28" Height="381" Width="300" AdapterExpanded="true" DataTableExpanded="true" OldAdapterHeight="0" OldDataTableHeight="0" SplitterPosition="254" />
|
||||
<Shape ID="DesignTable:MailAuto" ZOrder="6" X="799" Y="334" Height="324" Width="208" AdapterExpanded="true" DataTableExpanded="true" OldAdapterHeight="0" OldDataTableHeight="0" SplitterPosition="140" />
|
||||
<Shape ID="DesignTable:EETGW_LoginInfo" ZOrder="14" X="1152" Y="566" Height="210" Width="248" AdapterExpanded="true" DataTableExpanded="true" OldAdapterHeight="0" OldDataTableHeight="0" SplitterPosition="159" />
|
||||
<Shape ID="DesignTable:EETGW_JobReport_AutoInput" ZOrder="1" X="1184" Y="520" Height="324" Width="300" AdapterExpanded="true" DataTableExpanded="true" OldAdapterHeight="0" OldDataTableHeight="0" SplitterPosition="254" />
|
||||
<Shape ID="DesignTable:Purchase" ZOrder="16" X="1327" Y="944" Height="305" Width="197" AdapterExpanded="true" DataTableExpanded="true" OldAdapterHeight="0" OldDataTableHeight="0" SplitterPosition="254" />
|
||||
<Shape ID="DesignTable:vPurchase" ZOrder="21" X="1237" Y="1036" Height="324" Width="204" AdapterExpanded="true" DataTableExpanded="true" OldAdapterHeight="0" OldDataTableHeight="0" SplitterPosition="254" />
|
||||
<Shape ID="DesignTable:HolidayLIst" ZOrder="20" X="1218" Y="991" Height="191" Width="210" AdapterExpanded="true" DataTableExpanded="true" OldAdapterHeight="0" OldDataTableHeight="0" SplitterPosition="140" />
|
||||
<Shape ID="DesignTable:vFindSID" ZOrder="19" X="1538" Y="959" Height="305" Width="196" AdapterExpanded="true" DataTableExpanded="true" OldAdapterHeight="0" OldDataTableHeight="0" SplitterPosition="254" />
|
||||
<Shape ID="DesignTable:vJobReportForUser" ZOrder="18" X="1531" Y="972" Height="343" Width="300" AdapterExpanded="true" DataTableExpanded="true" OldAdapterHeight="0" OldDataTableHeight="0" SplitterPosition="254" />
|
||||
<Shape ID="DesignTable:Customs" ZOrder="17" X="1362" Y="291" Height="305" Width="195" AdapterExpanded="true" DataTableExpanded="true" OldAdapterHeight="0" OldDataTableHeight="0" SplitterPosition="254" />
|
||||
<Shape ID="DesignSources:QueriesTableAdapter" ZOrder="10" X="673" Y="84" Height="68" Width="218" AdapterExpanded="true" DataTableExpanded="true" OldAdapterHeight="0" OldDataTableHeight="0" SplitterPosition="64" />
|
||||
<Shape ID="DesignTable:Purchase" ZOrder="15" X="1327" Y="944" Height="305" Width="197" AdapterExpanded="true" DataTableExpanded="true" OldAdapterHeight="0" OldDataTableHeight="0" SplitterPosition="254" />
|
||||
<Shape ID="DesignTable:vPurchase" ZOrder="20" X="1237" Y="1036" Height="324" Width="204" AdapterExpanded="true" DataTableExpanded="true" OldAdapterHeight="0" OldDataTableHeight="0" SplitterPosition="254" />
|
||||
<Shape ID="DesignTable:HolidayLIst" ZOrder="19" X="1218" Y="991" Height="191" Width="210" AdapterExpanded="true" DataTableExpanded="true" OldAdapterHeight="0" OldDataTableHeight="0" SplitterPosition="140" />
|
||||
<Shape ID="DesignTable:vFindSID" ZOrder="18" X="1538" Y="959" Height="305" Width="196" AdapterExpanded="true" DataTableExpanded="true" OldAdapterHeight="0" OldDataTableHeight="0" SplitterPosition="254" />
|
||||
<Shape ID="DesignTable:vJobReportForUser" ZOrder="17" X="1531" Y="972" Height="343" Width="300" AdapterExpanded="true" DataTableExpanded="true" OldAdapterHeight="0" OldDataTableHeight="0" SplitterPosition="254" />
|
||||
<Shape ID="DesignTable:Customs" ZOrder="16" X="1362" Y="291" Height="305" Width="195" AdapterExpanded="true" DataTableExpanded="true" OldAdapterHeight="0" OldDataTableHeight="0" SplitterPosition="254" />
|
||||
<Shape ID="DesignSources:QueriesTableAdapter" ZOrder="9" X="673" Y="84" Height="68" Width="218" AdapterExpanded="true" DataTableExpanded="true" OldAdapterHeight="0" OldDataTableHeight="0" SplitterPosition="64" />
|
||||
</Shapes>
|
||||
<Connectors />
|
||||
</DiagramLayout>
|
||||
1438
Project/dsProjects.Designer.cs
generated
1438
Project/dsProjects.Designer.cs
generated
File diff suppressed because it is too large
Load Diff
124
Project/fMain.Designer.cs
generated
124
Project/fMain.Designer.cs
generated
@@ -78,7 +78,6 @@
|
||||
this.업무분류및형태설정ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.자동입력ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.toolStripMenuItem2 = new System.Windows.Forms.ToolStripSeparator();
|
||||
this.업무현황전자실ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.교육목록ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.비용절감ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.라이선스ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
@@ -105,16 +104,7 @@
|
||||
this.personalInventoryToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.mn_docu = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.메모장ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.toolStripMenuItem4 = new System.Windows.Forms.ToolStripSeparator();
|
||||
this.패치내역ToolStripMenuItem1 = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.메일내역ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.toolStripMenuItem3 = new System.Windows.Forms.ToolStripSeparator();
|
||||
this.minutesToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.requestITemToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.freeBoardToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.bugReportToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.todoListToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.메일전송ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.기타ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.품목검색ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.대쉬보드ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
@@ -315,7 +305,7 @@
|
||||
//
|
||||
this.userAccountToolStripMenuItem.ForeColor = System.Drawing.Color.Blue;
|
||||
this.userAccountToolStripMenuItem.Name = "userAccountToolStripMenuItem";
|
||||
this.userAccountToolStripMenuItem.Size = new System.Drawing.Size(152, 24);
|
||||
this.userAccountToolStripMenuItem.Size = new System.Drawing.Size(134, 24);
|
||||
this.userAccountToolStripMenuItem.Text = "계정정보";
|
||||
this.userAccountToolStripMenuItem.Click += new System.EventHandler(this.userAccountToolStripMenuItem_Click);
|
||||
//
|
||||
@@ -323,7 +313,7 @@
|
||||
//
|
||||
this.myAccouserToolStripMenuItem.ForeColor = System.Drawing.Color.Blue;
|
||||
this.myAccouserToolStripMenuItem.Name = "myAccouserToolStripMenuItem";
|
||||
this.myAccouserToolStripMenuItem.Size = new System.Drawing.Size(152, 24);
|
||||
this.myAccouserToolStripMenuItem.Size = new System.Drawing.Size(134, 24);
|
||||
this.myAccouserToolStripMenuItem.Text = "계정목록";
|
||||
this.myAccouserToolStripMenuItem.Click += new System.EventHandler(this.myAccouserToolStripMenuItem_Click);
|
||||
//
|
||||
@@ -331,7 +321,7 @@
|
||||
//
|
||||
this.권한설정ToolStripMenuItem.ForeColor = System.Drawing.Color.Blue;
|
||||
this.권한설정ToolStripMenuItem.Name = "권한설정ToolStripMenuItem";
|
||||
this.권한설정ToolStripMenuItem.Size = new System.Drawing.Size(152, 24);
|
||||
this.권한설정ToolStripMenuItem.Size = new System.Drawing.Size(134, 24);
|
||||
this.권한설정ToolStripMenuItem.Text = "권한설정";
|
||||
this.권한설정ToolStripMenuItem.Click += new System.EventHandler(this.권한설정ToolStripMenuItem_Click);
|
||||
//
|
||||
@@ -339,13 +329,13 @@
|
||||
//
|
||||
this.toolStripMenuItem12.ForeColor = System.Drawing.Color.Blue;
|
||||
this.toolStripMenuItem12.Name = "toolStripMenuItem12";
|
||||
this.toolStripMenuItem12.Size = new System.Drawing.Size(149, 6);
|
||||
this.toolStripMenuItem12.Size = new System.Drawing.Size(131, 6);
|
||||
//
|
||||
// 그룹정보ToolStripMenuItem
|
||||
//
|
||||
this.그룹정보ToolStripMenuItem.ForeColor = System.Drawing.Color.Blue;
|
||||
this.그룹정보ToolStripMenuItem.Name = "그룹정보ToolStripMenuItem";
|
||||
this.그룹정보ToolStripMenuItem.Size = new System.Drawing.Size(152, 24);
|
||||
this.그룹정보ToolStripMenuItem.Size = new System.Drawing.Size(134, 24);
|
||||
this.그룹정보ToolStripMenuItem.Text = "그룹정보";
|
||||
this.그룹정보ToolStripMenuItem.Click += new System.EventHandler(this.그룹정보ToolStripMenuItem_Click);
|
||||
//
|
||||
@@ -378,7 +368,6 @@
|
||||
this.mn_purchase,
|
||||
this.mn_project,
|
||||
this.mn_dailyhistory,
|
||||
this.업무현황전자실ToolStripMenuItem,
|
||||
this.교육목록ToolStripMenuItem,
|
||||
this.비용절감ToolStripMenuItem,
|
||||
this.라이선스ToolStripMenuItem,
|
||||
@@ -559,15 +548,6 @@
|
||||
this.toolStripMenuItem2.Name = "toolStripMenuItem2";
|
||||
this.toolStripMenuItem2.Size = new System.Drawing.Size(231, 6);
|
||||
//
|
||||
// 업무현황전자실ToolStripMenuItem
|
||||
//
|
||||
this.업무현황전자실ToolStripMenuItem.ForeColor = System.Drawing.Color.Red;
|
||||
this.업무현황전자실ToolStripMenuItem.Name = "업무현황전자실ToolStripMenuItem";
|
||||
this.업무현황전자실ToolStripMenuItem.Size = new System.Drawing.Size(203, 24);
|
||||
this.업무현황전자실ToolStripMenuItem.Text = "업무현황(전자실)";
|
||||
this.업무현황전자실ToolStripMenuItem.Visible = false;
|
||||
this.업무현황전자실ToolStripMenuItem.Click += new System.EventHandler(this.업무현황전자실ToolStripMenuItem_Click);
|
||||
//
|
||||
// 교육목록ToolStripMenuItem
|
||||
//
|
||||
this.교육목록ToolStripMenuItem.Name = "교육목록ToolStripMenuItem";
|
||||
@@ -764,16 +744,7 @@
|
||||
//
|
||||
this.mn_docu.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
|
||||
this.메모장ToolStripMenuItem,
|
||||
this.toolStripMenuItem4,
|
||||
this.패치내역ToolStripMenuItem1,
|
||||
this.메일내역ToolStripMenuItem,
|
||||
this.toolStripMenuItem3,
|
||||
this.minutesToolStripMenuItem,
|
||||
this.requestITemToolStripMenuItem,
|
||||
this.freeBoardToolStripMenuItem,
|
||||
this.bugReportToolStripMenuItem,
|
||||
this.todoListToolStripMenuItem,
|
||||
this.메일전송ToolStripMenuItem});
|
||||
this.메일내역ToolStripMenuItem});
|
||||
this.mn_docu.Image = ((System.Drawing.Image)(resources.GetObject("mn_docu.Image")));
|
||||
this.mn_docu.Name = "mn_docu";
|
||||
this.mn_docu.Size = new System.Drawing.Size(65, 23);
|
||||
@@ -782,86 +753,17 @@
|
||||
// 메모장ToolStripMenuItem
|
||||
//
|
||||
this.메모장ToolStripMenuItem.Name = "메모장ToolStripMenuItem";
|
||||
this.메모장ToolStripMenuItem.Size = new System.Drawing.Size(149, 24);
|
||||
this.메모장ToolStripMenuItem.Size = new System.Drawing.Size(152, 24);
|
||||
this.메모장ToolStripMenuItem.Text = "메모장";
|
||||
this.메모장ToolStripMenuItem.Click += new System.EventHandler(this.메모장ToolStripMenuItem_Click);
|
||||
//
|
||||
// toolStripMenuItem4
|
||||
//
|
||||
this.toolStripMenuItem4.Name = "toolStripMenuItem4";
|
||||
this.toolStripMenuItem4.Size = new System.Drawing.Size(146, 6);
|
||||
//
|
||||
// 패치내역ToolStripMenuItem1
|
||||
//
|
||||
this.패치내역ToolStripMenuItem1.Name = "패치내역ToolStripMenuItem1";
|
||||
this.패치내역ToolStripMenuItem1.Size = new System.Drawing.Size(149, 24);
|
||||
this.패치내역ToolStripMenuItem1.Text = "패치 내역";
|
||||
this.패치내역ToolStripMenuItem1.Click += new System.EventHandler(this.패치내역ToolStripMenuItem1_Click);
|
||||
//
|
||||
// 메일내역ToolStripMenuItem
|
||||
//
|
||||
this.메일내역ToolStripMenuItem.Name = "메일내역ToolStripMenuItem";
|
||||
this.메일내역ToolStripMenuItem.Size = new System.Drawing.Size(149, 24);
|
||||
this.메일내역ToolStripMenuItem.Size = new System.Drawing.Size(152, 24);
|
||||
this.메일내역ToolStripMenuItem.Text = "메일 내역";
|
||||
this.메일내역ToolStripMenuItem.Click += new System.EventHandler(this.메일내역ToolStripMenuItem_Click);
|
||||
//
|
||||
// toolStripMenuItem3
|
||||
//
|
||||
this.toolStripMenuItem3.Name = "toolStripMenuItem3";
|
||||
this.toolStripMenuItem3.Size = new System.Drawing.Size(146, 6);
|
||||
//
|
||||
// minutesToolStripMenuItem
|
||||
//
|
||||
this.minutesToolStripMenuItem.ForeColor = System.Drawing.Color.HotPink;
|
||||
this.minutesToolStripMenuItem.Name = "minutesToolStripMenuItem";
|
||||
this.minutesToolStripMenuItem.Size = new System.Drawing.Size(149, 24);
|
||||
this.minutesToolStripMenuItem.Text = "회의록";
|
||||
this.minutesToolStripMenuItem.Visible = false;
|
||||
this.minutesToolStripMenuItem.Click += new System.EventHandler(this.minutesToolStripMenuItem_Click);
|
||||
//
|
||||
// requestITemToolStripMenuItem
|
||||
//
|
||||
this.requestITemToolStripMenuItem.ForeColor = System.Drawing.Color.HotPink;
|
||||
this.requestITemToolStripMenuItem.Name = "requestITemToolStripMenuItem";
|
||||
this.requestITemToolStripMenuItem.Size = new System.Drawing.Size(149, 24);
|
||||
this.requestITemToolStripMenuItem.Text = "견적요청";
|
||||
this.requestITemToolStripMenuItem.Visible = false;
|
||||
this.requestITemToolStripMenuItem.Click += new System.EventHandler(this.requestITemToolStripMenuItem_Click);
|
||||
//
|
||||
// freeBoardToolStripMenuItem
|
||||
//
|
||||
this.freeBoardToolStripMenuItem.Enabled = false;
|
||||
this.freeBoardToolStripMenuItem.Name = "freeBoardToolStripMenuItem";
|
||||
this.freeBoardToolStripMenuItem.Size = new System.Drawing.Size(149, 24);
|
||||
this.freeBoardToolStripMenuItem.Text = "Free Board";
|
||||
this.freeBoardToolStripMenuItem.Visible = false;
|
||||
//
|
||||
// bugReportToolStripMenuItem
|
||||
//
|
||||
this.bugReportToolStripMenuItem.Enabled = false;
|
||||
this.bugReportToolStripMenuItem.Name = "bugReportToolStripMenuItem";
|
||||
this.bugReportToolStripMenuItem.Size = new System.Drawing.Size(149, 24);
|
||||
this.bugReportToolStripMenuItem.Text = "Bug Report";
|
||||
this.bugReportToolStripMenuItem.Visible = false;
|
||||
//
|
||||
// todoListToolStripMenuItem
|
||||
//
|
||||
this.todoListToolStripMenuItem.Enabled = false;
|
||||
this.todoListToolStripMenuItem.Name = "todoListToolStripMenuItem";
|
||||
this.todoListToolStripMenuItem.Size = new System.Drawing.Size(149, 24);
|
||||
this.todoListToolStripMenuItem.Text = "Todo List";
|
||||
this.todoListToolStripMenuItem.Visible = false;
|
||||
this.todoListToolStripMenuItem.Click += new System.EventHandler(this.todoListToolStripMenuItem_Click);
|
||||
//
|
||||
// 메일전송ToolStripMenuItem
|
||||
//
|
||||
this.메일전송ToolStripMenuItem.ForeColor = System.Drawing.Color.Red;
|
||||
this.메일전송ToolStripMenuItem.Name = "메일전송ToolStripMenuItem";
|
||||
this.메일전송ToolStripMenuItem.Size = new System.Drawing.Size(149, 24);
|
||||
this.메일전송ToolStripMenuItem.Text = "메일전송";
|
||||
this.메일전송ToolStripMenuItem.Visible = false;
|
||||
this.메일전송ToolStripMenuItem.Click += new System.EventHandler(this.메일전송ToolStripMenuItem_Click);
|
||||
//
|
||||
// 기타ToolStripMenuItem
|
||||
//
|
||||
this.기타ToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
|
||||
@@ -1245,14 +1147,9 @@
|
||||
private System.Windows.Forms.ToolStripStatusLabel sbLogin;
|
||||
private System.Windows.Forms.ToolStripMenuItem codesToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem mn_docu;
|
||||
private System.Windows.Forms.ToolStripMenuItem freeBoardToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem bugReportToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem todoListToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem managementToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem personalInventoryToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem userInfoToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem minutesToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem requestITemToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem mn_purchase;
|
||||
private System.Windows.Forms.ToolStripMenuItem btDev;
|
||||
private System.Windows.Forms.ToolStripMenuItem purchaseImportToolStripMenuItem;
|
||||
@@ -1267,9 +1164,7 @@
|
||||
private System.Windows.Forms.ToolStripMenuItem mn_project;
|
||||
private System.Windows.Forms.ToolStripMenuItem projectImportCompleteToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem purchaseOrderImportToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem 메일전송ToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem mn_dailyhistory;
|
||||
private System.Windows.Forms.ToolStripMenuItem 패치내역ToolStripMenuItem1;
|
||||
private System.Windows.Forms.ToolStripMenuItem workReportImportToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem mn_kuntae;
|
||||
private System.Windows.Forms.ToolStripMenuItem 즐겨찾기ToolStripMenuItem;
|
||||
@@ -1284,10 +1179,8 @@
|
||||
private System.Windows.Forms.ToolStripMenuItem 임의테이블조작ToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem 메일양식ToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem 메일내역ToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripSeparator toolStripMenuItem3;
|
||||
private System.Windows.Forms.ToolStripMenuItem mailBackupToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem 메모장ToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripSeparator toolStripMenuItem4;
|
||||
private System.Windows.Forms.ToolStrip toolStrip1;
|
||||
private System.Windows.Forms.ToolStripMenuItem toolStripMenuItem6;
|
||||
private System.Windows.Forms.ToolStripMenuItem toolStripMenuItem7;
|
||||
@@ -1302,7 +1195,6 @@
|
||||
private System.Windows.Forms.ToolStripButton toolStripButton2;
|
||||
private System.Windows.Forms.ToolStripMenuItem 목록ToolStripMenuItem1;
|
||||
private System.Windows.Forms.ToolStripMenuItem 자동입력ToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem 업무현황전자실ToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem 기타ToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem 품목검색ToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem layoutToolStripMenuItem;
|
||||
|
||||
@@ -158,14 +158,14 @@ namespace Project
|
||||
|
||||
Console.WriteLine($"WebView2 초기화 상태: {Pub.InitWebView}");
|
||||
|
||||
|
||||
|
||||
|
||||
// WebView2 로그인이 아닌 경우에만 여기서 후처리 실행
|
||||
// WebView2 로그인의 경우 OnLoginCompleted()에서 호출됨
|
||||
//if (Pub.InitWebView != 1)
|
||||
//{
|
||||
Menu_Dashboard();
|
||||
//OnLoginCompleted();
|
||||
Menu_Dashboard();
|
||||
//OnLoginCompleted();
|
||||
//}
|
||||
}
|
||||
|
||||
@@ -459,27 +459,10 @@ namespace Project
|
||||
|
||||
private void codesToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
//if (Pub.InitWebView > 0 && System.Diagnostics.Debugger.IsAttached)
|
||||
//{
|
||||
// var f = new Dialog.fCommon();
|
||||
// f.ShowDialog();
|
||||
//}
|
||||
//else
|
||||
{
|
||||
var f = new FCM0000.fCode();
|
||||
f.ShowDialog();
|
||||
}
|
||||
|
||||
var f = new FCM0000.fCode();
|
||||
f.ShowDialog();
|
||||
}
|
||||
|
||||
|
||||
private void requestITemToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
string formkey = "ITEMREQUEST";
|
||||
if (!ShowForm(formkey))
|
||||
AddForm(formkey, new FCM0000.fRequestItem());
|
||||
|
||||
}
|
||||
void menu_itemin()
|
||||
{
|
||||
string formkey = "ITEMIPKO";
|
||||
@@ -714,11 +697,6 @@ namespace Project
|
||||
|
||||
}
|
||||
|
||||
private void 패치내역ToolStripMenuItem1_Click(object sender, EventArgs e)
|
||||
{
|
||||
FCM0000.fPatchList f = new FCM0000.fPatchList();
|
||||
f.Show();
|
||||
}
|
||||
|
||||
private void workReportImportToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
@@ -1026,15 +1004,6 @@ namespace Project
|
||||
menu_work_reportautoinput();
|
||||
}
|
||||
|
||||
private void 업무현황전자실ToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
//업무현황 전자실
|
||||
Util.MsgE("자스민 이용하세요!!");
|
||||
string formkey = "EBOARD";
|
||||
if (!ShowForm(formkey))
|
||||
AddForm(formkey, new FPJ0000.fEboardList(), "ALL");
|
||||
}
|
||||
|
||||
|
||||
private void 품목검색ToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
|
||||
@@ -141,6 +141,83 @@
|
||||
0IOABBs4KBjggQGBjQM9XoQwIYIHBB4yUpjo8AFLBS9fKjAgYSJCAQEyeJAgIUAEnj4RAgjgQUPPCgwQ
|
||||
9Ey51APJABUIYKAwoADNBBlfRrCwtUOGBT49JEhAwIAFABQaFEDb0cMBAwg0DECbtOGFAoD7GlQomCHD
|
||||
gAA7
|
||||
</value>
|
||||
</data>
|
||||
<data name="commonToolStripMenuItem.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
R0lGODlhEAAQAIQfAP/umP/eN//kV//riOuzSuujJ+usOuu3VuvKgf/0uf/yq+ucF//bJf/pfOvOiO/O
|
||||
fP/52//ZGP/UAP/haf/ma//hR//ncevDceu8YP39/OuuQP/rp//pdP/nZf///////yH/C05FVFNDQVBF
|
||||
Mi4wAwEBAAAh+QQBAAAfACwAAAAAEAAQAAAIkAA/CByYwUOGgQgTfvDgwINChQw9IHD4UGBEABAuUETo
|
||||
oaNEDxg1erTo4IGDiQAGNFCA4QABAg4jdkxpoYOABAkUGKD4UaUFARUCMLCwk6NGCkADRKBQNKEHDB6E
|
||||
RpDQlCNUoRYkSJhQYKPFAyqZThjbleMBDxw07CxQYMKGBRs9vNTQcaGHBXjjjuS4t2LFgAA7
|
||||
</value>
|
||||
</data>
|
||||
<data name="managementToolStripMenuItem.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
R0lGODlhEAAQAIQQAP/cpv/GcNvb292RIP+kG/+5T6m5rMStgK13Jv+wOt2TJKmwrP/zcZOru////wAA
|
||||
AP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBF
|
||||
Mi4wAwEBAAAh+QQBAAAQACwAAAAAEAAQAAAIZQAhCBxIsKBABgQBGCTIoCEEBgEWDmRAgMAAAwUgKJQ4
|
||||
QIEDBwciSmw4QMDHBRIBBCiQAIGABw8aSBSosgHMmCkH2oQpc6bAnTh9QgDa0ydRoUMffCw6s4FJB0wl
|
||||
OoWKdGiDBgEBADs=
|
||||
</value>
|
||||
</data>
|
||||
<data name="mn_docu.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
R0lGODlhEAAQAIQfAJrM+3OSsKfS++zx9uXt9Yis1FdwkZW51ElVa8fj/bba/NXb5PL2+o276b3d/VJh
|
||||
e7TR4ENLXNXn8KLD536kwIyzzJ/E2KjL3t7n7ykxQz5FVa/W/OLp8I+w1P///////yH/C05FVFNDQVBF
|
||||
Mi4wAwEBAAAh+QQBAAAfACwAAAAAEAAQAAAItgA9CBxIcOCHgx4kQIBwwcKBChQiBgjgASEECQQGZNRI
|
||||
oGMDAxU9QCCQoGRJBygZNPgo8AKDBB1iyuzAoOYDgRZeonSgoGfPDQxuejjwcsLMAkgFBBVY4aVPBRui
|
||||
ClCKQCCFojGRIm0ggEBVDxQG8IQqdSoAAhGsijWqdSsADmk9BBBLdqoAAHgxaBAYgAHPsnjxDtjrwcAC
|
||||
Bhw5KOaAYQCEDHwNSH7wAAGCCBE0aMggtKBnggEBADs=
|
||||
</value>
|
||||
</data>
|
||||
<data name="기타ToolStripMenuItem.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
R0lGODlhEAAQAIQfAKnrVsfvlYnVOHy8KnfJLJneRWqyJLvth1W7GzGTEVWnHT2aFIPNMkuiGmrKJGKt
|
||||
Io7eOXa5KU64GDiWE1yqIHK3KHa5J0WeGGC/IG+1JW/FKGjCJHnRLWrOIP///////yH/C05FVFNDQVBF
|
||||
Mi4wAwEBAAAh+QQBAAAfACwAAAAAEAAQAAAIqwA/CBTooeDAgwg9DFjoAWHCAQEAFBjQ0CHBAQAEFLBQ
|
||||
8WDBjxEARPjY8YPChREiZshg4EFJhQFiFmBQAAIECh8vLoTAgAEBAgIUeGhwoaHCghUE/NTAgcCFAxAW
|
||||
GDWZQamGDRgwIEAgYUJBjh4MCODAAYODBRMkSEhQMAJLARQUNODgwGsCtiYjHBDgsuCCDl4NmqxQAK7R
|
||||
v3gHemD5QK4HtBMSEyRplOTBgAA7
|
||||
</value>
|
||||
</data>
|
||||
<data name="즐겨찾기ToolStripMenuItem.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
R0lGODlhEAAQAIMPAEWi6azap1WzS9LusYrSbApexXLHV+z41vH559Ltw8ns+pe75hBs0iCZEP//////
|
||||
/yH/C05FVFNDQVBFMi4wAwEBAAAh+QQBAAAPACwAAAAAEAAQAAAIiwAfCBxIsKDBBw4SOjg4MGGDBgwY
|
||||
JDzo4OEBBAgUMGiwkGBFBAcODAAAYMEAjh4ZIBgwQAAAAgZOdkTIQEGCAQRICoAZACVNBQACkHxpQEDP
|
||||
jg5qLhgKQIDTowIrJoA5NGKDABIbNpjqlEGBAguyag3QEiLYsDOjPgwQYEFYsQUdRpSY1qDCugzzBgQA
|
||||
Ow==
|
||||
</value>
|
||||
</data>
|
||||
<data name="btDev.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
R0lGODlhEAAQAIQfAKxoR8VkLxFw1feVPITSWv+eQv7Qo0Cc6OyIN/v7+3PLTSCZEFy17Wa6XuT1x2bG
|
||||
Q3nNUU6vRXPAa9mLXMTkwJZEHJt7LL5aJ/z8/O2KONx3L/ubP/r6+rtVI////////yH/C05FVFNDQVBF
|
||||
Mi4wAwEBAAAh+QQBAAAfACwAAAAAEAAQAAAIpgA/CBxIsOBADBgEeljoweAHDB06eIi4QICAhRwOdjAQ
|
||||
kaMDCgwELOiQ8WGHAQYMFIjIgMEBCBEzQkSwoUCBCR0OSGigIKZJDQM2cKxwoAGBBx0ykISoIcOGiAcO
|
||||
EFCAVCkHphueAtgaIYKFpks7NM0qFIDFCxk0kPyQQCzZiB0CbLAqsO1YslnTrq0r9m4GvSUHcoioobDa
|
||||
vQU5DIar2KFgxYEHBgQAOw==
|
||||
</value>
|
||||
</data>
|
||||
<data name="버젼확인ToolStripMenuItem.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
R0lGODlhEAAQAIQfAGm6/idTd4yTmF+v8Xa37KvW+lyh3KHJ62aq41ee2bXZ98nm/2mt5W2Ck5XN/C1c
|
||||
hEZieho8WXXA/2Gn4P39/W+y6V+l3qjP8Njt/lx2izxPYGyv51Oa1EJWZ////////yH/C05FVFNDQVBF
|
||||
Mi4wAwEBAAAh+QQBAAAfACwAAAAAEAAQAAAIqgA/CPzgoaBBDwMTEoQgoGGDBhAQKvSQAcOCBQUcaHwg
|
||||
USBFDARCEqhQgYEEjh47gKxwweAFBAgkREDooYMCAhs8XGCAwMOEmB1o2qywYSdMnxMABCVocwMDngUP
|
||||
GLAAYCZTBTARHPAgdWpVoQV+TrBgoGwCA1+ZOkgwduuBBAk4pCWogUBcDnjxAgjQkS4BAAMCD9jrgcJE
|
||||
DQ8eBAjwYKZhhQQPFoRMuXJAADs=
|
||||
</value>
|
||||
</data>
|
||||
<data name="설명서ToolStripMenuItem.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
R0lGODlhEAAQAIQAAP/99v/qaOvOSem4M+zSSv/ypf/ug//1w//2zP/xnv/te//zrf/0uv/41/nWdufB
|
||||
MP/vkevTVf/rcv/0s//wlv/57OvRM//vi+/OQtaXIuuYEuvTLNyhJ+vHUP///////yH/C05FVFNDQVBF
|
||||
Mi4wAwEBAAAh+QQBAAAfACwAAAAAEAAQAAAIoAA/CBxIsCBBDwgTKizogUCEhwQIYLBgYYOHgR4cKPQA
|
||||
oMEBBAgsfsjoAQGDCQsKJEhAAcKBChYQajyZkiWECwYMAHiAkAAAlCop4FRA9ABPDxgqABVqQIGEpxQG
|
||||
IMTQoCaEphICaFXAAaEABCmZZtUawECGi0gRHGigloFWCgzOYhRAt0OHASg1yD24cUAFDRcNMhwAWLBB
|
||||
D4UNMwz8ISAAOw==
|
||||
</value>
|
||||
</data>
|
||||
<data name="codesToolStripMenuItem.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
@@ -161,15 +238,6 @@
|
||||
gYDHjyAJKNjogQIBChoQZAgQAEGCiQUOKFxAIEMEDhsQPEDAQEKDBzI9dKiZIYOFowwENPg5QeHQlRIi
|
||||
SJAwYIADBwWaegCQIMAACQEEKK2KFYNCrgMihBXbwEHVBGY9GFCQIEGBu3jvKrhw1oBfCBAOHJgwAQOG
|
||||
CyQdKlaIsLHjggEBADs=
|
||||
</value>
|
||||
</data>
|
||||
<data name="commonToolStripMenuItem.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
R0lGODlhEAAQAIQfAP/umP/eN//kV//riOuzSuujJ+usOuu3VuvKgf/0uf/yq+ucF//bJf/pfOvOiO/O
|
||||
fP/52//ZGP/UAP/haf/ma//hR//ncevDceu8YP39/OuuQP/rp//pdP/nZf///////yH/C05FVFNDQVBF
|
||||
Mi4wAwEBAAAh+QQBAAAfACwAAAAAEAAQAAAIkAA/CByYwUOGgQgTfvDgwINChQw9IHD4UGBEABAuUETo
|
||||
oaNEDxg1erTo4IGDiQAGNFCA4QABAg4jdkxpoYOABAkUGKD4UaUFARUCMLCwk6NGCkADRKBQNKEHDB6E
|
||||
RpDQlCNUoRYkSJhQYKPFAyqZThjbleMBDxw07CxQYMKGBRs9vNTQcaGHBXjjjuS4t2LFgAA7
|
||||
</value>
|
||||
</data>
|
||||
<data name="mn_purchase.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
@@ -274,74 +342,6 @@
|
||||
Nq7P2vgxc+6aH3lHuKt0Ou1PJpM2vUR2cdVbXL34iF+O65kFQWDmnQCSJEn+WCxm00tc/2IyS5K0Yb4X
|
||||
QIpGoz5RFG16YM1mc6d5L4AUiUR8oVDIJjPP81vm/wJIgiD4eJ5/8O98rT+Jli/+ECJFiAAAAABJRU5E
|
||||
rkJggg==
|
||||
</value>
|
||||
</data>
|
||||
<data name="managementToolStripMenuItem.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
R0lGODlhEAAQAIQQAP/cpv/GcNvb292RIP+kG/+5T6m5rMStgK13Jv+wOt2TJKmwrP/zcZOru////wAA
|
||||
AP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBF
|
||||
Mi4wAwEBAAAh+QQBAAAQACwAAAAAEAAQAAAIZQAhCBxIsKBABgQBGCTIoCEEBgEWDmRAgMAAAwUgKJQ4
|
||||
QIEDBwciSmw4QMDHBRIBBCiQAIGABw8aSBSosgHMmCkH2oQpc6bAnTh9QgDa0ydRoUMffCw6s4FJB0wl
|
||||
OoWKdGiDBgEBADs=
|
||||
</value>
|
||||
</data>
|
||||
<data name="mn_docu.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
R0lGODlhEAAQAIQfAJrM+3OSsKfS++zx9uXt9Yis1FdwkZW51ElVa8fj/bba/NXb5PL2+o276b3d/VJh
|
||||
e7TR4ENLXNXn8KLD536kwIyzzJ/E2KjL3t7n7ykxQz5FVa/W/OLp8I+w1P///////yH/C05FVFNDQVBF
|
||||
Mi4wAwEBAAAh+QQBAAAfACwAAAAAEAAQAAAItgA9CBxIcOCHgx4kQIBwwcKBChQiBgjgASEECQQGZNRI
|
||||
oGMDAxU9QCCQoGRJBygZNPgo8AKDBB1iyuzAoOYDgRZeonSgoGfPDQxuejjwcsLMAkgFBBVY4aVPBRui
|
||||
ClCKQCCFojGRIm0ggEBVDxQG8IQqdSoAAhGsijWqdSsADmk9BBBLdqoAAHgxaBAYgAHPsnjxDtjrwcAC
|
||||
Bhw5KOaAYQCEDHwNSH7wAAGCCBE0aMggtKBnggEBADs=
|
||||
</value>
|
||||
</data>
|
||||
<data name="기타ToolStripMenuItem.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
R0lGODlhEAAQAIQfAKnrVsfvlYnVOHy8KnfJLJneRWqyJLvth1W7GzGTEVWnHT2aFIPNMkuiGmrKJGKt
|
||||
Io7eOXa5KU64GDiWE1yqIHK3KHa5J0WeGGC/IG+1JW/FKGjCJHnRLWrOIP///////yH/C05FVFNDQVBF
|
||||
Mi4wAwEBAAAh+QQBAAAfACwAAAAAEAAQAAAIqwA/CBTooeDAgwg9DFjoAWHCAQEAFBjQ0CHBAQAEFLBQ
|
||||
8WDBjxEARPjY8YPChREiZshg4EFJhQFiFmBQAAIECh8vLoTAgAEBAgIUeGhwoaHCghUE/NTAgcCFAxAW
|
||||
GDWZQamGDRgwIEAgYUJBjh4MCODAAYODBRMkSEhQMAJLARQUNODgwGsCtiYjHBDgsuCCDl4NmqxQAK7R
|
||||
v3gHemD5QK4HtBMSEyRplOTBgAA7
|
||||
</value>
|
||||
</data>
|
||||
<data name="즐겨찾기ToolStripMenuItem.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
R0lGODlhEAAQAIMPAEWi6azap1WzS9LusYrSbApexXLHV+z41vH559Ltw8ns+pe75hBs0iCZEP//////
|
||||
/yH/C05FVFNDQVBFMi4wAwEBAAAh+QQBAAAPACwAAAAAEAAQAAAIiwAfCBxIsKDBBw4SOjg4MGGDBgwY
|
||||
JDzo4OEBBAgUMGiwkGBFBAcODAAAYMEAjh4ZIBgwQAAAAgZOdkTIQEGCAQRICoAZACVNBQACkHxpQEDP
|
||||
jg5qLhgKQIDTowIrJoA5NGKDABIbNpjqlEGBAguyag3QEiLYsDOjPgwQYEFYsQUdRpSY1qDCugzzBgQA
|
||||
Ow==
|
||||
</value>
|
||||
</data>
|
||||
<data name="btDev.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
R0lGODlhEAAQAIQfAKxoR8VkLxFw1feVPITSWv+eQv7Qo0Cc6OyIN/v7+3PLTSCZEFy17Wa6XuT1x2bG
|
||||
Q3nNUU6vRXPAa9mLXMTkwJZEHJt7LL5aJ/z8/O2KONx3L/ubP/r6+rtVI////////yH/C05FVFNDQVBF
|
||||
Mi4wAwEBAAAh+QQBAAAfACwAAAAAEAAQAAAIpgA/CBxIsOBADBgEeljoweAHDB06eIi4QICAhRwOdjAQ
|
||||
kaMDCgwELOiQ8WGHAQYMFIjIgMEBCBEzQkSwoUCBCR0OSGigIKZJDQM2cKxwoAGBBx0ykISoIcOGiAcO
|
||||
EFCAVCkHphueAtgaIYKFpks7NM0qFIDFCxk0kPyQQCzZiB0CbLAqsO1YslnTrq0r9m4GvSUHcoioobDa
|
||||
vQU5DIar2KFgxYEHBgQAOw==
|
||||
</value>
|
||||
</data>
|
||||
<data name="버젼확인ToolStripMenuItem.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
R0lGODlhEAAQAIQfAGm6/idTd4yTmF+v8Xa37KvW+lyh3KHJ62aq41ee2bXZ98nm/2mt5W2Ck5XN/C1c
|
||||
hEZieho8WXXA/2Gn4P39/W+y6V+l3qjP8Njt/lx2izxPYGyv51Oa1EJWZ////////yH/C05FVFNDQVBF
|
||||
Mi4wAwEBAAAh+QQBAAAfACwAAAAAEAAQAAAIqgA/CPzgoaBBDwMTEoQgoGGDBhAQKvSQAcOCBQUcaHwg
|
||||
USBFDARCEqhQgYEEjh47gKxwweAFBAgkREDooYMCAhs8XGCAwMOEmB1o2qywYSdMnxMABCVocwMDngUP
|
||||
GLAAYCZTBTARHPAgdWpVoQV+TrBgoGwCA1+ZOkgwduuBBAk4pCWogUBcDnjxAgjQkS4BAAMCD9jrgcJE
|
||||
DQ8eBAjwYKZhhQQPFoRMuXJAADs=
|
||||
</value>
|
||||
</data>
|
||||
<data name="설명서ToolStripMenuItem.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
R0lGODlhEAAQAIQAAP/99v/qaOvOSem4M+zSSv/ypf/ug//1w//2zP/xnv/te//zrf/0uv/41/nWdufB
|
||||
MP/vkevTVf/rcv/0s//wlv/57OvRM//vi+/OQtaXIuuYEuvTLNyhJ+vHUP///////yH/C05FVFNDQVBF
|
||||
Mi4wAwEBAAAh+QQBAAAfACwAAAAAEAAQAAAIoAA/CBxIsCBBDwgTKizogUCEhwQIYLBgYYOHgR4cKPQA
|
||||
oMEBBAgsfsjoAQGDCQsKJEhAAcKBChYQajyZkiWECwYMAHiAkAAAlCop4FRA9ABPDxgqABVqQIGEpxQG
|
||||
IMTQoCaEphICaFXAAaEABCmZZtUawECGi0gRHGigloFWCgzOYhRAt0OHASg1yD24cUAFDRcNMhwAWLBB
|
||||
D4UNMwz8ISAAOw==
|
||||
</value>
|
||||
</data>
|
||||
<metadata name="toolStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
@@ -445,14 +445,14 @@
|
||||
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
|
||||
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAIHSURBVDhPY2CAgZnGrAxLtTMZluosYFim08KwWFcdLL5K
|
||||
i4dhmVYhwzLteQzLdFoZlqlLwfWggKU6qxmW6fyH48U6PxiWaHoyLNO8jiK+VOsJwyotCTTNWjYgSYG1
|
||||
lv/9Tub8T7lc/z/3ZMX/eXNiP9S05P9LvdzwP/Jc2X/VbV5QQ7T7UA1YpjOBf435seQr9d+Kb/X8L7nZ
|
||||
lv/9Tub8T7lc/z/3ZMX/eXNiP9Q05f9LvdzwP/Jc2X/VbV5QQ7T7UA1YpjOBf435seQr9d+Kb/X8L7nZ
|
||||
8//QzJz/jzrT/l+dlPkTJAbDJrtDTzAs0bmGasBSXcHiW911MEUdh+r/Xy+N+L+/1f7v9UKvzz3bCuEG
|
||||
FN/qugBSj2oAAwND8c3uuTBFlZc6lp0vDDl4Odnt28Ugtf/bKpz2Fl/s+AiSK7jZ8w5dLxjAXFB0s6cE
|
||||
xH/RGiH+uMzz/5UMrT9Psm3UCm/26hff6vpcdKvnPLpeMCi+02NcfKPrOIz/uNjT81Gpx38wLvHyAKu5
|
||||
2V1afKu7BUUjMii+0y0GYz+p8MqCGfCk0jsTJFZ/v54j99ZEPhRNuMCjCs++J+Ve/0H4cblXL7o8QfC4
|
||||
zHMj3AulHhvQ5fGC//vrOV50RZ1+0xP3H4RfdkaeAomhq0MB/w70a/490Nv3/0DvmfsHen//P9D7HxlD
|
||||
xc6A1ezv0UBo3DaR/e+B3vknOuv/oWvChUFq/x7omQfSy/B3f28vugJiMUgvw78DfbZ/D/Qs/3+gdxUp
|
||||
GKQHpBcAJ5WqUADLneEAAAAASUVORK5CYII=
|
||||
xc6A1ezv0UBo3DaR/e+B3vnH2uv/oWvChUFq/x7omQfSy/B3f28vugJiMUgvw78DfbZ/D/Qs/3+gdxUp
|
||||
GKQHpBcAG6GqRqSz0QwAAAAASUVORK5CYII=
|
||||
</value>
|
||||
</data>
|
||||
<metadata name="$this.TrayHeight" type="System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
|
||||
@@ -3,6 +3,7 @@ import { HashRouter, Routes, Route } from 'react-router-dom';
|
||||
import { Layout } from '@/components/layout';
|
||||
import { Dashboard, Todo, Kuntae, Jobreport, Project, Login, CommonCodePage, ItemsPage, UserListPage, MonthlyWorkPage, MailFormPage, UserAuthPage, Note } from '@/pages';
|
||||
import { PatchList } from '@/pages/PatchList';
|
||||
import { BugReport } from '@/pages/BugReport';
|
||||
import { MailList } from '@/pages/MailList';
|
||||
import { Customs } from '@/pages/Customs';
|
||||
import { comms } from '@/communication';
|
||||
@@ -98,6 +99,7 @@ export default function App() {
|
||||
<Route path="/mail-form" element={<MailFormPage />} />
|
||||
<Route path="/note" element={<Note />} />
|
||||
<Route path="/patch-list" element={<PatchList />} />
|
||||
<Route path="/bug-report" element={<BugReport />} />
|
||||
<Route path="/mail-list" element={<MailList />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
|
||||
@@ -1314,6 +1314,37 @@ class CommunicationLayer {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 게시판 댓글/답글 목록 조회
|
||||
* @param rootIdx 원글 인덱스
|
||||
* @returns ApiResponse<BoardItem[]>
|
||||
*/
|
||||
public async getBoardReplies(rootIdx: number): Promise<ApiResponse<BoardItem[]>> {
|
||||
if (isWebView && machine) {
|
||||
const result = await machine.Board_GetReplies(rootIdx);
|
||||
return JSON.parse(result);
|
||||
} else {
|
||||
return this.wsRequest<ApiResponse<BoardItem[]>>('BOARD_GET_REPLIES', 'BOARD_REPLIES_DATA', { rootIdx });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 게시판 댓글/답글 추가
|
||||
* @param rootIdx 원글 인덱스
|
||||
* @param pidx 부모 글 인덱스
|
||||
* @param contents 댓글 내용
|
||||
* @param isComment 댓글 타입 (true: 댓글, false: 답글)
|
||||
* @returns ApiResponse
|
||||
*/
|
||||
public async addBoardReply(rootIdx: number, pidx: number, title: string, contents: string, isComment: boolean = true): Promise<ApiResponse> {
|
||||
if (isWebView && machine) {
|
||||
const result = await machine.Board_AddReply(rootIdx, pidx, title, contents, isComment);
|
||||
return JSON.parse(result);
|
||||
} else {
|
||||
return this.wsRequest<ApiResponse>('BOARD_ADD_REPLY', 'BOARD_REPLY_ADDED', { rootIdx, pidx, title, contents, isComment });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 메일 발신 내역 조회
|
||||
* @param startDate 시작일 (yyyy-MM-dd)
|
||||
|
||||
@@ -118,6 +118,58 @@ export function JobreportEditModal({
|
||||
}
|
||||
}, [isOpen, loadCommonCodes]);
|
||||
|
||||
// 모달 열릴 때 프로젝트가 설정되어 있으면 자동으로 최종 설정 불러오기
|
||||
useEffect(() => {
|
||||
const loadLastSettings = async () => {
|
||||
// 신규 등록이고, 프로젝트가 설정되어 있으며, 기본값이 비어있는 경우에만 자동 로드
|
||||
if (isOpen && !editingItem && formData.pidx && formData.pidx > 0) {
|
||||
// 이미 설정된 값이 있으면 자동 로드하지 않음 (복사 기능 등에서 이미 값이 있을 수 있음)
|
||||
const hasExistingSettings = formData.requestpart || formData.package || formData.type || formData.process;
|
||||
if (hasExistingSettings) {
|
||||
setPreviousProjectIdx(formData.pidx);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const lastReport = await comms.getLastJobReportByProject(formData.pidx, formData.projectName);
|
||||
if (lastReport.Success && lastReport.Data) {
|
||||
const updatedFormData = { ...formData };
|
||||
|
||||
if (lastReport.Data.requestpart) {
|
||||
updatedFormData.requestpart = lastReport.Data.requestpart;
|
||||
}
|
||||
if (lastReport.Data.package) {
|
||||
updatedFormData.package = lastReport.Data.package;
|
||||
}
|
||||
if (lastReport.Data.type) {
|
||||
updatedFormData.type = lastReport.Data.type;
|
||||
}
|
||||
if (lastReport.Data.jobgrp) {
|
||||
updatedFormData.jobgrp = lastReport.Data.jobgrp;
|
||||
}
|
||||
if (lastReport.Data.process) {
|
||||
updatedFormData.process = lastReport.Data.process;
|
||||
}
|
||||
if (lastReport.Data.status) {
|
||||
updatedFormData.status = lastReport.Data.status;
|
||||
}
|
||||
|
||||
onFormChange(updatedFormData);
|
||||
}
|
||||
setPreviousProjectIdx(formData.pidx);
|
||||
} catch (error) {
|
||||
console.error('최종 설정 불러오기 오류:', error);
|
||||
setPreviousProjectIdx(formData.pidx);
|
||||
}
|
||||
} else if (isOpen && !editingItem) {
|
||||
// 신규 등록인데 프로젝트가 없으면 초기화
|
||||
setPreviousProjectIdx(null);
|
||||
}
|
||||
};
|
||||
|
||||
loadLastSettings();
|
||||
}, [isOpen, editingItem]);
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
const handleFieldChange = <K extends keyof JobreportFormData>(
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
AlertTriangle,
|
||||
Building,
|
||||
Star,
|
||||
Bug,
|
||||
} from 'lucide-react';
|
||||
import { clsx } from 'clsx';
|
||||
import { UserInfoDialog } from '@/components/user/UserInfoDialog';
|
||||
@@ -123,6 +124,7 @@ const dropdownMenus: DropdownMenuConfig[] = [
|
||||
items: [
|
||||
{ type: 'link', path: '/note', icon: FileText, label: '메모장' },
|
||||
{ type: 'link', path: '/patch-list', icon: FileText, label: '패치 내역' },
|
||||
{ type: 'link', path: '/bug-report', icon: Bug, label: '버그 신고' },
|
||||
{ type: 'link', path: '/mail-list', icon: Mail, label: '메일 내역' },
|
||||
],
|
||||
},
|
||||
|
||||
751
Project/frontend/src/pages/BoardList.tsx
Normal file
751
Project/frontend/src/pages/BoardList.tsx
Normal file
@@ -0,0 +1,751 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { FileText, Search, RefreshCw, Calendar, Edit3, User, Plus } from 'lucide-react';
|
||||
import { comms } from '@/communication';
|
||||
import { BoardItem } from '@/types';
|
||||
|
||||
interface BoardListProps {
|
||||
bidx: number;
|
||||
title: string;
|
||||
icon?: React.ReactNode;
|
||||
defaultCategory?: string;
|
||||
categories?: { value: string; label: string; color: string }[];
|
||||
}
|
||||
|
||||
export function BoardList({
|
||||
bidx,
|
||||
title,
|
||||
icon = <FileText className="w-5 h-5" />,
|
||||
defaultCategory = 'PATCH',
|
||||
categories = [
|
||||
{ value: 'PATCH', label: 'PATCH', color: 'red' },
|
||||
{ value: 'UPDATE', label: 'UPDATE', color: 'lime' }
|
||||
]
|
||||
}: BoardListProps) {
|
||||
const [boardList, setBoardList] = useState<BoardItem[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [searchKey, setSearchKey] = useState('');
|
||||
const [selectedItem, setSelectedItem] = useState<BoardItem | null>(null);
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const [showEditModal, setShowEditModal] = useState(false);
|
||||
const [showReplyModal, setShowReplyModal] = useState(false);
|
||||
const [editFormData, setEditFormData] = useState<BoardItem | null>(null);
|
||||
const [userLevel, setUserLevel] = useState(0);
|
||||
const [userId, setUserId] = useState('');
|
||||
const [replies, setReplies] = useState<BoardItem[]>([]); // 댓글 목록 (is_comment=true)
|
||||
const [replyPosts, setReplyPosts] = useState<BoardItem[]>([]); // 답글 목록 (is_comment=false)
|
||||
const [commentText, setCommentText] = useState('');
|
||||
const [replyFormData, setReplyFormData] = useState<{ title: string; contents: string }>({ title: '', contents: '' });
|
||||
|
||||
useEffect(() => {
|
||||
loadUserInfo();
|
||||
loadData();
|
||||
}, []);
|
||||
|
||||
// ESC 키 처리
|
||||
useEffect(() => {
|
||||
const handleEscKey = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape') {
|
||||
if (showReplyModal) {
|
||||
setShowReplyModal(false);
|
||||
} else if (showEditModal) {
|
||||
setShowEditModal(false);
|
||||
} else if (showModal) {
|
||||
setShowModal(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('keydown', handleEscKey);
|
||||
return () => document.removeEventListener('keydown', handleEscKey);
|
||||
}, [showModal, showEditModal, showReplyModal]);
|
||||
|
||||
const loadUserInfo = async () => {
|
||||
try {
|
||||
const response = await comms.checkLoginStatus();
|
||||
if (response.Success && response.User) {
|
||||
setUserLevel(response.User.Level);
|
||||
setUserId(response.User.Id);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('사용자 정보 로드 오류:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const loadData = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
console.log('게시판 조회:', { bidx, searchKey });
|
||||
const response = await comms.getBoardList(bidx, searchKey);
|
||||
console.log('게시판 응답:', response);
|
||||
if (response.Success && response.Data) {
|
||||
setBoardList(response.Data);
|
||||
} else {
|
||||
console.warn('게시판 없음:', response.Message);
|
||||
setBoardList([]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('게시판 로드 오류:', error);
|
||||
alert('데이터를 불러오는 중 오류가 발생했습니다.');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSearch = () => {
|
||||
loadData();
|
||||
};
|
||||
|
||||
const loadReplies = async (rootIdx: number) => {
|
||||
try {
|
||||
const response = await comms.getBoardReplies(rootIdx);
|
||||
if (response.Success && response.Data) {
|
||||
setReplies(response.Data);
|
||||
} else {
|
||||
setReplies([]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('댓글 로드 오류:', error);
|
||||
setReplies([]);
|
||||
}
|
||||
};
|
||||
|
||||
const loadReplyPosts = async (rootIdx: number) => {
|
||||
try {
|
||||
// 목록에서 해당 root_idx의 답글들만 필터링
|
||||
const replyList = boardList.filter(item => item.root_idx === rootIdx && !item.is_comment);
|
||||
setReplyPosts(replyList);
|
||||
} catch (error) {
|
||||
console.error('답글 로드 오류:', error);
|
||||
setReplyPosts([]);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddComment = async () => {
|
||||
if (!selectedItem || !commentText.trim()) return;
|
||||
|
||||
try {
|
||||
const response = await comms.addBoardReply(selectedItem.idx, selectedItem.idx, '', commentText, true);
|
||||
if (response.Success) {
|
||||
setCommentText('');
|
||||
loadReplies(selectedItem.idx);
|
||||
// reply_count 업데이트를 위해 목록 새로고침
|
||||
loadData();
|
||||
} else {
|
||||
alert(response.Message || '댓글 등록에 실패했습니다.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('댓글 등록 오류:', error);
|
||||
alert('댓글 등록 중 오류가 발생했습니다.');
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddReply = async () => {
|
||||
if (!selectedItem || !replyFormData.title.trim() || !replyFormData.contents.trim()) {
|
||||
alert('제목과 내용을 입력해주세요.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 답글은 is_comment=false, title 포함
|
||||
const rootIdx = selectedItem.root_idx || selectedItem.idx;
|
||||
const response = await comms.addBoardReply(rootIdx, selectedItem.idx, replyFormData.title, replyFormData.contents, false);
|
||||
if (response.Success) {
|
||||
setShowReplyModal(false);
|
||||
setReplyFormData({ title: '', contents: '' });
|
||||
await loadData(); // 목록 새로고침
|
||||
loadReplyPosts(rootIdx); // 답글 목록 새로고침
|
||||
} else {
|
||||
alert(response.Message || '답글 등록에 실패했습니다.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('답글 등록 오류:', error);
|
||||
alert('답글 등록 중 오류가 발생했습니다.');
|
||||
}
|
||||
};
|
||||
|
||||
const handleRowClick = async (item: BoardItem) => {
|
||||
try {
|
||||
const response = await comms.getBoardDetail(item.idx);
|
||||
if (response.Success && response.Data) {
|
||||
setSelectedItem(response.Data);
|
||||
const rootIdx = response.Data.root_idx || response.Data.idx;
|
||||
loadReplies(rootIdx); // 댓글 로드
|
||||
loadReplyPosts(rootIdx); // 답글 로드
|
||||
setShowModal(true); // 모두 뷰어로 보기
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('상세 조회 오류:', error);
|
||||
alert('데이터를 불러오는 중 오류가 발생했습니다.');
|
||||
}
|
||||
};
|
||||
|
||||
const handleEditClick = () => {
|
||||
if (selectedItem) {
|
||||
setEditFormData(selectedItem);
|
||||
setShowModal(false);
|
||||
setShowEditModal(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleEditSave = async () => {
|
||||
if (!editFormData) return;
|
||||
|
||||
try {
|
||||
const isNew = editFormData.idx === 0;
|
||||
|
||||
if (isNew) {
|
||||
// 신규 등록
|
||||
const response = await comms.addBoard(
|
||||
bidx,
|
||||
editFormData.header || '',
|
||||
editFormData.cate || '',
|
||||
editFormData.title || '',
|
||||
editFormData.contents || ''
|
||||
);
|
||||
|
||||
if (response.Success) {
|
||||
setShowEditModal(false);
|
||||
setEditFormData(null);
|
||||
loadData();
|
||||
} else {
|
||||
alert(response.Message || '등록에 실패했습니다.');
|
||||
}
|
||||
} else {
|
||||
// 수정
|
||||
const response = await comms.editBoard(
|
||||
editFormData.idx,
|
||||
editFormData.header || '',
|
||||
editFormData.cate || '',
|
||||
editFormData.title || '',
|
||||
editFormData.contents || ''
|
||||
);
|
||||
|
||||
if (response.Success) {
|
||||
setShowEditModal(false);
|
||||
setEditFormData(null);
|
||||
loadData();
|
||||
} else {
|
||||
alert(response.Message || '수정에 실패했습니다.');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('저장 오류:', error);
|
||||
alert('저장 중 오류가 발생했습니다.');
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async () => {
|
||||
if (!editFormData || editFormData.idx === 0) return;
|
||||
|
||||
if (!confirm('정말 삭제하시겠습니까?')) return;
|
||||
|
||||
try {
|
||||
const response = await comms.deleteBoard(editFormData.idx);
|
||||
|
||||
if (response.Success) {
|
||||
setShowEditModal(false);
|
||||
setEditFormData(null);
|
||||
loadData();
|
||||
} else {
|
||||
alert(response.Message || '삭제에 실패했습니다.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('삭제 오류:', error);
|
||||
alert('삭제 중 오류가 발생했습니다.');
|
||||
}
|
||||
};
|
||||
|
||||
const formatDate = (dateStr: string | null) => {
|
||||
if (!dateStr) return '-';
|
||||
try {
|
||||
const date = new Date(dateStr);
|
||||
const yy = String(date.getFullYear()).slice(-2);
|
||||
const mm = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const dd = String(date.getDate()).padStart(2, '0');
|
||||
return `${yy}.${mm}.${dd}`;
|
||||
} catch {
|
||||
return dateStr;
|
||||
}
|
||||
};
|
||||
|
||||
const isNew = (dateStr: string | null) => {
|
||||
if (!dateStr) return false;
|
||||
try {
|
||||
const date = new Date(dateStr);
|
||||
const now = new Date();
|
||||
const diffTime = now.getTime() - date.getTime();
|
||||
const diffDays = diffTime / (1000 * 60 * 60 * 24);
|
||||
return diffDays <= 3;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const getCategoryColor = (cate: string) => {
|
||||
const category = categories.find(c => c.value.toUpperCase() === cate.toUpperCase());
|
||||
if (!category) return 'bg-gray-500/20 text-gray-400';
|
||||
|
||||
switch (category.color) {
|
||||
case 'lime': return 'bg-lime-500/20 text-lime-400';
|
||||
case 'red': return 'bg-red-500/20 text-red-400';
|
||||
case 'blue': return 'bg-blue-500/20 text-blue-400';
|
||||
case 'yellow': return 'bg-yellow-500/20 text-yellow-400';
|
||||
default: return 'bg-gray-500/20 text-gray-400';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-6 animate-fade-in">
|
||||
{/* 검색 필터 */}
|
||||
<div className="glass-effect rounded-2xl p-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-2 flex-1">
|
||||
<label className="text-white/70 text-sm font-medium whitespace-nowrap">검색어</label>
|
||||
<input
|
||||
type="text"
|
||||
value={searchKey}
|
||||
onChange={(e) => setSearchKey(e.target.value)}
|
||||
onKeyDown={(e) => e.key === 'Enter' && handleSearch()}
|
||||
placeholder="제목, 내용, 작성자 등"
|
||||
className="flex-1 h-10 bg-white/20 border border-white/30 rounded-lg px-3 text-white placeholder-white/50 focus:outline-none focus:ring-2 focus:ring-primary-400"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={handleSearch}
|
||||
disabled={loading}
|
||||
className="h-10 bg-primary-500 hover:bg-primary-600 text-white px-6 rounded-lg transition-colors flex items-center justify-center disabled:opacity-50"
|
||||
>
|
||||
{loading ? (
|
||||
<RefreshCw className="w-4 h-4 mr-2 animate-spin" />
|
||||
) : (
|
||||
<Search className="w-4 h-4 mr-2" />
|
||||
)}
|
||||
조회
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => {
|
||||
setEditFormData({
|
||||
idx: 0,
|
||||
bidx: bidx,
|
||||
gcode: '',
|
||||
header: '',
|
||||
cate: defaultCategory,
|
||||
title: '',
|
||||
contents: '',
|
||||
file: '',
|
||||
guid: '',
|
||||
url: '',
|
||||
wuid: '',
|
||||
wuid_name: '',
|
||||
wdate: null,
|
||||
project: '',
|
||||
pidx: 0,
|
||||
close: false,
|
||||
remark: ''
|
||||
});
|
||||
setShowEditModal(true);
|
||||
}}
|
||||
disabled={!(userLevel >= 9 || userId === '395552')}
|
||||
className="h-10 bg-green-500 hover:bg-green-600 text-white px-6 rounded-lg transition-colors flex items-center justify-center disabled:opacity-30 disabled:cursor-not-allowed"
|
||||
>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
추가
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 게시판 목록 */}
|
||||
<div className="glass-effect rounded-2xl overflow-hidden">
|
||||
<div className="px-6 py-4 border-b border-white/10 flex items-center justify-between">
|
||||
<h3 className="text-lg font-semibold text-white flex items-center">
|
||||
{icon}
|
||||
<span className="ml-2">{title}</span>
|
||||
</h3>
|
||||
<span className="text-white/60 text-sm">{boardList.length}건</span>
|
||||
</div>
|
||||
|
||||
<div className="divide-y divide-white/10 max-h-[calc(100vh-300px)] overflow-y-auto">
|
||||
{loading ? (
|
||||
<div className="px-6 py-8 text-center">
|
||||
<div className="flex items-center justify-center">
|
||||
<RefreshCw className="w-5 h-5 mr-2 animate-spin text-white/50" />
|
||||
<span className="text-white/50">데이터를 불러오는 중...</span>
|
||||
</div>
|
||||
</div>
|
||||
) : boardList.length === 0 ? (
|
||||
<div className="px-6 py-8 text-center">
|
||||
<FileText className="w-12 h-12 mx-auto mb-3 text-white/30" />
|
||||
<p className="text-white/50">조회된 데이터가 없습니다.</p>
|
||||
</div>
|
||||
) : (
|
||||
boardList.map((item) => (
|
||||
<div
|
||||
key={item.idx}
|
||||
className="px-6 py-3 hover:bg-white/5 transition-colors cursor-pointer"
|
||||
onClick={() => handleRowClick(item)}
|
||||
style={{ paddingLeft: `${24 + (item.depth || 0) * 24}px` }}
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex items-center gap-2 flex-shrink-0">
|
||||
{item.depth && item.depth > 0 && (
|
||||
<span className="text-white/40 text-xs mr-1">↳</span>
|
||||
)}
|
||||
{item.cate && (
|
||||
<span className={`px-2 py-0.5 text-xs rounded whitespace-nowrap ${getCategoryColor(item.cate)}`}>
|
||||
{item.cate}
|
||||
</span>
|
||||
)}
|
||||
{item.header && (
|
||||
<span className="px-2 py-0.5 bg-primary-500/20 text-primary-400 text-xs rounded whitespace-nowrap">
|
||||
{item.header}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center text-white/60 text-xs flex-shrink-0 mr-3">
|
||||
<Calendar className="w-3 h-3 mr-1" />
|
||||
{formatDate(item.wdate)}
|
||||
</div>
|
||||
<h4 className="text-white font-medium flex-1 min-w-0 flex items-center gap-2">
|
||||
<span className="truncate">{item.title || '(댓글)'}</span>
|
||||
{isNew(item.wdate) && (
|
||||
<span className="px-1.5 py-0.5 bg-yellow-500 text-white text-[10px] rounded font-bold animate-pulse flex-shrink-0">
|
||||
NEW
|
||||
</span>
|
||||
)}
|
||||
{(item.reply_count ?? 0) > 0 && (
|
||||
<span className="px-1.5 py-0.5 bg-blue-500/20 text-blue-400 text-[10px] rounded flex-shrink-0">
|
||||
💬 {item.reply_count}
|
||||
</span>
|
||||
)}
|
||||
</h4>
|
||||
<div className="flex items-center text-white/60 text-xs flex-shrink-0">
|
||||
<User className="w-3 h-3 mr-1" />
|
||||
{item.wuid_name || item.wuid}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 상세 모달 */}
|
||||
{showModal && selectedItem && (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50 backdrop-blur-sm">
|
||||
<div className="bg-gray-900 rounded-2xl shadow-2xl w-full max-w-4xl max-h-[90vh] overflow-hidden border border-white/10">
|
||||
<div className="flex items-center justify-between px-6 py-4 border-b border-white/10">
|
||||
<div className="flex items-center gap-2">
|
||||
{selectedItem.header && (
|
||||
<span className="px-2 py-1 bg-primary-500/20 text-primary-400 text-sm rounded">
|
||||
{selectedItem.header}
|
||||
</span>
|
||||
)}
|
||||
{selectedItem.cate && (
|
||||
<span className={`px-2 py-1 text-sm rounded ${getCategoryColor(selectedItem.cate)}`}>
|
||||
{selectedItem.cate}
|
||||
</span>
|
||||
)}
|
||||
<h2 className="text-xl font-bold text-white ml-2">{selectedItem.title}</h2>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setShowModal(false)}
|
||||
className="text-white/50 hover:text-white transition-colors"
|
||||
>
|
||||
<span className="text-2xl">×</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="px-6 py-4 border-b border-white/10 flex items-center gap-4 text-sm text-white/60">
|
||||
<div className="flex items-center">
|
||||
<User className="w-4 h-4 mr-1" />
|
||||
{selectedItem.wuid_name || selectedItem.wuid}
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<Calendar className="w-4 h-4 mr-1" />
|
||||
{formatDate(selectedItem.wdate)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="overflow-y-auto max-h-[calc(90vh-400px)] p-6">
|
||||
{selectedItem.contents && selectedItem.contents.trim() && (
|
||||
<div className="prose prose-invert max-w-none mb-8">
|
||||
<div className="text-white whitespace-pre-wrap">{selectedItem.contents}</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 답글 목록 */}
|
||||
{replyPosts.length > 0 && (
|
||||
<div className={selectedItem.contents && selectedItem.contents.trim() ? "border-t border-white/10 pt-6 mb-6" : "mb-6"}>
|
||||
<h3 className="text-lg font-semibold text-white mb-4">
|
||||
답글 {replyPosts.length}개
|
||||
</h3>
|
||||
|
||||
<div className="space-y-3">
|
||||
{replyPosts.map((replyPost) => (
|
||||
<div
|
||||
key={replyPost.idx}
|
||||
className="bg-white/5 hover:bg-white/10 rounded-lg p-4 cursor-pointer transition-colors"
|
||||
onClick={() => handleRowClick(replyPost)}
|
||||
>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<h4 className="text-white font-medium flex items-center gap-2">
|
||||
{replyPost.depth && replyPost.depth > 0 && (
|
||||
<span className="text-white/40 text-sm">↳</span>
|
||||
)}
|
||||
<span>{replyPost.title}</span>
|
||||
{isNew(replyPost.wdate) && (
|
||||
<span className="px-1.5 py-0.5 bg-yellow-500 text-white text-[10px] rounded font-bold">
|
||||
NEW
|
||||
</span>
|
||||
)}
|
||||
</h4>
|
||||
<div className="flex items-center gap-3 text-xs text-white/60">
|
||||
<div className="flex items-center">
|
||||
<User className="w-3 h-3 mr-1" />
|
||||
{replyPost.wuid_name || replyPost.wuid}
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<Calendar className="w-3 h-3 mr-1" />
|
||||
{formatDate(replyPost.wdate)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{replyPost.contents && (
|
||||
<div className="text-white/60 text-sm line-clamp-2">
|
||||
{replyPost.contents}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 댓글 목록 */}
|
||||
<div className={(selectedItem.contents && selectedItem.contents.trim()) || replyPosts.length > 0 ? "border-t border-white/10 pt-6" : ""}>
|
||||
<h3 className="text-lg font-semibold text-white mb-4">
|
||||
댓글 {replies.length}개
|
||||
</h3>
|
||||
|
||||
<div className="space-y-4 mb-6">
|
||||
{replies.map((reply) => (
|
||||
<div key={reply.idx} className="bg-white/5 rounded-lg p-4">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<User className="w-4 h-4 text-white/60" />
|
||||
<span className="text-sm text-white/80">{reply.wuid_name || reply.wuid}</span>
|
||||
<span className="text-xs text-white/50">{formatDate(reply.wdate)}</span>
|
||||
</div>
|
||||
<div className="text-white/70 text-sm whitespace-pre-wrap">{reply.contents}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 댓글 입력 */}
|
||||
<div className="bg-white/5 rounded-lg p-4">
|
||||
<textarea
|
||||
value={commentText}
|
||||
onChange={(e) => setCommentText(e.target.value)}
|
||||
placeholder="댓글을 입력하세요..."
|
||||
rows={3}
|
||||
className="w-full bg-white/10 border border-white/30 rounded-lg px-3 py-2 text-white placeholder-white/50 focus:outline-none focus:ring-2 focus:ring-primary-400 resize-none"
|
||||
/>
|
||||
<div className="flex justify-end mt-2">
|
||||
<button
|
||||
onClick={handleAddComment}
|
||||
disabled={!commentText.trim()}
|
||||
className="px-4 py-2 bg-primary-500 hover:bg-primary-600 disabled:opacity-50 disabled:cursor-not-allowed text-white rounded-lg transition-colors"
|
||||
>
|
||||
댓글 등록
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between px-6 py-4 border-t border-white/10 bg-white/5">
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={() => setShowReplyModal(true)}
|
||||
className="px-4 py-2 rounded-lg bg-green-500 hover:bg-green-600 text-white transition-colors"
|
||||
>
|
||||
답글 달기
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{(userLevel >= 9 || userId === '395552') && (
|
||||
<button
|
||||
onClick={handleEditClick}
|
||||
className="px-4 py-2 rounded-lg bg-primary-500 hover:bg-primary-600 text-white transition-colors flex items-center"
|
||||
>
|
||||
<Edit3 className="w-4 h-4 mr-2" />
|
||||
편집
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={() => setShowModal(false)}
|
||||
className="px-4 py-2 rounded-lg bg-white/10 hover:bg-white/20 text-white transition-colors"
|
||||
>
|
||||
닫기
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 편집 모달 */}
|
||||
{showEditModal && editFormData && (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50 backdrop-blur-sm">
|
||||
<div className="bg-gray-900 rounded-2xl shadow-2xl w-full max-w-4xl max-h-[90vh] overflow-hidden border border-white/10">
|
||||
<div className="flex items-center justify-between px-6 py-4 border-b border-white/10">
|
||||
<h2 className="text-xl font-bold text-white flex items-center">
|
||||
<Edit3 className="w-5 h-5 mr-2" />
|
||||
{editFormData.idx === 0 ? `${title} 등록` : `${title} 편집`}
|
||||
</h2>
|
||||
<button
|
||||
onClick={() => setShowEditModal(false)}
|
||||
className="text-white/50 hover:text-white transition-colors"
|
||||
>
|
||||
<span className="text-2xl">×</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="overflow-y-auto max-h-[calc(90vh-180px)] p-6 space-y-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-32">
|
||||
<label className="block text-white/70 text-xs font-medium mb-1">카테고리</label>
|
||||
<select
|
||||
value={editFormData.cate || defaultCategory}
|
||||
onChange={(e) => setEditFormData({ ...editFormData, cate: e.target.value })}
|
||||
className="w-full h-9 bg-white/10 border border-white/30 rounded-lg px-2 text-white text-sm focus:outline-none focus:ring-2 focus:ring-primary-400"
|
||||
>
|
||||
{categories.map((cat) => (
|
||||
<option key={cat.value} value={cat.value} className="bg-gray-800">
|
||||
{cat.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="flex-1">
|
||||
<label className="block text-white/70 text-xs font-medium mb-1">제목</label>
|
||||
<input
|
||||
type="text"
|
||||
value={editFormData.title || ''}
|
||||
onChange={(e) => setEditFormData({ ...editFormData, title: e.target.value })}
|
||||
className="w-full h-9 bg-white/10 border border-white/30 rounded-lg px-3 text-white placeholder-white/50 focus:outline-none focus:ring-2 focus:ring-primary-400"
|
||||
placeholder="제목"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-white/70 text-sm font-medium mb-2">내용</label>
|
||||
<textarea
|
||||
value={editFormData.contents || ''}
|
||||
onChange={(e) => setEditFormData({ ...editFormData, contents: e.target.value })}
|
||||
rows={15}
|
||||
className="w-full bg-white/10 border border-white/30 rounded-lg px-3 py-2 text-white placeholder-white/50 focus:outline-none focus:ring-2 focus:ring-primary-400 resize-none"
|
||||
placeholder="내용을 입력하세요..."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between px-6 py-4 border-t border-white/10 bg-white/5">
|
||||
<div>
|
||||
{editFormData && editFormData.idx > 0 && (
|
||||
<button
|
||||
onClick={handleDelete}
|
||||
className="px-4 py-2 rounded-lg bg-red-500 hover:bg-red-600 text-white transition-colors"
|
||||
>
|
||||
삭제
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={() => setShowEditModal(false)}
|
||||
className="px-4 py-2 rounded-lg bg-white/10 hover:bg-white/20 text-white transition-colors"
|
||||
>
|
||||
취소
|
||||
</button>
|
||||
<button
|
||||
onClick={handleEditSave}
|
||||
className="px-4 py-2 rounded-lg bg-primary-500 hover:bg-primary-600 text-white transition-colors"
|
||||
>
|
||||
저장
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 답글 달기 모달 */}
|
||||
{showReplyModal && selectedItem && (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50 backdrop-blur-sm">
|
||||
<div className="bg-gray-900 rounded-2xl shadow-2xl w-full max-w-4xl max-h-[90vh] overflow-hidden border border-white/10">
|
||||
<div className="flex items-center justify-between px-6 py-4 border-b border-white/10">
|
||||
<h2 className="text-xl font-bold text-white flex items-center">
|
||||
<Edit3 className="w-5 h-5 mr-2" />
|
||||
답글 작성
|
||||
</h2>
|
||||
<button
|
||||
onClick={() => setShowReplyModal(false)}
|
||||
className="text-white/50 hover:text-white transition-colors"
|
||||
>
|
||||
<span className="text-2xl">×</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="overflow-y-auto max-h-[calc(90vh-180px)] p-6 space-y-4">
|
||||
<div className="bg-white/5 rounded-lg p-4 mb-4">
|
||||
<div className="text-sm text-white/60 mb-2">원글</div>
|
||||
<div className="text-white font-medium">{selectedItem.title}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-white/70 text-sm font-medium mb-2">답글 제목</label>
|
||||
<input
|
||||
type="text"
|
||||
value={replyFormData.title}
|
||||
onChange={(e) => setReplyFormData({ ...replyFormData, title: e.target.value })}
|
||||
className="w-full h-10 bg-white/10 border border-white/30 rounded-lg px-3 text-white placeholder-white/50 focus:outline-none focus:ring-2 focus:ring-primary-400"
|
||||
placeholder="답글 제목을 입력하세요"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-white/70 text-sm font-medium mb-2">답글 내용</label>
|
||||
<textarea
|
||||
value={replyFormData.contents}
|
||||
onChange={(e) => setReplyFormData({ ...replyFormData, contents: e.target.value })}
|
||||
rows={15}
|
||||
className="w-full bg-white/10 border border-white/30 rounded-lg px-3 py-2 text-white placeholder-white/50 focus:outline-none focus:ring-2 focus:ring-primary-400 resize-none"
|
||||
placeholder="답글 내용을 입력하세요..."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-end gap-2 px-6 py-4 border-t border-white/10 bg-white/5">
|
||||
<button
|
||||
onClick={() => setShowReplyModal(false)}
|
||||
className="px-4 py-2 rounded-lg bg-white/10 hover:bg-white/20 text-white transition-colors"
|
||||
>
|
||||
취소
|
||||
</button>
|
||||
<button
|
||||
onClick={handleAddReply}
|
||||
className="px-4 py-2 rounded-lg bg-primary-500 hover:bg-primary-600 text-white transition-colors"
|
||||
>
|
||||
답글 등록
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
18
Project/frontend/src/pages/BugReport.tsx
Normal file
18
Project/frontend/src/pages/BugReport.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { BoardList } from './BoardList';
|
||||
import { Bug } from 'lucide-react';
|
||||
|
||||
export function BugReport() {
|
||||
return (
|
||||
<BoardList
|
||||
bidx={2}
|
||||
title="버그 신고"
|
||||
icon={<Bug className="w-5 h-5" />}
|
||||
defaultCategory="BUG"
|
||||
categories={[
|
||||
{ value: 'BUG', label: 'BUG', color: 'red' },
|
||||
{ value: 'FIXED', label: 'FIXED', color: 'lime' },
|
||||
{ value: 'PENDING', label: 'PENDING', color: 'yellow' }
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -64,6 +64,8 @@ export function Dashboard() {
|
||||
|
||||
// 목록 데이터
|
||||
const [urgentTodos, setUrgentTodos] = useState<TodoModel[]>([]);
|
||||
const [allUrgentTodos, setAllUrgentTodos] = useState<TodoModel[]>([]); // 전체 할일 목록
|
||||
const [todoPage, setTodoPage] = useState(1); // 할일 페이지
|
||||
const [purchaseNRList, setPurchaseNRList] = useState<PurchaseItem[]>([]);
|
||||
const [purchaseCRList, setPurchaseCRList] = useState<PurchaseItem[]>([]);
|
||||
const [recentNotes, setRecentNotes] = useState<NoteItem[]>([]);
|
||||
@@ -92,6 +94,13 @@ export function Dashboard() {
|
||||
status: '0' as TodoStatus,
|
||||
});
|
||||
|
||||
// 할일 페이지 변경 시 목록 업데이트
|
||||
useEffect(() => {
|
||||
const start = (todoPage - 1) * 6;
|
||||
const end = start + 6;
|
||||
setUrgentTodos(allUrgentTodos.slice(start, end));
|
||||
}, [todoPage, allUrgentTodos]);
|
||||
|
||||
const loadDashboardData = useCallback(async () => {
|
||||
try {
|
||||
// 오늘 날짜 (로컬 시간 기준)
|
||||
@@ -128,7 +137,9 @@ export function Dashboard() {
|
||||
setPurchaseCR(purchaseCount.CR);
|
||||
|
||||
if (urgentTodosResponse.Success && urgentTodosResponse.Data) {
|
||||
setUrgentTodos(urgentTodosResponse.Data.slice(0, 5));
|
||||
setAllUrgentTodos(urgentTodosResponse.Data);
|
||||
setUrgentTodos(urgentTodosResponse.Data.slice(0, 6));
|
||||
setTodoPage(1);
|
||||
}
|
||||
|
||||
if (allTodosResponse.Success && allTodosResponse.Data) {
|
||||
@@ -559,20 +570,25 @@ export function Dashboard() {
|
||||
onClick={() => handleTodoEdit(todo)}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="flex items-center space-x-4 flex-1 min-w-0">
|
||||
{todo.flag && (
|
||||
<Flag className="w-4 h-4 text-warning-400" />
|
||||
<Flag className="w-4 h-4 text-warning-400 flex-shrink-0" />
|
||||
)}
|
||||
<div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-white font-medium">
|
||||
{todo.request && (
|
||||
<span className="text-xs text-primary-400 mr-2">
|
||||
({todo.request})
|
||||
</span>
|
||||
)}
|
||||
{todo.title || '제목 없음'}
|
||||
</p>
|
||||
<p className="text-white/60 text-sm line-clamp-1">
|
||||
<p className="text-white/60 text-sm line-clamp-1 mt-1">
|
||||
{todo.remark}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="flex items-center space-x-3 flex-shrink-0 ml-4">
|
||||
<span className={`px-2 py-1 rounded-full text-xs font-medium ${getPriorityClass(todo.seqno)}`}>
|
||||
{getPriorityText(todo.seqno)}
|
||||
</span>
|
||||
@@ -595,6 +611,29 @@ export function Dashboard() {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{allUrgentTodos.length > 6 && (
|
||||
<div className="px-6 py-3 border-t border-white/10 flex items-center justify-between">
|
||||
<span className="text-xs text-white/50">
|
||||
{(todoPage - 1) * 6 + 1}-{Math.min(todoPage * 6, allUrgentTodos.length)} / {allUrgentTodos.length}건
|
||||
</span>
|
||||
<div className="flex gap-1">
|
||||
<button
|
||||
onClick={() => setTodoPage(p => Math.max(1, p - 1))}
|
||||
disabled={todoPage === 1}
|
||||
className="px-2 py-1 rounded bg-white/10 hover:bg-white/20 text-white/70 disabled:opacity-30 disabled:cursor-not-allowed transition-colors text-xs"
|
||||
>
|
||||
이전
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setTodoPage(p => Math.min(Math.ceil(allUrgentTodos.length / 6), p + 1))}
|
||||
disabled={todoPage >= Math.ceil(allUrgentTodos.length / 6)}
|
||||
className="px-2 py-1 rounded bg-white/10 hover:bg-white/20 text-white/70 disabled:opacity-30 disabled:cursor-not-allowed transition-colors text-xs"
|
||||
>
|
||||
다음
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* NR 모달 */}
|
||||
|
||||
@@ -207,8 +207,8 @@ export function Jobreport() {
|
||||
ot: 0, // OT 초기화
|
||||
otStart: data.otStart ? data.otStart.substring(11, 16) : '18:00',
|
||||
otEnd: data.otEnd ? data.otEnd.substring(11, 16) : '20:00',
|
||||
jobgrp: '',
|
||||
tag: '',
|
||||
jobgrp: data.jobgrp || '',
|
||||
tag: data.tag || '',
|
||||
});
|
||||
setShowModal(true);
|
||||
}
|
||||
@@ -413,28 +413,28 @@ export function Jobreport() {
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<button
|
||||
onClick={setToday}
|
||||
className="h-8 bg-white/10 hover:bg-white/20 text-white text-xs px-3 rounded-lg transition-colors whitespace-nowrap"
|
||||
className="h-10 bg-white/10 hover:bg-white/20 text-white text-xs px-2 rounded-lg transition-colors whitespace-nowrap"
|
||||
title="오늘 날짜로 설정"
|
||||
>
|
||||
오늘
|
||||
</button>
|
||||
<button
|
||||
onClick={setYesterday}
|
||||
className="h-8 bg-white/10 hover:bg-white/20 text-white text-xs px-3 rounded-lg transition-colors whitespace-nowrap"
|
||||
className="h-10 bg-white/10 hover:bg-white/20 text-white text-xs px-2 rounded-lg transition-colors whitespace-nowrap"
|
||||
title="어제 날짜로 설정"
|
||||
>
|
||||
어제
|
||||
</button>
|
||||
<button
|
||||
onClick={setThisMonth}
|
||||
className="h-8 bg-white/10 hover:bg-white/20 text-white text-xs px-3 rounded-lg transition-colors whitespace-nowrap"
|
||||
className="h-10 bg-white/10 hover:bg-white/20 text-white text-xs px-2 rounded-lg transition-colors whitespace-nowrap"
|
||||
title="이번 달 1일부터 말일까지"
|
||||
>
|
||||
이번달
|
||||
</button>
|
||||
<button
|
||||
onClick={setLastMonth}
|
||||
className="h-8 bg-white/10 hover:bg-white/20 text-white text-xs px-3 rounded-lg transition-colors whitespace-nowrap"
|
||||
className="h-10 bg-white/10 hover:bg-white/20 text-white text-xs px-2 rounded-lg transition-colors whitespace-nowrap"
|
||||
title="저번달 1일부터 말일까지"
|
||||
>
|
||||
저번달
|
||||
@@ -510,7 +510,7 @@ export function Jobreport() {
|
||||
className="h-10 bg-success-500 hover:bg-success-600 text-white px-6 rounded-lg transition-colors flex items-center justify-center"
|
||||
>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
새 업무일지
|
||||
추가
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -520,14 +520,14 @@ export function Jobreport() {
|
||||
<div className="flex-shrink-0 flex flex-col gap-3 justify-center">
|
||||
<button
|
||||
onClick={() => setShowDayReportModal(true)}
|
||||
className="h-12 bg-indigo-500 hover:bg-indigo-600 text-white px-6 rounded-lg transition-colors flex items-center justify-center whitespace-nowrap"
|
||||
className="h-10 bg-indigo-500 hover:bg-indigo-600 text-white px-6 rounded-lg transition-colors flex items-center justify-center whitespace-nowrap"
|
||||
>
|
||||
<Calendar className="w-4 h-4 mr-2" />
|
||||
일별 집계
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setShowTypeReportModal(true)}
|
||||
className="h-12 bg-purple-500 hover:bg-purple-600 text-white px-6 rounded-lg transition-colors flex items-center justify-center whitespace-nowrap"
|
||||
className="h-10 bg-purple-500 hover:bg-purple-600 text-white px-6 rounded-lg transition-colors flex items-center justify-center whitespace-nowrap"
|
||||
>
|
||||
<FileText className="w-4 h-4 mr-2" />
|
||||
업무형태별 집계
|
||||
|
||||
@@ -1,441 +1,17 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { FileText, Search, RefreshCw, Calendar, Edit3, User, Plus } from 'lucide-react';
|
||||
import { comms } from '@/communication';
|
||||
import { BoardItem } from '@/types';
|
||||
import { BoardList } from './BoardList';
|
||||
import { FileText } from 'lucide-react';
|
||||
|
||||
export function PatchList() {
|
||||
const [boardList, setBoardList] = useState<BoardItem[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [searchKey, setSearchKey] = useState('');
|
||||
const [selectedItem, setSelectedItem] = useState<BoardItem | null>(null);
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const [showEditModal, setShowEditModal] = useState(false);
|
||||
const [editFormData, setEditFormData] = useState<BoardItem | null>(null);
|
||||
const [userLevel, setUserLevel] = useState(0);
|
||||
const [userId, setUserId] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
loadUserInfo();
|
||||
loadData();
|
||||
}, []);
|
||||
|
||||
const loadUserInfo = async () => {
|
||||
try {
|
||||
const response = await comms.checkLoginStatus();
|
||||
if (response.Success && response.User) {
|
||||
setUserLevel(response.User.Level);
|
||||
setUserId(response.User.Id);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('사용자 정보 로드 오류:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const loadData = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
console.log('패치내역 조회:', { bidx: 5, searchKey });
|
||||
const response = await comms.getBoardList(5, searchKey); // bidx=5: 패치내역
|
||||
console.log('패치내역 응답:', response);
|
||||
if (response.Success && response.Data) {
|
||||
setBoardList(response.Data);
|
||||
} else {
|
||||
console.warn('패치내역 없음:', response.Message);
|
||||
setBoardList([]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('패치내역 로드 오류:', error);
|
||||
alert('데이터를 불러오는 중 오류가 발생했습니다.');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSearch = () => {
|
||||
loadData();
|
||||
};
|
||||
|
||||
const handleRowClick = async (item: BoardItem) => {
|
||||
try {
|
||||
const response = await comms.getBoardDetail(item.idx);
|
||||
if (response.Success && response.Data) {
|
||||
setSelectedItem(response.Data);
|
||||
|
||||
// 개발자(레벨 >= 9) 또는 사번 395552이면 바로 편집 모드
|
||||
if (userLevel >= 9 || userId === '395552') {
|
||||
setEditFormData(response.Data);
|
||||
setShowEditModal(true);
|
||||
} else {
|
||||
setShowModal(true);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('상세 조회 오류:', error);
|
||||
alert('데이터를 불러오는 중 오류가 발생했습니다.');
|
||||
}
|
||||
};
|
||||
|
||||
const handleEditClick = () => {
|
||||
if (selectedItem) {
|
||||
setEditFormData(selectedItem);
|
||||
setShowModal(false);
|
||||
setShowEditModal(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleEditSave = async () => {
|
||||
if (!editFormData) return;
|
||||
|
||||
try {
|
||||
const isNew = editFormData.idx === 0;
|
||||
|
||||
if (isNew) {
|
||||
// 신규 등록
|
||||
const response = await comms.addBoard(
|
||||
5, // bidx: 패치내역
|
||||
editFormData.header || '',
|
||||
editFormData.cate || '',
|
||||
editFormData.title || '',
|
||||
editFormData.contents || ''
|
||||
);
|
||||
|
||||
if (response.Success) {
|
||||
alert('등록되었습니다.');
|
||||
setShowEditModal(false);
|
||||
setEditFormData(null);
|
||||
loadData();
|
||||
} else {
|
||||
alert(response.Message || '등록에 실패했습니다.');
|
||||
}
|
||||
} else {
|
||||
// 수정
|
||||
const response = await comms.editBoard(
|
||||
editFormData.idx,
|
||||
editFormData.header || '',
|
||||
editFormData.cate || '',
|
||||
editFormData.title || '',
|
||||
editFormData.contents || ''
|
||||
);
|
||||
|
||||
if (response.Success) {
|
||||
alert('수정되었습니다.');
|
||||
setShowEditModal(false);
|
||||
setEditFormData(null);
|
||||
loadData();
|
||||
} else {
|
||||
alert(response.Message || '수정에 실패했습니다.');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('저장 오류:', error);
|
||||
alert('저장 중 오류가 발생했습니다.');
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async () => {
|
||||
if (!editFormData || editFormData.idx === 0) return;
|
||||
|
||||
if (!confirm('정말 삭제하시겠습니까?')) return;
|
||||
|
||||
try {
|
||||
const response = await comms.deleteBoard(editFormData.idx);
|
||||
|
||||
if (response.Success) {
|
||||
alert('삭제되었습니다.');
|
||||
setShowEditModal(false);
|
||||
setEditFormData(null);
|
||||
loadData();
|
||||
} else {
|
||||
alert(response.Message || '삭제에 실패했습니다.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('삭제 오류:', error);
|
||||
alert('삭제 중 오류가 발생했습니다.');
|
||||
}
|
||||
};
|
||||
|
||||
const formatDate = (dateStr: string | null) => {
|
||||
if (!dateStr) return '-';
|
||||
try {
|
||||
const date = new Date(dateStr);
|
||||
const yy = String(date.getFullYear()).slice(-2);
|
||||
const mm = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const dd = String(date.getDate()).padStart(2, '0');
|
||||
return `${yy}.${mm}.${dd}`;
|
||||
} catch {
|
||||
return dateStr;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-6 animate-fade-in">
|
||||
{/* 검색 필터 */}
|
||||
<div className="glass-effect rounded-2xl p-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-2 flex-1">
|
||||
<label className="text-white/70 text-sm font-medium whitespace-nowrap">검색어</label>
|
||||
<input
|
||||
type="text"
|
||||
value={searchKey}
|
||||
onChange={(e) => setSearchKey(e.target.value)}
|
||||
onKeyDown={(e) => e.key === 'Enter' && handleSearch()}
|
||||
placeholder="제목, 내용, 작성자 등"
|
||||
className="flex-1 h-10 bg-white/20 border border-white/30 rounded-lg px-3 text-white placeholder-white/50 focus:outline-none focus:ring-2 focus:ring-primary-400"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={handleSearch}
|
||||
disabled={loading}
|
||||
className="h-10 bg-primary-500 hover:bg-primary-600 text-white px-6 rounded-lg transition-colors flex items-center justify-center disabled:opacity-50"
|
||||
>
|
||||
{loading ? (
|
||||
<RefreshCw className="w-4 h-4 mr-2 animate-spin" />
|
||||
) : (
|
||||
<Search className="w-4 h-4 mr-2" />
|
||||
)}
|
||||
조회
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => {
|
||||
setEditFormData({
|
||||
idx: 0,
|
||||
bidx: 5,
|
||||
gcode: '',
|
||||
header: '',
|
||||
cate: 'PATCH',
|
||||
title: '',
|
||||
contents: '',
|
||||
file: '',
|
||||
guid: '',
|
||||
url: '',
|
||||
wuid: '',
|
||||
wuid_name: '',
|
||||
wdate: null,
|
||||
project: '',
|
||||
pidx: 0,
|
||||
close: false,
|
||||
remark: ''
|
||||
});
|
||||
setShowEditModal(true);
|
||||
}}
|
||||
disabled={!(userLevel >= 9 || userId === '395552')}
|
||||
className="h-10 bg-green-500 hover:bg-green-600 text-white px-6 rounded-lg transition-colors flex items-center justify-center disabled:opacity-30 disabled:cursor-not-allowed"
|
||||
>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
추가
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 패치내역 목록 */}
|
||||
<div className="glass-effect rounded-2xl overflow-hidden">
|
||||
<div className="px-6 py-4 border-b border-white/10 flex items-center justify-between">
|
||||
<h3 className="text-lg font-semibold text-white flex items-center">
|
||||
<FileText className="w-5 h-5 mr-2" />
|
||||
패치 내역
|
||||
</h3>
|
||||
<span className="text-white/60 text-sm">{boardList.length}건</span>
|
||||
</div>
|
||||
|
||||
<div className="divide-y divide-white/10 max-h-[calc(100vh-300px)] overflow-y-auto">
|
||||
{loading ? (
|
||||
<div className="px-6 py-8 text-center">
|
||||
<div className="flex items-center justify-center">
|
||||
<RefreshCw className="w-5 h-5 mr-2 animate-spin text-white/50" />
|
||||
<span className="text-white/50">데이터를 불러오는 중...</span>
|
||||
</div>
|
||||
</div>
|
||||
) : boardList.length === 0 ? (
|
||||
<div className="px-6 py-8 text-center">
|
||||
<FileText className="w-12 h-12 mx-auto mb-3 text-white/30" />
|
||||
<p className="text-white/50">조회된 데이터가 없습니다.</p>
|
||||
</div>
|
||||
) : (
|
||||
boardList.map((item) => (
|
||||
<div
|
||||
key={item.idx}
|
||||
className="px-6 py-3 hover:bg-white/5 transition-colors cursor-pointer"
|
||||
onClick={() => handleRowClick(item)}
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex items-center gap-2 flex-shrink-0">
|
||||
{item.cate && (
|
||||
<span className={`px-2 py-0.5 text-xs rounded whitespace-nowrap ${
|
||||
item.cate.toUpperCase() === 'UPDATE'
|
||||
? 'bg-lime-500/20 text-lime-400'
|
||||
: 'bg-red-500/20 text-red-400'
|
||||
}`}>
|
||||
{item.cate}
|
||||
</span>
|
||||
)}
|
||||
{item.header && (
|
||||
<span className="px-2 py-0.5 bg-primary-500/20 text-primary-400 text-xs rounded whitespace-nowrap">
|
||||
{item.header}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<h4 className="text-white font-medium flex-1 min-w-0 truncate">{item.title}</h4>
|
||||
<div className="flex items-center text-white/60 text-xs flex-shrink-0">
|
||||
<Calendar className="w-3 h-3 mr-1" />
|
||||
{formatDate(item.wdate)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 상세 모달 */}
|
||||
{showModal && selectedItem && (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50 backdrop-blur-sm">
|
||||
<div className="bg-gray-900 rounded-2xl shadow-2xl w-full max-w-4xl max-h-[90vh] overflow-hidden border border-white/10">
|
||||
<div className="flex items-center justify-between px-6 py-4 border-b border-white/10">
|
||||
<div className="flex items-center gap-2">
|
||||
{selectedItem.header && (
|
||||
<span className="px-2 py-1 bg-primary-500/20 text-primary-400 text-sm rounded">
|
||||
{selectedItem.header}
|
||||
</span>
|
||||
)}
|
||||
{selectedItem.cate && (
|
||||
<span className="px-2 py-1 bg-white/10 text-white/70 text-sm rounded">
|
||||
{selectedItem.cate}
|
||||
</span>
|
||||
)}
|
||||
<h2 className="text-xl font-bold text-white ml-2">{selectedItem.title}</h2>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setShowModal(false)}
|
||||
className="text-white/50 hover:text-white transition-colors"
|
||||
>
|
||||
<span className="text-2xl">×</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="px-6 py-4 border-b border-white/10 flex items-center gap-4 text-sm text-white/60">
|
||||
<div className="flex items-center">
|
||||
<User className="w-4 h-4 mr-1" />
|
||||
{selectedItem.wuid_name || selectedItem.wuid}
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<Calendar className="w-4 h-4 mr-1" />
|
||||
{formatDate(selectedItem.wdate)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="overflow-y-auto max-h-[calc(90vh-180px)] p-6">
|
||||
<div className="prose prose-invert max-w-none">
|
||||
<div className="text-white whitespace-pre-wrap">{selectedItem.contents}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-end gap-2 px-6 py-4 border-t border-white/10 bg-white/5">
|
||||
{(userLevel >= 9 || userId === '395552') && (
|
||||
<button
|
||||
onClick={handleEditClick}
|
||||
className="px-4 py-2 rounded-lg bg-primary-500 hover:bg-primary-600 text-white transition-colors flex items-center"
|
||||
>
|
||||
<Edit3 className="w-4 h-4 mr-2" />
|
||||
편집
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={() => setShowModal(false)}
|
||||
className="px-4 py-2 rounded-lg bg-white/10 hover:bg-white/20 text-white transition-colors"
|
||||
>
|
||||
닫기
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 편집 모달 */}
|
||||
{showEditModal && editFormData && (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50 backdrop-blur-sm">
|
||||
<div className="bg-gray-900 rounded-2xl shadow-2xl w-full max-w-4xl max-h-[90vh] overflow-hidden border border-white/10">
|
||||
<div className="flex items-center justify-between px-6 py-4 border-b border-white/10">
|
||||
<h2 className="text-xl font-bold text-white flex items-center">
|
||||
<Edit3 className="w-5 h-5 mr-2" />
|
||||
패치내역 편집
|
||||
</h2>
|
||||
<button
|
||||
onClick={() => setShowEditModal(false)}
|
||||
className="text-white/50 hover:text-white transition-colors"
|
||||
>
|
||||
<span className="text-2xl">×</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="overflow-y-auto max-h-[calc(90vh-180px)] p-6 space-y-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-32">
|
||||
<label className="block text-white/70 text-xs font-medium mb-1">카테고리</label>
|
||||
<select
|
||||
value={editFormData.cate || 'PATCH'}
|
||||
onChange={(e) => setEditFormData({ ...editFormData, cate: e.target.value })}
|
||||
className="w-full h-9 bg-white/10 border border-white/30 rounded-lg px-2 text-white text-sm focus:outline-none focus:ring-2 focus:ring-primary-400"
|
||||
>
|
||||
<option value="PATCH" className="bg-gray-800">PATCH</option>
|
||||
<option value="UPDATE" className="bg-gray-800">UPDATE</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="flex-1">
|
||||
<label className="block text-white/70 text-xs font-medium mb-1">제목</label>
|
||||
<input
|
||||
type="text"
|
||||
value={editFormData.title || ''}
|
||||
onChange={(e) => setEditFormData({ ...editFormData, title: e.target.value })}
|
||||
className="w-full h-9 bg-white/10 border border-white/30 rounded-lg px-3 text-white placeholder-white/50 focus:outline-none focus:ring-2 focus:ring-primary-400"
|
||||
placeholder="패치 제목"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-white/70 text-sm font-medium mb-2">내용</label>
|
||||
<textarea
|
||||
value={editFormData.contents || ''}
|
||||
onChange={(e) => setEditFormData({ ...editFormData, contents: e.target.value })}
|
||||
rows={15}
|
||||
className="w-full bg-white/10 border border-white/30 rounded-lg px-3 py-2 text-white placeholder-white/50 focus:outline-none focus:ring-2 focus:ring-primary-400 resize-none"
|
||||
placeholder="패치 내용을 입력하세요..."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between px-6 py-4 border-t border-white/10 bg-white/5">
|
||||
<div>
|
||||
{editFormData && editFormData.idx > 0 && (
|
||||
<button
|
||||
onClick={handleDelete}
|
||||
className="px-4 py-2 rounded-lg bg-red-500 hover:bg-red-600 text-white transition-colors"
|
||||
>
|
||||
삭제
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={() => setShowEditModal(false)}
|
||||
className="px-4 py-2 rounded-lg bg-white/10 hover:bg-white/20 text-white transition-colors"
|
||||
>
|
||||
취소
|
||||
</button>
|
||||
<button
|
||||
onClick={handleEditSave}
|
||||
className="px-4 py-2 rounded-lg bg-primary-500 hover:bg-primary-600 text-white transition-colors"
|
||||
>
|
||||
저장
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<BoardList
|
||||
bidx={5}
|
||||
title="패치 내역"
|
||||
icon={<FileText className="w-5 h-5" />}
|
||||
defaultCategory="PATCH"
|
||||
categories={[
|
||||
{ value: 'PATCH', label: 'PATCH', color: 'red' },
|
||||
{ value: 'UPDATE', label: 'UPDATE', color: 'lime' }
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -462,6 +462,8 @@ export interface MachineBridgeInterface {
|
||||
Board_Add(bidx: number, header: string, cate: string, title: string, contents: string): Promise<string>;
|
||||
Board_Edit(idx: number, header: string, cate: string, title: string, contents: string): Promise<string>;
|
||||
Board_Delete(idx: number): Promise<string>;
|
||||
Board_GetReplies(rootIdx: number): Promise<string>;
|
||||
Board_AddReply(rootIdx: number, pidx: number, title: string, contents: string, isComment: boolean): Promise<string>;
|
||||
|
||||
// Mail API (메일 발신 내역)
|
||||
Mail_GetList(startDate: string, endDate: string, searchKey: string): Promise<string>;
|
||||
@@ -849,6 +851,12 @@ export interface BoardItem {
|
||||
close: boolean;
|
||||
remark: string;
|
||||
wuid_name: string;
|
||||
root_idx?: number | null;
|
||||
depth?: number;
|
||||
sort_order?: number;
|
||||
thread_path?: string;
|
||||
is_comment?: boolean;
|
||||
reply_count?: number;
|
||||
}
|
||||
|
||||
// Mail 발신 내역 타입
|
||||
|
||||
Reference in New Issue
Block a user