Add Dashboard todo edit/delete/complete features and Note view count tracking

This commit is contained in:
backuppc
2025-12-02 15:03:51 +09:00
parent 6a2485176b
commit e82f86191a
25 changed files with 3512 additions and 324 deletions

View File

@@ -363,6 +363,9 @@
<Compile Include="Web\MachineBridge\MachineBridge.Holiday.cs" /> <Compile Include="Web\MachineBridge\MachineBridge.Holiday.cs" />
<Compile Include="Web\MachineBridge\MachineBridge.Holyday.cs" /> <Compile Include="Web\MachineBridge\MachineBridge.Holyday.cs" />
<Compile Include="Web\MachineBridge\MachineBridge.MailForm.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.UserGroup.cs" />
<Compile Include="Web\MachineBridge\MachineBridge.UserAuth.cs" /> <Compile Include="Web\MachineBridge\MachineBridge.UserAuth.cs" />
<Compile Include="Web\MachineBridge\WebSocketServer.cs" /> <Compile Include="Web\MachineBridge\WebSocketServer.cs" />

View 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 });
}
}
}
}

View 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 });
}
}
}
}

View 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
}
}

View File

@@ -836,6 +836,99 @@ namespace Project.Web
break; 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 (월별근무표) ===== // ===== Holiday API (월별근무표) =====
case "HOLIDAY_GET_LIST": case "HOLIDAY_GET_LIST":
{ {

View File

@@ -1,7 +1,9 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { HashRouter, Routes, Route } from 'react-router-dom'; import { HashRouter, Routes, Route } from 'react-router-dom';
import { Layout } from '@/components/layout'; 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 { comms } from '@/communication';
import { UserInfo } from '@/types'; import { UserInfo } from '@/types';
import { Loader2 } from 'lucide-react'; import { Loader2 } from 'lucide-react';
@@ -92,6 +94,9 @@ export default function App() {
<Route path="/user/auth" element={<UserAuthPage />} /> <Route path="/user/auth" element={<UserAuthPage />} />
<Route path="/monthly-work" element={<MonthlyWorkPage />} /> <Route path="/monthly-work" element={<MonthlyWorkPage />} />
<Route path="/mail-form" element={<MailFormPage />} /> <Route path="/mail-form" element={<MailFormPage />} />
<Route path="/note" element={<Note />} />
<Route path="/patch-list" element={<PatchList />} />
<Route path="/mail-list" element={<MailList />} />
</Route> </Route>
</Routes> </Routes>
{/* Tailwind Breakpoint Indicator - 개발용 */} {/* Tailwind Breakpoint Indicator - 개발용 */}

View File

@@ -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 환경 감지 // WebView2 환경 감지
const isWebView = typeof window !== 'undefined' && const isWebView = typeof window !== 'undefined' &&
window.chrome?.webview?.hostObjects !== undefined; window.chrome?.webview?.hostObjects !== undefined;
@@ -799,7 +844,7 @@ class CommunicationLayer {
public async getJobReportTypeList(sd: string, ed: string, uid: string = ''): Promise<ApiResponse<JobReportTypeItem[]>> { public async getJobReportTypeList(sd: string, ed: string, uid: string = ''): Promise<ApiResponse<JobReportTypeItem[]>> {
if (isWebView && machine) { 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); return JSON.parse(result);
} else { } else {
return this.wsRequest<ApiResponse<JobReportTypeItem[]>>('JOBREPORT_GET_TYPE_LIST', 'JOBREPORT_TYPE_LIST_DATA', { sd, ed, uid }); 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'); 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(); export const comms = new CommunicationLayer();

View File

@@ -1,7 +1,7 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { X, ChevronLeft, ChevronRight, Download } from 'lucide-react'; import { X, ChevronLeft, ChevronRight, Download } from 'lucide-react';
import { comms } from '@/communication'; import { comms } from '@/communication';
import { JobReportDayItem, HolidayItem } from '@/types'; import { HolidayItem } from '@/types';
interface JobReportDayDialogProps { interface JobReportDayDialogProps {
isOpen: boolean; isOpen: boolean;
@@ -32,8 +32,6 @@ export function JobReportDayDialog({ isOpen, onClose, initialMonth }: JobReportD
const [dayColumns, setDayColumns] = useState<DayColumn[]>([]); const [dayColumns, setDayColumns] = useState<DayColumn[]>([]);
const [userRows, setUserRows] = useState<UserRow[]>([]); const [userRows, setUserRows] = useState<UserRow[]>([]);
const [currentUserId, setCurrentUserId] = useState<string>(''); const [currentUserId, setCurrentUserId] = useState<string>('');
const [currentUserLevel, setCurrentUserLevel] = useState<number>(0);
const [authLevel, setAuthLevel] = useState<number>(0);
const [canViewAll, setCanViewAll] = useState<boolean>(false); const [canViewAll, setCanViewAll] = useState<boolean>(false);
// 요일 배열 // 요일 배열
@@ -54,12 +52,10 @@ export function JobReportDayDialog({ isOpen, onClose, initialMonth }: JobReportD
const userId = loginStatus.User.Id; const userId = loginStatus.User.Id;
const userLevel = loginStatus.User.Level || 0; const userLevel = loginStatus.User.Level || 0;
setCurrentUserId(userId); setCurrentUserId(userId);
setCurrentUserLevel(userLevel);
// 업무일지(jobreport) 권한 가져오기 // 업무일지(jobreport) 권한 가져오기
const authResponse = await comms.checkAuth('jobreport', 5); const authResponse = await comms.checkAuth('jobreport', 5);
const jobReportAuthLevel = authResponse.EffectiveLevel || 0; const jobReportAuthLevel = authResponse.EffectiveLevel || 0;
setAuthLevel(jobReportAuthLevel);
// 유효 권한 레벨 = Max(사용자레벨, 권한레벨) // 유효 권한 레벨 = Max(사용자레벨, 권한레벨)
const effectiveLevel = Math.max(userLevel, jobReportAuthLevel); const effectiveLevel = Math.max(userLevel, jobReportAuthLevel);

View File

@@ -115,6 +115,15 @@ const dropdownMenus: DropdownMenuConfig[] = [
{ type: 'link', path: '/mail-form', icon: Mail, label: '메일양식' }, { 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({ function DropdownNavMenu({

View 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
);
}

View 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
);
}

View File

@@ -9,9 +9,20 @@ import {
RefreshCw, RefreshCw,
ClipboardList, ClipboardList,
Clock, Clock,
FileText,
Share2,
Lock,
List,
Plus,
X,
Loader2,
Edit2,
Trash2,
} from 'lucide-react'; } from 'lucide-react';
import { comms } from '@/communication'; 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 { interface StatCardProps {
title: string; title: string;
@@ -55,10 +66,31 @@ export function Dashboard() {
const [urgentTodos, setUrgentTodos] = useState<TodoModel[]>([]); const [urgentTodos, setUrgentTodos] = useState<TodoModel[]>([]);
const [purchaseNRList, setPurchaseNRList] = useState<PurchaseItem[]>([]); const [purchaseNRList, setPurchaseNRList] = useState<PurchaseItem[]>([]);
const [purchaseCRList, setPurchaseCRList] = useState<PurchaseItem[]>([]); const [purchaseCRList, setPurchaseCRList] = useState<PurchaseItem[]>([]);
const [recentNotes, setRecentNotes] = useState<NoteItem[]>([]);
// 모달 상태 // 모달 상태
const [showNRModal, setShowNRModal] = useState(false); const [showNRModal, setShowNRModal] = useState(false);
const [showCRModal, setShowCRModal] = 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 () => { const loadDashboardData = useCallback(async () => {
try { try {
@@ -83,11 +115,13 @@ export function Dashboard() {
urgentTodosResponse, urgentTodosResponse,
allTodosResponse, allTodosResponse,
jobreportResponse, jobreportResponse,
notesResponse,
] = await Promise.all([ ] = await Promise.all([
comms.getPurchaseWaitCount(), comms.getPurchaseWaitCount(),
comms.getUrgentTodos(), comms.getUrgentTodos(),
comms.getTodos(), comms.getTodos(),
comms.getJobReportList(todayStr, todayStr, currentUserId, ''), comms.getJobReportList(todayStr, todayStr, currentUserId, ''),
comms.getNoteList('2000-01-01', todayStr, ''),
]); ]);
setPurchaseNR(purchaseCount.NR); setPurchaseNR(purchaseCount.NR);
@@ -99,17 +133,22 @@ export function Dashboard() {
if (allTodosResponse.Success && allTodosResponse.Data) { 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); setTodoCount(pendingCount);
} }
// 오늘 업무일지 작성시간 계산 // 오늘 업무일지 작성시간 계산
if (jobreportResponse.Success && jobreportResponse.Data) { 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); setTodayWorkHrs(totalHrs);
} else { } else {
setTodayWorkHrs(0); setTodayWorkHrs(0);
} }
// 최근 메모 목록 (최대 10개)
if (notesResponse.Success && notesResponse.Data) {
setRecentNotes(notesResponse.Data.slice(0, 10));
}
} catch (error) { } catch (error) {
console.error('대시보드 데이터 로드 오류:', error); console.error('대시보드 데이터 로드 오류:', error);
} finally { } finally {
@@ -199,23 +238,263 @@ export function Dashboard() {
); );
} }
return ( const handleNoteClick = async (note: NoteItem) => {
<div className="space-y-6 animate-fade-in"> try {
{/* 헤더 */} const response = await comms.getNoteDetail(note.idx);
<div className="flex items-center justify-between"> if (response.Success && response.Data) {
<h2 className="text-2xl font-bold text-white"> </h2> setSelectedNote(response.Data);
<button setShowNoteModal(true);
onClick={handleRefresh} }
disabled={refreshing} } catch (error) {
className="flex items-center space-x-2 px-4 py-2 glass-effect rounded-lg text-white/70 hover:text-white transition-colors" console.error('메모 조회 오류:', error);
> }
<RefreshCw className={`w-4 h-4 ${refreshing ? 'animate-spin' : ''}`} /> };
<span></span>
</button>
</div>
{/* 통계 카드 */} const handleNoteAdd = () => {
<div className="grid grid-cols-1 md:grid-cols-4 gap-6"> 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 <StatCard
title="구매요청 (NR)" title="구매요청 (NR)"
value={purchaseNR} value={purchaseNR}
@@ -244,21 +523,31 @@ export function Dashboard() {
color="text-cyan-400" color="text-cyan-400"
onClick={() => navigate('/jobreport')} 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"> <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"> <h3 className="text-lg font-semibold text-white flex items-center">
<AlertTriangle className="w-5 h-5 mr-2 text-warning-400" /> <AlertTriangle className="w-5 h-5 mr-2 text-warning-400" />
</h3> </h3>
<button <div className="flex items-center gap-2">
onClick={() => navigate('/todo')} <button
className="text-sm text-primary-400 hover:text-primary-300 transition-colors" onClick={handleTodoAdd}
> className="p-1.5 rounded-lg bg-primary-500/20 text-primary-400 hover:bg-primary-500/30 transition-colors"
title="할일 추가"
</button> >
<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>
<div className="divide-y divide-white/10"> <div className="divide-y divide-white/10">
@@ -267,7 +556,7 @@ export function Dashboard() {
<div <div
key={todo.idx} key={todo.idx}
className="px-6 py-4 hover:bg-white/5 transition-colors cursor-pointer" 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 justify-between">
<div className="flex items-center space-x-4"> <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"> <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" /> <CheckCircle className="w-12 h-12 mx-auto mb-3 text-success-400/50" />
<p> </p> <p> </p>
</div> </div>
)} )}
</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> </div>
{/* NR 모달 */} {/* 우측 사이드바 - 메모 리스트 */}
{showNRModal && ( <div className="w-60 space-y-4">
<Modal title="구매요청 (NR) 목록" onClose={() => setShowNRModal(false)}> <div className="glass-effect rounded-2xl overflow-hidden">
<PurchaseTable data={purchaseNRList} /> <div className="px-4 py-3 border-b border-white/10 flex items-center justify-between">
</Modal> <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 모달 */} <div className="divide-y divide-white/10 max-h-[calc(100vh-200px)] overflow-y-auto">
{showCRModal && ( {recentNotes.length > 0 ? (
<Modal title="구매요청 (CR) 목록" onClose={() => setShowCRModal(false)}> recentNotes.map((note) => (
<PurchaseTable data={purchaseCRList} /> <div
</Modal> 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> </div>
); );
} }

View File

@@ -117,12 +117,12 @@ export function Jobreport() {
initialize(); initialize();
}, []); }, []);
// 초기화 완료 후 조회 실행 // 초기화 완료 후 조회 실행 (최초 1회만)
useEffect(() => { useEffect(() => {
if (initialized && startDate && endDate && selectedUser) { if (initialized && startDate && endDate && selectedUser) {
handleSearchAndLoadToday(); handleSearchAndLoadToday();
} }
}, [initialized, startDate, endDate, selectedUser]); }, [initialized]); // startDate, endDate, selectedUser 의존성 제거 (날짜 변경 시 자동 조회 방지)
// 검색 + 오늘 근무시간 로드 (순차 실행) // 검색 + 오늘 근무시간 로드 (순차 실행)
const handleSearchAndLoadToday = async () => { const handleSearchAndLoadToday = async () => {
@@ -373,6 +373,34 @@ export function Jobreport() {
handleSearch(); 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 ( return (
<div className="space-y-6 animate-fade-in"> <div className="space-y-6 animate-fade-in">
{/* 검색 필터 */} {/* 검색 필터 */}
@@ -381,6 +409,38 @@ export function Jobreport() {
{/* 좌측: 필터 영역 */} {/* 좌측: 필터 영역 */}
<div className="flex-1"> <div className="flex-1">
<div className="flex items-start gap-3"> <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열 */} {/* 필터 입력 영역: 2행 2열 */}
<div className="grid grid-cols-2 gap-x-4 gap-y-3"> <div className="grid grid-cols-2 gap-x-4 gap-y-3">
{/* 1행: 시작일, 담당자 */} {/* 1행: 시작일, 담당자 */}

View File

@@ -3,7 +3,6 @@ import {
Calendar, Calendar,
Search, Search,
Trash2, Trash2,
AlertTriangle,
Clock, Clock,
CheckCircle, CheckCircle,
XCircle, XCircle,
@@ -23,7 +22,7 @@ import { KuntaeEditModal, KuntaeFormData } from '@/components/kuntae/KuntaeEditM
export function Kuntae() { export function Kuntae() {
const [kuntaeList, setKuntaeList] = useState<KuntaeModel[]>([]); const [kuntaeList, setKuntaeList] = useState<KuntaeModel[]>([]);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [processing, setProcessing] = useState(false); const [_processing, setProcessing] = useState(false);
// 검색 조건 // 검색 조건
const [startDate, setStartDate] = useState(''); const [startDate, setStartDate] = useState('');
@@ -441,7 +440,7 @@ export function Kuntae() {
{item.contents || '-'} {item.contents || '-'}
</td> </td>
<td className="px-4 py-3 text-white text-sm"> <td className="px-4 py-3 text-white text-sm">
{item.UserName || item.uname || item.uid} {item.UserName || item.uid}
</td> </td>
<td className="px-4 py-3 text-white/50 text-xs"> <td className="px-4 py-3 text-white/50 text-xs">
{item.extcate ? `${item.extcate}` : '-'} {item.extcate ? `${item.extcate}` : '-'}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View File

@@ -12,3 +12,4 @@ export { MonthlyWorkPage } from './MonthlyWork';
export { MailFormPage } from './MailForm'; export { MailFormPage } from './MailForm';
export { UserGroupPage } from './UserGroup'; export { UserGroupPage } from './UserGroup';
export { default as UserAuthPage } from './UserAuth'; export { default as UserAuthPage } from './UserAuth';
export { Note } from './Note';

View File

@@ -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_GetList(statusFilter: string, category: string, process: string, userFilter: string, yearStart: string, yearEnd: string, dateType: string): Promise<string>;
Project_GetHistory(projectIdx: number): Promise<string>; Project_GetHistory(projectIdx: number): Promise<string>;
Project_GetDailyMemo(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; 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 { export interface MailFormItem {
idx: number; idx: number;
@@ -792,3 +823,39 @@ export interface JobReportDayData {
holidays: HolidayItem[]; 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;
}

File diff suppressed because it is too large Load Diff

View File

@@ -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"> <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> <DeleteCommand>
<DbCommand CommandType="Text" ModifiedByUser="false"> <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> <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="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="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="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="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="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> </Parameters>
</DbCommand> </DbCommand>
</DeleteCommand> </DeleteCommand>
<InsertCommand> <InsertCommand>
<DbCommand CommandType="Text" ModifiedByUser="false"> <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); <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 FROM EETGW_Note WHERE (idx = SCOPE_IDENTITY()) ORDER BY pdate DESC</CommandText> 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> <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="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" /> <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="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="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="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> </Parameters>
</DbCommand> </DbCommand>
</InsertCommand> </InsertCommand>
<SelectCommand> <SelectCommand>
<DbCommand CommandType="Text" ModifiedByUser="false"> <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) FROM EETGW_Note WITH (nolock)
WHERE (gcode = @gcode) AND (pdate BETWEEN @sd AND @ed) AND (uid = @uid) OR 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) (gcode = @gcode) AND (pdate BETWEEN @sd AND @ed) AND (ISNULL(share, 0) = 1)
@@ -66,8 +72,8 @@ ORDER BY pdate DESC</CommandText>
</SelectCommand> </SelectCommand>
<UpdateCommand> <UpdateCommand>
<DbCommand CommandType="Text" ModifiedByUser="false"> <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))); <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 FROM EETGW_Note WHERE (idx = @idx) ORDER BY pdate DESC</CommandText> 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> <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="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" /> <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="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="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="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="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="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" /> <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="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="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="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> </Parameters>
</DbCommand> </DbCommand>
</UpdateCommand> </UpdateCommand>
@@ -111,14 +123,14 @@ SELECT idx, gcode, pdate, title, uid, description2, share, wuid, wdate, descript
<Mapping SourceColumn="wdate" DataSetColumn="wdate" /> <Mapping SourceColumn="wdate" DataSetColumn="wdate" />
<Mapping SourceColumn="description" DataSetColumn="description" /> <Mapping SourceColumn="description" DataSetColumn="description" />
<Mapping SourceColumn="guid" DataSetColumn="guid" /> <Mapping SourceColumn="guid" DataSetColumn="guid" />
<Mapping SourceColumn="viewcount" DataSetColumn="viewcount" />
<Mapping SourceColumn="viewdate" DataSetColumn="viewdate" />
</Mappings> </Mappings>
<Sources> <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"> <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> <SelectCommand>
<DbCommand CommandType="Text" ModifiedByUser="true"> <DbCommand CommandType="Text" ModifiedByUser="true">
<CommandText>SELECT idx, gcode, pdate, title, uid, description2, share, wuid, wdate, description, guid <CommandText>SELECT description, description2, gcode, guid, idx, pdate, share, title, uid, viewcount, viewdate, wdate, wuid FROM EETGW_Note WITH (nolock) WHERE (idx = @idx)</CommandText>
FROM EETGW_Note WITH (nolock)
WHERE idx = @idx</CommandText>
<Parameters> <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" /> <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> </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"> <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> <SelectCommand>
<DbCommand CommandType="Text" ModifiedByUser="true"> <DbCommand CommandType="Text" ModifiedByUser="true">
<CommandText>SELECT '' AS description, '' AS description2, gcode, guid, idx, pdate, share, title, uid, wdate, wuid <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>
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> <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="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" /> <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> </DataSource>
</xs:appinfo> </xs:appinfo>
</xs:annotation> </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:complexType>
<xs:choice minOccurs="0" maxOccurs="unbounded"> <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:complexType>
<xs:sequence> <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="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_UserColumnName="gcode" msprop:Generator_ColumnPropNameInTable="gcodeColumn" msprop:Generator_ColumnPropNameInRow="gcode" msprop:Generator_ColumnVarNameInTable="columngcode"> <xs:element name="gcode" msprop:Generator_ColumnVarNameInTable="columngcode" msprop:Generator_ColumnPropNameInRow="gcode" msprop:Generator_ColumnPropNameInTable="gcodeColumn" msprop:Generator_UserColumnName="gcode">
<xs:simpleType> <xs:simpleType>
<xs:restriction base="xs:string"> <xs:restriction base="xs:string">
<xs:maxLength value="10" /> <xs:maxLength value="10" />
</xs:restriction> </xs:restriction>
</xs:simpleType> </xs:simpleType>
</xs:element> </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:simpleType>
<xs:restriction base="xs:string"> <xs:restriction base="xs:string">
<xs:maxLength value="10" /> <xs:maxLength value="10" />
</xs:restriction> </xs:restriction>
</xs:simpleType> </xs:simpleType>
</xs:element> </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:simpleType>
<xs:restriction base="xs:string"> <xs:restriction base="xs:string">
<xs:maxLength value="50" /> <xs:maxLength value="50" />
</xs:restriction> </xs:restriction>
</xs:simpleType> </xs:simpleType>
</xs:element> </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:simpleType>
<xs:restriction base="xs:string"> <xs:restriction base="xs:string">
<xs:maxLength value="20" /> <xs:maxLength value="20" />
</xs:restriction> </xs:restriction>
</xs:simpleType> </xs:simpleType>
</xs:element> </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:simpleType>
<xs:restriction base="xs:string"> <xs:restriction base="xs:string">
<xs:maxLength value="2147483647" /> <xs:maxLength value="2147483647" />
</xs:restriction> </xs:restriction>
</xs:simpleType> </xs:simpleType>
</xs:element> </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="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:Generator_ColumnPropNameInTable="wuidColumn" msprop:nullValue="_empty" msprop:Generator_ColumnPropNameInRow="wuid" msprop:Generator_UserColumnName="wuid" msprop:Generator_ColumnVarNameInTable="columnwuid"> <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:simpleType>
<xs:restriction base="xs:string"> <xs:restriction base="xs:string">
<xs:maxLength value="20" /> <xs:maxLength value="20" />
</xs:restriction> </xs:restriction>
</xs:simpleType> </xs:simpleType>
</xs:element> </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="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:Generator_ColumnPropNameInTable="descriptionColumn" msprop:nullValue="_empty" msprop:Generator_ColumnPropNameInRow="description" msprop:Generator_UserColumnName="description" msprop:Generator_ColumnVarNameInTable="columndescription" minOccurs="0"> <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:simpleType>
<xs:restriction base="xs:string"> <xs:restriction base="xs:string">
<xs:maxLength value="2147483647" /> <xs:maxLength value="2147483647" />
</xs:restriction> </xs:restriction>
</xs:simpleType> </xs:simpleType>
</xs:element> </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:simpleType>
<xs:restriction base="xs:string"> <xs:restriction base="xs:string">
<xs:maxLength value="50" /> <xs:maxLength value="50" />
</xs:restriction> </xs:restriction>
</xs:simpleType> </xs:simpleType>
</xs:element> </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:sequence>
</xs:complexType> </xs:complexType>
</xs:element> </xs:element>

View File

@@ -4,7 +4,7 @@
Changes to this file may cause incorrect behavior and will be lost if Changes to this file may cause incorrect behavior and will be lost if
the code is regenerated. the code is regenerated.
</autogenerated>--> </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> <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" /> <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> </Shapes>

View File

@@ -174,7 +174,6 @@
// //
this.bindingNavigatorPositionItem.AccessibleName = "위치"; this.bindingNavigatorPositionItem.AccessibleName = "위치";
this.bindingNavigatorPositionItem.AutoSize = false; this.bindingNavigatorPositionItem.AutoSize = false;
this.bindingNavigatorPositionItem.Font = new System.Drawing.Font("맑은 고딕", 9F);
this.bindingNavigatorPositionItem.Name = "bindingNavigatorPositionItem"; this.bindingNavigatorPositionItem.Name = "bindingNavigatorPositionItem";
this.bindingNavigatorPositionItem.Size = new System.Drawing.Size(50, 23); this.bindingNavigatorPositionItem.Size = new System.Drawing.Size(50, 23);
this.bindingNavigatorPositionItem.Text = "0"; this.bindingNavigatorPositionItem.Text = "0";
@@ -276,7 +275,6 @@
// tbFind // tbFind
// //
this.tbFind.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; this.tbFind.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.tbFind.Font = new System.Drawing.Font("맑은 고딕", 9F);
this.tbFind.Name = "tbFind"; this.tbFind.Name = "tbFind";
this.tbFind.Size = new System.Drawing.Size(100, 25); this.tbFind.Size = new System.Drawing.Size(100, 25);
this.tbFind.KeyDown += new System.Windows.Forms.KeyEventHandler(this.tbFind_KeyDown); this.tbFind.KeyDown += new System.Windows.Forms.KeyEventHandler(this.tbFind_KeyDown);
@@ -475,7 +473,6 @@
// dtSD // dtSD
// //
this.dtSD.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; this.dtSD.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.dtSD.Font = new System.Drawing.Font("맑은 고딕", 9F);
this.dtSD.Name = "dtSD"; this.dtSD.Name = "dtSD";
this.dtSD.Size = new System.Drawing.Size(90, 37); this.dtSD.Size = new System.Drawing.Size(90, 37);
this.dtSD.Text = "1982-11-23"; this.dtSD.Text = "1982-11-23";
@@ -490,7 +487,6 @@
// dtED // dtED
// //
this.dtED.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; this.dtED.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.dtED.Font = new System.Drawing.Font("맑은 고딕", 9F);
this.dtED.Name = "dtED"; this.dtED.Name = "dtED";
this.dtED.Size = new System.Drawing.Size(90, 37); this.dtED.Size = new System.Drawing.Size(90, 37);
this.dtED.Text = "1982-11-23"; this.dtED.Text = "1982-11-23";

View File

@@ -15,6 +15,7 @@ namespace FPJ0000.Note
string fn_fpcolsize = ""; string fn_fpcolsize = "";
public fNote() public fNote()
{ {
InitializeComponent(); InitializeComponent();
fn_fpcolsize = util.MakeFilePath(util.CurrentPath, "formSetting", "fp_" + this.Name + ".ini"); fn_fpcolsize = util.MakeFilePath(util.CurrentPath, "formSetting", "fp_" + this.Name + ".ini");
this.ds1.EETGW_Note.TableNewRow += Projects_TableNewRow; this.ds1.EETGW_Note.TableNewRow += Projects_TableNewRow;

View File

@@ -130,102 +130,102 @@
<data name="bindingNavigatorMoveFirstItem.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> <data name="bindingNavigatorMoveFirstItem.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value> <value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO
wwAADsMBx2+oZAAAASpJREFUOE9jGDygcNbz/00Lnv/PnPj4P1QIA4S3P8Apx5A789n/VUfe/8elKL77 wwAADsMBx2+oZAAAATFJREFUOE9jYBg0oHDW8/9NC57/z5z4+D+6HAyEtz/AKceQO/PZ/1VH3v/HpSi+
wf/ghmu4DciY8vT/wn0fsCqK73n4f+n+///9qy/gNiCh58n/aVveYyiKaL8P1pw56/9/r9ITuA2I7Hr0 +8H/4IZrWOXAIGPK0/8L933Aqii+5+H/pfv///evvoAhBwcJPU/+T9vyHkNRRPt9sObMWf//e5WewG1A
v3f1BxRFoa33wJpb1wFt7/z73yX/AG4DApsf/q+b/w6uKLjl7v9Fe///7wBqzpjz879d3c//9hnbcRvg ZNej/72rP6AoCm29B9bcuu7/f//Ov/9d8g/gNiCw+eH/uvnv4IqCW+7+X7T3//+Odf//Z8z5+d+u7ud/
UXX/f/60NyiK7Ipv/0+f8/u/f9e3/zqF7/5bJKzHbYB96d3/2ZNfYyjSTzn/36ToxX+VrE//jSOX4TbA +4ztuA3wqLr/P3/aGxRFdsW3/6fP+f3fv+vbf53Cd/8tEtbjNsC+9O7/7MmvMRTpp5z/b1L04r9K1qf/
Iu/O/9T+11gVGSSd+C+b9vW/bvA83AYYZt3+H9byEqci/dTL/zV8p+E2QCftxn+/6od4Fal4TMBtgFPu xpHLcBtgkXfnf2r/a6yKDJJO/JdN+/pfN3gehhwcGGbd/h/W8hKnIv3Uy/81fKdhlQMDnbQb//2qH+JV
lf8gBXgVDULAwAAA8HbAq6XlmnAAAAAASUVORK5CYII= pOIxAaccg1Pulf8gBXgVDUoAAPB2wKtYlLYeAAAAAElFTkSuQmCC
</value> </value>
</data> </data>
<data name="bindingNavigatorMovePreviousItem.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> <data name="bindingNavigatorMovePreviousItem.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value> <value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO
wwAADsMBx2+oZAAAALZJREFUOE9jGDogvP3BfyiTdBDf/eB/cMM18gyI73n4f+n+///9qy+QbkBE+32w wwAADsMBx2+oZAAAALtJREFUOE9jYBgyILz9wX90MaJBfPeD/8EN18gzIL7n4f+l+///96++QLoBEe33
5sxZ//97lZ4gzYDQ1ntgza3rgLZ3/v3vkn+AeAOCW+7+X7T3//8OoOaMOT//29X9/G+fsZ00F9gV3/6f wZozZ/3/71V6gjQDQlvvgTW3rvv/37/z73+X/APEGxDccvf/or3//3es+/8/Y87P/3Z1P//bZ2wn3gAQ
Puf3f/+ub/91Ct/9t0hYT3oY6Kec/29S9OK/Stan/8aRy0g3AAQMkk78l037+l83eB55BoCAfurl/xq+ sCu+/T99zu///l3f/usUvvtvkbCeNANAQD/l/H+Tohf/VbI+/TeOXEa6ASBgkHTiv2za1/+6wfPIMwAE
08g3AARUPCZQZsBgBQwMANAUYJgEulBVAAAAAElFTkSuQmCC 9FMv/9fwnUa+ASCg4jGBMgMGLwAA0BRgmCws/7cAAAAASUVORK5CYII=
</value> </value>
</data> </data>
<data name="bindingNavigatorMoveNextItem.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> <data name="bindingNavigatorMoveNextItem.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value> <value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO
wwAADsMBx2+oZAAAAKNJREFUOE9jGHygcNbz/1AmeSB35rP/Cd33yDckY8rT//P2//6f0HWHPEMSep78 wwAADsMBx2+oZAAAAKRJREFUOE9jYBh0oHDW8//oYiSB3JnP/id03yPfkIwpT//P2//7f0LXHfIMSeh5
n73v1//OrX//u5VeJt2QyK5H/6ds+/W/ZOnf/wnT//63yT1LmiGBzQ//t659D9ZsXPLlv3T0tf/GkcuI 8n/2vl//O7f+/e9Wepl0QyK7Hv2fsu3X/5Klf/8nTP/73yb3LGmGBDY//N+69j1Ys3HJl//S0df+G0cu
N8Sj6v7/krnv4JoVXXpIc4F96d3/gS3PyNMMAhZ5d/7bFFwhTzMIGGbdJl8zCOik3SBf81AEDAwAoH5f I94Qj6r7/0vmvoNrVnTpIV4zCNiX3v0f2PKMPM0gYJF3579NwRXyNIOAYdZt8jWDgE7aDfI1D00AAKB+
oAc0QjgAAAAASUVORK5CYII= X6Bjq5qXAAAAAElFTkSuQmCC
</value> </value>
</data> </data>
<data name="bindingNavigatorMoveLastItem.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> <data name="bindingNavigatorMoveLastItem.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value> <value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO
wwAADsMBx2+oZAAAASxJREFUOE9jGFygcNbz/1AmBgDJNS14/j9z4mOcahhyZz77n9B9D6sCkNyqI+// wwAADsMBx2+oZAAAAStJREFUOE9jYBhUoHDW8//oYjAAkmta8Px/5sTHONUw5M589j+h+x5WBSC5VUfe
h7c/wG1AxpSn/+ft//0/oesOhiKQ3MJ9H/4HN1zDbUBCz5P/s/f9+t+59e9/t9LLKApBctO2vP/vX30B /w9vf4BVHgwypjz9P2//7/8JXXcwFIHkFu778D+44RqGHBwk9Dz5P3vfr/+dW//+dyu9jKIQJDdty/v/
twGRXY/+T9n263/J0r//E6b//W+TexauGCTXu/rDf6/SE7gNCGx++L917XuwZuOSL/+lo6/9N45cBtYA /tUXcBsQ2fXo/5Rtv/6XLP37P2H63/82uWfhikFyvas//PcqPYHbgMDmh/9b174HazYu+fJfOvraf+PI
kqub/+6/S/4B3AZ4VN3/XzL3HVyzoksPXDFILn/am//2GdtxG2Bfevd/YMszDM0gAJLLnvz6v0XCetwG ZWANILm6+e/+u+QfwG2AR9X9/yVz38E1K7r0wBWD5PKnvflvn7EdtwH2pXf/B7Y8w9AMk8ue/Pq/RcJ6
WOTd+W9TcAVDMwiA5FL7X8O9hBUYZt3GqhkEQHJhLS//6wbPw22ATtoNnJIgOb/qh/81fKfhNgAfcMq9 3AZY5N35b1NwBUMzTC61/zXcS1iBYdZtrJpBACQX1vLyv27wPKzyYKCTdgOnJEjOr/rhfw3faTjV4AVO
8l/FYwIYQ4UGBWBgAAC+0b+zuQxOnAAAAABJRU5ErkJggg== uVf+q3hMAGN0uYEFAL7Rv7NmXVYYAAAAAElFTkSuQmCC
</value> </value>
</data> </data>
<data name="btAdd.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> <data name="btAdd.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value> <value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO
wwAADsMBx2+oZAAAAUpJREFUOE9jGLzg7gL2/7fmcf6/Oofr/8UZvP+hwsSD60CNfx41/v/zsOH/yckC wwAADsMBx2+oZAAAAVdJREFUOE/Nz0tLAmEUBmB3kWRoCUVEISFUJGb1OywiKrDsIpZdkJAkDUvDQkij
pBtwfjov3ICDPSKkG3B8kiBQc93/Pw+q/u9oFydswKWZPP/PTuX7fxKo8Ui/0P993SJAzeX//94r+r++ UKSbVIvatKhNi9oERRAGEQXhjJdp7Hd83/eGs2jhLGQ20QtndTgP71Gp/m0KZ1XInlTjM6XG+4EG5fuK
Qeb/qhq5/0srFf/PL1X+P6tIFdPAU0B//nlYD9RUC8SV///cKwHivP9/72b+/3sn+f/f23H//92MAOKQ yaTUIN8bIMUQ0gmtcuBtX/MLPMT0yoHnuA6kuA4iruI20lAZ+DiswWuyFum4Dk+7dbiP6kHEFVDBg+tQ
/5NyNDENONQrDHbu3/ulQI0FQI3ZQI2pQI0J///digZqDPv/70bQ/3/X/f53peliGrCzXeL/lmap/+vA My4DLbjwG3DqbcORxygHXxJakGIQRFwDEf0gwjKI4AYtzIHmHaA5Oxg/CsYPIb7YIQced+qluvTLCyIs
zpX/v6RC8f/fWzFAjeH/p+Zp/J+QpfW/O0P3f3uq/v/mREPCYTIb6E+Qc//dCPjfk6FDWAM6APnz3w1/ gRYWQPNO0NwkWNYGxg+DcYNgGSu2Z0xy4C7SiJtwE66kuq049xlAs2Ng/AiS7nbszXci6jIh4jQjPGWR
IPb735qsT7oB3em6YP+CcH2cEekGtCQZ/G+IN/xfE2v8vzLahHQD6AQYGAAkI9iedfyIaQAAAABJRU5E A+U59hiluowbQMzVVfmgPKU/GdcPxlmx5TArB6KzJunf0gTtPcqBzeluhCYsCIz3wm/rUw78WX4AJCPY
rkJggg== nlwVm9EAAAAASUVORK5CYII=
</value> </value>
</data> </data>
<data name="btEdit.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> <data name="btEdit.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value> <value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAEvSURBVDhPpZI/S0JRGIed+i6Nbk0t0iCuSkuDi1MfIKfm YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAEuSURBVDhPpZKxS0JBHMff1P/S2NbUIg7RqrQ0tDj5B9jU
+gAOQktTQkMggYtRkwgidKmgyQwMCgqz0CivvvK8cK7neG4KdeHhHs45v+c9/xLyz08Ft713OQ+6SqXV nH+Ag9DSlOAQhNCi6BRBBD1SaDIDg4JCLVSqpz/5HJzdz3vp4MH3vcfdfT73e3cXyJot4NHs9qUSdkxK
kfLlnXJw1lSK5VrERqGkMB4JCCLpvQ7lZfDlQJ+B4EnwI9nTkbYdAZMbjxOPq4eJPH1MvXC2sD8XsOzP t20p1lsmJxc3JkfFq3m2MwUTxucCQCTd96G8DcYq9NkAnoc/kiqPzLcSMPn6eeKl8TSRl8+pB6cyx38C
0bcX/C3MXEfAfmzBsnCnP10uWBWu3IS+gJOm0w5fHCZiw0aQzu3GC0xYgm2R+poTRnh8HeqNOALu920w yv4afXvgfzBzlYD/cQXL4HZvulywCi49RL6AnabThWv5IBa2gt10Nl5gYQn3RaobCkZ4dh+ZE1ECzvdj
9MK0F8NGkMrs+ALewqrwUXss3ed+vKB6H+rh2OT3SjpO0IBgcyvnCjgDBGCq8mcMiQ3FHAGdLB/J4vMF MPRgvhdhK0jsHfgC7sIq+PTuVzqvvXjB5WNkNsfNYa5gxgFtEOwk01rAHiAgdlXejCFxw2JKQCflI1m8
KhoI83LXk6m5gCpmufbyOWlgv0BVIMx4JPj7JzIDGHRUPz2nxiQAAAAASUVORK5CYII= voQVbYC5uZtbCV2BLdctn50m/C9hVQKs7sE6bQYYdFQ/+SVRqQAAAABJRU5ErkJggg==
</value> </value>
</data> </data>
<data name="btDel.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> <data name="btDel.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value> <value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO
wwAADsMBx2+oZAAAAW9JREFUOE+1kE0ow2Ecx3dV3krt4oJaOSCTvIRkMqSxyITIzCQHDouEdnFwIOVC wwAADsMBx2+oZAAAAWtJREFUOE+1kE0ow2Ecx/9X5a2UiwtKOSCTmJBMhuQlMo3IvCUHDouEXHZwIOVC
DrhIDiQl5UTiNG/z2ppafy1S2gX/uDwfY6i1v7Hie3nqeb7fz+/7/FR/Ilwn0G0Exw4fV5GJlXlEZxXC DrhIDiQl5USy07zNa2tKf2laaRf84/J8xBCetab4XL/f76fn+SnKX4DrGLqrwbHDzywkWJlHdJYjLEbY
rIet9bAQvB5Ymgn2sLYAvSZEux7RUQFzE4qQt4bCXAYjPaHvnDoCkLpsRGMB2JqCTGLIijDlwqQ9bEMV Wg8q4eYKlma+d1hbgF4TotWIaC+FuYmAktcXCksx2HrknBOHX1KbiTDngrXhW0kMdSBM2TA5Io+/wuI0
i9OIytR3EMNWcJ/BWH8A6j8/bOGFxwXNxYEvGbMQ9XnQ1/K78KfY3/VXzkMY0qFGG2H4RoLGQshJQNbG oiz5TcRwB7hPYazfLx3rDz7+gCsXNBb4v1SdgajTQ19TaOMP2NtFmPSIilSo0v1y7FHBnAdZMWi6aO51
86CNhdrsX9a/uQZTPhQl4rMY4OLofbl3aX7I8uwPC7y/g1YdjyVJuEvT8e1tfwUYteHUxCCfHChDeHmG kVCTGZoEzzWYciA/Dl9bBZwfvh3XmxIJy7PBJdx5odnAQ2E87qJUfPbtzwGjVpxJEWjH+4ElPD/BYBsY
QQvokjlOU+PbWA0x3pZnILVVI3uvQyHsbiLnqnGmRCF1NYD8pDhpRxOH7HQoAKZGkFKjceszQbpSrumX EjhKicW3sSoVb0vSUFsq0W6upUxhdxMtOxZnYhhqVz1oj3JJUZSdpCg0p0POmLKhJofjNqaDeikX3tFG
bO+G80MFwKUTxgfgcO/b8D9IpXoFiiMDHIQm0skAAAAASUVORK5CYII= uuHsQM65cML4ABzY5fA/eQGKIwMcVjm2bAAAAABJRU5ErkJggg==
</value> </value>
</data> </data>
<data name="toolStripButton1.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> <data name="toolStripButton1.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value> <value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAIDSURBVDhPpZLrS5NhGMb3j4SWh0oRQVExD4gonkDpg4hG YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAIFSURBVDhPpZLtS1NhGMbPPxJmmlYSgqHiKzGU1EDxg4iK
YKxG6WBogkMZKgPNCEVJFBGdGETEvgwyO9DJE5syZw3PIlPEE9pgBCLZ5XvdMB8Ew8gXbl54nuf63dd9 YKyG2WBogqMYJQOtCEVRFBGdTBCJfRnkS4VaaWNT5sqx1BUxRXxDHYxAJLvkusEeBaPAB+5z4Jzn+t3X
0OGSnwCahxbPRNPAPMw9Xpg6ZmF46kZZ0xSKzJPIrhpDWsVnpBhGkKx3nAX8Pv7z1zg8OoY/cITdn4fw /aLhnEfjo8m+dCoa+7/C3O2Hqe0zDC+8KG+cRZHZhdzaaWTVTCLDMIY0vfM04Nfh77/G/sEhwpEDbO3t
bf/C0kYAN3Ma/w3gWfZL5kzTKBxjWyK2DftwI9tyMYCZKXbNHaD91bLYJrDXsYbrWfUKwJrPE9M2M1Oc I7TxE8urEVy99fT/AL5gWDLrTB/hnF4XsW0khCu5ln8DmJliT2AXrcNBsU1gj/MH4nMeKwBrPktM28xM
VzOOpHI7Jr376Hi9ogHqFIANO0/MmmmbmSmm9a8ze+I4MrNWAdjtoJgWcx+PSzg166yZZ8xM8XvXDix9 cX79DFKrHHD5d9D26hvicx4pABt2lpg10zYzU0zr7+e3xXGcrkEB2O2TNec9nJFwB3alZn5jZorfeDZh
c4jIqFYAjoriBV9AhEPv1mH/sonogha0afbZMMZz+yreTGyhpusHwtNNCsA5U1zS4BLxzJIfg299qO32 6Q3g8s06BeCoKF4MRURoH1+BY2oNCbeb0TIclIYxOhzf8frTOuo7FxCbbVIAzpni0iceEc8vhzEwGkJD
Ir7UJtZfftyATqeT+8o2D8JSjQrAJblrncYL7ZJ2+bfaFnC/1S1NjL3diRat7qrO7wLRP3HjWsojBeCo lx83ymxifejdKjRNk/8PWnyIyTQqAJek0jqHwfEVscu31baIu8+90sTE4nY025dQ2/5FIPpnXlzKuK8A
mDEo5mNjuweFGvjWg2EBhCbpkW78htSHHwRyNdmgAFzPEee2iFkzayy2OLXzT4gr6UdUnlXrullsxxQ+ HBUzHot52djqQ6HZhfR7IwK4mKpHtvEDMqvfCiQ6zaAAXM8x94aIWTNrLLG4kVUzgaTSPlzLtyJOZxbb
kx0g8BTA3aZlButjSTyjODq/WcQcW/B/Je4OQhLvKQDnzN1mp0nnkvAhR8VuMzNrpm1mpjgkoVwB/v8D 1wtfyg4Q+AfA3aZlButjSfxGcUJBk4g5tuP3haQKRKXcUQDOmbvNTpPOJeFFjordZmbWTNvMTHFUcpUC
TgDQASA1MVpwzwAAAABJRU5ErkJggg== nOccAdABIDXXE1nzAAAAAElFTkSuQmCC
</value> </value>
</data> </data>
<data name="btFind.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> <data name="btFind.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value> <value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAE3SURBVDhPnZIxS8NQFEb7W/wLjh0Fl9a1SxBHBekkWFd1 YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAEwSURBVDhPpZIxSwNBEIXzW/wLKS0Fm2ib5hBLBbESjG20
qYg4Ci5dndSCg2AHl4LQSaRSKDqoFUE7VAjUmvTKueWmL2mw2gunL7zmO+/mJhmZoTJusdF868vpXUfO UUQsBRtbKzVgIWhhIwhWIhFBtFAjgqaIcKDmLiPfwO7NXg6NZuDdLnv3vn03uyX5R5VssdB8+ZC9q5Yc
b5/lpPEox9f3SvnsRtk8uojxHQ7HEgSEkXS6vrz3xqtdu+xdfUiheEBsJOGCk/mz/hROUHsIIrp+qIKY XD7K7sW9bJ9eq1b3z1WLW4eBumkvgwDADKTVjuW1k41ubrV2/CbV+Y0sCRN25uXZQ9qnk7vEqx2nCggg
hB/a9r+CVAG4Auj5g7iA5/1NACaptgIVLHkb0wWVw13ZL60p2+uerqkCJs1mMgwUU6d1k/xJwI10RZj1 PIgdfyZ95jwEAOrEXyGA//0JYCGNm0QBk9HC74CdzRVZr82q6nORjoUAOs1i3owouk50BxkIwIekwsz4
9TPUN7Wam9dgTMC75QR7TjCBkRQs5Jd1jQS8c1ewtZLTPcQW/peADpC44cudgnjZOQ1OCGjTwkwaGBon /J7qSc1UymoMAJwtO9iOO4BTHjA2MRUCOHMLWJqu6BpgZ/4TgARArPlouSrR6EgxgJj2qBBNY0cnzI3a
GoSrpcVIQqmAj6LZftFBup9vWiUlUQdIDCbsQrsGZRJKBbOXyA++SlEsu6QjvQAAAABJRU5ErkJggg== uId4AJeiefukjbTXt6jyEJ8AiBMdtiKuk4V4wDD1Db5KUSxr13uqAAAAAElFTkSuQmCC
</value> </value>
</data> </data>
<metadata name="cm.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> <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"> <data name="btSearch.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value> <value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAE4SURBVDhPtZPPasJAEMbzPn0FwWA92JtP4NGKB1/DP7ei YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAEiSURBVDhPY2DAAf6jAXRxVNVIAKYga5bH/7TpTmAMYsNA
QSsovkChh7ZBrCfpyR4sikopUgq9StFzM/UbZ5asSS4FfzAkO7vft5udiZMEnSBpk5dhFJmncjdHxXaG 3EQb3Ab8+/fv/71PZ/5nzXX+f+3T7v+X320H0yCct8Dz/9rTM/8HtGvDDcPQDAIgBSCNOx9NBuOOiw7/
A+9K4SbFT1luEwQBbXavVO5d0nI3ovnW5yeiMriiu+kt5asXbABEdgRigAUQDr+aHLU3lxoLl/yPJhvF u644/N9+ZzLYIKwGwARAkvP2dIJpGN52Y/r/9gsO/zuu2P/f+3Qq3ACQhVhtz5jlBLcBJnb43Qyw5p0P
GsiYJ/vPdX5qPK3bVJ25VFukafztGQNsKHJ791I3w+8KcpNth8XDz5YxACI/gsR1J8sTYcO4UIzwv1gG pmB3AcwV6TNcwBLIBmLDKAbAeWQAFAPQbUG2DeYimAsxnA4z4PLnDWD/Hv2AGl0gAAobZAOxGgDSCApt
cTshgJ5IT8hChTMHsHi+v+fvffmxywVwN2FDkdsGEOK2ceu4feQ0tDqKyKMGqDfqjvprLzyswuX7Tf4E UKiDQh/ZJbDYgQGcBoDiGxTvoPiHpYUt15Gj7y9uL4BSGijFbb81FYy33pgK1gxKyqAUiuJ0ZAAyAJbG
dBo6zn/3OB7XHovRyuhQ6+hhYKA9DpL+A1keRebNAhkaJH0OHOcP031C4EjYr6wAAAAASUVORK5CYII= QQBXPkDXBwfoCuA60MRpAgDTfULg/+7qPQAAAABJRU5ErkJggg==
</value> </value>
</data> </data>
<data name="toolStripButton2.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> <data name="toolStripButton2.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value> <value>
iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADlSURBVEhL7dQxCsIwFMbxnMrZWatzUXRUCo5ewSs4ewZR YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADiSURBVEhL7dVBCsIwEAXQnsq1a62ui6JLpeDSK3gF155B
DyDeQfEETsUbRL7YYNSXNC/UOiQP/kvzyE9KUcQ7y/VJtlHFvQYPjzf50xKsJsFmq81WHq538swMO9il 1AOId1A8gSvxBiNTCcTJJM3E2i6SwN9MhnklDW2WRbvW2wu0EepW8PkBf02CE2xks9vD6f4y6jTYg720
zhALxkWd7kAOJwsnjjPsYNeGs2B14fR5YTYq5O5c1u7sL987iAUjF+6LIjaMKJyDoiAYmVAvn8lsXHij riKCcVCvP4LxbOXEcQ97sNeGi+Bq4PwzMJ+UcLg+a3uON7NHDHODdZzu2dAgmAMQpzUXGgxTfFAsIJ+W
KBhGwDWof4APihqHqQ+OKhg2X3U/n7+9ah88CKY+pM9ndTgbplDbmQtnwS7UtmPDWfDf/jIRLnKhOuzY 3uhPMAZxBaoH8EExjcPcheMSDOtHPSyWX0ftgwfB3EWitTpcDFNAf6d0z4WLYDqYu0i0x4aL4M4+mZhO
UMSGmyrBauKE26jiohshHicE2B3dbRrmAAAAAElFTkSuQmCC fhJNJsERw22EuvGsNycE2B33w41tAAAAAElFTkSuQmCC
</value> </value>
</data> </data>
<metadata name="ta.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> <metadata name="ta.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">