diff --git a/Project/Dialog/fDashboardNew.Designer.cs b/Project/Dialog/fDashboardNew.Designer.cs
new file mode 100644
index 0000000..77bec37
--- /dev/null
+++ b/Project/Dialog/fDashboardNew.Designer.cs
@@ -0,0 +1,63 @@
+namespace Project.Dialog
+{
+ partial class fDashboardNew
+ {
+ ///
+ /// Required designer variable.
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ ///
+ /// Clean up any resources being used.
+ ///
+ /// true if managed resources should be disposed; otherwise, false.
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Windows Form Designer generated code
+
+ ///
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ ///
+ private void InitializeComponent()
+ {
+ this.label1 = new System.Windows.Forms.Label();
+ this.SuspendLayout();
+ //
+ // label1
+ //
+ this.label1.Dock = System.Windows.Forms.DockStyle.Fill;
+ this.label1.Font = new System.Drawing.Font("맑은 고딕", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
+ this.label1.ForeColor = System.Drawing.Color.DimGray;
+ this.label1.Location = new System.Drawing.Point(0, 0);
+ this.label1.Name = "label1";
+ this.label1.Size = new System.Drawing.Size(1063, 567);
+ this.label1.TabIndex = 0;
+ this.label1.Text = "요약 화면 구성 중 입니다.\r\n\r\n업무일지는 \"관리->업무일지->목록\" 을 사용하세요";
+ this.label1.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
+ this.label1.Click += new System.EventHandler(this.label1_Click);
+ //
+ // fDashboard
+ //
+ this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 12F);
+ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+ this.ClientSize = new System.Drawing.Size(1063, 567);
+ this.Controls.Add(this.label1);
+ this.Name = "fDashboard";
+ this.Text = "요약";
+ this.ResumeLayout(false);
+
+ }
+
+ #endregion
+
+ private System.Windows.Forms.Label label1;
+ }
+}
\ No newline at end of file
diff --git a/Project/Dialog/fDashboardNew.cs b/Project/Dialog/fDashboardNew.cs
new file mode 100644
index 0000000..031f011
--- /dev/null
+++ b/Project/Dialog/fDashboardNew.cs
@@ -0,0 +1,119 @@
+using FCM0000.Mail;
+using FCOMMON;
+using Microsoft.Web.WebView2.Core;
+using Microsoft.Web.WebView2.WinForms;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Data;
+using System.Drawing;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+
+namespace Project.Dialog
+{
+ public partial class fDashboardNew : fBase
+ {
+ private Web.WebSocketServer _wsServer;
+ private WebView2 webView;
+ public fDashboardNew()
+ {
+ InitializeComponent();
+
+
+ InitializeWebView2();
+
+ try
+ {
+ _wsServer = new Web.WebSocketServer("http://localhost:8081/", this);
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show("Failed to start WebSocket Server (Port 8081). Run as Admin or allow port.\n" + ex.Message);
+ }
+ }
+ bool loadok = false;
+ public void RefreshView()
+ {
+ if (loadok)
+ webView.Reload();
+ }
+ private void InitializeWebView2()
+ {
+ // 수동으로 WebView2 컨트롤 생성
+ this.webView = new WebView2();
+
+ // 기본 속성 설정
+ this.webView.CreationProperties = null;
+ this.webView.DefaultBackgroundColor = Color.White;
+ this.webView.Dock = DockStyle.Fill;
+ this.webView.Location = new Point(0, 0);
+ this.webView.Name = "webView21";
+ this.webView.Size = new Size(800, 600);
+ this.webView.TabIndex = 0;
+ this.webView.ZoomFactor = 1D;
+
+ // 폼에 추가
+ this.Controls.Add(this.webView);
+
+ // 비동기 초기화
+ InitializeAsync();
+ }
+ private async void InitializeAsync()
+ {
+ try
+ {
+ // Fixed Version 경로 설정
+ string runtimePath = Path.Combine(Application.StartupPath, "WebView2Runtime");
+
+ if (Directory.Exists(runtimePath))
+ {
+ var env = await CoreWebView2Environment.CreateAsync(runtimePath);
+ await this.webView.EnsureCoreWebView2Async(env);
+ }
+ else
+ {
+ // 시스템에 설치된 WebView2 사용
+ await this.webView.EnsureCoreWebView2Async();
+ }
+
+ var wwwroot = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Web", "wwwroot");
+ webView.CoreWebView2.SetVirtualHostNameToFolderMapping(
+ "hmi.local",
+ wwwroot,
+ CoreWebView2HostResourceAccessKind.Allow);
+
+
+ // 2. Inject Native Object
+ webView.CoreWebView2.AddHostObjectToScript("machine", new Web.MachineBridge(this));
+
+ Pub.WebServiceURL = "http://hmi.local";
+
+ // OWIN 서버의 DashBoard 페이지로 연결
+ if (FCOMMON.info.Login.no.isEmpty())
+ webView.Source = new Uri($"{Pub.WebServiceURL}/login.html");
+ else
+ webView.Source = new Uri($"{Pub.WebServiceURL}/DashBoard");
+
+ label1.Visible = false;
+ loadok = true;
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show($"WebView2 초기화 실패: {ex.Message}");
+ }
+ }
+ protected override void OnLoad(EventArgs e)
+ {
+ base.OnLoad(e);
+ EnsureVisibleAndUsableSize();
+ }
+ private void label1_Click(object sender, EventArgs e)
+ {
+
+ }
+ }
+}
diff --git a/Project/Dialog/fDashboardNew.resx b/Project/Dialog/fDashboardNew.resx
new file mode 100644
index 0000000..1af7de1
--- /dev/null
+++ b/Project/Dialog/fDashboardNew.resx
@@ -0,0 +1,120 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/Project/EETGW.csproj b/Project/EETGW.csproj
index 14ae08e..b3324b6 100644
--- a/Project/EETGW.csproj
+++ b/Project/EETGW.csproj
@@ -259,6 +259,12 @@
fCommon.cs
+
+ Form
+
+
+ fDashboardNew.cs
+
Form
@@ -424,6 +430,14 @@
+
+
+
+
+
+
+
+
@@ -432,6 +446,7 @@
+
Form
@@ -495,6 +510,9 @@
fCommon.cs
+
+ fDashboardNew.cs
+
fHolyday.cs
@@ -686,9 +704,6 @@
PreserveNewest
-
- PreserveNewest
-
PreserveNewest
@@ -707,9 +722,6 @@
PreserveNewest
-
- PreserveNewest
-
PreserveNewest
diff --git a/Project/Web/Controllers/DashBoardController.cs b/Project/Web/Controllers/DashBoardController.cs
index 8ad5471..80979c2 100644
--- a/Project/Web/Controllers/DashBoardController.cs
+++ b/Project/Web/Controllers/DashBoardController.cs
@@ -287,7 +287,7 @@ namespace Project.Web.Controllers
var cs = Properties.Settings.Default.gwcs;// "Data Source=K4FASQL.kr.ds.amkor.com,50150;Initial Catalog=EE;Persist Security Info=True;User ID=eeadm;Password=uJnU8a8q&DJ+ug-D!";
var cn = new System.Data.SqlClient.SqlConnection(cs);
var cmd = new System.Data.SqlClient.SqlCommand(sql, cn);
- cmd.Parameters.AddWithValue("gcode", FCOMMON.info.Login.gcode);
+ cmd.Parameters.AddWithValue("gcode", FCOMMON.info.Login.gcode ?? string.Empty);
var da = new System.Data.SqlClient.SqlDataAdapter(cmd);
var dt = new System.Data.DataTable();
da.Fill(dt);
diff --git a/Project/Web/MachineBridge/MachineBridge.Common.cs b/Project/Web/MachineBridge/MachineBridge.Common.cs
new file mode 100644
index 0000000..c6fbb17
--- /dev/null
+++ b/Project/Web/MachineBridge/MachineBridge.Common.cs
@@ -0,0 +1,233 @@
+using System;
+using System.Data;
+using System.Data.SqlClient;
+using Newtonsoft.Json;
+using FCOMMON;
+
+namespace Project.Web
+{
+ public partial class MachineBridge
+ {
+ #region Common API
+
+ ///
+ /// 네비게이션 메뉴 조회
+ ///
+ public string GetNavigationMenu()
+ {
+ try
+ {
+ var menuItems = new[]
+ {
+ new { key = "dashboard", title = "대시보드", url = "/DashBoard/index.html", icon = "M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2H5a2 2 0 00-2-2z M8 5a2 2 0 012-2h4a2 2 0 012 2v2H8V5z", isVisible = true, sortOrder = 1 },
+ new { key = "common", title = "공용코드", url = "/Common.html", icon = "M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z", isVisible = true, sortOrder = 2 },
+ new { key = "jobreport", title = "업무일지", url = "/Jobreport/index.html", icon = "M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2", isVisible = true, sortOrder = 3 },
+ new { key = "kuntae", title = "근태관리", url = "/Kuntae/index.html", icon = "M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z", isVisible = true, sortOrder = 4 },
+ new { key = "todo", title = "할일관리", url = "/Todo/index.html", icon = "M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2M12 12l2 2 4-4", isVisible = true, sortOrder = 5 },
+ new { key = "project", title = "프로젝트", url = "/Project/index.html", icon = "M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10", isVisible = true, sortOrder = 6 }
+ };
+
+ return JsonConvert.SerializeObject(new { Success = true, Data = menuItems, Message = "메뉴 정보를 성공적으로 가져왔습니다." });
+ }
+ catch (Exception ex)
+ {
+ return JsonConvert.SerializeObject(new { Success = false, Data = (object)null, Message = "메뉴 정보를 가져오는 중 오류가 발생했습니다: " + ex.Message });
+ }
+ }
+
+ ///
+ /// 공용코드 그룹 목록 조회
+ ///
+ public string Common_GetGroups()
+ {
+ try
+ {
+ var sql = "select code, svalue, memo from common WITH (nolock) " +
+ "where gcode = @gcode and grp = '99' " +
+ "order by code";
+
+ var cs = Properties.Settings.Default.gwcs;
+ var cn = new SqlConnection(cs);
+ var cmd = new SqlCommand(sql, cn);
+ cmd.Parameters.AddWithValue("@gcode", info.Login.gcode);
+
+ var da = new SqlDataAdapter(cmd);
+ var dt = new DataTable();
+ da.Fill(dt);
+ da.Dispose();
+ cmd.Dispose();
+ cn.Dispose();
+
+ return JsonConvert.SerializeObject(dt, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Common_GetGroups 오류: {ex.Message}");
+ return "[]";
+ }
+ }
+
+ ///
+ /// 공용코드 목록 조회
+ ///
+ public string Common_GetList(string grp)
+ {
+ try
+ {
+ if (string.IsNullOrEmpty(grp)) grp = "99";
+
+ var sql = "select * from common where gcode = @gcode and grp = @grp order by code, svalue";
+
+ var cs = Properties.Settings.Default.gwcs;
+ var cn = new SqlConnection(cs);
+ var cmd = new SqlCommand(sql, cn);
+ cmd.Parameters.AddWithValue("@gcode", info.Login.gcode);
+ cmd.Parameters.AddWithValue("@grp", grp);
+
+ var da = new SqlDataAdapter(cmd);
+ var dt = new DataTable();
+ da.Fill(dt);
+ da.Dispose();
+ cmd.Dispose();
+ cn.Dispose();
+
+ return JsonConvert.SerializeObject(dt, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Common_GetList 오류: {ex.Message}");
+ return "[]";
+ }
+ }
+
+ ///
+ /// 공용코드 저장
+ ///
+ public string Common_Save(int idx, string grp, string code, string svalue, int ivalue, float fvalue, string svalue2, string memo)
+ {
+ try
+ {
+ var cs = Properties.Settings.Default.gwcs;
+ var cn = new SqlConnection(cs);
+ var sql = string.Empty;
+ var cmd = new SqlCommand();
+ cmd.Connection = cn;
+
+ if (idx > 0)
+ {
+ // 업데이트
+ sql = @"UPDATE common SET
+ grp = @grp,
+ code = @code,
+ svalue = @svalue,
+ ivalue = @ivalue,
+ fvalue = @fvalue,
+ svalue2 = @svalue2,
+ memo = @memo,
+ wuid = @wuid,
+ wdate = GETDATE()
+ WHERE idx = @idx AND gcode = @gcode";
+ }
+ else
+ {
+ // 신규 추가
+ sql = @"INSERT INTO common (gcode, grp, code, svalue, ivalue, fvalue, svalue2, memo, wuid, wdate)
+ VALUES (@gcode, @grp, @code, @svalue, @ivalue, @fvalue, @svalue2, @memo, @wuid, GETDATE())";
+ }
+
+ cmd.CommandText = sql;
+ cmd.Parameters.AddWithValue("@gcode", info.Login.gcode);
+ cmd.Parameters.AddWithValue("@grp", grp ?? "");
+ cmd.Parameters.AddWithValue("@code", code ?? "");
+ cmd.Parameters.AddWithValue("@svalue", svalue ?? "");
+ cmd.Parameters.AddWithValue("@ivalue", ivalue);
+ cmd.Parameters.AddWithValue("@fvalue", fvalue);
+ cmd.Parameters.AddWithValue("@svalue2", svalue2 ?? "");
+ cmd.Parameters.AddWithValue("@memo", memo ?? "");
+ cmd.Parameters.AddWithValue("@wuid", info.Login.no);
+
+ if (idx > 0)
+ {
+ cmd.Parameters.AddWithValue("@idx", idx);
+ }
+
+ cn.Open();
+ var result = cmd.ExecuteNonQuery();
+ cn.Close();
+
+ cmd.Dispose();
+ cn.Dispose();
+
+ return JsonConvert.SerializeObject(new { Success = result > 0, Message = result > 0 ? "저장되었습니다." : "저장에 실패했습니다." });
+ }
+ catch (Exception ex)
+ {
+ return JsonConvert.SerializeObject(new { Success = false, Message = "오류가 발생했습니다: " + ex.Message });
+ }
+ }
+
+ ///
+ /// 공용코드 삭제
+ ///
+ public string Common_Delete(int idx)
+ {
+ try
+ {
+ var cs = Properties.Settings.Default.gwcs;
+ var cn = new SqlConnection(cs);
+ var sql = "DELETE FROM common WHERE idx = @idx AND gcode = @gcode";
+ var cmd = new SqlCommand(sql, cn);
+
+ cmd.Parameters.AddWithValue("@idx", idx);
+ cmd.Parameters.AddWithValue("@gcode", info.Login.gcode);
+
+ cn.Open();
+ var result = cmd.ExecuteNonQuery();
+ cn.Close();
+
+ cmd.Dispose();
+ cn.Dispose();
+
+ return JsonConvert.SerializeObject(new { Success = result > 0, Message = result > 0 ? "삭제되었습니다." : "삭제에 실패했습니다." });
+ }
+ catch (Exception ex)
+ {
+ return JsonConvert.SerializeObject(new { Success = false, Message = "오류가 발생했습니다: " + ex.Message });
+ }
+ }
+
+ ///
+ /// 현재 로그인 사용자 정보 조회
+ ///
+ public string GetCurrentUser()
+ {
+ try
+ {
+ if (string.IsNullOrEmpty(info.Login.no))
+ {
+ return JsonConvert.SerializeObject(new { Success = false, Message = "로그인되지 않은 상태입니다." });
+ }
+
+ return JsonConvert.SerializeObject(new
+ {
+ Success = true,
+ Data = new
+ {
+ id = info.Login.no,
+ name = info.Login.nameK,
+ userName = info.Login.nameK,
+ email = info.Login.email,
+ dept = info.Login.dept,
+ gcode = info.Login.gcode
+ }
+ });
+ }
+ catch (Exception ex)
+ {
+ return JsonConvert.SerializeObject(new { Success = false, Message = ex.Message });
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/Project/Web/MachineBridge/MachineBridge.Dashboard.cs b/Project/Web/MachineBridge/MachineBridge.Dashboard.cs
new file mode 100644
index 0000000..36031e9
--- /dev/null
+++ b/Project/Web/MachineBridge/MachineBridge.Dashboard.cs
@@ -0,0 +1,268 @@
+using System;
+using System.Data;
+using System.Data.SqlClient;
+using Newtonsoft.Json;
+using FCOMMON;
+
+namespace Project.Web
+{
+ public partial class MachineBridge
+ {
+ #region Dashboard API
+
+ ///
+ /// 오늘 휴가 인원 수 조회
+ ///
+ public string TodayCountH()
+ {
+ try
+ {
+ var sql = "select count(*) from EETGW_HolydayRequest WITH (nolock) " +
+ " where gcode = @gcode and isnull(conf,0) = 1 " +
+ " and sdate <= convert(varchar(10),GETDATE(),120) and edate >= convert(varchar(10),GETDATE(),120)";
+
+ var cn = DBM.getCn();
+ cn.Open();
+ var cmd = new SqlCommand(sql, cn);
+ cmd.Parameters.Add("gcode", SqlDbType.VarChar).Value = info.Login.gcode;
+ var cnt = (int)cmd.ExecuteScalar();
+ cmd.Dispose();
+ cn.Dispose();
+
+ return cnt.ToString();
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"TodayCountH 오류: {ex.Message}");
+ return "0";
+ }
+ }
+
+ ///
+ /// 휴가요청 대기 건수 조회
+ ///
+ public string GetHolydayRequestCount()
+ {
+ try
+ {
+ var sql = "select count(*) from EETGW_HolydayRequest WITH (nolock) " +
+ " where gcode = @gcode and isnull(conf,0) = 0";
+
+ var cn = DBM.getCn();
+ cn.Open();
+ var cmd = new SqlCommand(sql, cn);
+ cmd.Parameters.Add("gcode", SqlDbType.VarChar).Value = info.Login.gcode;
+ var cnt = (int)cmd.ExecuteScalar();
+ cmd.Dispose();
+ cn.Dispose();
+
+ return JsonConvert.SerializeObject(new { HOLY = cnt, Message = "" });
+ }
+ catch (Exception ex)
+ {
+ return JsonConvert.SerializeObject(new { HOLY = 0, Message = ex.Message });
+ }
+ }
+
+ ///
+ /// 현재 출근 대상 인원 수 조회
+ ///
+ public string GetCurrentUserCount()
+ {
+ try
+ {
+ var sql = "select count(*) from vGroupUser WITH (nolock) " +
+ " where gcode = @gcode and useUserState = 1 and useJobReport = 1" +
+ " and id not in (select uid from vEETGW_TodayNoneWorkUser where gcode = @gcode and kunmu = 0)";
+
+ var cn = DBM.getCn();
+ cn.Open();
+ var cmd = new SqlCommand(sql, cn);
+ cmd.Parameters.Add("gcode", SqlDbType.VarChar).Value = info.Login.gcode;
+ var cnt = (int)cmd.ExecuteScalar();
+ cmd.Dispose();
+ cn.Dispose();
+
+ return JsonConvert.SerializeObject(new { Count = cnt, Message = "" });
+ }
+ catch (Exception ex)
+ {
+ return JsonConvert.SerializeObject(new { Count = 0, Message = ex.Message });
+ }
+ }
+
+ ///
+ /// 구매요청 대기 건수 조회 (NR, CR)
+ ///
+ public string GetPurchaseWaitCount()
+ {
+ try
+ {
+ DBM.GetPurchaseWaitCount(info.Login.gcode, out int cnt1, out int cnt2);
+ return JsonConvert.SerializeObject(new { NR = cnt1, CR = cnt2, Message = "" });
+ }
+ catch (Exception ex)
+ {
+ return JsonConvert.SerializeObject(new { NR = 0, CR = 0, Message = ex.Message });
+ }
+ }
+
+ ///
+ /// 휴가자 목록 조회
+ ///
+ public string GetHolyUser()
+ {
+ try
+ {
+ var sql = " select uid,type,cate,sdate,edate,title,dbo.getusername(uid) as name " +
+ " from vEETGW_TodayNoneWorkUser WITH (nolock)" +
+ " where gcode = @gcode and kunmu=0";
+
+ var cs = Properties.Settings.Default.gwcs;
+ var cn = new SqlConnection(cs);
+ var cmd = new SqlCommand(sql, cn);
+ cmd.Parameters.AddWithValue("gcode", info.Login.gcode ?? "");
+ var da = new SqlDataAdapter(cmd);
+ var dt = new DataTable();
+ da.Fill(dt);
+ da.Dispose();
+ cmd.Dispose();
+ cn.Dispose();
+
+ return JsonConvert.SerializeObject(dt, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"GetHolyUser 오류: {ex.Message}");
+ return "[]";
+ }
+ }
+
+ ///
+ /// 휴가요청 목록 조회
+ ///
+ public string GetHolyRequestUser()
+ {
+ try
+ {
+ var sql = " select uid,cate,sdate,edate,HolyReason,Users.name,holydays,holytimes,remark " +
+ " from EETGW_HolydayRequest WITH (nolock) INNER JOIN " +
+ " Users ON EETGW_HolydayRequest.uid = Users.id " +
+ " where EETGW_HolydayRequest.gcode = @gcode" +
+ " and isnull(conf,0) = 0";
+
+ var cs = Properties.Settings.Default.gwcs;
+ var cn = new SqlConnection(cs);
+ var cmd = new SqlCommand(sql, cn);
+ cmd.Parameters.AddWithValue("gcode", info.Login.gcode);
+ var da = new SqlDataAdapter(cmd);
+ var dt = new DataTable();
+ da.Fill(dt);
+ da.Dispose();
+ cmd.Dispose();
+ cn.Dispose();
+
+ return JsonConvert.SerializeObject(dt, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"GetHolyRequestUser 오류: {ex.Message}");
+ return "[]";
+ }
+ }
+
+ ///
+ /// 출근 대상자 목록 조회
+ ///
+ public string GetPresentUserList()
+ {
+ try
+ {
+ var sql = "select * from vGroupUser WITH (nolock) " +
+ " where gcode = @gcode and useUserState = 1 and useJobReport = 1" +
+ " and id not in (select uid from vEETGW_TodayNoneWorkUser where gcode = @gcode and kunmu = 0)";
+
+ var cs = Properties.Settings.Default.gwcs;
+ var cn = new SqlConnection(cs);
+ var cmd = new SqlCommand(sql, cn);
+ cmd.Parameters.AddWithValue("gcode", info.Login.gcode);
+ var da = new SqlDataAdapter(cmd);
+ var dt = new DataTable();
+ da.Fill(dt);
+ da.Dispose();
+ cmd.Dispose();
+ cn.Dispose();
+
+ return JsonConvert.SerializeObject(dt, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"GetPresentUserList 오류: {ex.Message}");
+ return "[]";
+ }
+ }
+
+ ///
+ /// 구매요청(NR) 목록 조회
+ ///
+ public string GetPurchaseNRList()
+ {
+ try
+ {
+ var sql = "select pdate, process, pumname, pumscale, pumunit, pumqtyreq, pumprice, pumamt from Purchase WITH (nolock) where gcode = @gcode and state = '---' order by pdate desc";
+
+ var cs = Properties.Settings.Default.gwcs;
+ var cn = new SqlConnection(cs);
+ var cmd = new SqlCommand(sql, cn);
+ cmd.Parameters.AddWithValue("gcode", info.Login.gcode);
+ var da = new SqlDataAdapter(cmd);
+ var dt = new DataTable();
+ da.Fill(dt);
+ da.Dispose();
+ cmd.Dispose();
+ cn.Dispose();
+
+ return JsonConvert.SerializeObject(dt, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"GetPurchaseNRList 오류: {ex.Message}");
+ return "[]";
+ }
+ }
+
+ ///
+ /// 구매요청(CR) 목록 조회
+ ///
+ public string GetPurchaseCRList()
+ {
+ try
+ {
+ var sql = "select pdate, process, pumname, pumscale, pumunit, pumqtyreq, pumprice, pumamt " +
+ " from EETGW_PurchaseCR WITH (nolock) " +
+ " where gcode = @gcode and state = '---'" +
+ " order by pdate desc";
+
+ var cs = Properties.Settings.Default.gwcs;
+ var cn = new SqlConnection(cs);
+ var cmd = new SqlCommand(sql, cn);
+ cmd.Parameters.AddWithValue("gcode", info.Login.gcode);
+ var da = new SqlDataAdapter(cmd);
+ var dt = new DataTable();
+ da.Fill(dt);
+ da.Dispose();
+ cmd.Dispose();
+ cn.Dispose();
+
+ return JsonConvert.SerializeObject(dt, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"GetPurchaseCRList 오류: {ex.Message}");
+ return "[]";
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/Project/Web/MachineBridge/MachineBridge.Jobreport.cs b/Project/Web/MachineBridge/MachineBridge.Jobreport.cs
new file mode 100644
index 0000000..088843c
--- /dev/null
+++ b/Project/Web/MachineBridge/MachineBridge.Jobreport.cs
@@ -0,0 +1,260 @@
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Data.SqlClient;
+using Newtonsoft.Json;
+using FCOMMON;
+
+namespace Project.Web
+{
+ public partial class MachineBridge
+ {
+ #region Jobreport API
+
+ ///
+ /// 업무일지 목록 조회
+ ///
+ public string Jobreport_GetList(string sd, string ed, string uid, string cate, string doit)
+ {
+ try
+ {
+ var sql = @"SELECT j.idx, j.jdate, j.uid, j.cate, j.title, j.doit, j.remark, j.jfrom, j.jto,
+ u.name as userName, j.wdate
+ FROM EETGW_Jobreport j WITH (nolock)
+ LEFT JOIN Users u ON j.uid = u.id
+ WHERE j.gcode = @gcode";
+
+ var parameters = new List();
+ parameters.Add(new SqlParameter("@gcode", info.Login.gcode));
+
+ if (!string.IsNullOrEmpty(sd))
+ {
+ sql += " AND j.jdate >= @sd";
+ parameters.Add(new SqlParameter("@sd", sd));
+ }
+ if (!string.IsNullOrEmpty(ed))
+ {
+ sql += " AND j.jdate <= @ed";
+ parameters.Add(new SqlParameter("@ed", ed));
+ }
+ if (!string.IsNullOrEmpty(uid))
+ {
+ sql += " AND j.uid = @uid";
+ parameters.Add(new SqlParameter("@uid", uid));
+ }
+ if (!string.IsNullOrEmpty(cate))
+ {
+ sql += " AND j.cate = @cate";
+ parameters.Add(new SqlParameter("@cate", cate));
+ }
+ if (!string.IsNullOrEmpty(doit))
+ {
+ sql += " AND j.doit = @doit";
+ parameters.Add(new SqlParameter("@doit", doit));
+ }
+
+ sql += " ORDER BY j.jdate DESC, j.idx DESC";
+
+ var cs = Properties.Settings.Default.gwcs;
+ var cn = new SqlConnection(cs);
+ var cmd = new SqlCommand(sql, cn);
+ cmd.Parameters.AddRange(parameters.ToArray());
+
+ var da = new SqlDataAdapter(cmd);
+ var dt = new DataTable();
+ da.Fill(dt);
+ da.Dispose();
+ cmd.Dispose();
+ cn.Dispose();
+
+ return JsonConvert.SerializeObject(new { Success = true, Data = dt }, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
+ }
+ catch (Exception ex)
+ {
+ return JsonConvert.SerializeObject(new { Success = false, Message = ex.Message });
+ }
+ }
+
+ ///
+ /// 업무일지 사용자 목록 조회
+ ///
+ public string Jobreport_GetUsers()
+ {
+ try
+ {
+ var sql = @"SELECT u.id, u.name
+ FROM vGroupUser u WITH (nolock)
+ WHERE u.gcode = @gcode AND u.useJobReport = 1
+ ORDER BY u.name";
+
+ var cs = Properties.Settings.Default.gwcs;
+ var cn = new SqlConnection(cs);
+ var cmd = new SqlCommand(sql, cn);
+ cmd.Parameters.AddWithValue("@gcode", info.Login.gcode);
+
+ var da = new SqlDataAdapter(cmd);
+ var dt = new DataTable();
+ da.Fill(dt);
+ da.Dispose();
+ cmd.Dispose();
+ cn.Dispose();
+
+ return JsonConvert.SerializeObject(dt, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Jobreport_GetUsers 오류: {ex.Message}");
+ return "[]";
+ }
+ }
+
+ ///
+ /// 업무일지 상세 조회
+ ///
+ public string Jobreport_GetDetail(int id)
+ {
+ try
+ {
+ var sql = @"SELECT j.*, u.name as userName
+ FROM EETGW_Jobreport j WITH (nolock)
+ LEFT JOIN Users u ON j.uid = u.id
+ WHERE j.idx = @idx AND j.gcode = @gcode";
+
+ var cs = Properties.Settings.Default.gwcs;
+ var cn = new SqlConnection(cs);
+ var cmd = new SqlCommand(sql, cn);
+ cmd.Parameters.AddWithValue("@idx", id);
+ cmd.Parameters.AddWithValue("@gcode", info.Login.gcode);
+
+ var da = new SqlDataAdapter(cmd);
+ var dt = new DataTable();
+ da.Fill(dt);
+ da.Dispose();
+ cmd.Dispose();
+ cn.Dispose();
+
+ if (dt.Rows.Count > 0)
+ {
+ return JsonConvert.SerializeObject(new { Success = true, Data = dt.Rows[0] }, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
+ }
+
+ return JsonConvert.SerializeObject(new { Success = false, Message = "데이터를 찾을 수 없습니다." });
+ }
+ catch (Exception ex)
+ {
+ return JsonConvert.SerializeObject(new { Success = false, Message = ex.Message });
+ }
+ }
+
+ ///
+ /// 업무일지 추가
+ ///
+ public string Jobreport_Add(string jdate, string cate, string title, string doit, string remark, string jfrom, string jto)
+ {
+ try
+ {
+ var sql = @"INSERT INTO EETGW_Jobreport (gcode, uid, jdate, cate, title, doit, remark, jfrom, jto, wuid, wdate)
+ VALUES (@gcode, @uid, @jdate, @cate, @title, @doit, @remark, @jfrom, @jto, @wuid, GETDATE());
+ SELECT SCOPE_IDENTITY();";
+
+ var cs = Properties.Settings.Default.gwcs;
+ var cn = new SqlConnection(cs);
+ var cmd = new SqlCommand(sql, cn);
+ cmd.Parameters.AddWithValue("@gcode", info.Login.gcode);
+ cmd.Parameters.AddWithValue("@uid", info.Login.no);
+ cmd.Parameters.AddWithValue("@jdate", jdate ?? DateTime.Now.ToString("yyyy-MM-dd"));
+ cmd.Parameters.AddWithValue("@cate", cate ?? "");
+ cmd.Parameters.AddWithValue("@title", title ?? "");
+ cmd.Parameters.AddWithValue("@doit", doit ?? "");
+ cmd.Parameters.AddWithValue("@remark", remark ?? "");
+ cmd.Parameters.AddWithValue("@jfrom", jfrom ?? "");
+ cmd.Parameters.AddWithValue("@jto", jto ?? "");
+ cmd.Parameters.AddWithValue("@wuid", info.Login.no);
+
+ cn.Open();
+ var newId = Convert.ToInt32(cmd.ExecuteScalar());
+ cn.Close();
+ cmd.Dispose();
+ cn.Dispose();
+
+ return JsonConvert.SerializeObject(new { Success = true, Message = "저장되었습니다.", Data = new { idx = newId } });
+ }
+ catch (Exception ex)
+ {
+ return JsonConvert.SerializeObject(new { Success = false, Message = ex.Message });
+ }
+ }
+
+ ///
+ /// 업무일지 수정
+ ///
+ public string Jobreport_Edit(int idx, string jdate, string cate, string title, string doit, string remark, string jfrom, string jto)
+ {
+ try
+ {
+ var sql = @"UPDATE EETGW_Jobreport SET
+ jdate = @jdate, cate = @cate, title = @title, doit = @doit,
+ remark = @remark, jfrom = @jfrom, jto = @jto,
+ wuid = @wuid, wdate = GETDATE()
+ WHERE idx = @idx AND gcode = @gcode";
+
+ var cs = Properties.Settings.Default.gwcs;
+ var cn = new SqlConnection(cs);
+ var cmd = new SqlCommand(sql, cn);
+ cmd.Parameters.AddWithValue("@idx", idx);
+ cmd.Parameters.AddWithValue("@gcode", info.Login.gcode);
+ cmd.Parameters.AddWithValue("@jdate", jdate ?? DateTime.Now.ToString("yyyy-MM-dd"));
+ cmd.Parameters.AddWithValue("@cate", cate ?? "");
+ cmd.Parameters.AddWithValue("@title", title ?? "");
+ cmd.Parameters.AddWithValue("@doit", doit ?? "");
+ cmd.Parameters.AddWithValue("@remark", remark ?? "");
+ cmd.Parameters.AddWithValue("@jfrom", jfrom ?? "");
+ cmd.Parameters.AddWithValue("@jto", jto ?? "");
+ cmd.Parameters.AddWithValue("@wuid", info.Login.no);
+
+ cn.Open();
+ var result = cmd.ExecuteNonQuery();
+ cn.Close();
+ cmd.Dispose();
+ cn.Dispose();
+
+ return JsonConvert.SerializeObject(new { Success = result > 0, Message = result > 0 ? "수정되었습니다." : "수정에 실패했습니다." });
+ }
+ catch (Exception ex)
+ {
+ return JsonConvert.SerializeObject(new { Success = false, Message = ex.Message });
+ }
+ }
+
+ ///
+ /// 업무일지 삭제
+ ///
+ public string Jobreport_Delete(int idx)
+ {
+ try
+ {
+ var sql = "DELETE FROM EETGW_Jobreport WHERE idx = @idx AND gcode = @gcode";
+
+ var cs = Properties.Settings.Default.gwcs;
+ var cn = new SqlConnection(cs);
+ var cmd = new SqlCommand(sql, cn);
+ cmd.Parameters.AddWithValue("@idx", idx);
+ cmd.Parameters.AddWithValue("@gcode", info.Login.gcode);
+
+ cn.Open();
+ var result = cmd.ExecuteNonQuery();
+ cn.Close();
+ cmd.Dispose();
+ cn.Dispose();
+
+ return JsonConvert.SerializeObject(new { Success = result > 0, Message = result > 0 ? "삭제되었습니다." : "삭제에 실패했습니다." });
+ }
+ catch (Exception ex)
+ {
+ return JsonConvert.SerializeObject(new { Success = false, Message = ex.Message });
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/Project/Web/MachineBridge/MachineBridge.Kuntae.cs b/Project/Web/MachineBridge/MachineBridge.Kuntae.cs
new file mode 100644
index 0000000..b59cb95
--- /dev/null
+++ b/Project/Web/MachineBridge/MachineBridge.Kuntae.cs
@@ -0,0 +1,95 @@
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Data.SqlClient;
+using Newtonsoft.Json;
+using FCOMMON;
+
+namespace Project.Web
+{
+ public partial class MachineBridge
+ {
+ #region Kuntae API
+
+ ///
+ /// 근태 목록 조회
+ ///
+ public string Kuntae_GetList(string sd, string ed)
+ {
+ try
+ {
+ var sql = @"SELECT k.*, u.name as userName
+ FROM EETGW_Kuntae k WITH (nolock)
+ LEFT JOIN Users u ON k.uid = u.id
+ WHERE k.gcode = @gcode AND k.uid = @uid";
+
+ var parameters = new List();
+ parameters.Add(new SqlParameter("@gcode", info.Login.gcode));
+ parameters.Add(new SqlParameter("@uid", info.Login.no));
+
+ if (!string.IsNullOrEmpty(sd))
+ {
+ sql += " AND k.kdate >= @sd";
+ parameters.Add(new SqlParameter("@sd", sd));
+ }
+ if (!string.IsNullOrEmpty(ed))
+ {
+ sql += " AND k.kdate <= @ed";
+ parameters.Add(new SqlParameter("@ed", ed));
+ }
+
+ sql += " ORDER BY k.kdate DESC";
+
+ var cs = Properties.Settings.Default.gwcs;
+ var cn = new SqlConnection(cs);
+ var cmd = new SqlCommand(sql, cn);
+ cmd.Parameters.AddRange(parameters.ToArray());
+
+ var da = new SqlDataAdapter(cmd);
+ var dt = new DataTable();
+ da.Fill(dt);
+ da.Dispose();
+ cmd.Dispose();
+ cn.Dispose();
+
+ return JsonConvert.SerializeObject(new { Success = true, Data = dt }, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
+ }
+ catch (Exception ex)
+ {
+ return JsonConvert.SerializeObject(new { Success = false, Message = ex.Message });
+ }
+ }
+
+ ///
+ /// 근태 삭제
+ ///
+ public string Kuntae_Delete(int id)
+ {
+ try
+ {
+ var sql = "DELETE FROM EETGW_Kuntae WHERE idx = @idx AND gcode = @gcode AND uid = @uid";
+
+ var cs = Properties.Settings.Default.gwcs;
+ var cn = new SqlConnection(cs);
+ var cmd = new SqlCommand(sql, cn);
+ cmd.Parameters.AddWithValue("@idx", id);
+ cmd.Parameters.AddWithValue("@gcode", info.Login.gcode);
+ cmd.Parameters.AddWithValue("@uid", info.Login.no);
+
+ cn.Open();
+ var result = cmd.ExecuteNonQuery();
+ cn.Close();
+ cmd.Dispose();
+ cn.Dispose();
+
+ return JsonConvert.SerializeObject(new { Success = result > 0, Message = result > 0 ? "삭제되었습니다." : "삭제에 실패했습니다." });
+ }
+ catch (Exception ex)
+ {
+ return JsonConvert.SerializeObject(new { Success = false, Message = ex.Message });
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/Project/Web/MachineBridge/MachineBridge.Login.cs b/Project/Web/MachineBridge/MachineBridge.Login.cs
new file mode 100644
index 0000000..7761442
--- /dev/null
+++ b/Project/Web/MachineBridge/MachineBridge.Login.cs
@@ -0,0 +1,293 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Windows.Forms;
+using Newtonsoft.Json;
+using FCOMMON;
+
+namespace Project.Web
+{
+ public partial class MachineBridge
+ {
+ #region Login API
+
+ ///
+ /// 로그인 처리 (fLogin_WB.cs의 button1_Click 로직 참고)
+ ///
+ public string Login(string gcode, string id, string password, bool rememberMe)
+ {
+ var result = new LoginResult();
+ DateTime dt = DateTime.Now;
+
+ try
+ {
+ if (string.IsNullOrEmpty(id))
+ {
+ result.Success = false;
+ result.Message = "사용자 ID를 입력하세요.";
+ return JsonConvert.SerializeObject(result);
+ }
+
+ if (string.IsNullOrEmpty(password))
+ {
+ result.Success = false;
+ result.Message = "비밀번호를 입력하세요.";
+ return JsonConvert.SerializeObject(result);
+ }
+
+ if (string.IsNullOrEmpty(gcode))
+ {
+ result.Success = false;
+ result.Message = "소속 부서를 선택하세요.";
+ return JsonConvert.SerializeObject(result);
+ }
+
+ // 부서명 조회
+ var deptName = DBM.ExecuteScalar($"SELECT dept FROM UserGroup WHERE gcode = '{gcode}'") ?? "";
+
+ var encpass = Pub.MakePasswordEnc(password.Trim());
+
+ // 개발자 계정 처리
+ if (id.ToUpper().Equals("DEV"))
+ {
+ if (!password.Equals("123"))
+ {
+ result.Success = false;
+ result.Message = "암호가 일치하지 않습니다.";
+ return JsonConvert.SerializeObject(result);
+ }
+
+ SetDevLoginInfo(gcode, deptName);
+ }
+ else
+ {
+ // 일반 사용자 로그인 처리
+ var taGrpUser = new dsMSSQLTableAdapters.EETGW_GroupUserTableAdapter();
+ var drGrpUser = taGrpUser.GetbyID(gcode, id).FirstOrDefault();
+
+ if (drGrpUser == null)
+ {
+ result.Success = false;
+ result.Message = $"입력한 사용자 계정이 존재하지 않습니다.\n\n담당부서명:{deptName}\n부서코드:{gcode}\n\n접속 부서를 확인하시고 관리자 문의 하세요";
+ return JsonConvert.SerializeObject(result);
+ }
+
+ if (drGrpUser.level == 0)
+ {
+ result.Success = false;
+ result.Message = "해당 계정이 활성화되지 않았습니다.\n계정 담당자 문의하세요";
+ return JsonConvert.SerializeObject(result);
+ }
+
+ // 사용자 테이블에서 암호 확인
+ var taUser = new dsMSSQLTableAdapters.UsersTableAdapter();
+ var drUser = taUser.GetID(id).FirstOrDefault();
+
+ if (drUser == null)
+ {
+ result.Success = false;
+ result.Message = $"입력한 사용자 계정이 존재하지 않습니다.\n\n담당부서명:{deptName}\n부서코드:{gcode}\n\n접속 부서를 확인하시고 관리자 문의 하세요";
+ return JsonConvert.SerializeObject(result);
+ }
+
+ if (!drUser.password.Equals(encpass))
+ {
+ result.Success = false;
+ result.Message = "암호가 일치하지 않습니다.\n암호를 분실했을 경우에는 계정담당자에 초기화를 요청 하세요";
+ return JsonConvert.SerializeObject(result);
+ }
+
+ // 버전 체크
+ var MaxVersion = DBM.GetMaxVersion();
+ if (!MaxVersion.isEmpty())
+ {
+ var curversion = Application.ProductVersion;
+ var verchk = curversion.CompareTo(MaxVersion);
+ if (verchk < 0)
+ {
+ result.VersionWarning = "현재 구 버젼을 사용하고 있습니다.\n업데이트를 진행 하고 사용하시기 바랍니다";
+ }
+ }
+
+ // 로그인 정보 설정
+ var gperm = DBM.ExecuteScalar($"SELECT ISNULL(permission,0) FROM UserGroup WHERE gcode = '{gcode}'");
+
+ info.Login.no = drUser.id;
+ info.Login.nameK = drUser.name;
+ info.Login.dept = deptName;
+ info.Login.level = drGrpUser.level;
+ info.Login.email = drUser.email;
+ info.Login.nameE = drUser.nameE;
+ info.Login.hp = drUser.hp;
+ info.Login.tel = drUser.tel;
+ info.Login.title = drUser.dept + "(" + drUser.grade + ")";
+ info.NotShowJobReportview = Pub.setting.NotShowJobreportPRewView;
+ info.Login.gcode = gcode;
+ info.Login.process = drGrpUser.Process;
+ info.Login.permission = 0;
+ info.Login.gpermission = int.Parse(gperm);
+ info.ShowBuyerror = Pub.setting.Showbuyerror;
+ }
+
+ // 설정 저장 (rememberMe 처리)
+ if (rememberMe)
+ {
+ var idlist = new List { id.Trim() };
+ var vuserlist = "";
+ foreach (var item in idlist)
+ vuserlist += ";" + item;
+
+ Pub.setting.lastid = vuserlist;
+ Pub.setting.lastdpt = deptName;
+ Pub.setting.lastgcode = gcode;
+ Pub.setting.Save();
+ }
+
+ // 로그인 정보 기록
+ AddLoginInfo();
+
+ // 자동 업무일지 생성
+ Pub.MakeAutoJobReportbyLogin();
+ Pub.MakeAutoJobReportByAuto();
+
+ info.Login.loginusetime = (DateTime.Now - dt).TotalMilliseconds;
+
+ // fMain의 로그인 완료 후처리 호출
+ CallMainFormLoginCompleted();
+
+ result.Success = true;
+ result.Message = "로그인 성공";
+ result.RedirectUrl = "/DashBoard/index.html";
+ result.UserName = info.Login.nameK;
+ }
+ catch (Exception ex)
+ {
+ result.Success = false;
+ result.Message = "데이터베이스 조회 실패. 다음 오류 메세지를 참고하세요.\n\n" + ex.Message;
+ }
+
+ return JsonConvert.SerializeObject(result);
+ }
+
+ ///
+ /// fMain의 OnLoginCompleted() 호출
+ ///
+ private void CallMainFormLoginCompleted()
+ {
+ try
+ {
+ // Application.OpenForms에서 fMain 인스턴스 찾기
+ foreach (Form form in Application.OpenForms)
+ {
+ if (form is fMain mainForm)
+ {
+ // UI 스레드에서 실행
+ if (mainForm.InvokeRequired)
+ {
+ mainForm.Invoke(new Action(() => mainForm.OnLoginCompleted()));
+ }
+ else
+ {
+ mainForm.OnLoginCompleted();
+ }
+ break;
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"OnLoginCompleted 호출 오류: {ex.Message}");
+ }
+ }
+
+ ///
+ /// 개발자 로그인 정보 설정
+ ///
+ private void SetDevLoginInfo(string gcode, string deptName)
+ {
+ var gperm = DBM.ExecuteScalar($"SELECT ISNULL(permission,0) FROM UserGroup WHERE gcode = '{gcode}'");
+
+ info.Login.no = "dev";
+ info.Login.nameK = "개발자";
+ info.Login.dept = deptName;
+ info.Login.level = 10;
+ info.Login.email = "";
+ info.Login.nameE = "DEVELOPER";
+ info.Login.hp = "";
+ info.Login.tel = "";
+ info.Login.title = "업무일지 개발자";
+ info.NotShowJobReportview = Pub.setting.NotShowJobreportPRewView;
+ info.Login.gcode = gcode;
+ info.Login.process = "개발자";
+ info.Login.permission = 0;
+ info.Login.gpermission = int.Parse(gperm);
+ info.ShowBuyerror = Pub.setting.Showbuyerror;
+ }
+
+ ///
+ /// 로그인 정보 기록
+ ///
+ private void AddLoginInfo()
+ {
+ string ip = string.Empty;
+ string hostname = Dns.GetHostName();
+ string fullname = Dns.GetHostEntry("").HostName;
+
+ var host = Dns.GetHostEntry(hostname);
+ foreach (IPAddress r in host.AddressList)
+ {
+ string str = r.ToString();
+ if (!string.IsNullOrEmpty(str) && str.StartsWith("10."))
+ {
+ ip = str;
+ break;
+ }
+ }
+
+ if (string.IsNullOrEmpty(ip) || string.IsNullOrEmpty(hostname)) return;
+
+ try
+ {
+ var ta = new dsMSSQLTableAdapters.EETGW_LoginInfoTableAdapter();
+ ta.Insert(info.Login.no, DateTime.Now, ip, fullname, info.Login.no, DateTime.Now);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine(ex.Message);
+ }
+ }
+
+ ///
+ /// 그룹 목록 조회
+ ///
+ public string GetUserGroups()
+ {
+ var dt = DBM.GetUserGroups();
+ return JsonConvert.SerializeObject(dt, new JsonSerializerSettings
+ {
+ NullValueHandling = NullValueHandling.Ignore
+ });
+ }
+
+ ///
+ /// 이전 로그인 정보 조회
+ ///
+ public string GetPreviousLoginInfo()
+ {
+ var result = new
+ {
+ Success = true,
+ Data = new
+ {
+ LastGcode = Pub.setting.lastgcode ?? "",
+ LastDept = Pub.setting.lastdpt ?? "",
+ LastId = Pub.setting.lastid?.TrimStart(';').Split(';').FirstOrDefault() ?? ""
+ }
+ };
+ return JsonConvert.SerializeObject(result);
+ }
+
+ #endregion
+ }
+}
diff --git a/Project/Web/MachineBridge/MachineBridge.Project.cs b/Project/Web/MachineBridge/MachineBridge.Project.cs
new file mode 100644
index 0000000..d066cb2
--- /dev/null
+++ b/Project/Web/MachineBridge/MachineBridge.Project.cs
@@ -0,0 +1,210 @@
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Data.SqlClient;
+using Newtonsoft.Json;
+using FCOMMON;
+
+namespace Project.Web
+{
+ public partial class MachineBridge
+ {
+ #region Project API
+
+ ///
+ /// 프로젝트 목록 조회
+ ///
+ public string Project_GetProjects(string status, string userFilter)
+ {
+ try
+ {
+ var sql = @"SELECT * FROM EETGW_Project WITH (nolock)
+ WHERE gcode = @gcode";
+
+ var parameters = new List();
+ parameters.Add(new SqlParameter("@gcode", info.Login.gcode));
+
+ if (!string.IsNullOrEmpty(status))
+ {
+ sql += " AND 상태 = @status";
+ parameters.Add(new SqlParameter("@status", status));
+ }
+
+ if (userFilter == "my")
+ {
+ sql += " AND (프로젝트관리자 LIKE @userName OR 설계담당 LIKE @userName OR 전장담당 LIKE @userName OR 프로그램담당 LIKE @userName)";
+ parameters.Add(new SqlParameter("@userName", "%" + info.Login.nameK + "%"));
+ }
+
+ sql += " ORDER BY 시작일 DESC";
+
+ var cs = Properties.Settings.Default.gwcs;
+ var cn = new SqlConnection(cs);
+ var cmd = new SqlCommand(sql, cn);
+ cmd.Parameters.AddRange(parameters.ToArray());
+
+ var da = new SqlDataAdapter(cmd);
+ var dt = new DataTable();
+ da.Fill(dt);
+ da.Dispose();
+ cmd.Dispose();
+ cn.Dispose();
+
+ return JsonConvert.SerializeObject(new { Success = true, Data = dt, CurrentUser = info.Login.nameK }, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
+ }
+ catch (Exception ex)
+ {
+ return JsonConvert.SerializeObject(new { Success = false, Message = ex.Message });
+ }
+ }
+
+ ///
+ /// 프로젝트 상세 조회
+ ///
+ public string Project_GetProject(int id)
+ {
+ try
+ {
+ var sql = "SELECT * FROM EETGW_Project WHERE idx = @idx AND gcode = @gcode";
+
+ var cs = Properties.Settings.Default.gwcs;
+ var cn = new SqlConnection(cs);
+ var cmd = new SqlCommand(sql, cn);
+ cmd.Parameters.AddWithValue("@idx", id);
+ cmd.Parameters.AddWithValue("@gcode", info.Login.gcode);
+
+ var da = new SqlDataAdapter(cmd);
+ var dt = new DataTable();
+ da.Fill(dt);
+ da.Dispose();
+ cmd.Dispose();
+ cn.Dispose();
+
+ if (dt.Rows.Count > 0)
+ {
+ return JsonConvert.SerializeObject(new { Success = true, Data = dt.Rows[0] }, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
+ }
+
+ return JsonConvert.SerializeObject(new { Success = false, Message = "프로젝트를 찾을 수 없습니다." });
+ }
+ catch (Exception ex)
+ {
+ return JsonConvert.SerializeObject(new { Success = false, Message = ex.Message });
+ }
+ }
+
+ ///
+ /// 프로젝트 생성
+ ///
+ public string Project_CreateProject(string name, string process, string sdate, string edate, string ddate, string odate, string userManager, string status, string memo)
+ {
+ try
+ {
+ var sql = @"INSERT INTO EETGW_Project (gcode, 프로젝트명, 프로젝트공정, 시작일, 완료일, 만료일, 출고일, 프로젝트관리자, 상태, memo, wuid, wdate)
+ VALUES (@gcode, @name, @process, @sdate, @edate, @ddate, @odate, @userManager, @status, @memo, @wuid, GETDATE());
+ SELECT SCOPE_IDENTITY();";
+
+ var cs = Properties.Settings.Default.gwcs;
+ var cn = new SqlConnection(cs);
+ var cmd = new SqlCommand(sql, cn);
+ cmd.Parameters.AddWithValue("@gcode", info.Login.gcode);
+ cmd.Parameters.AddWithValue("@name", name ?? "");
+ cmd.Parameters.AddWithValue("@process", process ?? "");
+ cmd.Parameters.AddWithValue("@sdate", string.IsNullOrEmpty(sdate) ? (object)DBNull.Value : sdate);
+ cmd.Parameters.AddWithValue("@edate", string.IsNullOrEmpty(edate) ? (object)DBNull.Value : edate);
+ cmd.Parameters.AddWithValue("@ddate", string.IsNullOrEmpty(ddate) ? (object)DBNull.Value : ddate);
+ cmd.Parameters.AddWithValue("@odate", string.IsNullOrEmpty(odate) ? (object)DBNull.Value : odate);
+ cmd.Parameters.AddWithValue("@userManager", userManager ?? "");
+ cmd.Parameters.AddWithValue("@status", status ?? "진행");
+ cmd.Parameters.AddWithValue("@memo", memo ?? "");
+ cmd.Parameters.AddWithValue("@wuid", info.Login.no);
+
+ cn.Open();
+ var newId = Convert.ToInt32(cmd.ExecuteScalar());
+ cn.Close();
+ cmd.Dispose();
+ cn.Dispose();
+
+ return JsonConvert.SerializeObject(new { Success = true, Message = "프로젝트가 생성되었습니다.", Data = new { idx = newId } });
+ }
+ catch (Exception ex)
+ {
+ return JsonConvert.SerializeObject(new { Success = false, Message = ex.Message });
+ }
+ }
+
+ ///
+ /// 프로젝트 수정
+ ///
+ public string Project_UpdateProject(int idx, string name, string process, string sdate, string edate, string ddate, string odate, string userManager, string status, string memo)
+ {
+ try
+ {
+ var sql = @"UPDATE EETGW_Project SET
+ 프로젝트명 = @name, 프로젝트공정 = @process, 시작일 = @sdate, 완료일 = @edate,
+ 만료일 = @ddate, 출고일 = @odate, 프로젝트관리자 = @userManager, 상태 = @status, memo = @memo,
+ wuid = @wuid, wdate = GETDATE()
+ WHERE idx = @idx AND gcode = @gcode";
+
+ var cs = Properties.Settings.Default.gwcs;
+ var cn = new SqlConnection(cs);
+ var cmd = new SqlCommand(sql, cn);
+ cmd.Parameters.AddWithValue("@idx", idx);
+ cmd.Parameters.AddWithValue("@gcode", info.Login.gcode);
+ cmd.Parameters.AddWithValue("@name", name ?? "");
+ cmd.Parameters.AddWithValue("@process", process ?? "");
+ cmd.Parameters.AddWithValue("@sdate", string.IsNullOrEmpty(sdate) ? (object)DBNull.Value : sdate);
+ cmd.Parameters.AddWithValue("@edate", string.IsNullOrEmpty(edate) ? (object)DBNull.Value : edate);
+ cmd.Parameters.AddWithValue("@ddate", string.IsNullOrEmpty(ddate) ? (object)DBNull.Value : ddate);
+ cmd.Parameters.AddWithValue("@odate", string.IsNullOrEmpty(odate) ? (object)DBNull.Value : odate);
+ cmd.Parameters.AddWithValue("@userManager", userManager ?? "");
+ cmd.Parameters.AddWithValue("@status", status ?? "진행");
+ cmd.Parameters.AddWithValue("@memo", memo ?? "");
+ cmd.Parameters.AddWithValue("@wuid", info.Login.no);
+
+ cn.Open();
+ var result = cmd.ExecuteNonQuery();
+ cn.Close();
+ cmd.Dispose();
+ cn.Dispose();
+
+ return JsonConvert.SerializeObject(new { Success = result > 0, Message = result > 0 ? "수정되었습니다." : "수정에 실패했습니다." });
+ }
+ catch (Exception ex)
+ {
+ return JsonConvert.SerializeObject(new { Success = false, Message = ex.Message });
+ }
+ }
+
+ ///
+ /// 프로젝트 삭제
+ ///
+ public string Project_DeleteProject(int id)
+ {
+ try
+ {
+ var sql = "DELETE FROM EETGW_Project WHERE idx = @idx AND gcode = @gcode";
+
+ var cs = Properties.Settings.Default.gwcs;
+ var cn = new SqlConnection(cs);
+ var cmd = new SqlCommand(sql, cn);
+ cmd.Parameters.AddWithValue("@idx", id);
+ cmd.Parameters.AddWithValue("@gcode", info.Login.gcode);
+
+ cn.Open();
+ var result = cmd.ExecuteNonQuery();
+ cn.Close();
+ cmd.Dispose();
+ cn.Dispose();
+
+ return JsonConvert.SerializeObject(new { Success = result > 0, Message = result > 0 ? "삭제되었습니다." : "삭제에 실패했습니다." });
+ }
+ catch (Exception ex)
+ {
+ return JsonConvert.SerializeObject(new { Success = false, Message = ex.Message });
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/Project/Web/MachineBridge/MachineBridge.Todo.cs b/Project/Web/MachineBridge/MachineBridge.Todo.cs
new file mode 100644
index 0000000..fecdc29
--- /dev/null
+++ b/Project/Web/MachineBridge/MachineBridge.Todo.cs
@@ -0,0 +1,280 @@
+using System;
+using System.Data;
+using System.Data.SqlClient;
+using Newtonsoft.Json;
+using FCOMMON;
+using Project.Web.Model;
+
+namespace Project.Web
+{
+ public partial class MachineBridge
+ {
+ #region Todo API
+
+ ///
+ /// 급한 할일 목록 조회
+ ///
+ public string GetUrgentTodos()
+ {
+ try
+ {
+ if (string.IsNullOrEmpty(info.Login.no))
+ {
+ return JsonConvert.SerializeObject(new { Success = false, Message = "로그인되지 않은 상태입니다." });
+ }
+
+ var sql = @"SELECT * FROM EETGW_Todo
+ WHERE gcode = @gcode AND uid = @uid
+ and isnull(status,'0') not in ('2','3','5')
+ ORDER BY flag DESC, seqno DESC, expire ASC, wdate ASC";
+
+ var todos = DBM.Query(sql, new { gcode = info.Login.gcode, uid = info.Login.no });
+
+ return JsonConvert.SerializeObject(new { Success = true, Data = todos }, new JsonSerializerSettings
+ {
+ NullValueHandling = NullValueHandling.Ignore,
+ DateFormatString = "yyyy-MM-dd HH:mm:ss"
+ });
+ }
+ catch (Exception ex)
+ {
+ return JsonConvert.SerializeObject(new { Success = false, Message = "급한 Todo 목록을 가져오는 중 오류가 발생했습니다: " + ex.Message });
+ }
+ }
+
+ ///
+ /// 할일 상세 조회
+ ///
+ public string GetTodo(int id)
+ {
+ try
+ {
+ if (string.IsNullOrEmpty(info.Login.no))
+ {
+ return JsonConvert.SerializeObject(new { Success = false, Message = "로그인되지 않은 상태입니다." });
+ }
+
+ if (id <= 0)
+ {
+ return JsonConvert.SerializeObject(new { Success = false, Message = "유효하지 않은 Todo ID입니다." });
+ }
+
+ var sql = "SELECT * FROM EETGW_Todo WHERE idx = @idx AND gcode = @gcode AND uid = @uid";
+ var todo = DBM.QuerySingleOrDefault(sql, new { idx = id, gcode = info.Login.gcode, uid = info.Login.no });
+
+ if (todo == null)
+ {
+ return JsonConvert.SerializeObject(new { Success = false, Message = "할일을 찾을 수 없습니다." });
+ }
+
+ return JsonConvert.SerializeObject(new { Success = true, Data = todo }, new JsonSerializerSettings
+ {
+ NullValueHandling = NullValueHandling.Ignore,
+ DateFormatString = "yyyy-MM-dd HH:mm:ss"
+ });
+ }
+ catch (Exception ex)
+ {
+ return JsonConvert.SerializeObject(new { Success = false, Message = "할일 조회 중 오류가 발생했습니다: " + ex.Message });
+ }
+ }
+
+ ///
+ /// 할일 추가
+ ///
+ public string CreateTodo(string title, string remark, string expire, int seqno, bool flag, string request, string status)
+ {
+ try
+ {
+ if (string.IsNullOrEmpty(info.Login.no))
+ {
+ return JsonConvert.SerializeObject(new { Success = false, Message = "로그인되지 않은 상태입니다." });
+ }
+
+ if (string.IsNullOrEmpty(remark))
+ {
+ return JsonConvert.SerializeObject(new { Success = false, Message = "할일 내용은 필수입니다." });
+ }
+
+ DateTime? expireDate = null;
+ if (!string.IsNullOrEmpty(expire))
+ {
+ if (DateTime.TryParse(expire, out DateTime parsed))
+ expireDate = parsed;
+ }
+
+ DateTime? okdateValue = null;
+ if (status == "5")
+ {
+ okdateValue = DateTime.Now;
+ }
+
+ var sql = @"
+ INSERT INTO EETGW_Todo (gcode, uid, title, remark, flag, expire, seqno, request, status, okdate, wuid, wdate)
+ VALUES (@gcode, @uid, @title, @remark, @flag, @expire, @seqno, @request, @status, @okdate, @wuid, @wdate);
+ SELECT SCOPE_IDENTITY();";
+
+ var newId = DBM.QuerySingle(sql, new
+ {
+ gcode = info.Login.gcode,
+ uid = info.Login.no,
+ title = title,
+ remark = remark,
+ flag = flag,
+ expire = expireDate,
+ seqno = seqno,
+ request = request,
+ status = string.IsNullOrEmpty(status) ? "0" : status,
+ okdate = okdateValue,
+ wuid = info.Login.no,
+ wdate = DateTime.Now
+ });
+
+ return JsonConvert.SerializeObject(new { Success = true, Message = "할일이 추가되었습니다.", Data = new { idx = newId } });
+ }
+ catch (Exception ex)
+ {
+ return JsonConvert.SerializeObject(new { Success = false, Message = "할일 추가 중 오류가 발생했습니다: " + ex.Message });
+ }
+ }
+
+ #endregion
+
+ #region Todo Extended API
+
+ ///
+ /// 할일 전체 목록 조회
+ ///
+ public string Todo_GetTodos()
+ {
+ try
+ {
+ if (string.IsNullOrEmpty(info.Login.no))
+ {
+ return JsonConvert.SerializeObject(new { Success = false, Message = "로그인되지 않은 상태입니다." });
+ }
+
+ var sql = @"SELECT * FROM EETGW_Todo
+ WHERE gcode = @gcode AND uid = @uid
+ ORDER BY flag DESC, seqno DESC, expire ASC, wdate ASC";
+
+ var cs = Properties.Settings.Default.gwcs;
+ var cn = new SqlConnection(cs);
+ var cmd = new SqlCommand(sql, cn);
+ cmd.Parameters.AddWithValue("@gcode", info.Login.gcode);
+ cmd.Parameters.AddWithValue("@uid", info.Login.no);
+
+ var da = new SqlDataAdapter(cmd);
+ var dt = new DataTable();
+ da.Fill(dt);
+ da.Dispose();
+ cmd.Dispose();
+ cn.Dispose();
+
+ return JsonConvert.SerializeObject(new { Success = true, Data = dt }, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore, DateFormatString = "yyyy-MM-dd HH:mm:ss" });
+ }
+ catch (Exception ex)
+ {
+ return JsonConvert.SerializeObject(new { Success = false, Message = ex.Message });
+ }
+ }
+
+ ///
+ /// 할일 수정
+ ///
+ public string Todo_UpdateTodo(int idx, string title, string remark, string expire, int seqno, bool flag, string request, string status)
+ {
+ try
+ {
+ if (string.IsNullOrEmpty(info.Login.no))
+ {
+ return JsonConvert.SerializeObject(new { Success = false, Message = "로그인되지 않은 상태입니다." });
+ }
+
+ DateTime? expireDate = null;
+ if (!string.IsNullOrEmpty(expire))
+ {
+ if (DateTime.TryParse(expire, out DateTime parsed))
+ expireDate = parsed;
+ }
+
+ DateTime? okdateValue = null;
+ if (status == "5")
+ {
+ okdateValue = DateTime.Now;
+ }
+
+ var sql = @"UPDATE EETGW_Todo SET
+ title = @title, remark = @remark, expire = @expire, seqno = @seqno,
+ flag = @flag, request = @request, status = @status, okdate = @okdate,
+ wuid = @wuid, wdate = GETDATE()
+ WHERE idx = @idx AND gcode = @gcode AND uid = @uid";
+
+ var cs = Properties.Settings.Default.gwcs;
+ var cn = new SqlConnection(cs);
+ var cmd = new SqlCommand(sql, cn);
+ cmd.Parameters.AddWithValue("@idx", idx);
+ cmd.Parameters.AddWithValue("@gcode", info.Login.gcode);
+ cmd.Parameters.AddWithValue("@uid", info.Login.no);
+ cmd.Parameters.AddWithValue("@title", title ?? "");
+ cmd.Parameters.AddWithValue("@remark", remark ?? "");
+ cmd.Parameters.AddWithValue("@expire", expireDate.HasValue ? (object)expireDate.Value : DBNull.Value);
+ cmd.Parameters.AddWithValue("@seqno", seqno);
+ cmd.Parameters.AddWithValue("@flag", flag);
+ cmd.Parameters.AddWithValue("@request", request ?? "");
+ cmd.Parameters.AddWithValue("@status", string.IsNullOrEmpty(status) ? "0" : status);
+ cmd.Parameters.AddWithValue("@okdate", okdateValue.HasValue ? (object)okdateValue.Value : DBNull.Value);
+ cmd.Parameters.AddWithValue("@wuid", info.Login.no);
+
+ cn.Open();
+ var result = cmd.ExecuteNonQuery();
+ cn.Close();
+ cmd.Dispose();
+ cn.Dispose();
+
+ return JsonConvert.SerializeObject(new { Success = result > 0, Message = result > 0 ? "수정되었습니다." : "수정에 실패했습니다." });
+ }
+ catch (Exception ex)
+ {
+ return JsonConvert.SerializeObject(new { Success = false, Message = ex.Message });
+ }
+ }
+
+ ///
+ /// 할일 삭제
+ ///
+ public string Todo_DeleteTodo(int id)
+ {
+ try
+ {
+ if (string.IsNullOrEmpty(info.Login.no))
+ {
+ return JsonConvert.SerializeObject(new { Success = false, Message = "로그인되지 않은 상태입니다." });
+ }
+
+ var sql = "DELETE FROM EETGW_Todo WHERE idx = @idx AND gcode = @gcode AND uid = @uid";
+
+ var cs = Properties.Settings.Default.gwcs;
+ var cn = new SqlConnection(cs);
+ var cmd = new SqlCommand(sql, cn);
+ cmd.Parameters.AddWithValue("@idx", id);
+ cmd.Parameters.AddWithValue("@gcode", info.Login.gcode);
+ cmd.Parameters.AddWithValue("@uid", info.Login.no);
+
+ cn.Open();
+ var result = cmd.ExecuteNonQuery();
+ cn.Close();
+ cmd.Dispose();
+ cn.Dispose();
+
+ return JsonConvert.SerializeObject(new { Success = result > 0, Message = result > 0 ? "삭제되었습니다." : "삭제에 실패했습니다." });
+ }
+ catch (Exception ex)
+ {
+ return JsonConvert.SerializeObject(new { Success = false, Message = ex.Message });
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/Project/Web/MachineBridge/MachineBridge.cs b/Project/Web/MachineBridge/MachineBridge.cs
new file mode 100644
index 0000000..92152da
--- /dev/null
+++ b/Project/Web/MachineBridge/MachineBridge.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Runtime.InteropServices;
+using System.Windows.Forms;
+using Newtonsoft.Json;
+using System.IO;
+using System.Linq;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.NetworkInformation;
+using System.Data;
+using System.Data.SqlClient;
+using FCOMMON;
+using Project.Web.Model;
+
+namespace Project.Web
+{
+ // Important: Allows JavaScript to see this class
+ [ClassInterface(ClassInterfaceType.AutoDual)]
+ [ComVisible(true)]
+ public partial class MachineBridge
+ {
+ // Reference to the main form to update logic
+ private Dialog.fDashboardNew _host;
+
+ public MachineBridge(Dialog.fDashboardNew host)
+ {
+ _host = host;
+ }
+ }
+
+ ///
+ /// 로그인 결과 클래스
+ ///
+ public class LoginResult
+ {
+ public bool Success { get; set; }
+ public string Message { get; set; }
+ public string RedirectUrl { get; set; }
+ public string UserName { get; set; }
+ public string VersionWarning { get; set; }
+ }
+}
diff --git a/Project/Web/WebSocketServer.cs b/Project/Web/WebSocketServer.cs
new file mode 100644
index 0000000..7d700ca
--- /dev/null
+++ b/Project/Web/WebSocketServer.cs
@@ -0,0 +1,221 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.WebSockets;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+
+namespace Project.Web
+{
+ public class WebSocketServer
+ {
+ private HttpListener _httpListener;
+ private List _clients = new List();
+ private Dialog.fDashboardNew _mainForm;
+
+ public WebSocketServer(string url, Dialog.fDashboardNew form)
+ {
+ _mainForm = form;
+ _httpListener = new HttpListener();
+ _httpListener.Prefixes.Add(url);
+ _httpListener.Start();
+ Console.WriteLine($"[WS] Listening on {url}");
+ Task.Run(AcceptConnections);
+ }
+
+ private async Task AcceptConnections()
+ {
+ while (_httpListener.IsListening)
+ {
+ try
+ {
+ var context = await _httpListener.GetContextAsync();
+ if (context.Request.IsWebSocketRequest)
+ {
+ ProcessRequest(context);
+ }
+ else
+ {
+ context.Response.StatusCode = 400;
+ context.Response.Close();
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"[WS] Error: {ex.Message}");
+ }
+ }
+ }
+
+ private System.Collections.Concurrent.ConcurrentDictionary _socketLocks = new System.Collections.Concurrent.ConcurrentDictionary();
+
+ private async void ProcessRequest(HttpListenerContext context)
+ {
+ WebSocketContext wsContext = null;
+ try
+ {
+ wsContext = await context.AcceptWebSocketAsync(subProtocol: null);
+ WebSocket socket = wsContext.WebSocket;
+ _socketLocks.TryAdd(socket, new SemaphoreSlim(1, 1));
+
+ lock (_clients) { _clients.Add(socket); }
+ Console.WriteLine("[WS] Client Connected");
+
+ await ReceiveLoop(socket);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"[WS] Accept Error: {ex.Message}");
+ }
+ finally
+ {
+ if (wsContext != null)
+ {
+ WebSocket socket = wsContext.WebSocket;
+ lock (_clients) { _clients.Remove(socket); }
+
+ if (_socketLocks.TryRemove(socket, out var semaphore))
+ {
+ semaphore.Dispose();
+ }
+ socket.Dispose();
+ }
+ }
+ }
+
+ private async Task ReceiveLoop(WebSocket socket)
+ {
+ var buffer = new byte[1024 * 4];
+ while (socket.State == WebSocketState.Open)
+ {
+ try
+ {
+ var result = await socket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None);
+ if (result.MessageType == WebSocketMessageType.Close)
+ {
+ await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", CancellationToken.None);
+ }
+ else if (result.MessageType == WebSocketMessageType.Text)
+ {
+ string msg = Encoding.UTF8.GetString(buffer, 0, result.Count);
+ HandleMessage(msg, socket);
+ }
+ }
+ catch
+ {
+ break;
+ }
+ }
+ }
+
+ private async void HandleMessage(string msg, WebSocket socket)
+ {
+ // Simple JSON parsing (manual or Newtonsoft)
+ // Expected format: { "type": "...", "data": ... }
+ try
+ {
+ dynamic json = Newtonsoft.Json.JsonConvert.DeserializeObject(msg);
+ string type = json.type;
+
+ Console.WriteLine($"HandleMessage:{type}");
+ if (type == "GET_CONFIG")
+ {
+ //// Simulate Delay for Loading Screen Test
+ //await Task.Delay(1000);
+
+ //// Send Config back
+ //var bridge = new MachineBridge(_mainForm); // Re-use logic
+ //string configJson = bridge.GetConfig();
+ //var response = new { type = "CONFIG_DATA", data = Newtonsoft.Json.JsonConvert.DeserializeObject(configJson) };
+ //await Send(socket, Newtonsoft.Json.JsonConvert.SerializeObject(response));
+ }
+ else if (type == "GET_IO_LIST")
+ {
+ // var bridge = new MachineBridge(_mainForm);
+ // string ioJson = bridge.GetIOList();
+ // var response = new { type = "IO_LIST_DATA", data = Newtonsoft.Json.JsonConvert.DeserializeObject(ioJson) };
+ // await Send(socket, Newtonsoft.Json.JsonConvert.SerializeObject(response));
+ //}
+ //else if (type == "GET_RECIPE_LIST")
+ //{
+ // var bridge = new MachineBridge(_mainForm);
+ // string recipeJson = bridge.GetRecipeList();
+ // var response = new { type = "RECIPE_LIST_DATA", data = Newtonsoft.Json.JsonConvert.DeserializeObject(recipeJson) };
+ // await Send(socket, Newtonsoft.Json.JsonConvert.SerializeObject(response));
+ }
+ else if (type == "SAVE_CONFIG")
+ {
+ //string configJson = Newtonsoft.Json.JsonConvert.SerializeObject(json.data);
+ //var bridge = new MachineBridge(_mainForm);
+ //bridge.SaveConfig(configJson);
+ }
+
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"[WS] Msg Error: {ex.Message}");
+ }
+ }
+
+ private async Task Send(WebSocket socket, string message)
+ {
+ if (_socketLocks.TryGetValue(socket, out var semaphore))
+ {
+ await semaphore.WaitAsync();
+ try
+ {
+ if (socket.State == WebSocketState.Open)
+ {
+ byte[] buffer = Encoding.UTF8.GetBytes(message);
+ await socket.SendAsync(new ArraySegment(buffer), WebSocketMessageType.Text, true, CancellationToken.None);
+ }
+ }
+ finally
+ {
+ semaphore.Release();
+ }
+ }
+ }
+
+ public async void Broadcast(string message)
+ {
+ byte[] buffer = Encoding.UTF8.GetBytes(message);
+ WebSocket[] clientsCopy;
+
+ lock (_clients)
+ {
+ clientsCopy = _clients.ToArray();
+ }
+
+ foreach (var client in clientsCopy)
+ {
+ if (client.State == WebSocketState.Open && _socketLocks.TryGetValue(client, out var semaphore))
+ {
+ // Fire and forget, but safely
+ _ = Task.Run(async () =>
+ {
+ // Try to get lock immediately. If busy (sending previous frame), skip this frame to prevent lag.
+ if (await semaphore.WaitAsync(0))
+ {
+ try
+ {
+ if (client.State == WebSocketState.Open)
+ {
+ await client.SendAsync(new ArraySegment(buffer), WebSocketMessageType.Text, true, CancellationToken.None);
+ }
+ }
+ catch { /* Ignore send errors */ }
+ finally
+ {
+ semaphore.Release();
+ }
+ }
+ });
+ }
+ }
+ }
+ }
+}
diff --git a/Project/Web/wwwroot/Jobreport/index.html b/Project/Web/wwwroot/Jobreport/index.html
index 9eea7ce..7be8f88 100644
--- a/Project/Web/wwwroot/Jobreport/index.html
+++ b/Project/Web/wwwroot/Jobreport/index.html
@@ -462,151 +462,12 @@
+
+
+
+