Add Dashboard todo edit/delete/complete features and Note view count tracking
This commit is contained in:
@@ -363,6 +363,9 @@
|
||||
<Compile Include="Web\MachineBridge\MachineBridge.Holiday.cs" />
|
||||
<Compile Include="Web\MachineBridge\MachineBridge.Holyday.cs" />
|
||||
<Compile Include="Web\MachineBridge\MachineBridge.MailForm.cs" />
|
||||
<Compile Include="Web\MachineBridge\MachineBridge.MailData.cs" />
|
||||
<Compile Include="Web\MachineBridge\MachineBridge.Note.cs" />
|
||||
<Compile Include="Web\MachineBridge\MachineBridge.Board.cs" />
|
||||
<Compile Include="Web\MachineBridge\MachineBridge.UserGroup.cs" />
|
||||
<Compile Include="Web\MachineBridge\MachineBridge.UserAuth.cs" />
|
||||
<Compile Include="Web\MachineBridge\WebSocketServer.cs" />
|
||||
|
||||
158
Project/Web/MachineBridge/MachineBridge.Board.cs
Normal file
158
Project/Web/MachineBridge/MachineBridge.Board.cs
Normal file
@@ -0,0 +1,158 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Data.SqlClient;
|
||||
using System.Linq;
|
||||
using FCOMMON;
|
||||
|
||||
namespace Project.Web
|
||||
{
|
||||
public partial class MachineBridge
|
||||
{
|
||||
/// <summary>
|
||||
/// 게시판 목록 조회 (bidx로 구분: 5=패치내역, 기타=일반게시판)
|
||||
/// </summary>
|
||||
public string Board_GetList(int bidx, string searchKey)
|
||||
{
|
||||
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;// Properties.Settings.Default.CS;
|
||||
using (var conn = new SqlConnection(connStr))
|
||||
{
|
||||
conn.Open();
|
||||
|
||||
var sql = @"
|
||||
SELECT idx, bidx, header, cate, title, contents, [file], guid, url, wuid, wdate, project, pidx, gcode, [close], remark,
|
||||
dbo.getUserName(wuid) AS wuid_name
|
||||
FROM Board WITH (nolock)
|
||||
WHERE gcode = @gcode AND bidx = @bidx
|
||||
AND (ISNULL(title,'') LIKE @search OR ISNULL(contents,'') LIKE @search OR ISNULL(wuid,'') LIKE @search)
|
||||
ORDER BY wdate DESC";
|
||||
|
||||
if(bidx == 5) //패치내역은 모두가 다 확인할 수있도록 그룹코드를 제한하지 않는다
|
||||
{
|
||||
sql = @"
|
||||
SELECT idx, bidx, header, cate, title, contents, [file], guid, url, wuid, wdate, project, pidx, gcode, [close], remark,
|
||||
dbo.getUserName(wuid) AS wuid_name
|
||||
FROM Board WITH (nolock)
|
||||
WHERE bidx = @bidx
|
||||
AND (ISNULL(title,'') LIKE @search OR ISNULL(contents,'') LIKE @search OR ISNULL(wuid,'') LIKE @search)
|
||||
ORDER BY wdate DESC";
|
||||
}
|
||||
|
||||
var cmd = new SqlCommand(sql, conn);
|
||||
|
||||
cmd.Parameters.Add("@gcode", SqlDbType.VarChar).Value = info.Login.gcode;
|
||||
cmd.Parameters.Add("@bidx", SqlDbType.Int).Value = bidx;
|
||||
cmd.Parameters.Add("@search", SqlDbType.NVarChar).Value = $"%{searchKey}%";
|
||||
|
||||
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),
|
||||
wuid_name = reader.IsDBNull(16) ? "" : reader.GetString(16)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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_GetDetail(int idx)
|
||||
{
|
||||
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;//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,
|
||||
dbo.getUserName(wuid) AS wuid_name
|
||||
FROM Board WITH (nolock)
|
||||
WHERE idx = @idx AND gcode = @gcode", conn);
|
||||
|
||||
cmd.Parameters.Add("@idx", SqlDbType.Int).Value = idx;
|
||||
cmd.Parameters.Add("@gcode", SqlDbType.VarChar).Value = info.Login.gcode;
|
||||
|
||||
using (var reader = cmd.ExecuteReader())
|
||||
{
|
||||
if (reader.Read())
|
||||
{
|
||||
var data = 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),
|
||||
wuid_name = reader.IsDBNull(16) ? "" : reader.GetString(16)
|
||||
};
|
||||
|
||||
return JsonConvert.SerializeObject(new { Success = true, Data = data });
|
||||
}
|
||||
else
|
||||
{
|
||||
return JsonConvert.SerializeObject(new { Success = false, Message = "데이터를 찾을 수 없습니다." });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return JsonConvert.SerializeObject(new { Success = false, Message = ex.Message });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
75
Project/Web/MachineBridge/MachineBridge.MailData.cs
Normal file
75
Project/Web/MachineBridge/MachineBridge.MailData.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Data.SqlClient;
|
||||
using System.Linq;
|
||||
using FCOMMON;
|
||||
|
||||
namespace Project.Web
|
||||
{
|
||||
public partial class MachineBridge
|
||||
{
|
||||
/// <summary>
|
||||
/// 메일 발신 내역 조회
|
||||
/// </summary>
|
||||
public string Mail_GetList(string startDate, string endDate, string searchKey)
|
||||
{
|
||||
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, gcode, subject, body, fromlist, tolist, cc AS cclist, bcc AS bcclist, project, cate, pdate
|
||||
FROM MailData WITH (nolock)
|
||||
WHERE gcode = @gcode
|
||||
AND (pdate BETWEEN @startDate AND @endDate)
|
||||
AND (ISNULL(subject,'') LIKE @search OR ISNULL(fromlist,'') LIKE @search OR ISNULL(tolist,'') LIKE @search OR ISNULL(cate,'') LIKE @search)
|
||||
ORDER BY pdate DESC", conn);
|
||||
|
||||
cmd.Parameters.Add("@gcode", SqlDbType.VarChar).Value = info.Login.gcode;
|
||||
cmd.Parameters.Add("@startDate", SqlDbType.VarChar).Value = startDate;
|
||||
cmd.Parameters.Add("@endDate", SqlDbType.VarChar).Value = endDate;
|
||||
cmd.Parameters.Add("@search", SqlDbType.NVarChar).Value = $"%{searchKey}%";
|
||||
|
||||
var list = new List<object>();
|
||||
using (var reader = cmd.ExecuteReader())
|
||||
{
|
||||
while (reader.Read())
|
||||
{
|
||||
list.Add(new
|
||||
{
|
||||
idx = reader.GetInt32(0),
|
||||
gcode = reader.IsDBNull(1) ? "" : reader.GetString(1),
|
||||
uid = "", // uid 컬럼 없음
|
||||
subject = reader.IsDBNull(2) ? "" : reader.GetString(2),
|
||||
htmlbody = reader.IsDBNull(3) ? "" : reader.GetString(3), // body를 htmlbody로 반환
|
||||
fromlist = reader.IsDBNull(4) ? "" : reader.GetString(4),
|
||||
tolist = reader.IsDBNull(5) ? "" : reader.GetString(5),
|
||||
cclist = reader.IsDBNull(6) ? "" : reader.GetString(6),
|
||||
bcclist = reader.IsDBNull(7) ? "" : reader.GetString(7),
|
||||
project = reader.IsDBNull(8) ? "" : reader.GetInt32(8).ToString(),
|
||||
cate = reader.IsDBNull(9) ? "" : reader.GetString(9),
|
||||
wdate = reader.IsDBNull(10) ? "" : reader.GetString(10) // pdate를 wdate로 반환 (프론트엔드 호환)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return JsonConvert.SerializeObject(new { Success = true, Data = list });
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return JsonConvert.SerializeObject(new { Success = false, Message = ex.Message });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
343
Project/Web/MachineBridge/MachineBridge.Note.cs
Normal file
343
Project/Web/MachineBridge/MachineBridge.Note.cs
Normal file
@@ -0,0 +1,343 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Data.SqlClient;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using Newtonsoft.Json;
|
||||
using FCOMMON;
|
||||
|
||||
namespace Project.Web
|
||||
{
|
||||
public partial class MachineBridge
|
||||
{
|
||||
#region Note API
|
||||
|
||||
/// <summary>
|
||||
/// 메모장 목록 조회
|
||||
/// </summary>
|
||||
public string Note_GetList(string startDate, string endDate, string uid = "")
|
||||
{
|
||||
try
|
||||
{
|
||||
// 로그인 체크
|
||||
if (string.IsNullOrEmpty(info.Login.no) || string.IsNullOrEmpty(info.Login.gcode))
|
||||
{
|
||||
return JsonConvert.SerializeObject(new { Success = false, Message = "로그인이 필요합니다." });
|
||||
}
|
||||
|
||||
var connStr = Properties.Settings.Default.CS;
|
||||
using (var conn = new SqlConnection(connStr))
|
||||
{
|
||||
conn.Open();
|
||||
var cmd = new SqlCommand();
|
||||
cmd.Connection = conn;
|
||||
|
||||
// 권한 체크: 레벨5 미만이면 자기 것만 보거나 공유된 것만 조회
|
||||
int curLevel = Math.Max(info.Login.level, DBM.getAuth(DBM.eAuthType.jobreport));
|
||||
|
||||
if (curLevel >= 5)
|
||||
{
|
||||
// 관리자: 모든 메모 조회 가능
|
||||
if (string.IsNullOrEmpty(uid))
|
||||
{
|
||||
cmd.CommandText = @"
|
||||
SELECT idx, gcode, pdate, title, uid, share, wuid, wdate, guid,
|
||||
ISNULL(viewcount, 0) as viewcount, viewdate,
|
||||
'' as description, '' as description2
|
||||
FROM EETGW_Note WITH (nolock)
|
||||
WHERE gcode = @gcode AND pdate BETWEEN @startDate AND @endDate
|
||||
ORDER BY ISNULL(viewdate, '1900-01-01') DESC, ISNULL(viewcount, 0) DESC, pdate DESC";
|
||||
}
|
||||
else
|
||||
{
|
||||
cmd.CommandText = @"
|
||||
SELECT idx, gcode, pdate, title, uid, share, wuid, wdate, guid,
|
||||
ISNULL(viewcount, 0) as viewcount, viewdate,
|
||||
'' as description, '' as description2
|
||||
FROM EETGW_Note WITH (nolock)
|
||||
WHERE gcode = @gcode AND pdate BETWEEN @startDate AND @endDate AND uid = @uid
|
||||
ORDER BY ISNULL(viewdate, '1900-01-01') DESC, ISNULL(viewcount, 0) DESC, pdate DESC";
|
||||
cmd.Parameters.Add("@uid", SqlDbType.VarChar).Value = uid;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 일반 사용자: 자신이 작성했거나 공유된 메모만 조회
|
||||
cmd.CommandText = @"
|
||||
SELECT idx, gcode, pdate, title, uid, share, wuid, wdate, guid,
|
||||
ISNULL(viewcount, 0) as viewcount, viewdate,
|
||||
'' as description, '' as description2
|
||||
FROM EETGW_Note WITH (nolock)
|
||||
WHERE (gcode = @gcode AND pdate BETWEEN @startDate AND @endDate AND uid = @currentUid)
|
||||
OR (gcode = @gcode AND pdate BETWEEN @startDate AND @endDate AND ISNULL(share, 0) = 1)
|
||||
ORDER BY ISNULL(viewdate, '1900-01-01') DESC, ISNULL(viewcount, 0) DESC, pdate DESC";
|
||||
cmd.Parameters.Add("@currentUid", SqlDbType.VarChar).Value = info.Login.no;
|
||||
}
|
||||
|
||||
cmd.Parameters.Add("@gcode", SqlDbType.VarChar).Value = info.Login.gcode;
|
||||
cmd.Parameters.Add("@startDate", SqlDbType.VarChar).Value = startDate;
|
||||
cmd.Parameters.Add("@endDate", SqlDbType.VarChar).Value = endDate;
|
||||
|
||||
var list = new List<object>();
|
||||
using (var reader = cmd.ExecuteReader())
|
||||
{
|
||||
while (reader.Read())
|
||||
{
|
||||
list.Add(new
|
||||
{
|
||||
idx = reader["idx"],
|
||||
gcode = reader["gcode"],
|
||||
pdate = reader["pdate"],
|
||||
title = reader["title"],
|
||||
uid = reader["uid"],
|
||||
share = reader["share"],
|
||||
wuid = reader["wuid"],
|
||||
wdate = reader["wdate"],
|
||||
guid = reader["guid"],
|
||||
viewcount = reader["viewcount"],
|
||||
viewdate = reader["viewdate"] != DBNull.Value ? reader["viewdate"] : null,
|
||||
description = reader["description"],
|
||||
description2 = reader["description2"]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return JsonConvert.SerializeObject(new { Success = true, Data = list });
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return JsonConvert.SerializeObject(new { Success = false, Message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 메모장 상세 조회
|
||||
/// </summary>
|
||||
public string Note_GetDetail(int idx)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(info.Login.no) || string.IsNullOrEmpty(info.Login.gcode))
|
||||
{
|
||||
return JsonConvert.SerializeObject(new { Success = false, Message = "로그인이 필요합니다." });
|
||||
}
|
||||
|
||||
var cs = Properties.Settings.Default.CS;
|
||||
using (var conn = new SqlConnection(cs))
|
||||
{
|
||||
conn.Open();
|
||||
|
||||
// 조회수 증가 및 조회일 업데이트
|
||||
var updateCmd = new SqlCommand(@"
|
||||
UPDATE EETGW_Note
|
||||
SET viewcount = ISNULL(viewcount, 0) + 1, viewdate = GETDATE()
|
||||
WHERE gcode = @gcode AND idx = @idx", conn);
|
||||
updateCmd.Parameters.Add("@gcode", SqlDbType.VarChar).Value = info.Login.gcode;
|
||||
updateCmd.Parameters.Add("@idx", SqlDbType.Int).Value = idx;
|
||||
updateCmd.ExecuteNonQuery();
|
||||
|
||||
var cmd = new SqlCommand(@"
|
||||
SELECT idx, gcode, pdate, title, uid, description, description2, share, wuid, wdate, guid,
|
||||
ISNULL(viewcount, 0) as viewcount, viewdate
|
||||
FROM EETGW_Note WITH (nolock)
|
||||
WHERE gcode = @gcode AND idx = @idx", conn);
|
||||
|
||||
cmd.Parameters.Add("@gcode", SqlDbType.VarChar).Value = info.Login.gcode;
|
||||
cmd.Parameters.Add("@idx", SqlDbType.Int).Value = idx;
|
||||
|
||||
using (var reader = cmd.ExecuteReader())
|
||||
{
|
||||
if (reader.Read())
|
||||
{
|
||||
var item = new
|
||||
{
|
||||
idx = reader["idx"],
|
||||
gcode = reader["gcode"],
|
||||
pdate = reader["pdate"],
|
||||
title = reader["title"],
|
||||
uid = reader["uid"],
|
||||
description = reader["description"],
|
||||
description2 = reader["description2"],
|
||||
share = reader["share"],
|
||||
wuid = reader["wuid"],
|
||||
wdate = reader["wdate"],
|
||||
guid = reader["guid"],
|
||||
viewcount = reader["viewcount"],
|
||||
viewdate = reader["viewdate"] != DBNull.Value ? reader["viewdate"] : null
|
||||
};
|
||||
return JsonConvert.SerializeObject(new { Success = true, Data = item });
|
||||
}
|
||||
else
|
||||
{
|
||||
return JsonConvert.SerializeObject(new { Success = false, Message = "데이터를 찾을 수 없습니다." });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return JsonConvert.SerializeObject(new { Success = false, Message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 메모장 추가
|
||||
/// </summary>
|
||||
public string Note_Add(string pdate, string title, string uid, string description, string description2, bool share, string guid)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(info.Login.no) || string.IsNullOrEmpty(info.Login.gcode))
|
||||
{
|
||||
return JsonConvert.SerializeObject(new { Success = false, Message = "로그인이 필요합니다." });
|
||||
}
|
||||
|
||||
// GUID가 비어있으면 생성
|
||||
if (string.IsNullOrEmpty(guid))
|
||||
{
|
||||
guid = Guid.NewGuid().ToString();
|
||||
}
|
||||
|
||||
var cs = Properties.Settings.Default.gwcs;
|
||||
using (var conn = new SqlConnection(cs))
|
||||
{
|
||||
conn.Open();
|
||||
var cmd = new SqlCommand(@"
|
||||
INSERT INTO EETGW_Note (gcode, pdate, title, uid, description, description2, share, wuid, wdate, guid)
|
||||
VALUES (@gcode, @pdate, @title, @uid, @description, @description2, @share, @wuid, @wdate, @guid);
|
||||
SELECT CAST(SCOPE_IDENTITY() AS INT);", conn);
|
||||
|
||||
cmd.Parameters.Add("@gcode", SqlDbType.VarChar).Value = info.Login.gcode;
|
||||
cmd.Parameters.Add("@pdate", SqlDbType.VarChar).Value = pdate;
|
||||
cmd.Parameters.Add("@title", SqlDbType.NVarChar).Value = title;
|
||||
cmd.Parameters.Add("@uid", SqlDbType.VarChar).Value = uid;
|
||||
cmd.Parameters.Add("@description", SqlDbType.NVarChar).Value = description ?? "";
|
||||
cmd.Parameters.Add("@description2", SqlDbType.NText).Value = description2 ?? "";
|
||||
cmd.Parameters.Add("@share", SqlDbType.Bit).Value = share;
|
||||
cmd.Parameters.Add("@wuid", SqlDbType.VarChar).Value = info.Login.no;
|
||||
cmd.Parameters.Add("@wdate", SqlDbType.DateTime).Value = DateTime.Now;
|
||||
cmd.Parameters.Add("@guid", SqlDbType.VarChar).Value = guid;
|
||||
|
||||
var newIdx = cmd.ExecuteScalar();
|
||||
return JsonConvert.SerializeObject(new { Success = true, Idx = newIdx });
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return JsonConvert.SerializeObject(new { Success = false, Message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 메모장 수정
|
||||
/// </summary>
|
||||
public string Note_Edit(int idx, string pdate, string title, string uid, string description, string description2, bool share, string guid)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(info.Login.no) || string.IsNullOrEmpty(info.Login.gcode))
|
||||
{
|
||||
return JsonConvert.SerializeObject(new { Success = false, Message = "로그인이 필요합니다." });
|
||||
}
|
||||
|
||||
var connStr = Properties.Settings.Default.CS;
|
||||
using (var conn = new SqlConnection(connStr))
|
||||
{
|
||||
conn.Open();
|
||||
|
||||
// 권한 체크: 자신의 메모이거나 관리자인 경우만 수정 가능
|
||||
int curLevel = Math.Max(info.Login.level, DBM.getAuth(DBM.eAuthType.jobreport));
|
||||
|
||||
var checkCmd = new SqlCommand(@"
|
||||
SELECT uid FROM EETGW_Note WHERE gcode = @gcode AND idx = @idx", conn);
|
||||
checkCmd.Parameters.Add("@gcode", SqlDbType.VarChar).Value = info.Login.gcode;
|
||||
checkCmd.Parameters.Add("@idx", SqlDbType.Int).Value = idx;
|
||||
|
||||
var originalUid = checkCmd.ExecuteScalar()?.ToString();
|
||||
if (originalUid != info.Login.no && curLevel < 5)
|
||||
{
|
||||
return JsonConvert.SerializeObject(new { Success = false, Message = "타인의 자료는 수정할 수 없습니다." });
|
||||
}
|
||||
|
||||
var cmd = new SqlCommand(@"
|
||||
UPDATE EETGW_Note
|
||||
SET pdate = @pdate, title = @title, uid = @uid,
|
||||
description = @description, description2 = @description2,
|
||||
share = @share, guid = @guid, wuid = @wuid, wdate = @wdate
|
||||
WHERE gcode = @gcode AND idx = @idx", conn);
|
||||
|
||||
cmd.Parameters.Add("@gcode", SqlDbType.VarChar).Value = info.Login.gcode;
|
||||
cmd.Parameters.Add("@idx", SqlDbType.Int).Value = idx;
|
||||
cmd.Parameters.Add("@pdate", SqlDbType.VarChar).Value = pdate;
|
||||
cmd.Parameters.Add("@title", SqlDbType.NVarChar).Value = title;
|
||||
cmd.Parameters.Add("@uid", SqlDbType.VarChar).Value = uid;
|
||||
cmd.Parameters.Add("@description", SqlDbType.NVarChar).Value = description ?? "";
|
||||
cmd.Parameters.Add("@description2", SqlDbType.NText).Value = description2 ?? "";
|
||||
cmd.Parameters.Add("@share", SqlDbType.Bit).Value = share;
|
||||
cmd.Parameters.Add("@guid", SqlDbType.VarChar).Value = guid;
|
||||
cmd.Parameters.Add("@wuid", SqlDbType.VarChar).Value = info.Login.no;
|
||||
cmd.Parameters.Add("@wdate", SqlDbType.DateTime).Value = DateTime.Now;
|
||||
|
||||
cmd.ExecuteNonQuery();
|
||||
return JsonConvert.SerializeObject(new { Success = true });
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return JsonConvert.SerializeObject(new { Success = false, Message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 멤모장 삭제
|
||||
/// </summary>
|
||||
public string Note_Delete(int idx)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(info.Login.no) || string.IsNullOrEmpty(info.Login.gcode))
|
||||
{
|
||||
return JsonConvert.SerializeObject(new { Success = false, Message = "로그인이 필요합니다." });
|
||||
}
|
||||
|
||||
var connStr = Properties.Settings.Default.CS;
|
||||
using (var conn = new SqlConnection(connStr))
|
||||
{
|
||||
conn.Open();
|
||||
|
||||
// 권한 체크: 자신의 메모이거나 관리자인 경우만 삭제 가능
|
||||
int curLevel = Math.Max(info.Login.level, DBM.getAuth(DBM.eAuthType.jobreport));
|
||||
|
||||
var checkCmd = new SqlCommand(@"
|
||||
SELECT uid FROM EETGW_Note WHERE gcode = @gcode AND idx = @idx", conn);
|
||||
checkCmd.Parameters.Add("@gcode", SqlDbType.VarChar).Value = info.Login.gcode;
|
||||
checkCmd.Parameters.Add("@idx", SqlDbType.Int).Value = idx;
|
||||
|
||||
var originalUid = checkCmd.ExecuteScalar()?.ToString();
|
||||
if (originalUid != info.Login.no && curLevel < 5)
|
||||
{
|
||||
return JsonConvert.SerializeObject(new { Success = false, Message = "타인의 자료는 삭제할 수 없습니다." });
|
||||
}
|
||||
|
||||
var cmd = new SqlCommand(@"
|
||||
DELETE FROM EETGW_Note
|
||||
WHERE gcode = @gcode AND idx = @idx", conn);
|
||||
|
||||
cmd.Parameters.Add("@gcode", SqlDbType.VarChar).Value = info.Login.gcode;
|
||||
cmd.Parameters.Add("@idx", SqlDbType.Int).Value = idx;
|
||||
|
||||
cmd.ExecuteNonQuery();
|
||||
return JsonConvert.SerializeObject(new { Success = true });
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return JsonConvert.SerializeObject(new { Success = false, Message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -836,6 +836,99 @@ namespace Project.Web
|
||||
break;
|
||||
|
||||
|
||||
// ===== Note API (메모장) =====
|
||||
case "NOTE_GET_LIST":
|
||||
{
|
||||
string startDate = json.startDate ?? "";
|
||||
string endDate = json.endDate ?? "";
|
||||
string uid = json.uid ?? "";
|
||||
string result = _bridge.Note_GetList(startDate, endDate, uid);
|
||||
var response = new { type = "NOTE_LIST_DATA", data = JsonConvert.DeserializeObject(result) };
|
||||
await Send(socket, JsonConvert.SerializeObject(response));
|
||||
}
|
||||
break;
|
||||
|
||||
case "NOTE_GET_DETAIL":
|
||||
{
|
||||
int idx = json.idx ?? 0;
|
||||
string result = _bridge.Note_GetDetail(idx);
|
||||
var response = new { type = "NOTE_DETAIL_DATA", data = JsonConvert.DeserializeObject(result) };
|
||||
await Send(socket, JsonConvert.SerializeObject(response));
|
||||
}
|
||||
break;
|
||||
|
||||
case "NOTE_ADD":
|
||||
{
|
||||
string pdate = json.pdate ?? "";
|
||||
string title = json.title ?? "";
|
||||
string uid = json.uid ?? "";
|
||||
string description = json.description ?? "";
|
||||
string description2 = json.description2 ?? "";
|
||||
bool share = json.share ?? false;
|
||||
string guid = json.guid ?? "";
|
||||
string result = _bridge.Note_Add(pdate, title, uid, description, description2, share, guid);
|
||||
var response = new { type = "NOTE_ADDED", data = JsonConvert.DeserializeObject(result) };
|
||||
await Send(socket, JsonConvert.SerializeObject(response));
|
||||
}
|
||||
break;
|
||||
|
||||
case "NOTE_EDIT":
|
||||
{
|
||||
int idx = json.idx ?? 0;
|
||||
string pdate = json.pdate ?? "";
|
||||
string title = json.title ?? "";
|
||||
string uid = json.uid ?? "";
|
||||
string description = json.description ?? "";
|
||||
string description2 = json.description2 ?? "";
|
||||
bool share = json.share ?? false;
|
||||
string guid = json.guid ?? "";
|
||||
string result = _bridge.Note_Edit(idx, pdate, title, uid, description, description2, share, guid);
|
||||
var response = new { type = "NOTE_EDITED", data = JsonConvert.DeserializeObject(result) };
|
||||
await Send(socket, JsonConvert.SerializeObject(response));
|
||||
}
|
||||
break;
|
||||
|
||||
case "NOTE_DELETE":
|
||||
{
|
||||
int idx = json.idx ?? 0;
|
||||
string result = _bridge.Note_Delete(idx);
|
||||
var response = new { type = "NOTE_DELETED", data = JsonConvert.DeserializeObject(result) };
|
||||
await Send(socket, JsonConvert.SerializeObject(response));
|
||||
}
|
||||
break;
|
||||
|
||||
// ===== Board API (게시판 - 패치내역 등) =====
|
||||
case "BOARD_GET_LIST":
|
||||
{
|
||||
int bidx = json.bidx ?? 5;
|
||||
string searchKey = json.searchKey ?? "";
|
||||
string result = _bridge.Board_GetList(bidx, searchKey);
|
||||
var response = new { type = "BOARD_LIST_DATA", data = JsonConvert.DeserializeObject(result) };
|
||||
await Send(socket, JsonConvert.SerializeObject(response));
|
||||
}
|
||||
break;
|
||||
|
||||
case "BOARD_GET_DETAIL":
|
||||
{
|
||||
int idx = json.idx ?? 0;
|
||||
string result = _bridge.Board_GetDetail(idx);
|
||||
var response = new { type = "BOARD_DETAIL_DATA", data = JsonConvert.DeserializeObject(result) };
|
||||
await Send(socket, JsonConvert.SerializeObject(response));
|
||||
}
|
||||
break;
|
||||
|
||||
// ===== Mail API (메일 발신 내역) =====
|
||||
case "MAIL_GET_LIST":
|
||||
{
|
||||
string startDate = json.startDate ?? "";
|
||||
string endDate = json.endDate ?? "";
|
||||
string searchKey = json.searchKey ?? "";
|
||||
string result = _bridge.Mail_GetList(startDate, endDate, searchKey);
|
||||
var response = new { type = "MAIL_LIST_DATA", data = JsonConvert.DeserializeObject(result) };
|
||||
await Send(socket, JsonConvert.SerializeObject(response));
|
||||
}
|
||||
break;
|
||||
|
||||
// ===== Holiday API (월별근무표) =====
|
||||
case "HOLIDAY_GET_LIST":
|
||||
{
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
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 } from '@/pages';
|
||||
import { Dashboard, Todo, Kuntae, Jobreport, Project, Login, CommonCodePage, ItemsPage, UserListPage, MonthlyWorkPage, MailFormPage, UserAuthPage, Note } from '@/pages';
|
||||
import { PatchList } from '@/pages/PatchList';
|
||||
import { MailList } from '@/pages/MailList';
|
||||
import { comms } from '@/communication';
|
||||
import { UserInfo } from '@/types';
|
||||
import { Loader2 } from 'lucide-react';
|
||||
@@ -92,6 +94,9 @@ export default function App() {
|
||||
<Route path="/user/auth" element={<UserAuthPage />} />
|
||||
<Route path="/monthly-work" element={<MonthlyWorkPage />} />
|
||||
<Route path="/mail-form" element={<MailFormPage />} />
|
||||
<Route path="/note" element={<Note />} />
|
||||
<Route path="/patch-list" element={<PatchList />} />
|
||||
<Route path="/mail-list" element={<MailList />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
{/* Tailwind Breakpoint Indicator - 개발용 */}
|
||||
|
||||
@@ -1,3 +1,48 @@
|
||||
import type {
|
||||
ApiResponse,
|
||||
LoginStatusResponse,
|
||||
CheckAuthResponse,
|
||||
TodoModel,
|
||||
PurchaseItem,
|
||||
PurchaseCount,
|
||||
JobReportItem,
|
||||
JobReportUser,
|
||||
JobReportPermission,
|
||||
JobReportTypeItem,
|
||||
JobTypeItem,
|
||||
MailFormItem,
|
||||
UserGroupItem,
|
||||
PermissionInfo,
|
||||
AuthItem,
|
||||
AuthFieldInfo,
|
||||
AuthType,
|
||||
MyAuthInfo,
|
||||
ProjectSearchItem,
|
||||
NoteItem,
|
||||
KuntaeModel,
|
||||
HolydayBalance,
|
||||
HolyUser,
|
||||
HolyRequestUser,
|
||||
LoginResult,
|
||||
UserGroup,
|
||||
PreviousLoginInfo,
|
||||
UserInfoDetail,
|
||||
CommonCodeGroup,
|
||||
CommonCode,
|
||||
ItemInfo,
|
||||
ItemDetail,
|
||||
SupplierStaff,
|
||||
PurchaseHistoryItem,
|
||||
UserLevelInfo,
|
||||
GroupUser,
|
||||
UserFullData,
|
||||
AppVersionInfo,
|
||||
HolidayItem,
|
||||
MachineBridgeInterface,
|
||||
BoardItem,
|
||||
MailItem,
|
||||
} from '@/types';
|
||||
|
||||
// WebView2 환경 감지
|
||||
const isWebView = typeof window !== 'undefined' &&
|
||||
window.chrome?.webview?.hostObjects !== undefined;
|
||||
@@ -799,7 +844,7 @@ class CommunicationLayer {
|
||||
|
||||
public async getJobReportTypeList(sd: string, ed: string, uid: string = ''): Promise<ApiResponse<JobReportTypeItem[]>> {
|
||||
if (isWebView && machine) {
|
||||
const result = await machine.Jobreport_GetTypeList(sd, ed, uid);
|
||||
const result = await machine.Jobreport_GetList(sd, ed, uid, '', '');
|
||||
return JSON.parse(result);
|
||||
} else {
|
||||
return this.wsRequest<ApiResponse<JobReportTypeItem[]>>('JOBREPORT_GET_TYPE_LIST', 'JOBREPORT_TYPE_LIST_DATA', { sd, ed, uid });
|
||||
@@ -1080,6 +1125,159 @@ class CommunicationLayer {
|
||||
return this.wsRequest<ApiResponse<{ idx: number, name: string, status: string }[]>>('PROJECT_GET_USER_PROJECTS', 'PROJECT_USER_PROJECTS_DATA');
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Note API (메모장) =====
|
||||
|
||||
/**
|
||||
* 메모장 목록 조회
|
||||
* @param startDate 시작일 (yyyy-MM-dd)
|
||||
* @param endDate 종료일 (yyyy-MM-dd)
|
||||
* @param uid 사용자 ID (빈 문자열이면 전체)
|
||||
* @returns ApiResponse<NoteItem[]>
|
||||
*/
|
||||
public async getNoteList(startDate: string, endDate: string, uid: string = ''): Promise<ApiResponse<NoteItem[]>> {
|
||||
if (isWebView && machine) {
|
||||
const result = await machine.Note_GetList(startDate, endDate, uid);
|
||||
return JSON.parse(result);
|
||||
} else {
|
||||
return this.wsRequest<ApiResponse<NoteItem[]>>('NOTE_GET_LIST', 'NOTE_LIST_DATA', { startDate, endDate, uid });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 메모장 상세 조회
|
||||
* @param idx 메모 인덱스
|
||||
* @returns ApiResponse<NoteItem>
|
||||
*/
|
||||
public async getNoteDetail(idx: number): Promise<ApiResponse<NoteItem>> {
|
||||
if (isWebView && machine) {
|
||||
const result = await machine.Note_GetDetail(idx);
|
||||
return JSON.parse(result);
|
||||
} else {
|
||||
return this.wsRequest<ApiResponse<NoteItem>>('NOTE_GET_DETAIL', 'NOTE_DETAIL_DATA', { idx });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 메모장 추가
|
||||
* @param pdate 날짜 (yyyy-MM-dd)
|
||||
* @param title 제목
|
||||
* @param uid 작성자 ID
|
||||
* @param description 내용 (plain text)
|
||||
* @param description2 내용 (RTF - not used in web)
|
||||
* @param share 공유 여부
|
||||
* @param guid 폴더 GUID (빈 문자열이면 자동 생성)
|
||||
* @returns ApiResponse with new Idx
|
||||
*/
|
||||
public async addNote(
|
||||
pdate: string,
|
||||
title: string,
|
||||
uid: string,
|
||||
description: string,
|
||||
description2: string = '',
|
||||
share: boolean = false,
|
||||
guid: string = ''
|
||||
): Promise<ApiResponse<{ Idx: number }>> {
|
||||
if (isWebView && machine) {
|
||||
const result = await machine.Note_Add(pdate, title, uid, description, description2, share, guid);
|
||||
return JSON.parse(result);
|
||||
} else {
|
||||
return this.wsRequest<ApiResponse<{ Idx: number }>>('NOTE_ADD', 'NOTE_ADDED', {
|
||||
pdate, title, uid, description, description2, share, guid
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 메모장 수정
|
||||
* @param idx 메모 인덱스
|
||||
* @param pdate 날짜 (yyyy-MM-dd)
|
||||
* @param title 제목
|
||||
* @param uid 작성자 ID
|
||||
* @param description 내용 (plain text)
|
||||
* @param description2 내용 (RTF - not used in web)
|
||||
* @param share 공유 여부
|
||||
* @param guid 폴더 GUID
|
||||
* @returns ApiResponse
|
||||
*/
|
||||
public async editNote(
|
||||
idx: number,
|
||||
pdate: string,
|
||||
title: string,
|
||||
uid: string,
|
||||
description: string,
|
||||
description2: string = '',
|
||||
share: boolean = false,
|
||||
guid: string = ''
|
||||
): Promise<ApiResponse> {
|
||||
if (isWebView && machine) {
|
||||
const result = await machine.Note_Edit(idx, pdate, title, uid, description, description2, share, guid);
|
||||
return JSON.parse(result);
|
||||
} else {
|
||||
return this.wsRequest<ApiResponse>('NOTE_EDIT', 'NOTE_EDITED', {
|
||||
idx, pdate, title, uid, description, description2, share, guid
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 메모장 삭제
|
||||
* @param idx 메모 인덱스
|
||||
* @returns ApiResponse
|
||||
*/
|
||||
public async deleteNote(idx: number): Promise<ApiResponse> {
|
||||
if (isWebView && machine) {
|
||||
const result = await machine.Note_Delete(idx);
|
||||
return JSON.parse(result);
|
||||
} else {
|
||||
return this.wsRequest<ApiResponse>('NOTE_DELETE', 'NOTE_DELETED', { idx });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 게시판 목록 조회
|
||||
* @param bidx 게시판 인덱스 (5=패치내역)
|
||||
* @param searchKey 검색어
|
||||
* @returns ApiResponse<BoardItem[]>
|
||||
*/
|
||||
public async getBoardList(bidx: number, searchKey: string = ''): Promise<ApiResponse<BoardItem[]>> {
|
||||
if (isWebView && machine) {
|
||||
const result = await machine.Board_GetList(bidx, searchKey);
|
||||
return JSON.parse(result);
|
||||
} else {
|
||||
return this.wsRequest<ApiResponse<BoardItem[]>>('BOARD_GET_LIST', 'BOARD_LIST_DATA', { bidx, searchKey });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 게시판 상세 조회
|
||||
* @param idx 게시판 글 인덱스
|
||||
* @returns ApiResponse<BoardItem>
|
||||
*/
|
||||
public async getBoardDetail(idx: number): Promise<ApiResponse<BoardItem>> {
|
||||
if (isWebView && machine) {
|
||||
const result = await machine.Board_GetDetail(idx);
|
||||
return JSON.parse(result);
|
||||
} else {
|
||||
return this.wsRequest<ApiResponse<BoardItem>>('BOARD_GET_DETAIL', 'BOARD_DETAIL_DATA', { idx });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 메일 발신 내역 조회
|
||||
* @param startDate 시작일 (yyyy-MM-dd)
|
||||
* @param endDate 종료일 (yyyy-MM-dd)
|
||||
* @param searchKey 검색어
|
||||
* @returns ApiResponse<MailItem[]>
|
||||
*/
|
||||
public async getMailList(startDate: string, endDate: string, searchKey: string = ''): Promise<ApiResponse<MailItem[]>> {
|
||||
if (isWebView && machine) {
|
||||
const result = await machine.Mail_GetList(startDate, endDate, searchKey);
|
||||
return JSON.parse(result);
|
||||
} else {
|
||||
return this.wsRequest<ApiResponse<MailItem[]>>('MAIL_GET_LIST', 'MAIL_LIST_DATA', { startDate, endDate, searchKey });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const comms = new CommunicationLayer();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { X, ChevronLeft, ChevronRight, Download } from 'lucide-react';
|
||||
import { comms } from '@/communication';
|
||||
import { JobReportDayItem, HolidayItem } from '@/types';
|
||||
import { HolidayItem } from '@/types';
|
||||
|
||||
interface JobReportDayDialogProps {
|
||||
isOpen: boolean;
|
||||
@@ -32,8 +32,6 @@ export function JobReportDayDialog({ isOpen, onClose, initialMonth }: JobReportD
|
||||
const [dayColumns, setDayColumns] = useState<DayColumn[]>([]);
|
||||
const [userRows, setUserRows] = useState<UserRow[]>([]);
|
||||
const [currentUserId, setCurrentUserId] = useState<string>('');
|
||||
const [currentUserLevel, setCurrentUserLevel] = useState<number>(0);
|
||||
const [authLevel, setAuthLevel] = useState<number>(0);
|
||||
const [canViewAll, setCanViewAll] = useState<boolean>(false);
|
||||
|
||||
// 요일 배열
|
||||
@@ -54,12 +52,10 @@ export function JobReportDayDialog({ isOpen, onClose, initialMonth }: JobReportD
|
||||
const userId = loginStatus.User.Id;
|
||||
const userLevel = loginStatus.User.Level || 0;
|
||||
setCurrentUserId(userId);
|
||||
setCurrentUserLevel(userLevel);
|
||||
|
||||
// 업무일지(jobreport) 권한 가져오기
|
||||
const authResponse = await comms.checkAuth('jobreport', 5);
|
||||
const jobReportAuthLevel = authResponse.EffectiveLevel || 0;
|
||||
setAuthLevel(jobReportAuthLevel);
|
||||
|
||||
// 유효 권한 레벨 = Max(사용자레벨, 권한레벨)
|
||||
const effectiveLevel = Math.max(userLevel, jobReportAuthLevel);
|
||||
|
||||
@@ -115,6 +115,15 @@ const dropdownMenus: DropdownMenuConfig[] = [
|
||||
{ type: 'link', path: '/mail-form', icon: Mail, label: '메일양식' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '문서',
|
||||
icon: FileText,
|
||||
items: [
|
||||
{ type: 'link', path: '/note', icon: FileText, label: '메모장' },
|
||||
{ type: 'link', path: '/patch-list', icon: FileText, label: '패치 내역' },
|
||||
{ type: 'link', path: '/mail-list', icon: Mail, label: '메일 내역' },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
function DropdownNavMenu({
|
||||
|
||||
283
Project/frontend/src/components/note/NoteEditModal.tsx
Normal file
283
Project/frontend/src/components/note/NoteEditModal.tsx
Normal file
@@ -0,0 +1,283 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { X, Save, User, Calendar } from 'lucide-react';
|
||||
import { NoteItem } from '@/types';
|
||||
import { comms } from '@/communication';
|
||||
|
||||
interface NoteEditModalProps {
|
||||
isOpen: boolean;
|
||||
editingItem: NoteItem | null;
|
||||
processing: boolean;
|
||||
onClose: () => void;
|
||||
onSave: (formData: {
|
||||
pdate: string;
|
||||
title: string;
|
||||
uid: string;
|
||||
description: string;
|
||||
share: boolean;
|
||||
guid: string;
|
||||
}) => void;
|
||||
initialEditMode?: boolean;
|
||||
}
|
||||
|
||||
export function NoteEditModal({
|
||||
isOpen,
|
||||
editingItem,
|
||||
processing,
|
||||
onClose,
|
||||
onSave,
|
||||
initialEditMode = false,
|
||||
}: NoteEditModalProps) {
|
||||
const [pdate, setPdate] = useState('');
|
||||
const [title, setTitle] = useState('');
|
||||
const [uid, setUid] = useState('');
|
||||
const [description, setDescription] = useState('');
|
||||
const [share, setShare] = useState(false);
|
||||
const [guid, setGuid] = useState('');
|
||||
const [isEditMode, setIsEditMode] = useState(false);
|
||||
|
||||
// 현재 로그인 사용자 정보 로드
|
||||
const [currentUserId, setCurrentUserId] = useState('');
|
||||
const [currentUserLevel, setCurrentUserLevel] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
const loadUserInfo = async () => {
|
||||
try {
|
||||
const loginStatus = await comms.checkLoginStatus();
|
||||
if (loginStatus.Success && loginStatus.IsLoggedIn && loginStatus.User) {
|
||||
setCurrentUserId(loginStatus.User.Id);
|
||||
setCurrentUserLevel(loginStatus.User.Level);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('로그인 정보 로드 오류:', error);
|
||||
}
|
||||
};
|
||||
loadUserInfo();
|
||||
}, []);
|
||||
|
||||
// 모달이 열릴 때 폼 데이터 초기화
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
if (editingItem) {
|
||||
// 기존 메모
|
||||
setPdate(editingItem.pdate ? editingItem.pdate.split('T')[0] : '');
|
||||
setTitle(editingItem.title || '');
|
||||
setUid(editingItem.uid || currentUserId);
|
||||
setDescription(editingItem.description || '');
|
||||
setShare(editingItem.share || false);
|
||||
setGuid(editingItem.guid || '');
|
||||
setIsEditMode(initialEditMode);
|
||||
} else {
|
||||
// 신규 메모 - 편집 모드로 시작
|
||||
setPdate(new Date().toISOString().split('T')[0]);
|
||||
setTitle('');
|
||||
setUid(currentUserId);
|
||||
setDescription('');
|
||||
setShare(false);
|
||||
setGuid('');
|
||||
setIsEditMode(true);
|
||||
}
|
||||
}
|
||||
}, [isOpen, editingItem, currentUserId, initialEditMode]);
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
onSave({ pdate, title, uid, description, share, guid });
|
||||
};
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
// 권한 체크: 자신의 메모이거나 관리자만 수정 가능
|
||||
const canEdit = !editingItem || editingItem.uid === currentUserId || currentUserLevel >= 5;
|
||||
const canChangeUser = currentUserLevel >= 5;
|
||||
const canChangeShare = !editingItem || editingItem.uid === currentUserId;
|
||||
|
||||
return createPortal(
|
||||
<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-5xl 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">
|
||||
{!editingItem ? '새 메모 작성' : '메모 편집'}
|
||||
</h2>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="text-white/50 hover:text-white transition-colors"
|
||||
>
|
||||
<X className="w-6 h-6" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 본문 - 좌우 레이아웃 */}
|
||||
<form onSubmit={handleSubmit} className="overflow-y-auto max-h-[calc(90vh-140px)]">
|
||||
<div className="flex gap-6 p-6">
|
||||
{/* 좌측 - 정보 영역 */}
|
||||
<div className="w-64 flex-shrink-0 space-y-4">
|
||||
{/* 날짜 */}
|
||||
<div>
|
||||
<label className="flex items-center text-sm font-medium text-white/70 mb-2">
|
||||
<Calendar className="w-4 h-4 mr-2" />
|
||||
날짜 {isEditMode && '*'}
|
||||
</label>
|
||||
{isEditMode ? (
|
||||
<input
|
||||
type="date"
|
||||
value={pdate}
|
||||
onChange={(e) => setPdate(e.target.value)}
|
||||
required
|
||||
disabled={!canEdit}
|
||||
className="w-full h-10 bg-white/20 border border-white/30 rounded-lg px-3 text-white text-sm focus:outline-none focus:ring-2 focus:ring-primary-400 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
/>
|
||||
) : (
|
||||
<div className="text-white text-sm">{pdate}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 작성자 */}
|
||||
<div>
|
||||
<label className="flex items-center text-sm font-medium text-white/70 mb-2">
|
||||
<User className="w-4 h-4 mr-2" />
|
||||
작성자 {isEditMode && '*'}
|
||||
</label>
|
||||
{isEditMode ? (
|
||||
<>
|
||||
<input
|
||||
type="text"
|
||||
value={uid}
|
||||
onChange={(e) => setUid(e.target.value)}
|
||||
required
|
||||
disabled={!canEdit || !canChangeUser}
|
||||
className="w-full h-10 bg-white/20 border border-white/30 rounded-lg px-3 text-white text-sm focus:outline-none focus:ring-2 focus:ring-primary-400 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
placeholder="사용자 ID"
|
||||
/>
|
||||
{!canChangeUser && (
|
||||
<p className="text-xs text-white/50 mt-1">
|
||||
관리자만 변경 가능
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<div className="text-white text-sm">{uid}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 공유 여부 */}
|
||||
<div>
|
||||
<label className="text-sm font-medium text-white/70 mb-2 block">
|
||||
공유 설정
|
||||
</label>
|
||||
{isEditMode ? (
|
||||
<>
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="note-share"
|
||||
checked={share}
|
||||
onChange={(e) => setShare(e.target.checked)}
|
||||
disabled={!canEdit || !canChangeShare}
|
||||
className="w-4 h-4 bg-white/20 border border-white/30 rounded focus:ring-2 focus:ring-primary-400 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
/>
|
||||
<label htmlFor="note-share" className="text-sm text-white/70 cursor-pointer">
|
||||
공유
|
||||
</label>
|
||||
</div>
|
||||
{!canChangeShare && (
|
||||
<p className="text-xs text-white/50 mt-1">
|
||||
본인 메모만 변경 가능
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<div className="text-white text-sm">{share ? '공유됨' : '비공유'}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{!canEdit && isEditMode && (
|
||||
<div className="bg-red-500/20 border border-red-500/50 rounded-lg p-3 text-red-300 text-xs">
|
||||
타인의 자료는 수정할 수 없습니다.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 우측 - 제목 및 내용 영역 */}
|
||||
<div className="flex-1 space-y-4">
|
||||
{/* 제목 */}
|
||||
<div>
|
||||
<label className="text-sm font-medium text-white/70 mb-2 block">
|
||||
제목 {isEditMode && '*'}
|
||||
</label>
|
||||
{isEditMode ? (
|
||||
<input
|
||||
type="text"
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
required
|
||||
disabled={!canEdit}
|
||||
className="w-full h-10 bg-white/20 border border-white/30 rounded-lg px-3 text-white focus:outline-none focus:ring-2 focus:ring-primary-400 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
placeholder="메모 제목을 입력하세요"
|
||||
/>
|
||||
) : (
|
||||
<div className="text-white text-lg font-semibold">{title}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 내용 */}
|
||||
<div className="flex-1">
|
||||
<label className="text-sm font-medium text-white/70 mb-2 block">
|
||||
내용
|
||||
</label>
|
||||
{isEditMode ? (
|
||||
<textarea
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
disabled={!canEdit}
|
||||
rows={18}
|
||||
className="w-full bg-white/20 border border-white/30 rounded-lg p-3 text-white focus:outline-none focus:ring-2 focus:ring-primary-400 disabled:opacity-50 disabled:cursor-not-allowed resize-none"
|
||||
placeholder="메모 내용을 입력하세요"
|
||||
/>
|
||||
) : (
|
||||
<div className="bg-white/10 border border-white/20 rounded-lg p-4 text-white whitespace-pre-wrap min-h-[400px]">
|
||||
{description || '내용이 없습니다.'}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{/* 하단 버튼 */}
|
||||
<div className="flex items-center justify-end gap-3 px-6 py-4 border-t border-white/10 bg-white/5">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
className="px-4 py-2 rounded-lg bg-white/10 hover:bg-white/20 text-white transition-colors"
|
||||
disabled={processing}
|
||||
>
|
||||
닫기
|
||||
</button>
|
||||
{isEditMode && canEdit && (
|
||||
<button
|
||||
type="submit"
|
||||
onClick={handleSubmit}
|
||||
disabled={processing}
|
||||
className="px-4 py-2 rounded-lg bg-primary-500 hover:bg-primary-600 text-white transition-colors flex items-center disabled:opacity-50"
|
||||
>
|
||||
{processing ? (
|
||||
<>
|
||||
<div className="w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin mr-2"></div>
|
||||
처리 중...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Save className="w-4 h-4 mr-2" />
|
||||
저장
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
document.body
|
||||
);
|
||||
}
|
||||
74
Project/frontend/src/components/note/NoteViewModal.tsx
Normal file
74
Project/frontend/src/components/note/NoteViewModal.tsx
Normal file
@@ -0,0 +1,74 @@
|
||||
import { createPortal } from 'react-dom';
|
||||
import { X, Trash2 } from 'lucide-react';
|
||||
import { NoteItem } from '@/types';
|
||||
|
||||
interface NoteViewModalProps {
|
||||
isOpen: boolean;
|
||||
note: NoteItem | null;
|
||||
onClose: () => void;
|
||||
onEdit: (note: NoteItem) => void;
|
||||
onDelete?: (note: NoteItem) => void;
|
||||
}
|
||||
|
||||
export function NoteViewModal({ isOpen, note, onClose, onEdit, onDelete }: NoteViewModalProps) {
|
||||
if (!isOpen || !note) return null;
|
||||
|
||||
const handleDelete = () => {
|
||||
if (window.confirm('정말 삭제하시겠습니까?')) {
|
||||
onDelete?.(note);
|
||||
}
|
||||
};
|
||||
|
||||
return createPortal(
|
||||
<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-2xl max-h-[80vh] 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">{note.title || '제목 없음'}</h2>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="text-white/50 hover:text-white transition-colors"
|
||||
>
|
||||
<X className="w-6 h-6" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 본문 - 메모 내용만 표시 */}
|
||||
<div className="overflow-y-auto max-h-[calc(80vh-140px)] p-6">
|
||||
<div className="bg-white/10 border border-white/20 rounded-lg p-4 text-white whitespace-pre-wrap min-h-[300px]">
|
||||
{note.description || '내용이 없습니다.'}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 하단 버튼 */}
|
||||
<div className="flex items-center justify-between px-6 py-4 border-t border-white/10 bg-white/5">
|
||||
<button
|
||||
onClick={handleDelete}
|
||||
className="px-4 py-2 rounded-lg bg-danger-500 hover:bg-danger-600 text-white transition-colors flex items-center gap-2"
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
삭제
|
||||
</button>
|
||||
<div className="flex items-center gap-3">
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="px-4 py-2 rounded-lg bg-white/10 hover:bg-white/20 text-white transition-colors"
|
||||
>
|
||||
닫기
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
onClose();
|
||||
onEdit(note);
|
||||
}}
|
||||
className="px-4 py-2 bg-success-500 hover:bg-success-600 text-white rounded-lg transition-colors"
|
||||
>
|
||||
편집
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
document.body
|
||||
);
|
||||
}
|
||||
@@ -9,9 +9,20 @@ import {
|
||||
RefreshCw,
|
||||
ClipboardList,
|
||||
Clock,
|
||||
FileText,
|
||||
Share2,
|
||||
Lock,
|
||||
List,
|
||||
Plus,
|
||||
X,
|
||||
Loader2,
|
||||
Edit2,
|
||||
Trash2,
|
||||
} from 'lucide-react';
|
||||
import { comms } from '@/communication';
|
||||
import { TodoModel, PurchaseItem } from '@/types';
|
||||
import { TodoModel, TodoStatus, TodoPriority, PurchaseItem, NoteItem, JobReportItem } from '@/types';
|
||||
import { NoteViewModal } from '@/components/note/NoteViewModal';
|
||||
import { NoteEditModal } from '@/components/note/NoteEditModal';
|
||||
|
||||
interface StatCardProps {
|
||||
title: string;
|
||||
@@ -55,10 +66,31 @@ export function Dashboard() {
|
||||
const [urgentTodos, setUrgentTodos] = useState<TodoModel[]>([]);
|
||||
const [purchaseNRList, setPurchaseNRList] = useState<PurchaseItem[]>([]);
|
||||
const [purchaseCRList, setPurchaseCRList] = useState<PurchaseItem[]>([]);
|
||||
const [recentNotes, setRecentNotes] = useState<NoteItem[]>([]);
|
||||
|
||||
// 모달 상태
|
||||
const [showNRModal, setShowNRModal] = useState(false);
|
||||
const [showCRModal, setShowCRModal] = useState(false);
|
||||
const [showNoteModal, setShowNoteModal] = useState(false);
|
||||
const [showNoteEditModal, setShowNoteEditModal] = useState(false);
|
||||
const [showNoteAddModal, setShowNoteAddModal] = useState(false);
|
||||
const [selectedNote, setSelectedNote] = useState<NoteItem | null>(null);
|
||||
const [editingNote, setEditingNote] = useState<NoteItem | null>(null);
|
||||
const [processing, setProcessing] = useState(false);
|
||||
|
||||
// 할일 추가 모달 상태
|
||||
const [showTodoAddModal, setShowTodoAddModal] = useState(false);
|
||||
const [showTodoEditModal, setShowTodoEditModal] = useState(false);
|
||||
const [editingTodo, setEditingTodo] = useState<TodoModel | null>(null);
|
||||
const [todoFormData, setTodoFormData] = useState({
|
||||
title: '',
|
||||
remark: '',
|
||||
expire: '',
|
||||
seqno: 0 as TodoPriority,
|
||||
flag: false,
|
||||
request: '',
|
||||
status: '0' as TodoStatus,
|
||||
});
|
||||
|
||||
const loadDashboardData = useCallback(async () => {
|
||||
try {
|
||||
@@ -83,11 +115,13 @@ export function Dashboard() {
|
||||
urgentTodosResponse,
|
||||
allTodosResponse,
|
||||
jobreportResponse,
|
||||
notesResponse,
|
||||
] = await Promise.all([
|
||||
comms.getPurchaseWaitCount(),
|
||||
comms.getUrgentTodos(),
|
||||
comms.getTodos(),
|
||||
comms.getJobReportList(todayStr, todayStr, currentUserId, ''),
|
||||
comms.getNoteList('2000-01-01', todayStr, ''),
|
||||
]);
|
||||
|
||||
setPurchaseNR(purchaseCount.NR);
|
||||
@@ -99,17 +133,22 @@ export function Dashboard() {
|
||||
|
||||
if (allTodosResponse.Success && allTodosResponse.Data) {
|
||||
// 진행, 대기 상태의 할일만 카운트 (보류, 취소 제외)
|
||||
const pendingCount = allTodosResponse.Data.filter(t => t.status === '0' || t.status === '1').length;
|
||||
const pendingCount = allTodosResponse.Data.filter((t: TodoModel) => t.status === '0' || t.status === '1').length;
|
||||
setTodoCount(pendingCount);
|
||||
}
|
||||
|
||||
// 오늘 업무일지 작성시간 계산
|
||||
if (jobreportResponse.Success && jobreportResponse.Data) {
|
||||
const totalHrs = jobreportResponse.Data.reduce((acc, item) => acc + (item.hrs || 0), 0);
|
||||
const totalHrs = jobreportResponse.Data.reduce((acc: number, item: JobReportItem) => acc + (item.hrs || 0), 0);
|
||||
setTodayWorkHrs(totalHrs);
|
||||
} else {
|
||||
setTodayWorkHrs(0);
|
||||
}
|
||||
|
||||
// 최근 메모 목록 (최대 10개)
|
||||
if (notesResponse.Success && notesResponse.Data) {
|
||||
setRecentNotes(notesResponse.Data.slice(0, 10));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('대시보드 데이터 로드 오류:', error);
|
||||
} finally {
|
||||
@@ -199,23 +238,263 @@ export function Dashboard() {
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6 animate-fade-in">
|
||||
{/* 헤더 */}
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="text-2xl font-bold text-white">오늘의 현황</h2>
|
||||
<button
|
||||
onClick={handleRefresh}
|
||||
disabled={refreshing}
|
||||
className="flex items-center space-x-2 px-4 py-2 glass-effect rounded-lg text-white/70 hover:text-white transition-colors"
|
||||
>
|
||||
<RefreshCw className={`w-4 h-4 ${refreshing ? 'animate-spin' : ''}`} />
|
||||
<span>새로고침</span>
|
||||
</button>
|
||||
</div>
|
||||
const handleNoteClick = async (note: NoteItem) => {
|
||||
try {
|
||||
const response = await comms.getNoteDetail(note.idx);
|
||||
if (response.Success && response.Data) {
|
||||
setSelectedNote(response.Data);
|
||||
setShowNoteModal(true);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('메모 조회 오류:', error);
|
||||
}
|
||||
};
|
||||
|
||||
{/* 통계 카드 */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
|
||||
const handleNoteAdd = () => {
|
||||
setEditingNote(null);
|
||||
setShowNoteAddModal(true);
|
||||
};
|
||||
|
||||
const handleNoteDelete = async (note: NoteItem) => {
|
||||
setProcessing(true);
|
||||
try {
|
||||
const response = await comms.deleteNote(note.idx);
|
||||
if (response.Success) {
|
||||
setShowNoteModal(false);
|
||||
loadDashboardData();
|
||||
} else {
|
||||
alert(response.Message || '삭제에 실패했습니다.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('삭제 오류:', error);
|
||||
alert('서버 연결에 실패했습니다: ' + (error instanceof Error ? error.message : String(error)));
|
||||
} finally {
|
||||
setProcessing(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleTodoAdd = () => {
|
||||
setTodoFormData({
|
||||
title: '',
|
||||
remark: '',
|
||||
expire: '',
|
||||
seqno: 0,
|
||||
flag: false,
|
||||
request: '',
|
||||
status: '0',
|
||||
});
|
||||
setShowTodoAddModal(true);
|
||||
};
|
||||
|
||||
const handleTodoEdit = (todo: TodoModel) => {
|
||||
setEditingTodo(todo);
|
||||
setTodoFormData({
|
||||
title: todo.title || '',
|
||||
remark: todo.remark,
|
||||
expire: todo.expire || '',
|
||||
seqno: todo.seqno as TodoPriority,
|
||||
flag: todo.flag,
|
||||
request: todo.request || '',
|
||||
status: todo.status as TodoStatus,
|
||||
});
|
||||
setShowTodoEditModal(true);
|
||||
};
|
||||
|
||||
const handleTodoSave = async () => {
|
||||
if (!todoFormData.remark.trim()) {
|
||||
alert('할일 내용을 입력해주세요.');
|
||||
return;
|
||||
}
|
||||
|
||||
setProcessing(true);
|
||||
try {
|
||||
const response = await comms.createTodo(
|
||||
todoFormData.title,
|
||||
todoFormData.remark,
|
||||
todoFormData.expire || null,
|
||||
todoFormData.seqno,
|
||||
todoFormData.flag,
|
||||
todoFormData.request || null,
|
||||
todoFormData.status
|
||||
);
|
||||
|
||||
if (response.Success) {
|
||||
setShowTodoAddModal(false);
|
||||
loadDashboardData();
|
||||
} else {
|
||||
alert(response.Message || '할일 추가에 실패했습니다.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('할일 추가 오류:', error);
|
||||
alert('서버 연결에 실패했습니다: ' + (error instanceof Error ? error.message : String(error)));
|
||||
} finally {
|
||||
setProcessing(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleTodoUpdate = async () => {
|
||||
if (!editingTodo || !todoFormData.remark.trim()) {
|
||||
alert('할일 내용을 입력해주세요.');
|
||||
return;
|
||||
}
|
||||
|
||||
setProcessing(true);
|
||||
try {
|
||||
const response = await comms.updateTodo(
|
||||
editingTodo.idx,
|
||||
todoFormData.title,
|
||||
todoFormData.remark,
|
||||
todoFormData.expire || null,
|
||||
todoFormData.seqno,
|
||||
todoFormData.flag,
|
||||
todoFormData.request || null,
|
||||
todoFormData.status
|
||||
);
|
||||
|
||||
if (response.Success) {
|
||||
setShowTodoEditModal(false);
|
||||
setEditingTodo(null);
|
||||
loadDashboardData();
|
||||
} else {
|
||||
alert(response.Message || '할일 수정에 실패했습니다.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('할일 수정 오류:', error);
|
||||
alert('서버 연결에 실패했습니다: ' + (error instanceof Error ? error.message : String(error)));
|
||||
} finally {
|
||||
setProcessing(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleTodoDelete = async () => {
|
||||
if (!editingTodo) return;
|
||||
if (!confirm('정말로 이 할일을 삭제하시겠습니까?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
setProcessing(true);
|
||||
try {
|
||||
const response = await comms.deleteTodo(editingTodo.idx);
|
||||
if (response.Success) {
|
||||
setShowTodoEditModal(false);
|
||||
setEditingTodo(null);
|
||||
loadDashboardData();
|
||||
} else {
|
||||
alert(response.Message || '할일 삭제에 실패했습니다.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('할일 삭제 오류:', error);
|
||||
alert('서버 연결에 실패했습니다.');
|
||||
} finally {
|
||||
setProcessing(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleTodoComplete = async () => {
|
||||
if (!editingTodo) return;
|
||||
|
||||
setProcessing(true);
|
||||
try {
|
||||
const response = await comms.updateTodo(
|
||||
editingTodo.idx,
|
||||
editingTodo.title,
|
||||
editingTodo.remark,
|
||||
editingTodo.expire,
|
||||
editingTodo.seqno,
|
||||
editingTodo.flag,
|
||||
editingTodo.request,
|
||||
'5' // 완료 상태
|
||||
);
|
||||
|
||||
if (response.Success) {
|
||||
setShowTodoEditModal(false);
|
||||
setEditingTodo(null);
|
||||
loadDashboardData();
|
||||
} else {
|
||||
alert(response.Message || '할일 완료 처리에 실패했습니다.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('할일 완료 오류:', error);
|
||||
alert('서버 연결에 실패했습니다.');
|
||||
} finally {
|
||||
setProcessing(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleNoteSave = async (formData: {
|
||||
pdate: string;
|
||||
title: string;
|
||||
uid: string;
|
||||
description: string;
|
||||
share: boolean;
|
||||
guid: string;
|
||||
}) => {
|
||||
if (!formData.pdate) {
|
||||
alert('날짜를 입력해주세요.');
|
||||
return;
|
||||
}
|
||||
if (!formData.title.trim()) {
|
||||
alert('제목을 입력해주세요.');
|
||||
return;
|
||||
}
|
||||
|
||||
setProcessing(true);
|
||||
try {
|
||||
const response = editingNote
|
||||
? await comms.editNote(
|
||||
editingNote.idx,
|
||||
formData.pdate,
|
||||
formData.title,
|
||||
formData.uid,
|
||||
formData.description,
|
||||
'',
|
||||
formData.share,
|
||||
formData.guid
|
||||
)
|
||||
: await comms.addNote(
|
||||
formData.pdate,
|
||||
formData.title,
|
||||
formData.uid,
|
||||
formData.description,
|
||||
'',
|
||||
formData.share,
|
||||
formData.guid
|
||||
);
|
||||
|
||||
if (response.Success) {
|
||||
setShowNoteEditModal(false);
|
||||
setShowNoteAddModal(false);
|
||||
loadDashboardData();
|
||||
} else {
|
||||
alert(response.Message || '저장에 실패했습니다.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('저장 오류:', error);
|
||||
alert('서버 연결에 실패했습니다: ' + (error instanceof Error ? error.message : String(error)));
|
||||
} finally {
|
||||
setProcessing(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex gap-6 animate-fade-in">
|
||||
{/* 메인 컨텐츠 */}
|
||||
<div className="flex-1 space-y-6">
|
||||
{/* 헤더 */}
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="text-2xl font-bold text-white">오늘의 현황</h2>
|
||||
<button
|
||||
onClick={handleRefresh}
|
||||
disabled={refreshing}
|
||||
className="flex items-center space-x-2 px-4 py-2 glass-effect rounded-lg text-white/70 hover:text-white transition-colors"
|
||||
>
|
||||
<RefreshCw className={`w-4 h-4 ${refreshing ? 'animate-spin' : ''}`} />
|
||||
<span>새로고침</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 통계 카드 */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
|
||||
<StatCard
|
||||
title="구매요청 (NR)"
|
||||
value={purchaseNR}
|
||||
@@ -244,21 +523,31 @@ export function Dashboard() {
|
||||
color="text-cyan-400"
|
||||
onClick={() => navigate('/jobreport')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 급한 할일 목록 */}
|
||||
<div className="glass-effect rounded-2xl overflow-hidden">
|
||||
{/* 할일 목록 */}
|
||||
<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">
|
||||
<AlertTriangle className="w-5 h-5 mr-2 text-warning-400" />
|
||||
급한 할일
|
||||
할일
|
||||
</h3>
|
||||
<button
|
||||
onClick={() => navigate('/todo')}
|
||||
className="text-sm text-primary-400 hover:text-primary-300 transition-colors"
|
||||
>
|
||||
전체보기
|
||||
</button>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={handleTodoAdd}
|
||||
className="p-1.5 rounded-lg bg-primary-500/20 text-primary-400 hover:bg-primary-500/30 transition-colors"
|
||||
title="할일 추가"
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => navigate('/todo')}
|
||||
className="p-1.5 rounded-lg bg-white/10 text-white/70 hover:bg-white/20 transition-colors"
|
||||
title="전체보기"
|
||||
>
|
||||
<List className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="divide-y divide-white/10">
|
||||
@@ -267,7 +556,7 @@ export function Dashboard() {
|
||||
<div
|
||||
key={todo.idx}
|
||||
className="px-6 py-4 hover:bg-white/5 transition-colors cursor-pointer"
|
||||
onClick={() => navigate('/todo')}
|
||||
onClick={() => handleTodoEdit(todo)}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-4">
|
||||
@@ -302,25 +591,436 @@ export function Dashboard() {
|
||||
) : (
|
||||
<div className="px-6 py-8 text-center text-white/50">
|
||||
<CheckCircle className="w-12 h-12 mx-auto mb-3 text-success-400/50" />
|
||||
<p>급한 할일이 없습니다</p>
|
||||
<p>할일이 없습니다</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* NR 모달 */}
|
||||
{showNRModal && (
|
||||
<Modal title="구매요청 (NR) 목록" onClose={() => setShowNRModal(false)}>
|
||||
<PurchaseTable data={purchaseNRList} />
|
||||
</Modal>
|
||||
)}
|
||||
|
||||
{/* CR 모달 */}
|
||||
{showCRModal && (
|
||||
<Modal title="구매요청 (CR) 목록" onClose={() => setShowCRModal(false)}>
|
||||
<PurchaseTable data={purchaseCRList} />
|
||||
</Modal>
|
||||
)}
|
||||
|
||||
{/* 메모 보기 모달 */}
|
||||
<NoteViewModal
|
||||
isOpen={showNoteModal}
|
||||
note={selectedNote}
|
||||
onClose={() => setShowNoteModal(false)}
|
||||
onEdit={(note) => {
|
||||
setShowNoteModal(false);
|
||||
setEditingNote(note);
|
||||
setShowNoteEditModal(true);
|
||||
}}
|
||||
onDelete={handleNoteDelete}
|
||||
/>
|
||||
|
||||
{/* 메모 편집 모달 */}
|
||||
<NoteEditModal
|
||||
isOpen={showNoteEditModal}
|
||||
editingItem={editingNote}
|
||||
processing={processing}
|
||||
onClose={() => setShowNoteEditModal(false)}
|
||||
onSave={handleNoteSave}
|
||||
initialEditMode={true}
|
||||
/>
|
||||
|
||||
{/* 메모 추가 모달 */}
|
||||
<NoteEditModal
|
||||
isOpen={showNoteAddModal}
|
||||
editingItem={null}
|
||||
processing={processing}
|
||||
onClose={() => setShowNoteAddModal(false)}
|
||||
onSave={handleNoteSave}
|
||||
initialEditMode={true}
|
||||
/>
|
||||
|
||||
{/* 할일 추가 모달 */}
|
||||
{showTodoAddModal && (
|
||||
<div className="fixed inset-0 bg-black/50 backdrop-blur-sm z-50" onClick={() => setShowTodoAddModal(false)}>
|
||||
<div className="flex items-center justify-center min-h-screen p-4">
|
||||
<div
|
||||
className="glass-effect rounded-2xl w-full max-w-2xl animate-slide-up"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{/* 헤더 */}
|
||||
<div className="px-6 py-4 border-b border-white/10 flex items-center justify-between">
|
||||
<h2 className="text-xl font-semibold text-white flex items-center">
|
||||
<Plus className="w-5 h-5 mr-2" />
|
||||
할일 추가
|
||||
</h2>
|
||||
<button onClick={() => setShowTodoAddModal(false)} className="text-white/70 hover:text-white transition-colors">
|
||||
<X className="w-6 h-6" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 내용 */}
|
||||
<div className="p-6 space-y-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-white/70 text-sm font-medium mb-2">제목 (선택사항)</label>
|
||||
<input
|
||||
type="text"
|
||||
value={todoFormData.title}
|
||||
onChange={(e) => setTodoFormData(prev => ({ ...prev, title: e.target.value }))}
|
||||
className="w-full bg-white/20 backdrop-blur-sm border border-white/30 rounded-lg px-4 py-2 text-white placeholder-white/50 focus:outline-none focus:ring-2 focus:ring-primary-400 transition-all"
|
||||
placeholder="할일 제목을 입력하세요"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-white/70 text-sm font-medium mb-2">만료일 (선택사항)</label>
|
||||
<input
|
||||
type="date"
|
||||
value={todoFormData.expire}
|
||||
onChange={(e) => setTodoFormData(prev => ({ ...prev, expire: e.target.value }))}
|
||||
className="w-full bg-white/20 backdrop-blur-sm border border-white/30 rounded-lg px-4 py-2 text-white focus:outline-none focus:ring-2 focus:ring-primary-400 transition-all"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-white/70 text-sm font-medium mb-2">내용 *</label>
|
||||
<textarea
|
||||
value={todoFormData.remark}
|
||||
onChange={(e) => setTodoFormData(prev => ({ ...prev, remark: e.target.value }))}
|
||||
rows={3}
|
||||
className="w-full bg-white/20 backdrop-blur-sm border border-white/30 rounded-lg px-4 py-2 text-white placeholder-white/50 focus:outline-none focus:ring-2 focus:ring-primary-400 transition-all"
|
||||
placeholder="할일 내용을 입력하세요 (필수)"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-white/70 text-sm font-medium mb-2">요청자</label>
|
||||
<input
|
||||
type="text"
|
||||
value={todoFormData.request}
|
||||
onChange={(e) => setTodoFormData(prev => ({ ...prev, request: e.target.value }))}
|
||||
className="w-full bg-white/20 backdrop-blur-sm border border-white/30 rounded-lg px-4 py-2 text-white placeholder-white/50 focus:outline-none focus:ring-2 focus:ring-primary-400 transition-all"
|
||||
placeholder="업무 요청자를 입력하세요"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label className="block text-white/70 text-sm font-medium mb-2">진행상태</label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{[
|
||||
{ value: '0', label: '대기' },
|
||||
{ value: '1', label: '진행' },
|
||||
{ value: '3', label: '보류' },
|
||||
{ value: '2', label: '취소' },
|
||||
{ value: '5', label: '완료' },
|
||||
].map((option) => (
|
||||
<button
|
||||
key={option.value}
|
||||
type="button"
|
||||
onClick={() => setTodoFormData(prev => ({ ...prev, status: option.value as TodoStatus }))}
|
||||
className={`px-3 py-1 rounded-lg text-xs font-medium border transition-all ${
|
||||
todoFormData.status === option.value
|
||||
? getStatusClass(option.value)
|
||||
: 'bg-white/10 text-white/50 border-white/20 hover:bg-white/20'
|
||||
}`}
|
||||
>
|
||||
{option.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-white/70 text-sm font-medium mb-2">중요도</label>
|
||||
<select
|
||||
value={todoFormData.seqno}
|
||||
onChange={(e) => setTodoFormData(prev => ({ ...prev, seqno: parseInt(e.target.value) as TodoPriority }))}
|
||||
className="w-full bg-white/20 backdrop-blur-sm border border-white/30 rounded-lg px-4 py-2 text-white focus:outline-none focus:ring-2 focus:ring-primary-400 transition-all"
|
||||
>
|
||||
<option value={0}>보통</option>
|
||||
<option value={1}>중요</option>
|
||||
<option value={2}>매우 중요</option>
|
||||
<option value={3}>긴급</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex items-end">
|
||||
<label className="flex items-center text-white/70 text-sm font-medium cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={todoFormData.flag}
|
||||
onChange={(e) => setTodoFormData(prev => ({ ...prev, flag: e.target.checked }))}
|
||||
className="mr-2 text-primary-500 focus:ring-primary-400 focus:ring-offset-0 rounded"
|
||||
/>
|
||||
플래그 (상단 고정)
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 푸터 */}
|
||||
<div className="px-6 py-4 border-t border-white/10 flex justify-end space-x-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowTodoAddModal(false)}
|
||||
className="bg-white/20 hover:bg-white/30 text-white px-4 py-2 rounded-lg transition-colors"
|
||||
>
|
||||
취소
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleTodoSave}
|
||||
disabled={processing}
|
||||
className="bg-primary-500 hover:bg-primary-600 text-white px-6 py-2 rounded-lg transition-colors flex items-center disabled:opacity-50"
|
||||
>
|
||||
{processing ? (
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||
) : (
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
)}
|
||||
추가
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 할일 수정 모달 */}
|
||||
{showTodoEditModal && editingTodo && (
|
||||
<div className="fixed inset-0 bg-black/50 backdrop-blur-sm z-50" onClick={() => setShowTodoEditModal(false)}>
|
||||
<div className="flex items-center justify-center min-h-screen p-4">
|
||||
<div
|
||||
className="glass-effect rounded-2xl w-full max-w-2xl animate-slide-up"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{/* 헤더 */}
|
||||
<div className="px-6 py-4 border-b border-white/10 flex items-center justify-between">
|
||||
<h2 className="text-xl font-semibold text-white flex items-center">
|
||||
<Edit2 className="w-5 h-5 mr-2" />
|
||||
할일 수정
|
||||
</h2>
|
||||
<button onClick={() => setShowTodoEditModal(false)} className="text-white/70 hover:text-white transition-colors">
|
||||
<X className="w-6 h-6" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 내용 */}
|
||||
<div className="p-6 space-y-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-white/70 text-sm font-medium mb-2">제목 (선택사항)</label>
|
||||
<input
|
||||
type="text"
|
||||
value={todoFormData.title}
|
||||
onChange={(e) => setTodoFormData(prev => ({ ...prev, title: e.target.value }))}
|
||||
className="w-full bg-white/20 backdrop-blur-sm border border-white/30 rounded-lg px-4 py-2 text-white placeholder-white/50 focus:outline-none focus:ring-2 focus:ring-primary-400 transition-all"
|
||||
placeholder="할일 제목을 입력하세요"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-white/70 text-sm font-medium mb-2">만료일 (선택사항)</label>
|
||||
<input
|
||||
type="date"
|
||||
value={todoFormData.expire}
|
||||
onChange={(e) => setTodoFormData(prev => ({ ...prev, expire: e.target.value }))}
|
||||
className="w-full bg-white/20 backdrop-blur-sm border border-white/30 rounded-lg px-4 py-2 text-white focus:outline-none focus:ring-2 focus:ring-primary-400 transition-all"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-white/70 text-sm font-medium mb-2">내용 *</label>
|
||||
<textarea
|
||||
value={todoFormData.remark}
|
||||
onChange={(e) => setTodoFormData(prev => ({ ...prev, remark: e.target.value }))}
|
||||
rows={3}
|
||||
className="w-full bg-white/20 backdrop-blur-sm border border-white/30 rounded-lg px-4 py-2 text-white placeholder-white/50 focus:outline-none focus:ring-2 focus:ring-primary-400 transition-all"
|
||||
placeholder="할일 내용을 입력하세요 (필수)"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-white/70 text-sm font-medium mb-2">요청자</label>
|
||||
<input
|
||||
type="text"
|
||||
value={todoFormData.request}
|
||||
onChange={(e) => setTodoFormData(prev => ({ ...prev, request: e.target.value }))}
|
||||
className="w-full bg-white/20 backdrop-blur-sm border border-white/30 rounded-lg px-4 py-2 text-white placeholder-white/50 focus:outline-none focus:ring-2 focus:ring-primary-400 transition-all"
|
||||
placeholder="업무 요청자를 입력하세요"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label className="block text-white/70 text-sm font-medium mb-2">진행상태</label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{[
|
||||
{ value: '0', label: '대기' },
|
||||
{ value: '1', label: '진행' },
|
||||
{ value: '3', label: '보류' },
|
||||
{ value: '2', label: '취소' },
|
||||
{ value: '5', label: '완료' },
|
||||
].map((option) => (
|
||||
<button
|
||||
key={option.value}
|
||||
type="button"
|
||||
onClick={() => setTodoFormData(prev => ({ ...prev, status: option.value as TodoStatus }))}
|
||||
className={`px-3 py-1 rounded-lg text-xs font-medium border transition-all ${
|
||||
todoFormData.status === option.value
|
||||
? getStatusClass(option.value)
|
||||
: 'bg-white/10 text-white/50 border-white/20 hover:bg-white/20'
|
||||
}`}
|
||||
>
|
||||
{option.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-white/70 text-sm font-medium mb-2">중요도</label>
|
||||
<select
|
||||
value={todoFormData.seqno}
|
||||
onChange={(e) => setTodoFormData(prev => ({ ...prev, seqno: parseInt(e.target.value) as TodoPriority }))}
|
||||
className="w-full bg-white/20 backdrop-blur-sm border border-white/30 rounded-lg px-4 py-2 text-white focus:outline-none focus:ring-2 focus:ring-primary-400 transition-all"
|
||||
>
|
||||
<option value={0}>보통</option>
|
||||
<option value={1}>중요</option>
|
||||
<option value={2}>매우 중요</option>
|
||||
<option value={3}>긴급</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex items-end">
|
||||
<label className="flex items-center text-white/70 text-sm font-medium cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={todoFormData.flag}
|
||||
onChange={(e) => setTodoFormData(prev => ({ ...prev, flag: e.target.checked }))}
|
||||
className="mr-2 text-primary-500 focus:ring-primary-400 focus:ring-offset-0 rounded"
|
||||
/>
|
||||
플래그 (상단 고정)
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 푸터 */}
|
||||
<div className="px-6 py-4 border-t border-white/10 flex justify-between">
|
||||
{/* 왼쪽: 삭제 버튼 */}
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleTodoDelete}
|
||||
disabled={processing}
|
||||
className="bg-danger-500 hover:bg-danger-600 text-white px-4 py-2 rounded-lg transition-colors flex items-center disabled:opacity-50"
|
||||
>
|
||||
<Trash2 className="w-4 h-4 mr-2" />
|
||||
삭제
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 오른쪽: 취소, 완료, 수정 버튼 */}
|
||||
<div className="flex space-x-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowTodoEditModal(false)}
|
||||
className="bg-white/20 hover:bg-white/30 text-white px-4 py-2 rounded-lg transition-colors"
|
||||
>
|
||||
취소
|
||||
</button>
|
||||
{editingTodo.status !== '5' && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleTodoComplete}
|
||||
disabled={processing}
|
||||
className="bg-success-500 hover:bg-success-600 text-white px-4 py-2 rounded-lg transition-colors flex items-center disabled:opacity-50"
|
||||
>
|
||||
<CheckCircle className="w-4 h-4 mr-2" />
|
||||
완료 처리
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleTodoUpdate}
|
||||
disabled={processing}
|
||||
className="bg-primary-500 hover:bg-primary-600 text-white px-6 py-2 rounded-lg transition-colors flex items-center disabled:opacity-50"
|
||||
>
|
||||
{processing ? (
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||
) : (
|
||||
<Edit2 className="w-4 h-4 mr-2" />
|
||||
)}
|
||||
수정
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* NR 모달 */}
|
||||
{showNRModal && (
|
||||
<Modal title="구매요청 (NR) 목록" onClose={() => setShowNRModal(false)}>
|
||||
<PurchaseTable data={purchaseNRList} />
|
||||
</Modal>
|
||||
)}
|
||||
{/* 우측 사이드바 - 메모 리스트 */}
|
||||
<div className="w-60 space-y-4">
|
||||
<div className="glass-effect rounded-2xl overflow-hidden">
|
||||
<div className="px-4 py-3 border-b border-white/10 flex items-center justify-between">
|
||||
<h3 className="text-base font-semibold text-white flex items-center">
|
||||
<FileText className="w-4 h-4 mr-2" />
|
||||
최근 메모
|
||||
</h3>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={handleNoteAdd}
|
||||
className="p-1.5 rounded-lg bg-primary-500/20 text-primary-400 hover:bg-primary-500/30 transition-colors"
|
||||
title="메모 추가"
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => navigate('/note')}
|
||||
className="p-1.5 rounded-lg bg-white/10 text-white/70 hover:bg-white/20 transition-colors"
|
||||
title="전체보기"
|
||||
>
|
||||
<List className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* CR 모달 */}
|
||||
{showCRModal && (
|
||||
<Modal title="구매요청 (CR) 목록" onClose={() => setShowCRModal(false)}>
|
||||
<PurchaseTable data={purchaseCRList} />
|
||||
</Modal>
|
||||
)}
|
||||
<div className="divide-y divide-white/10 max-h-[calc(100vh-200px)] overflow-y-auto">
|
||||
{recentNotes.length > 0 ? (
|
||||
recentNotes.map((note) => (
|
||||
<div
|
||||
key={note.idx}
|
||||
className="px-4 py-2.5 hover:bg-white/5 transition-colors cursor-pointer group"
|
||||
onClick={() => handleNoteClick(note)}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
{note.share ? (
|
||||
<Share2 className="w-3 h-3 text-green-400 flex-shrink-0" />
|
||||
) : (
|
||||
<Lock className="w-3 h-3 text-blue-400 flex-shrink-0" />
|
||||
)}
|
||||
<p className="text-white text-sm truncate flex-1">
|
||||
{(note.title || '제목 없음').length > 15 ? `${(note.title || '제목 없음').substring(0, 15)}...` : (note.title || '제목 없음')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="px-4 py-8 text-center text-white/50">
|
||||
<FileText className="w-10 h-10 mx-auto mb-2 text-white/30" />
|
||||
<p className="text-sm">등록된 메모가 없습니다</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -117,12 +117,12 @@ export function Jobreport() {
|
||||
initialize();
|
||||
}, []);
|
||||
|
||||
// 초기화 완료 후 조회 실행
|
||||
// 초기화 완료 후 조회 실행 (최초 1회만)
|
||||
useEffect(() => {
|
||||
if (initialized && startDate && endDate && selectedUser) {
|
||||
handleSearchAndLoadToday();
|
||||
}
|
||||
}, [initialized, startDate, endDate, selectedUser]);
|
||||
}, [initialized]); // startDate, endDate, selectedUser 의존성 제거 (날짜 변경 시 자동 조회 방지)
|
||||
|
||||
// 검색 + 오늘 근무시간 로드 (순차 실행)
|
||||
const handleSearchAndLoadToday = async () => {
|
||||
@@ -373,6 +373,34 @@ export function Jobreport() {
|
||||
handleSearch();
|
||||
};
|
||||
|
||||
// 빠른 날짜 선택 함수들
|
||||
const setToday = () => {
|
||||
const today = new Date();
|
||||
setStartDate(formatDateLocal(today));
|
||||
};
|
||||
|
||||
const setThisMonth = () => {
|
||||
const now = new Date();
|
||||
const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
|
||||
const endOfMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0);
|
||||
setStartDate(formatDateLocal(startOfMonth));
|
||||
setEndDate(formatDateLocal(endOfMonth));
|
||||
};
|
||||
|
||||
const setYesterday = () => {
|
||||
const yesterday = new Date();
|
||||
yesterday.setDate(yesterday.getDate() - 1);
|
||||
setStartDate(formatDateLocal(yesterday));
|
||||
};
|
||||
|
||||
const setLastMonth = () => {
|
||||
const now = new Date();
|
||||
const lastMonthStart = new Date(now.getFullYear(), now.getMonth() - 1, 1);
|
||||
const lastMonthEnd = new Date(now.getFullYear(), now.getMonth(), 0);
|
||||
setStartDate(formatDateLocal(lastMonthStart));
|
||||
setEndDate(formatDateLocal(lastMonthEnd));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-6 animate-fade-in">
|
||||
{/* 검색 필터 */}
|
||||
@@ -381,6 +409,38 @@ export function Jobreport() {
|
||||
{/* 좌측: 필터 영역 */}
|
||||
<div className="flex-1">
|
||||
<div className="flex items-start gap-3">
|
||||
{/* 빠른 날짜 선택 버튼 */}
|
||||
<div className="flex flex-col 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"
|
||||
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"
|
||||
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"
|
||||
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"
|
||||
title="저번달 1일부터 말일까지"
|
||||
>
|
||||
저번달
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 필터 입력 영역: 2행 2열 */}
|
||||
<div className="grid grid-cols-2 gap-x-4 gap-y-3">
|
||||
{/* 1행: 시작일, 담당자 */}
|
||||
|
||||
@@ -3,7 +3,6 @@ import {
|
||||
Calendar,
|
||||
Search,
|
||||
Trash2,
|
||||
AlertTriangle,
|
||||
Clock,
|
||||
CheckCircle,
|
||||
XCircle,
|
||||
@@ -23,7 +22,7 @@ import { KuntaeEditModal, KuntaeFormData } from '@/components/kuntae/KuntaeEditM
|
||||
export function Kuntae() {
|
||||
const [kuntaeList, setKuntaeList] = useState<KuntaeModel[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [processing, setProcessing] = useState(false);
|
||||
const [_processing, setProcessing] = useState(false);
|
||||
|
||||
// 검색 조건
|
||||
const [startDate, setStartDate] = useState('');
|
||||
@@ -441,7 +440,7 @@ export function Kuntae() {
|
||||
{item.contents || '-'}
|
||||
</td>
|
||||
<td className="px-4 py-3 text-white text-sm">
|
||||
{item.UserName || item.uname || item.uid}
|
||||
{item.UserName || item.uid}
|
||||
</td>
|
||||
<td className="px-4 py-3 text-white/50 text-xs">
|
||||
{item.extcate ? `${item.extcate}` : '-'}
|
||||
|
||||
288
Project/frontend/src/pages/MailList.tsx
Normal file
288
Project/frontend/src/pages/MailList.tsx
Normal file
@@ -0,0 +1,288 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Mail, Search, RefreshCw, Calendar } from 'lucide-react';
|
||||
import { comms } from '@/communication';
|
||||
import { MailItem, UserInfo } from '@/types';
|
||||
|
||||
export function MailList() {
|
||||
const [mailList, setMailList] = useState<MailItem[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [startDate, setStartDate] = useState('');
|
||||
const [endDate, setEndDate] = useState('');
|
||||
const [searchKey, setSearchKey] = useState('');
|
||||
const [selectedItem, setSelectedItem] = useState<MailItem | null>(null);
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const [currentUser, setCurrentUser] = useState<UserInfo | null>(null);
|
||||
|
||||
const formatDateLocal = (date: Date) => {
|
||||
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// 로그인 정보 로드
|
||||
const loadLoginInfo = async () => {
|
||||
try {
|
||||
const response = await comms.checkLoginStatus();
|
||||
if (response.Success && response.IsLoggedIn && response.User) {
|
||||
setCurrentUser(response.User);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('로그인 정보 로드 오류:', error);
|
||||
}
|
||||
};
|
||||
|
||||
loadLoginInfo();
|
||||
|
||||
const now = new Date();
|
||||
const tenDaysAgo = new Date();
|
||||
tenDaysAgo.setDate(now.getDate() - 10);
|
||||
|
||||
const start = formatDateLocal(tenDaysAgo);
|
||||
const end = formatDateLocal(now);
|
||||
|
||||
setStartDate(start);
|
||||
setEndDate(end);
|
||||
|
||||
// 날짜 설정 후 바로 데이터 로드
|
||||
setTimeout(() => {
|
||||
loadDataWithDates(start, end);
|
||||
}, 100);
|
||||
}, []);
|
||||
|
||||
const loadDataWithDates = async (start: string, end: string) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
console.log('메일내역 조회:', { start, end, searchKey });
|
||||
const response = await comms.getMailList(start, end, searchKey);
|
||||
console.log('메일내역 응답:', response);
|
||||
if (response.Success && response.Data) {
|
||||
setMailList(response.Data);
|
||||
} else {
|
||||
console.warn('메일내역 없음:', response.Message);
|
||||
setMailList([]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('메일내역 로드 오류:', error);
|
||||
alert('데이터를 불러오는 중 오류가 발생했습니다.');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const loadData = async () => {
|
||||
if (!startDate || !endDate) return;
|
||||
await loadDataWithDates(startDate, endDate);
|
||||
};
|
||||
|
||||
const handleSearch = () => {
|
||||
if (new Date(startDate) > new Date(endDate)) {
|
||||
alert('시작일은 종료일보다 늦을 수 없습니다.');
|
||||
return;
|
||||
}
|
||||
loadData();
|
||||
};
|
||||
|
||||
const handleRowClick = (item: MailItem) => {
|
||||
// 레벨 9 이상(개발자)만 상세보기 가능
|
||||
if (!currentUser || currentUser.Level < 9) {
|
||||
return;
|
||||
}
|
||||
setSelectedItem(item);
|
||||
setShowModal(true);
|
||||
};
|
||||
|
||||
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');
|
||||
const hh = String(date.getHours()).padStart(2, '0');
|
||||
const mi = String(date.getMinutes()).padStart(2, '0');
|
||||
return `${yy}.${mm}.${dd} ${hh}:${mi}`;
|
||||
} 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">
|
||||
<label className="text-white/70 text-sm font-medium whitespace-nowrap">기간</label>
|
||||
<input
|
||||
type="date"
|
||||
value={startDate}
|
||||
onChange={(e) => setStartDate(e.target.value)}
|
||||
className="w-36 h-10 bg-white/20 border border-white/30 rounded-lg px-3 text-white focus:outline-none focus:ring-2 focus:ring-primary-400"
|
||||
/>
|
||||
<span className="text-white/70">~</span>
|
||||
<input
|
||||
type="date"
|
||||
value={endDate}
|
||||
onChange={(e) => setEndDate(e.target.value)}
|
||||
className="w-36 h-10 bg-white/20 border border-white/30 rounded-lg px-3 text-white focus:outline-none focus:ring-2 focus:ring-primary-400"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
</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">
|
||||
<Mail className="w-5 h-5 mr-2" />
|
||||
메일 발신 내역
|
||||
</h3>
|
||||
<span className="text-white/60 text-sm">{mailList.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>
|
||||
) : mailList.length === 0 ? (
|
||||
<div className="px-6 py-8 text-center">
|
||||
<Mail className="w-12 h-12 mx-auto mb-3 text-white/30" />
|
||||
<p className="text-white/50">조회된 데이터가 없습니다.</p>
|
||||
</div>
|
||||
) : (
|
||||
mailList.map((item) => (
|
||||
<div
|
||||
key={item.idx}
|
||||
className={`px-6 py-4 transition-colors ${currentUser && currentUser.Level >= 9 ? 'hover:bg-white/5 cursor-pointer' : 'cursor-default'}`}
|
||||
onClick={() => handleRowClick(item)}
|
||||
>
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
{item.cate && (
|
||||
<span className="px-2 py-0.5 bg-primary-500/20 text-primary-400 text-xs rounded">
|
||||
{item.cate}
|
||||
</span>
|
||||
)}
|
||||
{item.project && (
|
||||
<span className="px-2 py-0.5 bg-white/10 text-white/70 text-xs rounded">
|
||||
{item.project}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<h4 className="text-white font-medium mb-1">{item.subject}</h4>
|
||||
<div className="flex items-center gap-4 text-white/60 text-sm">
|
||||
<div>발신: {item.fromlist}</div>
|
||||
<div>수신: {item.tolist}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col items-end gap-1 flex-shrink-0">
|
||||
<div className="flex items-center text-white/60 text-xs">
|
||||
<Calendar className="w-3 h-3 mr-1" />
|
||||
{formatDate(item.wdate)}
|
||||
</div>
|
||||
</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.cate && (
|
||||
<span className="px-2 py-1 bg-primary-500/20 text-primary-400 text-sm rounded">
|
||||
{selectedItem.cate}
|
||||
</span>
|
||||
)}
|
||||
<h2 className="text-xl font-bold text-white ml-2">{selectedItem.subject}</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 space-y-2 text-sm">
|
||||
<div className="flex items-start gap-2 text-white/70">
|
||||
<span className="font-medium w-16">발신:</span>
|
||||
<span className="text-white">{selectedItem.fromlist}</span>
|
||||
</div>
|
||||
<div className="flex items-start gap-2 text-white/70">
|
||||
<span className="font-medium w-16">수신:</span>
|
||||
<span className="text-white">{selectedItem.tolist}</span>
|
||||
</div>
|
||||
{selectedItem.cclist && (
|
||||
<div className="flex items-start gap-2 text-white/70">
|
||||
<span className="font-medium w-16">참조:</span>
|
||||
<span className="text-white">{selectedItem.cclist}</span>
|
||||
</div>
|
||||
)}
|
||||
{selectedItem.bcclist && (
|
||||
<div className="flex items-start gap-2 text-white/70">
|
||||
<span className="font-medium w-16">숨은참조:</span>
|
||||
<span className="text-white">{selectedItem.bcclist}</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center gap-2 text-white/60">
|
||||
<Calendar className="w-4 h-4" />
|
||||
{formatDate(selectedItem.wdate)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="overflow-y-auto max-h-[calc(90vh-280px)] p-6">
|
||||
<div
|
||||
className="prose prose-invert max-w-none"
|
||||
dangerouslySetInnerHTML={{ __html: selectedItem.htmlbody }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-end px-6 py-4 border-t border-white/10 bg-white/5">
|
||||
<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>
|
||||
);
|
||||
}
|
||||
452
Project/frontend/src/pages/Note.tsx
Normal file
452
Project/frontend/src/pages/Note.tsx
Normal file
@@ -0,0 +1,452 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import {
|
||||
FileText,
|
||||
Search,
|
||||
RefreshCw,
|
||||
Plus,
|
||||
Edit,
|
||||
Trash2,
|
||||
Share2,
|
||||
Lock,
|
||||
} from 'lucide-react';
|
||||
import { comms } from '@/communication';
|
||||
import { NoteItem } from '@/types';
|
||||
import { NoteEditModal } from '@/components/note/NoteEditModal';
|
||||
import { NoteViewModal } from '@/components/note/NoteViewModal';
|
||||
|
||||
export function Note() {
|
||||
const [noteList, setNoteList] = useState<NoteItem[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [processing, setProcessing] = useState(false);
|
||||
|
||||
// 검색 조건
|
||||
const [startDate, setStartDate] = useState('');
|
||||
const [endDate, setEndDate] = useState('');
|
||||
const [searchKey, setSearchKey] = useState('');
|
||||
|
||||
// 모달 상태
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const [showViewModal, setShowViewModal] = useState(false);
|
||||
const [editingItem, setEditingItem] = useState<NoteItem | null>(null);
|
||||
const [selectedNote, setSelectedNote] = useState<NoteItem | null>(null);
|
||||
|
||||
// 페이징 상태
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const pageSize = 10;
|
||||
|
||||
// 날짜 포맷 헬퍼 함수 (로컬 시간 기준)
|
||||
const formatDateLocal = (date: Date) => {
|
||||
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
|
||||
};
|
||||
|
||||
// 초기화 완료 플래그
|
||||
const [initialized, setInitialized] = useState(false);
|
||||
|
||||
// 날짜 초기화
|
||||
useEffect(() => {
|
||||
const now = new Date();
|
||||
// 2000년부터 현재까지 데이터 조회
|
||||
const startOfPeriod = new Date(2000, 0, 1);
|
||||
|
||||
const sd = formatDateLocal(startOfPeriod);
|
||||
const ed = formatDateLocal(now);
|
||||
|
||||
setStartDate(sd);
|
||||
setEndDate(ed);
|
||||
|
||||
// 초기화 완료 표시
|
||||
setInitialized(true);
|
||||
}, []);
|
||||
|
||||
// 초기화 완료 후 조회 실행 (최초 1회만)
|
||||
useEffect(() => {
|
||||
if (initialized && startDate && endDate) {
|
||||
handleSearch();
|
||||
}
|
||||
}, [initialized]);
|
||||
|
||||
// 데이터 로드
|
||||
const loadData = useCallback(async () => {
|
||||
if (!startDate || !endDate) return;
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
console.log('메모장 조회 요청:', { startDate, endDate });
|
||||
const response = await comms.getNoteList(startDate, endDate, '');
|
||||
console.log('메모장 조회 응답:', response);
|
||||
if (response.Success && response.Data) {
|
||||
console.log('메모장 데이터 개수:', response.Data.length);
|
||||
setNoteList(response.Data);
|
||||
} else {
|
||||
console.log('메모장 조회 실패 또는 데이터 없음:', response);
|
||||
setNoteList([]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('메모장 목록 로드 오류:', error);
|
||||
alert('데이터를 불러오는 중 오류가 발생했습니다.');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [startDate, endDate]);
|
||||
|
||||
// 검색
|
||||
const handleSearch = async () => {
|
||||
if (new Date(startDate) > new Date(endDate)) {
|
||||
alert('시작일은 종료일보다 늦을 수 없습니다.');
|
||||
return;
|
||||
}
|
||||
await loadData();
|
||||
};
|
||||
|
||||
// 새 메모 추가 모달
|
||||
const openAddModal = () => {
|
||||
setEditingItem(null);
|
||||
setShowModal(true);
|
||||
};
|
||||
|
||||
// 메모 클릭 (보기 모달)
|
||||
const handleNoteClick = async (item: NoteItem) => {
|
||||
try {
|
||||
const response = await comms.getNoteDetail(item.idx);
|
||||
if (response.Success && response.Data) {
|
||||
setSelectedNote(response.Data);
|
||||
setShowViewModal(true);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('메모 조회 오류:', error);
|
||||
alert('데이터를 불러오는 중 오류가 발생했습니다.');
|
||||
}
|
||||
};
|
||||
|
||||
// 편집 모달
|
||||
const openEditModal = async (item: NoteItem) => {
|
||||
try {
|
||||
const response = await comms.getNoteDetail(item.idx);
|
||||
if (response.Success && response.Data) {
|
||||
setEditingItem(response.Data);
|
||||
setShowModal(true);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('메모 조회 오류:', error);
|
||||
alert('데이터를 불러오는 중 오류가 발생했습니다.');
|
||||
}
|
||||
};
|
||||
|
||||
// 저장
|
||||
const handleSave = async (formData: {
|
||||
pdate: string;
|
||||
title: string;
|
||||
uid: string;
|
||||
description: string;
|
||||
share: boolean;
|
||||
guid: string;
|
||||
}) => {
|
||||
if (!formData.pdate) {
|
||||
alert('날짜를 입력해주세요.');
|
||||
return;
|
||||
}
|
||||
if (!formData.title.trim()) {
|
||||
alert('제목을 입력해주세요.');
|
||||
return;
|
||||
}
|
||||
|
||||
setProcessing(true);
|
||||
try {
|
||||
let response;
|
||||
if (editingItem) {
|
||||
response = await comms.editNote(
|
||||
editingItem.idx,
|
||||
formData.pdate,
|
||||
formData.title,
|
||||
formData.uid,
|
||||
formData.description,
|
||||
'',
|
||||
formData.share,
|
||||
formData.guid
|
||||
);
|
||||
} else {
|
||||
response = await comms.addNote(
|
||||
formData.pdate,
|
||||
formData.title,
|
||||
formData.uid,
|
||||
formData.description,
|
||||
'',
|
||||
formData.share,
|
||||
formData.guid
|
||||
);
|
||||
}
|
||||
|
||||
if (response.Success) {
|
||||
setShowModal(false);
|
||||
loadData();
|
||||
} else {
|
||||
alert(response.Message || '저장에 실패했습니다.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('저장 오류:', error);
|
||||
alert('서버 연결에 실패했습니다: ' + (error instanceof Error ? error.message : String(error)));
|
||||
} finally {
|
||||
setProcessing(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 삭제
|
||||
const handleDelete = async (idx: number) => {
|
||||
if (!confirm('정말로 이 메모를 삭제하시겠습니까?')) return;
|
||||
|
||||
setProcessing(true);
|
||||
try {
|
||||
const response = await comms.deleteNote(idx);
|
||||
if (response.Success) {
|
||||
alert('삭제되었습니다.');
|
||||
loadData();
|
||||
} else {
|
||||
alert(response.Message || '삭제에 실패했습니다.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('삭제 오류:', error);
|
||||
alert('서버 연결에 실패했습니다.');
|
||||
} finally {
|
||||
setProcessing(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 날짜 포맷 (YY.MM.DD)
|
||||
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 filteredList = noteList.filter(item => {
|
||||
if (!searchKey.trim()) return true;
|
||||
const search = searchKey.toLowerCase();
|
||||
return (
|
||||
item.title?.toLowerCase().includes(search) ||
|
||||
item.description?.toLowerCase().includes(search) ||
|
||||
item.uid?.toLowerCase().includes(search)
|
||||
);
|
||||
});
|
||||
|
||||
// 페이징 계산
|
||||
const totalPages = Math.ceil(filteredList.length / pageSize);
|
||||
const paginatedList = filteredList.slice(
|
||||
(currentPage - 1) * pageSize,
|
||||
currentPage * pageSize
|
||||
);
|
||||
|
||||
// 검색 시 페이지 초기화
|
||||
const handleSearchWithReset = () => {
|
||||
setCurrentPage(1);
|
||||
handleSearch();
|
||||
};
|
||||
|
||||
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">
|
||||
<label className="text-white/70 text-sm font-medium whitespace-nowrap">기간</label>
|
||||
<input
|
||||
type="date"
|
||||
value={startDate}
|
||||
onChange={(e) => setStartDate(e.target.value)}
|
||||
className="w-36 h-10 bg-white/20 border border-white/30 rounded-lg px-3 text-white focus:outline-none focus:ring-2 focus:ring-primary-400"
|
||||
/>
|
||||
<span className="text-white/70">~</span>
|
||||
<input
|
||||
type="date"
|
||||
value={endDate}
|
||||
onChange={(e) => setEndDate(e.target.value)}
|
||||
className="w-36 h-10 bg-white/20 border border-white/30 rounded-lg px-3 text-white focus:outline-none focus:ring-2 focus:ring-primary-400"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<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' && handleSearchWithReset()}
|
||||
placeholder="제목, 내용, 작성자 등"
|
||||
className="w-60 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={handleSearchWithReset}
|
||||
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={openAddModal}
|
||||
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>
|
||||
|
||||
{/* 메모 리스트 */}
|
||||
<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">{filteredList.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>
|
||||
) : filteredList.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>
|
||||
) : (
|
||||
paginatedList.map((item) => (
|
||||
<div
|
||||
key={item.idx}
|
||||
className="px-6 py-3 hover:bg-white/5 transition-colors cursor-pointer group"
|
||||
onClick={() => handleNoteClick(item)}
|
||||
>
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<div className="flex items-center gap-3 flex-1 min-w-0">
|
||||
{item.share ? (
|
||||
<Share2 className="w-4 h-4 text-green-400 flex-shrink-0" />
|
||||
) : (
|
||||
<Lock className="w-4 h-4 text-blue-400 flex-shrink-0" />
|
||||
)}
|
||||
<p className="text-white text-sm font-medium truncate flex-1">
|
||||
{(item.title || '제목 없음').length > 15 ? `${(item.title || '제목 없음').substring(0, 15)}...` : (item.title || '제목 없음')}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-4 flex-shrink-0">
|
||||
<span className="text-white/60 text-xs">{item.uid || '-'}</span>
|
||||
<span className="text-white/60 text-xs">{formatDate(item.pdate)}</span>
|
||||
<span className="text-white/50 text-xs">조회 {item.viewcount || 0}</span>
|
||||
<div className="flex items-center gap-2 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
openEditModal(item);
|
||||
}}
|
||||
className="text-white/40 hover:text-primary-400 transition-colors"
|
||||
title="편집"
|
||||
>
|
||||
<Edit className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleDelete(item.idx);
|
||||
}}
|
||||
className="text-white/40 hover:text-red-400 transition-colors"
|
||||
title="삭제"
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 페이징 */}
|
||||
{totalPages > 1 && (
|
||||
<div className="px-6 py-4 border-t border-white/10 flex items-center justify-between">
|
||||
<div className="text-white/50 text-sm">
|
||||
총 {filteredList.length}건 중 {(currentPage - 1) * pageSize + 1}-{Math.min(currentPage * pageSize, filteredList.length)}건
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={() => setCurrentPage(1)}
|
||||
disabled={currentPage === 1}
|
||||
className="px-3 py-1 rounded bg-white/10 text-white/70 hover:bg-white/20 disabled:opacity-30 disabled:cursor-not-allowed transition-colors"
|
||||
>
|
||||
«
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setCurrentPage(p => Math.max(1, p - 1))}
|
||||
disabled={currentPage === 1}
|
||||
className="px-3 py-1 rounded bg-white/10 text-white/70 hover:bg-white/20 disabled:opacity-30 disabled:cursor-not-allowed transition-colors"
|
||||
>
|
||||
‹
|
||||
</button>
|
||||
<span className="text-white/70 px-3">
|
||||
{currentPage} / {totalPages}
|
||||
</span>
|
||||
<button
|
||||
onClick={() => setCurrentPage(p => Math.min(totalPages, p + 1))}
|
||||
disabled={currentPage === totalPages}
|
||||
className="px-3 py-1 rounded bg-white/10 text-white/70 hover:bg-white/20 disabled:opacity-30 disabled:cursor-not-allowed transition-colors"
|
||||
>
|
||||
›
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setCurrentPage(totalPages)}
|
||||
disabled={currentPage === totalPages}
|
||||
className="px-3 py-1 rounded bg-white/10 text-white/70 hover:bg-white/20 disabled:opacity-30 disabled:cursor-not-allowed transition-colors"
|
||||
>
|
||||
»
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 메모 보기 모달 */}
|
||||
<NoteViewModal
|
||||
isOpen={showViewModal}
|
||||
note={selectedNote}
|
||||
onClose={() => setShowViewModal(false)}
|
||||
onEdit={(note) => {
|
||||
setShowViewModal(false);
|
||||
setEditingItem(note);
|
||||
setShowModal(true);
|
||||
}}
|
||||
onDelete={(note) => {
|
||||
setShowViewModal(false);
|
||||
handleDelete(note.idx);
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* 추가/수정 모달 */}
|
||||
<NoteEditModal
|
||||
isOpen={showModal}
|
||||
editingItem={editingItem}
|
||||
processing={processing}
|
||||
onClose={() => setShowModal(false)}
|
||||
onSave={handleSave}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
219
Project/frontend/src/pages/PatchList.tsx
Normal file
219
Project/frontend/src/pages/PatchList.tsx
Normal file
@@ -0,0 +1,219 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { FileText, Search, RefreshCw, Calendar, User } from 'lucide-react';
|
||||
import { comms } from '@/communication';
|
||||
import { BoardItem } from '@/types';
|
||||
|
||||
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);
|
||||
|
||||
useEffect(() => {
|
||||
loadData();
|
||||
}, []);
|
||||
|
||||
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);
|
||||
setShowModal(true);
|
||||
}
|
||||
} 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>
|
||||
</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-4 hover:bg-white/5 transition-colors cursor-pointer"
|
||||
onClick={() => handleRowClick(item)}
|
||||
>
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
{item.header && (
|
||||
<span className="px-2 py-0.5 bg-primary-500/20 text-primary-400 text-xs rounded">
|
||||
{item.header}
|
||||
</span>
|
||||
)}
|
||||
{item.cate && (
|
||||
<span className="px-2 py-0.5 bg-white/10 text-white/70 text-xs rounded">
|
||||
{item.cate}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<h4 className="text-white font-medium mb-1">{item.title}</h4>
|
||||
<p className="text-white/60 text-sm line-clamp-1">{item.contents}</p>
|
||||
</div>
|
||||
<div className="flex flex-col items-end gap-1 flex-shrink-0">
|
||||
<div className="flex items-center text-white/60 text-xs">
|
||||
<User className="w-3 h-3 mr-1" />
|
||||
{item.wuid_name || item.wuid}
|
||||
</div>
|
||||
<div className="flex items-center text-white/60 text-xs">
|
||||
<Calendar className="w-3 h-3 mr-1" />
|
||||
{formatDate(item.wdate)}
|
||||
</div>
|
||||
</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 px-6 py-4 border-t border-white/10 bg-white/5">
|
||||
<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>
|
||||
);
|
||||
}
|
||||
@@ -12,3 +12,4 @@ export { MonthlyWorkPage } from './MonthlyWork';
|
||||
export { MailFormPage } from './MailForm';
|
||||
export { UserGroupPage } from './UserGroup';
|
||||
export { default as UserAuthPage } from './UserAuth';
|
||||
export { Note } from './Note';
|
||||
|
||||
@@ -448,6 +448,20 @@ export interface MachineBridgeInterface {
|
||||
Project_GetList(statusFilter: string, category: string, process: string, userFilter: string, yearStart: string, yearEnd: string, dateType: string): Promise<string>;
|
||||
Project_GetHistory(projectIdx: number): Promise<string>;
|
||||
Project_GetDailyMemo(projectIdx: number): Promise<string>;
|
||||
|
||||
// Note API (메모장)
|
||||
Note_GetList(startDate: string, endDate: string, uid: string): Promise<string>;
|
||||
Note_GetDetail(idx: number): Promise<string>;
|
||||
Note_Add(pdate: string, title: string, uid: string, description: string, description2: string, share: boolean, guid: string): Promise<string>;
|
||||
Note_Edit(idx: number, pdate: string, title: string, uid: string, description: string, description2: string, share: boolean, guid: string): Promise<string>;
|
||||
Note_Delete(idx: number): Promise<string>;
|
||||
|
||||
// Board API (게시판 - 패치내역 등)
|
||||
Board_GetList(bidx: number, searchKey: string): Promise<string>;
|
||||
Board_GetDetail(idx: number): Promise<string>;
|
||||
|
||||
// Mail API (메일 발신 내역)
|
||||
Mail_GetList(startDate: string, endDate: string, searchKey: string): Promise<string>;
|
||||
}
|
||||
|
||||
// 사용자 권한 정보 타입
|
||||
@@ -512,6 +526,23 @@ export interface HolidayItem {
|
||||
wdate?: string;
|
||||
}
|
||||
|
||||
// 메모장 관련 타입 (EETGW_Note 테이블)
|
||||
export interface NoteItem {
|
||||
idx: number;
|
||||
gcode: string;
|
||||
pdate: string; // 날짜
|
||||
title: string; // 제목
|
||||
uid: string; // 작성자 ID
|
||||
description: string; // 내용 (plain text)
|
||||
description2: string; // 내용 (RTF format - not used in web)
|
||||
share: boolean; // 공유 여부
|
||||
wuid: string; // 등록자 ID
|
||||
wdate: string; // 등록일
|
||||
guid: string; // 폴더 GUID
|
||||
viewcount?: number; // 조회수
|
||||
viewdate?: string; // 최종 조회일
|
||||
}
|
||||
|
||||
// 메일양식 항목 타입
|
||||
export interface MailFormItem {
|
||||
idx: number;
|
||||
@@ -792,3 +823,39 @@ export interface JobReportDayData {
|
||||
holidays: HolidayItem[];
|
||||
}
|
||||
|
||||
// Board 게시판 타입 (패치내역 등)
|
||||
export interface BoardItem {
|
||||
idx: number;
|
||||
bidx: number;
|
||||
header: string;
|
||||
cate: string;
|
||||
title: string;
|
||||
contents: string;
|
||||
file: string;
|
||||
guid: string;
|
||||
url: string;
|
||||
wuid: string;
|
||||
wdate: string | null;
|
||||
project: string;
|
||||
pidx: number;
|
||||
gcode: string;
|
||||
close: boolean;
|
||||
remark: string;
|
||||
wuid_name: string;
|
||||
}
|
||||
|
||||
// Mail 발신 내역 타입
|
||||
export interface MailItem {
|
||||
idx: number;
|
||||
gcode: string;
|
||||
uid: string;
|
||||
subject: string;
|
||||
htmlbody: string;
|
||||
fromlist: string;
|
||||
tolist: string;
|
||||
cclist: string;
|
||||
bcclist: string;
|
||||
project: string;
|
||||
cate: string;
|
||||
wdate: string | null;
|
||||
}
|
||||
|
||||
496
SubProject/FPJ0000/DSNote.Designer.cs
generated
496
SubProject/FPJ0000/DSNote.Designer.cs
generated
File diff suppressed because it is too large
Load Diff
@@ -12,7 +12,7 @@
|
||||
<DbSource ConnectionRef="gwcs (Settings)" DbObjectName="EE.dbo.EETGW_Note" DbObjectType="Table" FillMethodModifier="Public" FillMethodName="Fill" GenerateMethods="Both" GenerateShortCommands="true" GeneratorGetMethodName="GetData" GeneratorSourceName="Fill" GetMethodModifier="Public" GetMethodName="GetData" QueryType="Rowset" ScalarCallRetval="System.Object, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" UseOptimisticConcurrency="true" UserGetMethodName="GetData" UserSourceName="Fill">
|
||||
<DeleteCommand>
|
||||
<DbCommand CommandType="Text" ModifiedByUser="false">
|
||||
<CommandText>DELETE FROM [EETGW_Note] WHERE (([idx] = @Original_idx) AND ([gcode] = @Original_gcode) AND ((@IsNull_pdate = 1 AND [pdate] IS NULL) OR ([pdate] = @Original_pdate)) AND ((@IsNull_title = 1 AND [title] IS NULL) OR ([title] = @Original_title)) AND ((@IsNull_uid = 1 AND [uid] IS NULL) OR ([uid] = @Original_uid)) AND ((@IsNull_share = 1 AND [share] IS NULL) OR ([share] = @Original_share)) AND ([wuid] = @Original_wuid) AND ([wdate] = @Original_wdate) AND ((@IsNull_guid = 1 AND [guid] IS NULL) OR ([guid] = @Original_guid)))</CommandText>
|
||||
<CommandText>DELETE FROM [EETGW_Note] WHERE (([idx] = @Original_idx) AND ([gcode] = @Original_gcode) AND ((@IsNull_pdate = 1 AND [pdate] IS NULL) OR ([pdate] = @Original_pdate)) AND ((@IsNull_title = 1 AND [title] IS NULL) OR ([title] = @Original_title)) AND ((@IsNull_uid = 1 AND [uid] IS NULL) OR ([uid] = @Original_uid)) AND ((@IsNull_share = 1 AND [share] IS NULL) OR ([share] = @Original_share)) AND ([wuid] = @Original_wuid) AND ([wdate] = @Original_wdate) AND ((@IsNull_guid = 1 AND [guid] IS NULL) OR ([guid] = @Original_guid)) AND ((@IsNull_viewcount = 1 AND [viewcount] IS NULL) OR ([viewcount] = @Original_viewcount)) AND ((@IsNull_viewdate = 1 AND [viewdate] IS NULL) OR ([viewdate] = @Original_viewdate)))</CommandText>
|
||||
<Parameters>
|
||||
<Parameter AllowDbNull="false" AutogeneratedName="" DataSourceName="" DbType="Int32" Direction="Input" ParameterName="@Original_idx" Precision="0" ProviderType="Int" Scale="0" Size="0" SourceColumn="idx" SourceColumnNullMapping="false" SourceVersion="Original" />
|
||||
<Parameter AllowDbNull="false" AutogeneratedName="" DataSourceName="" DbType="AnsiString" Direction="Input" ParameterName="@Original_gcode" Precision="0" ProviderType="VarChar" Scale="0" Size="0" SourceColumn="gcode" SourceColumnNullMapping="false" SourceVersion="Original" />
|
||||
@@ -28,13 +28,17 @@
|
||||
<Parameter AllowDbNull="false" AutogeneratedName="" DataSourceName="" DbType="DateTime" Direction="Input" ParameterName="@Original_wdate" Precision="0" ProviderType="SmallDateTime" Scale="0" Size="0" SourceColumn="wdate" SourceColumnNullMapping="false" SourceVersion="Original" />
|
||||
<Parameter AllowDbNull="true" AutogeneratedName="" DataSourceName="" DbType="Int32" Direction="Input" ParameterName="@IsNull_guid" Precision="0" ProviderType="Int" Scale="0" Size="0" SourceColumn="guid" SourceColumnNullMapping="true" SourceVersion="Original" />
|
||||
<Parameter AllowDbNull="true" AutogeneratedName="" DataSourceName="" DbType="AnsiString" Direction="Input" ParameterName="@Original_guid" Precision="0" ProviderType="VarChar" Scale="0" Size="0" SourceColumn="guid" SourceColumnNullMapping="false" SourceVersion="Original" />
|
||||
<Parameter AllowDbNull="true" AutogeneratedName="" DataSourceName="" DbType="Int32" Direction="Input" ParameterName="@IsNull_viewcount" Precision="0" ProviderType="Int" Scale="0" Size="0" SourceColumn="viewcount" SourceColumnNullMapping="true" SourceVersion="Original" />
|
||||
<Parameter AllowDbNull="true" AutogeneratedName="" DataSourceName="" DbType="Int32" Direction="Input" ParameterName="@Original_viewcount" Precision="0" ProviderType="Int" Scale="0" Size="0" SourceColumn="viewcount" SourceColumnNullMapping="false" SourceVersion="Original" />
|
||||
<Parameter AllowDbNull="true" AutogeneratedName="" DataSourceName="" DbType="Int32" Direction="Input" ParameterName="@IsNull_viewdate" Precision="0" ProviderType="Int" Scale="0" Size="0" SourceColumn="viewdate" SourceColumnNullMapping="true" SourceVersion="Original" />
|
||||
<Parameter AllowDbNull="true" AutogeneratedName="" DataSourceName="" DbType="DateTime" Direction="Input" ParameterName="@Original_viewdate" Precision="0" ProviderType="SmallDateTime" Scale="0" Size="0" SourceColumn="viewdate" SourceColumnNullMapping="false" SourceVersion="Original" />
|
||||
</Parameters>
|
||||
</DbCommand>
|
||||
</DeleteCommand>
|
||||
<InsertCommand>
|
||||
<DbCommand CommandType="Text" ModifiedByUser="false">
|
||||
<CommandText>INSERT INTO [EETGW_Note] ([gcode], [pdate], [title], [uid], [description2], [share], [wuid], [wdate], [description], [guid]) VALUES (@gcode, @pdate, @title, @uid, @description2, @share, @wuid, @wdate, @description, @guid);
|
||||
SELECT idx, gcode, pdate, title, uid, description2, share, wuid, wdate, description, guid FROM EETGW_Note WHERE (idx = SCOPE_IDENTITY()) ORDER BY pdate DESC</CommandText>
|
||||
<CommandText>INSERT INTO [EETGW_Note] ([gcode], [pdate], [title], [uid], [description2], [share], [wuid], [wdate], [description], [guid], [viewcount], [viewdate]) VALUES (@gcode, @pdate, @title, @uid, @description2, @share, @wuid, @wdate, @description, @guid, @viewcount, @viewdate);
|
||||
SELECT idx, gcode, pdate, title, uid, description2, share, wuid, wdate, description, guid, viewcount, viewdate FROM EETGW_Note WITH (nolock) WHERE (idx = SCOPE_IDENTITY()) ORDER BY pdate DESC</CommandText>
|
||||
<Parameters>
|
||||
<Parameter AllowDbNull="false" AutogeneratedName="" DataSourceName="" DbType="AnsiString" Direction="Input" ParameterName="@gcode" Precision="0" ProviderType="VarChar" Scale="0" Size="0" SourceColumn="gcode" SourceColumnNullMapping="false" SourceVersion="Current" />
|
||||
<Parameter AllowDbNull="true" AutogeneratedName="" DataSourceName="" DbType="AnsiString" Direction="Input" ParameterName="@pdate" Precision="0" ProviderType="VarChar" Scale="0" Size="0" SourceColumn="pdate" SourceColumnNullMapping="false" SourceVersion="Current" />
|
||||
@@ -46,12 +50,14 @@ SELECT idx, gcode, pdate, title, uid, description2, share, wuid, wdate, descript
|
||||
<Parameter AllowDbNull="false" AutogeneratedName="" DataSourceName="" DbType="DateTime" Direction="Input" ParameterName="@wdate" Precision="0" ProviderType="SmallDateTime" Scale="0" Size="0" SourceColumn="wdate" SourceColumnNullMapping="false" SourceVersion="Current" />
|
||||
<Parameter AllowDbNull="true" AutogeneratedName="" DataSourceName="" DbType="String" Direction="Input" ParameterName="@description" Precision="0" ProviderType="NVarChar" Scale="0" Size="0" SourceColumn="description" SourceColumnNullMapping="false" SourceVersion="Current" />
|
||||
<Parameter AllowDbNull="true" AutogeneratedName="" DataSourceName="" DbType="AnsiString" Direction="Input" ParameterName="@guid" Precision="0" ProviderType="VarChar" Scale="0" Size="0" SourceColumn="guid" SourceColumnNullMapping="false" SourceVersion="Current" />
|
||||
<Parameter AllowDbNull="true" AutogeneratedName="" DataSourceName="" DbType="Int32" Direction="Input" ParameterName="@viewcount" Precision="0" ProviderType="Int" Scale="0" Size="0" SourceColumn="viewcount" SourceColumnNullMapping="false" SourceVersion="Current" />
|
||||
<Parameter AllowDbNull="true" AutogeneratedName="" DataSourceName="" DbType="DateTime" Direction="Input" ParameterName="@viewdate" Precision="0" ProviderType="SmallDateTime" Scale="0" Size="0" SourceColumn="viewdate" SourceColumnNullMapping="false" SourceVersion="Current" />
|
||||
</Parameters>
|
||||
</DbCommand>
|
||||
</InsertCommand>
|
||||
<SelectCommand>
|
||||
<DbCommand CommandType="Text" ModifiedByUser="false">
|
||||
<CommandText>SELECT idx, gcode, pdate, title, uid, description2, share, wuid, wdate, description, guid
|
||||
<CommandText>SELECT idx, gcode, pdate, title, uid, description2, share, wuid, wdate, description, guid, viewcount, viewdate
|
||||
FROM EETGW_Note WITH (nolock)
|
||||
WHERE (gcode = @gcode) AND (pdate BETWEEN @sd AND @ed) AND (uid = @uid) OR
|
||||
(gcode = @gcode) AND (pdate BETWEEN @sd AND @ed) AND (ISNULL(share, 0) = 1)
|
||||
@@ -66,8 +72,8 @@ ORDER BY pdate DESC</CommandText>
|
||||
</SelectCommand>
|
||||
<UpdateCommand>
|
||||
<DbCommand CommandType="Text" ModifiedByUser="false">
|
||||
<CommandText>UPDATE [EETGW_Note] SET [gcode] = @gcode, [pdate] = @pdate, [title] = @title, [uid] = @uid, [description2] = @description2, [share] = @share, [wuid] = @wuid, [wdate] = @wdate, [description] = @description, [guid] = @guid WHERE (([idx] = @Original_idx) AND ([gcode] = @Original_gcode) AND ((@IsNull_pdate = 1 AND [pdate] IS NULL) OR ([pdate] = @Original_pdate)) AND ((@IsNull_title = 1 AND [title] IS NULL) OR ([title] = @Original_title)) AND ((@IsNull_uid = 1 AND [uid] IS NULL) OR ([uid] = @Original_uid)) AND ((@IsNull_share = 1 AND [share] IS NULL) OR ([share] = @Original_share)) AND ([wuid] = @Original_wuid) AND ([wdate] = @Original_wdate) AND ((@IsNull_guid = 1 AND [guid] IS NULL) OR ([guid] = @Original_guid)));
|
||||
SELECT idx, gcode, pdate, title, uid, description2, share, wuid, wdate, description, guid FROM EETGW_Note WHERE (idx = @idx) ORDER BY pdate DESC</CommandText>
|
||||
<CommandText>UPDATE [EETGW_Note] SET [gcode] = @gcode, [pdate] = @pdate, [title] = @title, [uid] = @uid, [description2] = @description2, [share] = @share, [wuid] = @wuid, [wdate] = @wdate, [description] = @description, [guid] = @guid, [viewcount] = @viewcount, [viewdate] = @viewdate WHERE (([idx] = @Original_idx) AND ([gcode] = @Original_gcode) AND ((@IsNull_pdate = 1 AND [pdate] IS NULL) OR ([pdate] = @Original_pdate)) AND ((@IsNull_title = 1 AND [title] IS NULL) OR ([title] = @Original_title)) AND ((@IsNull_uid = 1 AND [uid] IS NULL) OR ([uid] = @Original_uid)) AND ((@IsNull_share = 1 AND [share] IS NULL) OR ([share] = @Original_share)) AND ([wuid] = @Original_wuid) AND ([wdate] = @Original_wdate) AND ((@IsNull_guid = 1 AND [guid] IS NULL) OR ([guid] = @Original_guid)) AND ((@IsNull_viewcount = 1 AND [viewcount] IS NULL) OR ([viewcount] = @Original_viewcount)) AND ((@IsNull_viewdate = 1 AND [viewdate] IS NULL) OR ([viewdate] = @Original_viewdate)));
|
||||
SELECT idx, gcode, pdate, title, uid, description2, share, wuid, wdate, description, guid, viewcount, viewdate FROM EETGW_Note WITH (nolock) WHERE (idx = @idx) ORDER BY pdate DESC</CommandText>
|
||||
<Parameters>
|
||||
<Parameter AllowDbNull="false" AutogeneratedName="" DataSourceName="" DbType="AnsiString" Direction="Input" ParameterName="@gcode" Precision="0" ProviderType="VarChar" Scale="0" Size="0" SourceColumn="gcode" SourceColumnNullMapping="false" SourceVersion="Current" />
|
||||
<Parameter AllowDbNull="true" AutogeneratedName="" DataSourceName="" DbType="AnsiString" Direction="Input" ParameterName="@pdate" Precision="0" ProviderType="VarChar" Scale="0" Size="0" SourceColumn="pdate" SourceColumnNullMapping="false" SourceVersion="Current" />
|
||||
@@ -79,6 +85,8 @@ SELECT idx, gcode, pdate, title, uid, description2, share, wuid, wdate, descript
|
||||
<Parameter AllowDbNull="false" AutogeneratedName="" DataSourceName="" DbType="DateTime" Direction="Input" ParameterName="@wdate" Precision="0" ProviderType="SmallDateTime" Scale="0" Size="0" SourceColumn="wdate" SourceColumnNullMapping="false" SourceVersion="Current" />
|
||||
<Parameter AllowDbNull="true" AutogeneratedName="" DataSourceName="" DbType="String" Direction="Input" ParameterName="@description" Precision="0" ProviderType="NVarChar" Scale="0" Size="0" SourceColumn="description" SourceColumnNullMapping="false" SourceVersion="Current" />
|
||||
<Parameter AllowDbNull="true" AutogeneratedName="" DataSourceName="" DbType="AnsiString" Direction="Input" ParameterName="@guid" Precision="0" ProviderType="VarChar" Scale="0" Size="0" SourceColumn="guid" SourceColumnNullMapping="false" SourceVersion="Current" />
|
||||
<Parameter AllowDbNull="true" AutogeneratedName="" DataSourceName="" DbType="Int32" Direction="Input" ParameterName="@viewcount" Precision="0" ProviderType="Int" Scale="0" Size="0" SourceColumn="viewcount" SourceColumnNullMapping="false" SourceVersion="Current" />
|
||||
<Parameter AllowDbNull="true" AutogeneratedName="" DataSourceName="" DbType="DateTime" Direction="Input" ParameterName="@viewdate" Precision="0" ProviderType="SmallDateTime" Scale="0" Size="0" SourceColumn="viewdate" SourceColumnNullMapping="false" SourceVersion="Current" />
|
||||
<Parameter AllowDbNull="false" AutogeneratedName="" DataSourceName="" DbType="Int32" Direction="Input" ParameterName="@Original_idx" Precision="0" ProviderType="Int" Scale="0" Size="0" SourceColumn="idx" SourceColumnNullMapping="false" SourceVersion="Original" />
|
||||
<Parameter AllowDbNull="false" AutogeneratedName="" DataSourceName="" DbType="AnsiString" Direction="Input" ParameterName="@Original_gcode" Precision="0" ProviderType="VarChar" Scale="0" Size="0" SourceColumn="gcode" SourceColumnNullMapping="false" SourceVersion="Original" />
|
||||
<Parameter AllowDbNull="true" AutogeneratedName="" DataSourceName="" DbType="Int32" Direction="Input" ParameterName="@IsNull_pdate" Precision="0" ProviderType="Int" Scale="0" Size="0" SourceColumn="pdate" SourceColumnNullMapping="true" SourceVersion="Original" />
|
||||
@@ -93,7 +101,11 @@ SELECT idx, gcode, pdate, title, uid, description2, share, wuid, wdate, descript
|
||||
<Parameter AllowDbNull="false" AutogeneratedName="" DataSourceName="" DbType="DateTime" Direction="Input" ParameterName="@Original_wdate" Precision="0" ProviderType="SmallDateTime" Scale="0" Size="0" SourceColumn="wdate" SourceColumnNullMapping="false" SourceVersion="Original" />
|
||||
<Parameter AllowDbNull="true" AutogeneratedName="" DataSourceName="" DbType="Int32" Direction="Input" ParameterName="@IsNull_guid" Precision="0" ProviderType="Int" Scale="0" Size="0" SourceColumn="guid" SourceColumnNullMapping="true" SourceVersion="Original" />
|
||||
<Parameter AllowDbNull="true" AutogeneratedName="" DataSourceName="" DbType="AnsiString" Direction="Input" ParameterName="@Original_guid" Precision="0" ProviderType="VarChar" Scale="0" Size="0" SourceColumn="guid" SourceColumnNullMapping="false" SourceVersion="Original" />
|
||||
<Parameter AllowDbNull="false" AutogeneratedName="idx" ColumnName="idx" DataSourceName="" DataTypeServer="int" DbType="Int32" Direction="Input" ParameterName="@idx" Precision="0" ProviderType="Int" Scale="0" Size="4" SourceColumn="idx" SourceColumnNullMapping="false" SourceVersion="Current" />
|
||||
<Parameter AllowDbNull="true" AutogeneratedName="" DataSourceName="" DbType="Int32" Direction="Input" ParameterName="@IsNull_viewcount" Precision="0" ProviderType="Int" Scale="0" Size="0" SourceColumn="viewcount" SourceColumnNullMapping="true" SourceVersion="Original" />
|
||||
<Parameter AllowDbNull="true" AutogeneratedName="" DataSourceName="" DbType="Int32" Direction="Input" ParameterName="@Original_viewcount" Precision="0" ProviderType="Int" Scale="0" Size="0" SourceColumn="viewcount" SourceColumnNullMapping="false" SourceVersion="Original" />
|
||||
<Parameter AllowDbNull="true" AutogeneratedName="" DataSourceName="" DbType="Int32" Direction="Input" ParameterName="@IsNull_viewdate" Precision="0" ProviderType="Int" Scale="0" Size="0" SourceColumn="viewdate" SourceColumnNullMapping="true" SourceVersion="Original" />
|
||||
<Parameter AllowDbNull="true" AutogeneratedName="" DataSourceName="" DbType="DateTime" Direction="Input" ParameterName="@Original_viewdate" Precision="0" ProviderType="SmallDateTime" Scale="0" Size="0" SourceColumn="viewdate" SourceColumnNullMapping="false" SourceVersion="Original" />
|
||||
<Parameter AllowDbNull="false" AutogeneratedName="idx" ColumnName="idx" DataSourceName="EE.dbo.EETGW_Note" DataTypeServer="int" DbType="Int32" Direction="Input" ParameterName="@idx" Precision="0" ProviderType="Int" Scale="0" Size="4" SourceColumn="idx" SourceColumnNullMapping="false" SourceVersion="Current" />
|
||||
</Parameters>
|
||||
</DbCommand>
|
||||
</UpdateCommand>
|
||||
@@ -111,14 +123,14 @@ SELECT idx, gcode, pdate, title, uid, description2, share, wuid, wdate, descript
|
||||
<Mapping SourceColumn="wdate" DataSetColumn="wdate" />
|
||||
<Mapping SourceColumn="description" DataSetColumn="description" />
|
||||
<Mapping SourceColumn="guid" DataSetColumn="guid" />
|
||||
<Mapping SourceColumn="viewcount" DataSetColumn="viewcount" />
|
||||
<Mapping SourceColumn="viewdate" DataSetColumn="viewdate" />
|
||||
</Mappings>
|
||||
<Sources>
|
||||
<DbSource ConnectionRef="gwcs (Settings)" DbObjectName="EE.dbo.EETGW_Note" DbObjectType="Table" FillMethodModifier="Public" FillMethodName="FillByIdx" GenerateMethods="Both" GenerateShortCommands="true" GeneratorGetMethodName="GetbyIdx" GeneratorSourceName="FillByIdx" GetMethodModifier="Public" GetMethodName="GetbyIdx" QueryType="Rowset" ScalarCallRetval="System.Object, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" UseOptimisticConcurrency="true" UserGetMethodName="GetbyIdx" UserSourceName="FillByIdx">
|
||||
<SelectCommand>
|
||||
<DbCommand CommandType="Text" ModifiedByUser="true">
|
||||
<CommandText>SELECT idx, gcode, pdate, title, uid, description2, share, wuid, wdate, description, guid
|
||||
FROM EETGW_Note WITH (nolock)
|
||||
WHERE idx = @idx</CommandText>
|
||||
<CommandText>SELECT description, description2, gcode, guid, idx, pdate, share, title, uid, viewcount, viewdate, wdate, wuid FROM EETGW_Note WITH (nolock) WHERE (idx = @idx)</CommandText>
|
||||
<Parameters>
|
||||
<Parameter AllowDbNull="false" AutogeneratedName="idx" ColumnName="idx" DataSourceName="EE.dbo.EETGW_Note" DataTypeServer="int" DbType="Int32" Direction="Input" ParameterName="@idx" Precision="0" ProviderType="Int" Scale="0" Size="4" SourceColumn="idx" SourceColumnNullMapping="false" SourceVersion="Current" />
|
||||
</Parameters>
|
||||
@@ -128,11 +140,7 @@ WHERE idx = @idx</CommandText>
|
||||
<DbSource ConnectionRef="gwcs (Settings)" DbObjectName="EE.dbo.EETGW_Note" DbObjectType="Table" FillMethodModifier="Public" FillMethodName="FillByNoDesc" GenerateMethods="Both" GenerateShortCommands="true" GeneratorGetMethodName="GetByNoDesc" GeneratorSourceName="FillByNoDesc" GetMethodModifier="Public" GetMethodName="GetByNoDesc" QueryType="Rowset" ScalarCallRetval="System.Object, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" UseOptimisticConcurrency="true" UserGetMethodName="GetByNoDesc" UserSourceName="FillByNoDesc">
|
||||
<SelectCommand>
|
||||
<DbCommand CommandType="Text" ModifiedByUser="true">
|
||||
<CommandText>SELECT '' AS description, '' AS description2, gcode, guid, idx, pdate, share, title, uid, wdate, wuid
|
||||
FROM EETGW_Note WITH (nolock)
|
||||
WHERE (gcode = @gcode) AND (pdate BETWEEN @sd AND @ed) AND (uid = @uid) OR
|
||||
(gcode = @gcode) AND (pdate BETWEEN @sd AND @ed) AND (ISNULL(share, 0) = 1)
|
||||
ORDER BY pdate DESC</CommandText>
|
||||
<CommandText>SELECT gcode, guid, idx, pdate, share, title, uid, viewcount, viewdate, wdate, wuid FROM EETGW_Note WITH (nolock) WHERE (gcode = @gcode) AND (pdate BETWEEN @sd AND @ed) AND (uid = @uid) OR (gcode = @gcode) AND (pdate BETWEEN @sd AND @ed) AND (ISNULL(share, 0) = 1) ORDER BY pdate DESC</CommandText>
|
||||
<Parameters>
|
||||
<Parameter AllowDbNull="false" AutogeneratedName="gcode" ColumnName="gcode" DataSourceName="EE.dbo.EETGW_Note" DataTypeServer="varchar(10)" DbType="AnsiString" Direction="Input" ParameterName="@gcode" Precision="0" ProviderType="VarChar" Scale="0" Size="10" SourceColumn="gcode" SourceColumnNullMapping="false" SourceVersion="Current" />
|
||||
<Parameter AllowDbNull="true" AutogeneratedName="sd" ColumnName="pdate" DataSourceName="EE.dbo.EETGW_Note" DataTypeServer="varchar(10)" DbType="AnsiString" Direction="Input" ParameterName="@sd" Precision="0" ProviderType="VarChar" Scale="0" Size="10" SourceColumn="pdate" SourceColumnNullMapping="false" SourceVersion="Current" />
|
||||
@@ -149,71 +157,73 @@ ORDER BY pdate DESC</CommandText>
|
||||
</DataSource>
|
||||
</xs:appinfo>
|
||||
</xs:annotation>
|
||||
<xs:element name="DSNote" msdata:IsDataSet="true" msdata:UseCurrentLocale="true" msprop:Generator_UserDSName="DSNote" msprop:EnableTableAdapterManager="true" msprop:Generator_DataSetName="DSNote">
|
||||
<xs:element name="DSNote" msdata:IsDataSet="true" msdata:UseCurrentLocale="true" msprop:EnableTableAdapterManager="true" msprop:Generator_DataSetName="DSNote" msprop:Generator_UserDSName="DSNote">
|
||||
<xs:complexType>
|
||||
<xs:choice minOccurs="0" maxOccurs="unbounded">
|
||||
<xs:element name="EETGW_Note" msprop:Generator_RowClassName="EETGW_NoteRow" msprop:Generator_RowEvHandlerName="EETGW_NoteRowChangeEventHandler" msprop:Generator_RowDeletedName="EETGW_NoteRowDeleted" msprop:Generator_RowDeletingName="EETGW_NoteRowDeleting" msprop:Generator_RowEvArgName="EETGW_NoteRowChangeEvent" msprop:Generator_TablePropName="EETGW_Note" msprop:Generator_RowChangedName="EETGW_NoteRowChanged" msprop:Generator_UserTableName="EETGW_Note" msprop:Generator_RowChangingName="EETGW_NoteRowChanging" msprop:Generator_TableClassName="EETGW_NoteDataTable" msprop:Generator_TableVarName="tableEETGW_Note">
|
||||
<xs:element name="EETGW_Note" msprop:Generator_UserTableName="EETGW_Note" msprop:Generator_RowEvArgName="EETGW_NoteRowChangeEvent" msprop:Generator_TableVarName="tableEETGW_Note" msprop:Generator_TablePropName="EETGW_Note" msprop:Generator_RowDeletingName="EETGW_NoteRowDeleting" msprop:Generator_RowChangingName="EETGW_NoteRowChanging" msprop:Generator_RowEvHandlerName="EETGW_NoteRowChangeEventHandler" msprop:Generator_RowDeletedName="EETGW_NoteRowDeleted" msprop:Generator_TableClassName="EETGW_NoteDataTable" msprop:Generator_RowChangedName="EETGW_NoteRowChanged" msprop:Generator_RowClassName="EETGW_NoteRow">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="idx" msdata:ReadOnly="true" msdata:AutoIncrement="true" msdata:AutoIncrementSeed="-1" msdata:AutoIncrementStep="-1" msprop:Generator_UserColumnName="idx" msprop:Generator_ColumnPropNameInTable="idxColumn" msprop:Generator_ColumnPropNameInRow="idx" msprop:Generator_ColumnVarNameInTable="columnidx" type="xs:int" />
|
||||
<xs:element name="gcode" msprop:Generator_UserColumnName="gcode" msprop:Generator_ColumnPropNameInTable="gcodeColumn" msprop:Generator_ColumnPropNameInRow="gcode" msprop:Generator_ColumnVarNameInTable="columngcode">
|
||||
<xs:element name="idx" msdata:ReadOnly="true" msdata:AutoIncrement="true" msdata:AutoIncrementSeed="-1" msdata:AutoIncrementStep="-1" msprop:Generator_ColumnVarNameInTable="columnidx" msprop:Generator_ColumnPropNameInRow="idx" msprop:Generator_ColumnPropNameInTable="idxColumn" msprop:Generator_UserColumnName="idx" type="xs:int" />
|
||||
<xs:element name="gcode" msprop:Generator_ColumnVarNameInTable="columngcode" msprop:Generator_ColumnPropNameInRow="gcode" msprop:Generator_ColumnPropNameInTable="gcodeColumn" msprop:Generator_UserColumnName="gcode">
|
||||
<xs:simpleType>
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:maxLength value="10" />
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
</xs:element>
|
||||
<xs:element name="pdate" msprop:Generator_ColumnPropNameInTable="pdateColumn" msprop:nullValue="_empty" msprop:Generator_ColumnPropNameInRow="pdate" msprop:Generator_UserColumnName="pdate" msprop:Generator_ColumnVarNameInTable="columnpdate" minOccurs="0">
|
||||
<xs:element name="pdate" msprop:nullValue="_empty" msprop:Generator_ColumnPropNameInRow="pdate" msprop:Generator_ColumnVarNameInTable="columnpdate" msprop:Generator_ColumnPropNameInTable="pdateColumn" msprop:Generator_UserColumnName="pdate" minOccurs="0">
|
||||
<xs:simpleType>
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:maxLength value="10" />
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
</xs:element>
|
||||
<xs:element name="title" msprop:Generator_ColumnPropNameInTable="titleColumn" msprop:nullValue="_empty" msprop:Generator_ColumnPropNameInRow="title" msprop:Generator_UserColumnName="title" msprop:Generator_ColumnVarNameInTable="columntitle" minOccurs="0">
|
||||
<xs:element name="title" msprop:nullValue="_empty" msprop:Generator_ColumnPropNameInRow="title" msprop:Generator_ColumnVarNameInTable="columntitle" msprop:Generator_ColumnPropNameInTable="titleColumn" msprop:Generator_UserColumnName="title" minOccurs="0">
|
||||
<xs:simpleType>
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:maxLength value="50" />
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
</xs:element>
|
||||
<xs:element name="uid" msprop:Generator_ColumnPropNameInTable="uidColumn" msprop:nullValue="_empty" msprop:Generator_ColumnPropNameInRow="uid" msprop:Generator_UserColumnName="uid" msprop:Generator_ColumnVarNameInTable="columnuid" minOccurs="0">
|
||||
<xs:element name="uid" msprop:nullValue="_empty" msprop:Generator_ColumnPropNameInRow="uid" msprop:Generator_ColumnVarNameInTable="columnuid" msprop:Generator_ColumnPropNameInTable="uidColumn" msprop:Generator_UserColumnName="uid" minOccurs="0">
|
||||
<xs:simpleType>
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:maxLength value="20" />
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
</xs:element>
|
||||
<xs:element name="description2" msprop:Generator_ColumnPropNameInTable="description2Column" msprop:nullValue="_empty" msprop:Generator_ColumnPropNameInRow="description2" msprop:Generator_UserColumnName="description2" msprop:Generator_ColumnVarNameInTable="columndescription2" minOccurs="0">
|
||||
<xs:element name="description2" msprop:nullValue="_empty" msprop:Generator_ColumnPropNameInRow="description2" msprop:Generator_ColumnVarNameInTable="columndescription2" msprop:Generator_ColumnPropNameInTable="description2Column" msprop:Generator_UserColumnName="description2" minOccurs="0">
|
||||
<xs:simpleType>
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:maxLength value="2147483647" />
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
</xs:element>
|
||||
<xs:element name="share" msprop:Generator_ColumnPropNameInTable="shareColumn" msprop:nullValue="0" msprop:Generator_ColumnPropNameInRow="share" msprop:Generator_UserColumnName="share" msprop:Generator_ColumnVarNameInTable="columnshare" type="xs:boolean" minOccurs="0" />
|
||||
<xs:element name="wuid" msprop:Generator_ColumnPropNameInTable="wuidColumn" msprop:nullValue="_empty" msprop:Generator_ColumnPropNameInRow="wuid" msprop:Generator_UserColumnName="wuid" msprop:Generator_ColumnVarNameInTable="columnwuid">
|
||||
<xs:element name="share" msprop:nullValue="0" msprop:Generator_ColumnPropNameInRow="share" msprop:Generator_ColumnVarNameInTable="columnshare" msprop:Generator_ColumnPropNameInTable="shareColumn" msprop:Generator_UserColumnName="share" type="xs:boolean" minOccurs="0" />
|
||||
<xs:element name="wuid" msprop:nullValue="_empty" msprop:Generator_ColumnPropNameInRow="wuid" msprop:Generator_ColumnVarNameInTable="columnwuid" msprop:Generator_ColumnPropNameInTable="wuidColumn" msprop:Generator_UserColumnName="wuid">
|
||||
<xs:simpleType>
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:maxLength value="20" />
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
</xs:element>
|
||||
<xs:element name="wdate" msprop:Generator_UserColumnName="wdate" msprop:Generator_ColumnPropNameInTable="wdateColumn" msprop:Generator_ColumnPropNameInRow="wdate" msprop:Generator_ColumnVarNameInTable="columnwdate" type="xs:dateTime" />
|
||||
<xs:element name="description" msprop:Generator_ColumnPropNameInTable="descriptionColumn" msprop:nullValue="_empty" msprop:Generator_ColumnPropNameInRow="description" msprop:Generator_UserColumnName="description" msprop:Generator_ColumnVarNameInTable="columndescription" minOccurs="0">
|
||||
<xs:element name="wdate" msprop:Generator_ColumnVarNameInTable="columnwdate" msprop:Generator_ColumnPropNameInRow="wdate" msprop:Generator_ColumnPropNameInTable="wdateColumn" msprop:Generator_UserColumnName="wdate" type="xs:dateTime" />
|
||||
<xs:element name="description" msprop:nullValue="_empty" msprop:Generator_ColumnPropNameInRow="description" msprop:Generator_ColumnVarNameInTable="columndescription" msprop:Generator_ColumnPropNameInTable="descriptionColumn" msprop:Generator_UserColumnName="description" minOccurs="0">
|
||||
<xs:simpleType>
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:maxLength value="2147483647" />
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
</xs:element>
|
||||
<xs:element name="guid" msprop:Generator_ColumnPropNameInTable="guidColumn" msprop:nullValue="_empty" msprop:Generator_ColumnPropNameInRow="guid" msprop:Generator_UserColumnName="guid" msprop:Generator_ColumnVarNameInTable="columnguid" minOccurs="0">
|
||||
<xs:element name="guid" msprop:nullValue="_empty" msprop:Generator_ColumnPropNameInRow="guid" msprop:Generator_ColumnVarNameInTable="columnguid" msprop:Generator_ColumnPropNameInTable="guidColumn" msprop:Generator_UserColumnName="guid" minOccurs="0">
|
||||
<xs:simpleType>
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:maxLength value="50" />
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
</xs:element>
|
||||
<xs:element name="viewcount" msprop:Generator_ColumnVarNameInTable="columnviewcount" msprop:Generator_ColumnPropNameInRow="viewcount" msprop:Generator_ColumnPropNameInTable="viewcountColumn" msprop:Generator_UserColumnName="viewcount" type="xs:int" minOccurs="0" />
|
||||
<xs:element name="viewdate" msprop:Generator_ColumnVarNameInTable="columnviewdate" msprop:Generator_ColumnPropNameInRow="viewdate" msprop:Generator_ColumnPropNameInTable="viewdateColumn" msprop:Generator_UserColumnName="viewdate" type="xs:dateTime" minOccurs="0" />
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
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="115" ViewPortY="0" 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="115" ViewPortY="0" xmlns:ex="urn:schemas-microsoft-com:xml-msdatasource-layout-extended" xmlns="urn:schemas-microsoft-com:xml-msdatasource-layout">
|
||||
<Shapes>
|
||||
<Shape ID="DesignTable:EETGW_Note" ZOrder="1" X="260" Y="172" Height="324" Width="300" AdapterExpanded="true" DataTableExpanded="true" OldAdapterHeight="0" OldDataTableHeight="0" SplitterPosition="235" />
|
||||
</Shapes>
|
||||
|
||||
4
SubProject/FPJ0000/Note/fNote.Designer.cs
generated
4
SubProject/FPJ0000/Note/fNote.Designer.cs
generated
@@ -174,7 +174,6 @@
|
||||
//
|
||||
this.bindingNavigatorPositionItem.AccessibleName = "위치";
|
||||
this.bindingNavigatorPositionItem.AutoSize = false;
|
||||
this.bindingNavigatorPositionItem.Font = new System.Drawing.Font("맑은 고딕", 9F);
|
||||
this.bindingNavigatorPositionItem.Name = "bindingNavigatorPositionItem";
|
||||
this.bindingNavigatorPositionItem.Size = new System.Drawing.Size(50, 23);
|
||||
this.bindingNavigatorPositionItem.Text = "0";
|
||||
@@ -276,7 +275,6 @@
|
||||
// tbFind
|
||||
//
|
||||
this.tbFind.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
|
||||
this.tbFind.Font = new System.Drawing.Font("맑은 고딕", 9F);
|
||||
this.tbFind.Name = "tbFind";
|
||||
this.tbFind.Size = new System.Drawing.Size(100, 25);
|
||||
this.tbFind.KeyDown += new System.Windows.Forms.KeyEventHandler(this.tbFind_KeyDown);
|
||||
@@ -475,7 +473,6 @@
|
||||
// dtSD
|
||||
//
|
||||
this.dtSD.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
|
||||
this.dtSD.Font = new System.Drawing.Font("맑은 고딕", 9F);
|
||||
this.dtSD.Name = "dtSD";
|
||||
this.dtSD.Size = new System.Drawing.Size(90, 37);
|
||||
this.dtSD.Text = "1982-11-23";
|
||||
@@ -490,7 +487,6 @@
|
||||
// dtED
|
||||
//
|
||||
this.dtED.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
|
||||
this.dtED.Font = new System.Drawing.Font("맑은 고딕", 9F);
|
||||
this.dtED.Name = "dtED";
|
||||
this.dtED.Size = new System.Drawing.Size(90, 37);
|
||||
this.dtED.Text = "1982-11-23";
|
||||
|
||||
@@ -15,6 +15,7 @@ namespace FPJ0000.Note
|
||||
string fn_fpcolsize = "";
|
||||
public fNote()
|
||||
{
|
||||
|
||||
InitializeComponent();
|
||||
fn_fpcolsize = util.MakeFilePath(util.CurrentPath, "formSetting", "fp_" + this.Name + ".ini");
|
||||
this.ds1.EETGW_Note.TableNewRow += Projects_TableNewRow;
|
||||
|
||||
@@ -130,102 +130,102 @@
|
||||
<data name="bindingNavigatorMoveFirstItem.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO
|
||||
wwAADsMBx2+oZAAAASpJREFUOE9jGDygcNbz/00Lnv/PnPj4P1QIA4S3P8Apx5A789n/VUfe/8elKL77
|
||||
wf/ghmu4DciY8vT/wn0fsCqK73n4f+n+///9qy/gNiCh58n/aVveYyiKaL8P1pw56/9/r9ITuA2I7Hr0
|
||||
v3f1BxRFoa33wJpb1wFt7/z73yX/AG4DApsf/q+b/w6uKLjl7v9Fe///7wBqzpjz879d3c//9hnbcRvg
|
||||
UXX/f/60NyiK7Ipv/0+f8/u/f9e3/zqF7/5bJKzHbYB96d3/2ZNfYyjSTzn/36ToxX+VrE//jSOX4TbA
|
||||
Iu/O/9T+11gVGSSd+C+b9vW/bvA83AYYZt3+H9byEqci/dTL/zV8p+E2QCftxn+/6od4Fal4TMBtgFPu
|
||||
lf8gBXgVDULAwAAA8HbAq6XlmnAAAAAASUVORK5CYII=
|
||||
wwAADsMBx2+oZAAAATFJREFUOE9jYBg0oHDW8/9NC57/z5z4+D+6HAyEtz/AKceQO/PZ/1VH3v/HpSi+
|
||||
+8H/4IZrWOXAIGPK0/8L933Aqii+5+H/pfv///evvoAhBwcJPU/+T9vyHkNRRPt9sObMWf//e5WewG1A
|
||||
ZNej/72rP6AoCm29B9bcuu7/f//Ov/9d8g/gNiCw+eH/uvnv4IqCW+7+X7T3//+Odf//Z8z5+d+u7ud/
|
||||
+4ztuA3wqLr/P3/aGxRFdsW3/6fP+f3fv+vbf53Cd/8tEtbjNsC+9O7/7MmvMRTpp5z/b1L04r9K1qf/
|
||||
xpHLcBtgkXfnf2r/a6yKDJJO/JdN+/pfN3gehhwcGGbd/h/W8hKnIv3Uy/81fKdhlQMDnbQb//2qH+JV
|
||||
pOIxAaccg1Pulf8gBXgVDUoAAPB2wKtYlLYeAAAAAElFTkSuQmCC
|
||||
</value>
|
||||
</data>
|
||||
<data name="bindingNavigatorMovePreviousItem.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO
|
||||
wwAADsMBx2+oZAAAALZJREFUOE9jGDogvP3BfyiTdBDf/eB/cMM18gyI73n4f+n+///9qy+QbkBE+32w
|
||||
5sxZ//97lZ4gzYDQ1ntgza3rgLZ3/v3vkn+AeAOCW+7+X7T3//8OoOaMOT//29X9/G+fsZ00F9gV3/6f
|
||||
Puf3f/+ub/91Ct/9t0hYT3oY6Kec/29S9OK/Stan/8aRy0g3AAQMkk78l037+l83eB55BoCAfurl/xq+
|
||||
08g3AARUPCZQZsBgBQwMANAUYJgEulBVAAAAAElFTkSuQmCC
|
||||
wwAADsMBx2+oZAAAALtJREFUOE9jYBgyILz9wX90MaJBfPeD/8EN18gzIL7n4f+l+///96++QLoBEe33
|
||||
wZozZ/3/71V6gjQDQlvvgTW3rvv/37/z73+X/APEGxDccvf/or3//3es+/8/Y87P/3Z1P//bZ2wn3gAQ
|
||||
sCu+/T99zu///l3f/usUvvtvkbCeNANAQD/l/H+Tohf/VbI+/TeOXEa6ASBgkHTiv2za1/+6wfPIMwAE
|
||||
9FMv/9fwnUa+ASCg4jGBMgMGLwAA0BRgmCws/7cAAAAASUVORK5CYII=
|
||||
</value>
|
||||
</data>
|
||||
<data name="bindingNavigatorMoveNextItem.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO
|
||||
wwAADsMBx2+oZAAAAKNJREFUOE9jGHygcNbz/1AmeSB35rP/Cd33yDckY8rT//P2//6f0HWHPEMSep78
|
||||
n73v1//OrX//u5VeJt2QyK5H/6ds+/W/ZOnf/wnT//63yT1LmiGBzQ//t659D9ZsXPLlv3T0tf/GkcuI
|
||||
N8Sj6v7/krnv4JoVXXpIc4F96d3/gS3PyNMMAhZ5d/7bFFwhTzMIGGbdJl8zCOik3SBf81AEDAwAoH5f
|
||||
oAc0QjgAAAAASUVORK5CYII=
|
||||
wwAADsMBx2+oZAAAAKRJREFUOE9jYBh0oHDW8//oYiSB3JnP/id03yPfkIwpT//P2//7f0LXHfIMSeh5
|
||||
8n/2vl//O7f+/e9Wepl0QyK7Hv2fsu3X/5Klf/8nTP/73yb3LGmGBDY//N+69j1Ys3HJl//S0df+G0cu
|
||||
I94Qj6r7/0vmvoNrVnTpIV4zCNiX3v0f2PKMPM0gYJF3579NwRXyNIOAYdZt8jWDgE7aDfI1D00AAKB+
|
||||
X6Bjq5qXAAAAAElFTkSuQmCC
|
||||
</value>
|
||||
</data>
|
||||
<data name="bindingNavigatorMoveLastItem.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO
|
||||
wwAADsMBx2+oZAAAASxJREFUOE9jGFygcNbz/1AmBgDJNS14/j9z4mOcahhyZz77n9B9D6sCkNyqI+//
|
||||
h7c/wG1AxpSn/+ft//0/oesOhiKQ3MJ9H/4HN1zDbUBCz5P/s/f9+t+59e9/t9LLKApBctO2vP/vX30B
|
||||
twGRXY/+T9n263/J0r//E6b//W+TexauGCTXu/rDf6/SE7gNCGx++L917XuwZuOSL/+lo6/9N45cBtYA
|
||||
kqub/+6/S/4B3AZ4VN3/XzL3HVyzoksPXDFILn/am//2GdtxG2Bfevd/YMszDM0gAJLLnvz6v0XCetwG
|
||||
WOTd+W9TcAVDMwiA5FL7X8O9hBUYZt3GqhkEQHJhLS//6wbPw22ATtoNnJIgOb/qh/81fKfhNgAfcMq9
|
||||
8l/FYwIYQ4UGBWBgAAC+0b+zuQxOnAAAAABJRU5ErkJggg==
|
||||
wwAADsMBx2+oZAAAAStJREFUOE9jYBhUoHDW8//oYjAAkmta8Px/5sTHONUw5M589j+h+x5WBSC5VUfe
|
||||
/w9vf4BVHgwypjz9P2//7/8JXXcwFIHkFu778D+44RqGHBwk9Dz5P3vfr/+dW//+dyu9jKIQJDdty/v/
|
||||
/tUXcBsQ2fXo/5Rtv/6XLP37P2H63/82uWfhikFyvas//PcqPYHbgMDmh/9b174HazYu+fJfOvraf+PI
|
||||
ZWANILm6+e/+u+QfwG2AR9X9/yVz38E1K7r0wBWD5PKnvflvn7EdtwH2pXf/B7Y8w9AMk8ue/Pq/RcJ6
|
||||
3AZY5N35b1NwBUMzTC61/zXcS1iBYdZtrJpBACQX1vLyv27wPKzyYKCTdgOnJEjOr/rhfw3faTjV4AVO
|
||||
uVf+q3hMAGN0uYEFAL7Rv7NmXVYYAAAAAElFTkSuQmCC
|
||||
</value>
|
||||
</data>
|
||||
<data name="btAdd.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO
|
||||
wwAADsMBx2+oZAAAAUpJREFUOE9jGLzg7gL2/7fmcf6/Oofr/8UZvP+hwsSD60CNfx41/v/zsOH/yckC
|
||||
pBtwfjov3ICDPSKkG3B8kiBQc93/Pw+q/u9oFydswKWZPP/PTuX7fxKo8Ui/0P993SJAzeX//94r+r++
|
||||
Qeb/qhq5/0srFf/PL1X+P6tIFdPAU0B//nlYD9RUC8SV///cKwHivP9/72b+/3sn+f/f23H//92MAOKQ
|
||||
/5NyNDENONQrDHbu3/ulQI0FQI3ZQI2pQI0J///digZqDPv/70bQ/3/X/f53peliGrCzXeL/lmap/+vA
|
||||
zpX/v6RC8f/fWzFAjeH/p+Zp/J+QpfW/O0P3f3uq/v/mREPCYTIb6E+Qc//dCPjfk6FDWAM6APnz3w1/
|
||||
IPb735qsT7oB3em6YP+CcH2cEekGtCQZ/G+IN/xfE2v8vzLahHQD6AQYGAAkI9iedfyIaQAAAABJRU5E
|
||||
rkJggg==
|
||||
wwAADsMBx2+oZAAAAVdJREFUOE/Nz0tLAmEUBmB3kWRoCUVEISFUJGb1OywiKrDsIpZdkJAkDUvDQkij
|
||||
UKSbVIvatKhNi9oERRAGEQXhjJdp7Hd83/eGs2jhLGQ20QtndTgP71Gp/m0KZ1XInlTjM6XG+4EG5fuK
|
||||
yaTUIN8bIMUQ0gmtcuBtX/MLPMT0yoHnuA6kuA4iruI20lAZ+DiswWuyFum4Dk+7dbiP6kHEFVDBg+tQ
|
||||
My4DLbjwG3DqbcORxygHXxJakGIQRFwDEf0gwjKI4AYtzIHmHaA5Oxg/CsYPIb7YIQced+qluvTLCyIs
|
||||
gRYWQPNO0NwkWNYGxg+DcYNgGSu2Z0xy4C7SiJtwE66kuq049xlAs2Ng/AiS7nbszXci6jIh4jQjPGWR
|
||||
A+U59hiluowbQMzVVfmgPKU/GdcPxlmx5TArB6KzJunf0gTtPcqBzeluhCYsCIz3wm/rUw78WX4AJCPY
|
||||
nlwVm9EAAAAASUVORK5CYII=
|
||||
</value>
|
||||
</data>
|
||||
<data name="btEdit.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
|
||||
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAEvSURBVDhPpZI/S0JRGIed+i6Nbk0t0iCuSkuDi1MfIKfm
|
||||
+gAOQktTQkMggYtRkwgidKmgyQwMCgqz0CivvvK8cK7neG4KdeHhHs45v+c9/xLyz08Ft713OQ+6SqXV
|
||||
kfLlnXJw1lSK5VrERqGkMB4JCCLpvQ7lZfDlQJ+B4EnwI9nTkbYdAZMbjxOPq4eJPH1MvXC2sD8XsOzP
|
||||
0bcX/C3MXEfAfmzBsnCnP10uWBWu3IS+gJOm0w5fHCZiw0aQzu3GC0xYgm2R+poTRnh8HeqNOALu920w
|
||||
9MK0F8NGkMrs+ALewqrwUXss3ed+vKB6H+rh2OT3SjpO0IBgcyvnCjgDBGCq8mcMiQ3FHAGdLB/J4vMF
|
||||
KhoI83LXk6m5gCpmufbyOWlgv0BVIMx4JPj7JzIDGHRUPz2nxiQAAAAASUVORK5CYII=
|
||||
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAEuSURBVDhPpZKxS0JBHMff1P/S2NbUIg7RqrQ0tDj5B9jU
|
||||
nH+Ag9DSlOAQhNCi6BRBBD1SaDIDg4JCLVSqpz/5HJzdz3vp4MH3vcfdfT73e3cXyJot4NHs9qUSdkxK
|
||||
t20p1lsmJxc3JkfFq3m2MwUTxucCQCTd96G8DcYq9NkAnoc/kiqPzLcSMPn6eeKl8TSRl8+pB6cyx38C
|
||||
yv4afXvgfzBzlYD/cQXL4HZvulywCi49RL6AnabThWv5IBa2gt10Nl5gYQn3RaobCkZ4dh+ZE1ECzvdj
|
||||
MPRgvhdhK0jsHfgC7sIq+PTuVzqvvXjB5WNkNsfNYa5gxgFtEOwk01rAHiAgdlXejCFxw2JKQCflI1m8
|
||||
voQVbYC5uZtbCV2BLdctn50m/C9hVQKs7sE6bQYYdFQ/+SVRqQAAAABJRU5ErkJggg==
|
||||
</value>
|
||||
</data>
|
||||
<data name="btDel.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO
|
||||
wwAADsMBx2+oZAAAAW9JREFUOE+1kE0ow2Ecx3dV3krt4oJaOSCTvIRkMqSxyITIzCQHDouEdnFwIOVC
|
||||
DrhIDiQl5UTiNG/z2ppafy1S2gX/uDwfY6i1v7Hie3nqeb7fz+/7/FR/Ilwn0G0Exw4fV5GJlXlEZxXC
|
||||
rIet9bAQvB5Ymgn2sLYAvSZEux7RUQFzE4qQt4bCXAYjPaHvnDoCkLpsRGMB2JqCTGLIijDlwqQ9bEMV
|
||||
i9OIytR3EMNWcJ/BWH8A6j8/bOGFxwXNxYEvGbMQ9XnQ1/K78KfY3/VXzkMY0qFGG2H4RoLGQshJQNbG
|
||||
86CNhdrsX9a/uQZTPhQl4rMY4OLofbl3aX7I8uwPC7y/g1YdjyVJuEvT8e1tfwUYteHUxCCfHChDeHmG
|
||||
QQvokjlOU+PbWA0x3pZnILVVI3uvQyHsbiLnqnGmRCF1NYD8pDhpRxOH7HQoAKZGkFKjceszQbpSrumX
|
||||
bO+G80MFwKUTxgfgcO/b8D9IpXoFiiMDHIQm0skAAAAASUVORK5CYII=
|
||||
wwAADsMBx2+oZAAAAWtJREFUOE+1kE0ow2Ecx/9X5a2UiwtKOSCTmJBMhuQlMo3IvCUHDouEXHZwIOVC
|
||||
DrhIDiQl5USy07zNa2tKf2laaRf84/J8xBCetab4XL/f76fn+SnKX4DrGLqrwbHDzywkWJlHdJYjLEbY
|
||||
Wg8q4eYKlma+d1hbgF4TotWIaC+FuYmAktcXCksx2HrknBOHX1KbiTDngrXhW0kMdSBM2TA5Io+/wuI0
|
||||
oiz5TcRwB7hPYazfLx3rDz7+gCsXNBb4v1SdgajTQ19TaOMP2NtFmPSIilSo0v1y7FHBnAdZMWi6aO51
|
||||
kVCTGZoEzzWYciA/Dl9bBZwfvh3XmxIJy7PBJdx5odnAQ2E87qJUfPbtzwGjVpxJEWjH+4ElPD/BYBsY
|
||||
EjhKicW3sSoVb0vSUFsq0W6upUxhdxMtOxZnYhhqVz1oj3JJUZSdpCg0p0POmLKhJofjNqaDeikX3tFG
|
||||
uuHsQM65cML4ABzY5fA/eQGKIwMcVjm2bAAAAABJRU5ErkJggg==
|
||||
</value>
|
||||
</data>
|
||||
<data name="toolStripButton1.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
|
||||
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAIDSURBVDhPpZLrS5NhGMb3j4SWh0oRQVExD4gonkDpg4hG
|
||||
YKxG6WBogkMZKgPNCEVJFBGdGETEvgwyO9DJE5syZw3PIlPEE9pgBCLZ5XvdMB8Ew8gXbl54nuf63dd9
|
||||
0OGSnwCahxbPRNPAPMw9Xpg6ZmF46kZZ0xSKzJPIrhpDWsVnpBhGkKx3nAX8Pv7z1zg8OoY/cITdn4fw
|
||||
bf/C0kYAN3Ma/w3gWfZL5kzTKBxjWyK2DftwI9tyMYCZKXbNHaD91bLYJrDXsYbrWfUKwJrPE9M2M1Oc
|
||||
VzOOpHI7Jr376Hi9ogHqFIANO0/MmmmbmSmm9a8ze+I4MrNWAdjtoJgWcx+PSzg166yZZ8xM8XvXDix9
|
||||
c4jIqFYAjoriBV9AhEPv1mH/sonogha0afbZMMZz+yreTGyhpusHwtNNCsA5U1zS4BLxzJIfg299qO32
|
||||
Ir7UJtZfftyATqeT+8o2D8JSjQrAJblrncYL7ZJ2+bfaFnC/1S1NjL3diRat7qrO7wLRP3HjWsojBeCo
|
||||
mDEo5mNjuweFGvjWg2EBhCbpkW78htSHHwRyNdmgAFzPEee2iFkzayy2OLXzT4gr6UdUnlXrullsxxQ+
|
||||
kx0g8BTA3aZlButjSTyjODq/WcQcW/B/Je4OQhLvKQDnzN1mp0nnkvAhR8VuMzNrpm1mpjgkoVwB/v8D
|
||||
TgDQASA1MVpwzwAAAABJRU5ErkJggg==
|
||||
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAIFSURBVDhPpZLtS1NhGMbPPxJmmlYSgqHiKzGU1EDxg4iK
|
||||
YKyG2WBogqMYJQOtCEVRFBGdTBCJfRnkS4VaaWNT5sqx1BUxRXxDHYxAJLvkusEeBaPAB+5z4Jzn+t3X
|
||||
/aLhnEfjo8m+dCoa+7/C3O2Hqe0zDC+8KG+cRZHZhdzaaWTVTCLDMIY0vfM04Nfh77/G/sEhwpEDbO3t
|
||||
I7TxE8urEVy99fT/AL5gWDLrTB/hnF4XsW0khCu5ln8DmJliT2AXrcNBsU1gj/MH4nMeKwBrPktM28xM
|
||||
cX79DFKrHHD5d9D26hvicx4pABt2lpg10zYzU0zr7+e3xXGcrkEB2O2TNec9nJFwB3alZn5jZorfeDZh
|
||||
6Q3g8s06BeCoKF4MRURoH1+BY2oNCbeb0TIclIYxOhzf8frTOuo7FxCbbVIAzpni0iceEc8vhzEwGkJD
|
||||
lx83ymxifejdKjRNk/8PWnyIyTQqAJek0jqHwfEVscu31baIu8+90sTE4nY025dQ2/5FIPpnXlzKuK8A
|
||||
HBUzHot52djqQ6HZhfR7IwK4mKpHtvEDMqvfCiQ6zaAAXM8x94aIWTNrLLG4kVUzgaTSPlzLtyJOZxbb
|
||||
1wtfyg4Q+AfA3aZlButjSfxGcUJBk4g5tuP3haQKRKXcUQDOmbvNTpPOJeFFjordZmbWTNvMTHFUcpUC
|
||||
nOccAdABIDXXE1nzAAAAAElFTkSuQmCC
|
||||
</value>
|
||||
</data>
|
||||
<data name="btFind.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
|
||||
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAE3SURBVDhPnZIxS8NQFEb7W/wLjh0Fl9a1SxBHBekkWFd1
|
||||
qYg4Ci5dndSCg2AHl4LQSaRSKDqoFUE7VAjUmvTKueWmL2mw2gunL7zmO+/mJhmZoTJusdF868vpXUfO
|
||||
b5/lpPEox9f3SvnsRtk8uojxHQ7HEgSEkXS6vrz3xqtdu+xdfUiheEBsJOGCk/mz/hROUHsIIrp+qIKY
|
||||
hB/a9r+CVAG4Auj5g7iA5/1NACaptgIVLHkb0wWVw13ZL60p2+uerqkCJs1mMgwUU6d1k/xJwI10RZj1
|
||||
9TPUN7Wam9dgTMC75QR7TjCBkRQs5Jd1jQS8c1ewtZLTPcQW/peADpC44cudgnjZOQ1OCGjTwkwaGBon
|
||||
GoSrpcVIQqmAj6LZftFBup9vWiUlUQdIDCbsQrsGZRJKBbOXyA++SlEsu6QjvQAAAABJRU5ErkJggg==
|
||||
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAEwSURBVDhPpZIxSwNBEIXzW/wLKS0Fm2ib5hBLBbESjG20
|
||||
UUQsBRtbKzVgIWhhIwhWIhFBtFAjgqaIcKDmLiPfwO7NXg6NZuDdLnv3vn03uyX5R5VssdB8+ZC9q5Yc
|
||||
XD7K7sW9bJ9eq1b3z1WLW4eBumkvgwDADKTVjuW1k41ubrV2/CbV+Y0sCRN25uXZQ9qnk7vEqx2nCggg
|
||||
PIgdfyZ95jwEAOrEXyGA//0JYCGNm0QBk9HC74CdzRVZr82q6nORjoUAOs1i3owouk50BxkIwIekwsz4
|
||||
/J7qSc1UymoMAJwtO9iOO4BTHjA2MRUCOHMLWJqu6BpgZ/4TgARArPlouSrR6EgxgJj2qBBNY0cnzI3a
|
||||
uId4AJeiefukjbTXt6jyEJ8AiBMdtiKuk4V4wDD1Db5KUSxr13uqAAAAAElFTkSuQmCC
|
||||
</value>
|
||||
</data>
|
||||
<metadata name="cm.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
@@ -279,22 +279,22 @@
|
||||
<data name="btSearch.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
|
||||
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAE4SURBVDhPtZPPasJAEMbzPn0FwWA92JtP4NGKB1/DP7ei
|
||||
QSsovkChh7ZBrCfpyR4sikopUgq9StFzM/UbZ5asSS4FfzAkO7vft5udiZMEnSBpk5dhFJmncjdHxXaG
|
||||
A+9K4SbFT1luEwQBbXavVO5d0nI3ovnW5yeiMriiu+kt5asXbABEdgRigAUQDr+aHLU3lxoLl/yPJhvF
|
||||
GsiYJ/vPdX5qPK3bVJ25VFukafztGQNsKHJ791I3w+8KcpNth8XDz5YxACI/gsR1J8sTYcO4UIzwv1gG
|
||||
cTshgJ5IT8hChTMHsHi+v+fvffmxywVwN2FDkdsGEOK2ceu4feQ0tDqKyKMGqDfqjvprLzyswuX7Tf4E
|
||||
dBo6zn/3OB7XHovRyuhQ6+hhYKA9DpL+A1keRebNAhkaJH0OHOcP031C4EjYr6wAAAAASUVORK5CYII=
|
||||
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAEiSURBVDhPY2DAAf6jAXRxVNVIAKYga5bH/7TpTmAMYsNA
|
||||
3EQb3Ab8+/fv/71PZ/5nzXX+f+3T7v+X320H0yCct8Dz/9rTM/8HtGvDDcPQDAIgBSCNOx9NBuOOiw7/
|
||||
u644/N9+ZzLYIKwGwARAkvP2dIJpGN52Y/r/9gsO/zuu2P/f+3Qq3ACQhVhtz5jlBLcBJnb43Qyw5p0P
|
||||
pmB3AcwV6TNcwBLIBmLDKAbAeWQAFAPQbUG2DeYimAsxnA4z4PLnDWD/Hv2AGl0gAAobZAOxGgDSCApt
|
||||
UKiDQh/ZJbDYgQGcBoDiGxTvoPiHpYUt15Gj7y9uL4BSGijFbb81FYy33pgK1gxKyqAUiuJ0ZAAyAJbG
|
||||
QQBXPkDXBwfoCuA60MRpAgDTfULg/+7qPQAAAABJRU5ErkJggg==
|
||||
</value>
|
||||
</data>
|
||||
<data name="toolStripButton2.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
|
||||
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADlSURBVEhL7dQxCsIwFMbxnMrZWatzUXRUCo5ewSs4ewZR
|
||||
DyDeQfEETsUbRL7YYNSXNC/UOiQP/kvzyE9KUcQ7y/VJtlHFvQYPjzf50xKsJsFmq81WHq538swMO9il
|
||||
zhALxkWd7kAOJwsnjjPsYNeGs2B14fR5YTYq5O5c1u7sL987iAUjF+6LIjaMKJyDoiAYmVAvn8lsXHij
|
||||
KBhGwDWof4APihqHqQ+OKhg2X3U/n7+9ah88CKY+pM9ndTgbplDbmQtnwS7UtmPDWfDf/jIRLnKhOuzY
|
||||
UMSGmyrBauKE26jiohshHicE2B3dbRrmAAAAAElFTkSuQmCC
|
||||
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADiSURBVEhL7dVBCsIwEAXQnsq1a62ui6JLpeDSK3gF155B
|
||||
1AOId1A8gSvxBiNTCcTJJM3E2i6SwN9MhnklDW2WRbvW2wu0EepW8PkBf02CE2xks9vD6f4y6jTYg720
|
||||
riKCcVCvP4LxbOXEcQ97sNeGi+Bq4PwzMJ+UcLg+a3uON7NHDHODdZzu2dAgmAMQpzUXGgxTfFAsIJ+W
|
||||
3uhPMAZxBaoH8EExjcPcheMSDOtHPSyWX0ftgwfB3EWitTpcDFNAf6d0z4WLYDqYu0i0x4aL4M4+mZhO
|
||||
fhJNJsERw22EuvGsNycE2B33w41tAAAAAElFTkSuQmCC
|
||||
</value>
|
||||
</data>
|
||||
<metadata name="ta.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
|
||||
Reference in New Issue
Block a user