feat: MachineBridge 추가 및 fetch API를 HostObject 호출로 전환

- WebView2 HostObject 기반 MachineBridge 브릿지 클래스 추가
  - MachineBridge.cs (메인), Login, Dashboard, Todo, Common, Jobreport, Kuntae, Project 모듈
- WebSocketServer.cs 추가 (실시간 통신용)
- fDashboardNew 다이얼로그 추가
- Jobreport/index.html, Project/index.html의 fetch API를 machine HostObject 호출로 전환
- DashBoardController.cs의 gcode null 처리 추가
- 사용하지 않는 파일 삭제 (navigation.html, common-nav.js, navigation.js, _add_to_project.py, _project_updater.js)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
backuppc
2025-11-25 16:06:35 +09:00
parent f0d46b7cb1
commit 44af041d1a
24 changed files with 2622 additions and 1454 deletions

63
Project/Dialog/fDashboardNew.Designer.cs generated Normal file
View File

@@ -0,0 +1,63 @@
namespace Project.Dialog
{
partial class fDashboardNew
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
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;
}
}

View File

@@ -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)
{
}
}
}

View File

@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@@ -259,6 +259,12 @@
<Compile Include="Dialog\fCommon.Designer.cs"> <Compile Include="Dialog\fCommon.Designer.cs">
<DependentUpon>fCommon.cs</DependentUpon> <DependentUpon>fCommon.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Dialog\fDashboardNew.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Dialog\fDashboardNew.Designer.cs">
<DependentUpon>fDashboardNew.cs</DependentUpon>
</Compile>
<Compile Include="Dialog\fHolyday.cs"> <Compile Include="Dialog\fHolyday.cs">
<SubType>Form</SubType> <SubType>Form</SubType>
</Compile> </Compile>
@@ -424,6 +430,14 @@
<Compile Include="Web\Controllers\ResultController.cs" /> <Compile Include="Web\Controllers\ResultController.cs" />
<Compile Include="Web\Controllers\SettingController.cs" /> <Compile Include="Web\Controllers\SettingController.cs" />
<Compile Include="Web\Controllers\TodoController.cs" /> <Compile Include="Web\Controllers\TodoController.cs" />
<Compile Include="Web\MachineBridge\MachineBridge.cs" />
<Compile Include="Web\MachineBridge\MachineBridge.Login.cs" />
<Compile Include="Web\MachineBridge\MachineBridge.Dashboard.cs" />
<Compile Include="Web\MachineBridge\MachineBridge.Todo.cs" />
<Compile Include="Web\MachineBridge\MachineBridge.Common.cs" />
<Compile Include="Web\MachineBridge\MachineBridge.Jobreport.cs" />
<Compile Include="Web\MachineBridge\MachineBridge.Kuntae.cs" />
<Compile Include="Web\MachineBridge\MachineBridge.Project.cs" />
<Compile Include="Web\Model\PageModel.cs" /> <Compile Include="Web\Model\PageModel.cs" />
<Compile Include="Web\Model\ProjectModel.cs" /> <Compile Include="Web\Model\ProjectModel.cs" />
<Compile Include="Web\Model\TodoModel.cs" /> <Compile Include="Web\Model\TodoModel.cs" />
@@ -432,6 +446,7 @@
<Compile Include="Settings.cs" /> <Compile Include="Settings.cs" />
<Compile Include="SqlServerTypes\Loader.cs" /> <Compile Include="SqlServerTypes\Loader.cs" />
<Compile Include="StateMachine\ReportUserData.cs" /> <Compile Include="StateMachine\ReportUserData.cs" />
<Compile Include="Web\WebSocketServer.cs" />
<Compile Include="_Common\fADSUserList.cs"> <Compile Include="_Common\fADSUserList.cs">
<SubType>Form</SubType> <SubType>Form</SubType>
</Compile> </Compile>
@@ -495,6 +510,9 @@
<EmbeddedResource Include="Dialog\fCommon.resx"> <EmbeddedResource Include="Dialog\fCommon.resx">
<DependentUpon>fCommon.cs</DependentUpon> <DependentUpon>fCommon.cs</DependentUpon>
</EmbeddedResource> </EmbeddedResource>
<EmbeddedResource Include="Dialog\fDashboardNew.resx">
<DependentUpon>fDashboardNew.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Dialog\fHolyday.resx"> <EmbeddedResource Include="Dialog\fHolyday.resx">
<DependentUpon>fHolyday.cs</DependentUpon> <DependentUpon>fHolyday.cs</DependentUpon>
</EmbeddedResource> </EmbeddedResource>
@@ -686,9 +704,6 @@
<None Include="Web\wwwroot\js\common-navigation.js"> <None Include="Web\wwwroot\js\common-navigation.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>
<None Include="Web\wwwroot\js\navigation.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Web\wwwroot\js\tailwind-config.js"> <None Include="Web\wwwroot\js\tailwind-config.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>
@@ -707,9 +722,6 @@
<None Include="Web\wwwroot\Jobreport\index.html"> <None Include="Web\wwwroot\Jobreport\index.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>
<None Include="Web\wwwroot\js\common-nav.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Web\wwwroot\Kuntae\index.html"> <None Include="Web\wwwroot\Kuntae\index.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>

View File

@@ -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 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 cn = new System.Data.SqlClient.SqlConnection(cs);
var cmd = new System.Data.SqlClient.SqlCommand(sql, cn); 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 da = new System.Data.SqlClient.SqlDataAdapter(cmd);
var dt = new System.Data.DataTable(); var dt = new System.Data.DataTable();
da.Fill(dt); da.Fill(dt);

View File

@@ -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
/// <summary>
/// 네비게이션 메뉴 조회
/// </summary>
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 });
}
}
/// <summary>
/// 공용코드 그룹 목록 조회
/// </summary>
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 "[]";
}
}
/// <summary>
/// 공용코드 목록 조회
/// </summary>
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 "[]";
}
}
/// <summary>
/// 공용코드 저장
/// </summary>
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 });
}
}
/// <summary>
/// 공용코드 삭제
/// </summary>
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 });
}
}
/// <summary>
/// 현재 로그인 사용자 정보 조회
/// </summary>
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
}
}

View File

@@ -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
/// <summary>
/// 오늘 휴가 인원 수 조회
/// </summary>
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";
}
}
/// <summary>
/// 휴가요청 대기 건수 조회
/// </summary>
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 });
}
}
/// <summary>
/// 현재 출근 대상 인원 수 조회
/// </summary>
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 });
}
}
/// <summary>
/// 구매요청 대기 건수 조회 (NR, CR)
/// </summary>
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 });
}
}
/// <summary>
/// 휴가자 목록 조회
/// </summary>
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 "[]";
}
}
/// <summary>
/// 휴가요청 목록 조회
/// </summary>
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 "[]";
}
}
/// <summary>
/// 출근 대상자 목록 조회
/// </summary>
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 "[]";
}
}
/// <summary>
/// 구매요청(NR) 목록 조회
/// </summary>
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 "[]";
}
}
/// <summary>
/// 구매요청(CR) 목록 조회
/// </summary>
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
}
}

View File

@@ -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
/// <summary>
/// 업무일지 목록 조회
/// </summary>
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<SqlParameter>();
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 });
}
}
/// <summary>
/// 업무일지 사용자 목록 조회
/// </summary>
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 "[]";
}
}
/// <summary>
/// 업무일지 상세 조회
/// </summary>
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 });
}
}
/// <summary>
/// 업무일지 추가
/// </summary>
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 });
}
}
/// <summary>
/// 업무일지 수정
/// </summary>
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 });
}
}
/// <summary>
/// 업무일지 삭제
/// </summary>
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
}
}

View File

@@ -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
/// <summary>
/// 근태 목록 조회
/// </summary>
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<SqlParameter>();
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 });
}
}
/// <summary>
/// 근태 삭제
/// </summary>
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
}
}

View File

@@ -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
/// <summary>
/// 로그인 처리 (fLogin_WB.cs의 button1_Click 로직 참고)
/// </summary>
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<string> { 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);
}
/// <summary>
/// fMain의 OnLoginCompleted() 호출
/// </summary>
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}");
}
}
/// <summary>
/// 개발자 로그인 정보 설정
/// </summary>
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;
}
/// <summary>
/// 로그인 정보 기록
/// </summary>
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);
}
}
/// <summary>
/// 그룹 목록 조회
/// </summary>
public string GetUserGroups()
{
var dt = DBM.GetUserGroups();
return JsonConvert.SerializeObject(dt, new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore
});
}
/// <summary>
/// 이전 로그인 정보 조회
/// </summary>
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
}
}

View File

@@ -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
/// <summary>
/// 프로젝트 목록 조회
/// </summary>
public string Project_GetProjects(string status, string userFilter)
{
try
{
var sql = @"SELECT * FROM EETGW_Project WITH (nolock)
WHERE gcode = @gcode";
var parameters = new List<SqlParameter>();
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 });
}
}
/// <summary>
/// 프로젝트 상세 조회
/// </summary>
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 });
}
}
/// <summary>
/// 프로젝트 생성
/// </summary>
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 });
}
}
/// <summary>
/// 프로젝트 수정
/// </summary>
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 });
}
}
/// <summary>
/// 프로젝트 삭제
/// </summary>
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
}
}

View File

@@ -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
/// <summary>
/// 급한 할일 목록 조회
/// </summary>
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<TodoModel>(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 });
}
}
/// <summary>
/// 할일 상세 조회
/// </summary>
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<TodoModel>(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 });
}
}
/// <summary>
/// 할일 추가
/// </summary>
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<int>(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
/// <summary>
/// 할일 전체 목록 조회
/// </summary>
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 });
}
}
/// <summary>
/// 할일 수정
/// </summary>
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 });
}
}
/// <summary>
/// 할일 삭제
/// </summary>
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
}
}

View File

@@ -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;
}
}
/// <summary>
/// 로그인 결과 클래스
/// </summary>
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; }
}
}

View File

@@ -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<WebSocket> _clients = new List<WebSocket>();
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<WebSocket, SemaphoreSlim> _socketLocks = new System.Collections.Concurrent.ConcurrentDictionary<WebSocket, SemaphoreSlim>();
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<byte>(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<byte>(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<byte>(buffer), WebSocketMessageType.Text, true, CancellationToken.None);
}
}
catch { /* Ignore send errors */ }
finally
{
semaphore.Release();
}
}
});
}
}
}
}
}

View File

@@ -462,151 +462,12 @@
</div> </div>
</div> </div>
<!-- 공통 네비게이션 -->
<script src="/js/common-navigation.js"></script>
<script> <script>
/** // Machine HostObject 초기화
* 공통 네비게이션 컴포넌트 const machine = window.chrome.webview.hostObjects.machine;
*/
class CommonNavigation {
constructor(currentPage = '') {
this.currentPage = currentPage;
this.menuItems = [];
this.init();
}
async init() {
try {
await this.loadMenuItems();
this.createNavigation();
this.addEventListeners();
} catch (error) {
console.error('Navigation initialization failed:', error);
this.createFallbackNavigation();
}
}
async loadMenuItems() {
try {
const response = await fetch('/Common/GetNavigationMenu');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
if (data.Success && data.Data) {
this.menuItems = data.Data;
} else {
throw new Error(data.Message || 'Failed to load menu items');
}
} catch (error) {
console.error('Failed to load navigation menu:', error);
this.menuItems = this.getDefaultMenuItems();
}
}
getDefaultMenuItems() {
return [
{ key: 'dashboard', title: '대시보드', url: '/Dashboard/', 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 },
{ key: 'common', title: '공용코드', url: '/Common', 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 },
{ key: 'jobreport', title: '업무일지', url: '/Jobreport/', 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 },
{ key: 'kuntae', title: '근태관리', url: '/Kuntae/', icon: 'M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z', isVisible: true, sortOrder: 4 },
{ key: 'todo', title: '할일관리', url: '/Todo/', 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 }
];
}
createNavigation() {
const nav = document.createElement('nav');
nav.className = 'glass-effect border-b border-white/10';
nav.innerHTML = this.getNavigationHTML();
document.body.insertBefore(nav, document.body.firstChild);
}
createFallbackNavigation() {
this.createNavigation();
}
getNavigationHTML() {
const visibleItems = this.menuItems.filter(item => item.isVisible).sort((a, b) => a.sortOrder - b.sortOrder);
return `
<div class="container mx-auto px-4">
<div class="flex items-center justify-between h-16">
<div class="flex items-center space-x-8">
<a href="/Dashboard/" class="flex items-center space-x-2 hover:opacity-80 transition-opacity cursor-pointer">
<svg class="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="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"></path>
</svg>
<span class="text-xl font-bold text-white">GroupWare</span>
</a>
<nav class="hidden md:flex space-x-1">
${visibleItems.map(item => `
<a href="${item.url}" class="px-3 py-2 rounded-md text-sm font-medium transition-colors ${
this.currentPage === item.key
? 'bg-white/20 text-white'
: 'text-white/60 hover:text-white hover:bg-white/10'
}">
<svg class="w-4 h-4 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="${item.icon}"></path>
</svg>
${item.title}
</a>
`).join('')}
</nav>
</div>
<div class="flex items-center space-x-4">
<div class="text-sm text-white/60">
<span id="currentUser">사용자</span>
</div>
</div>
</div>
</div>
`;
}
getMenuItemHTML(item) {
const isActive = this.currentPage === item.key;
const activeClass = isActive ? 'text-white bg-white/20' : 'text-white/80 hover:text-white hover:bg-white/10';
return `
<a href="${item.url}" class="${activeClass} transition-colors px-3 py-2 rounded-lg">
<svg class="w-4 h-4 inline mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="${item.icon}"></path>
</svg>
${item.title}
</a>
`;
}
getMobileMenuItemHTML(item) {
const isActive = this.currentPage === item.key;
const activeClass = isActive ? 'text-white bg-white/20' : 'text-white/80 hover:text-white hover:bg-white/10';
return `
<a href="${item.url}" class="block ${activeClass} transition-colors px-3 py-2 rounded-lg mb-2">
<svg class="w-4 h-4 inline mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="${item.icon}"></path>
</svg>
${item.title}
</a>
`;
}
addEventListeners() {
const mobileMenuButton = document.getElementById('mobile-menu-button');
const mobileMenu = document.getElementById('mobile-menu');
if (mobileMenuButton && mobileMenu) {
mobileMenuButton.addEventListener('click', function() {
mobileMenu.classList.toggle('hidden');
});
}
}
}
function initNavigation(currentPage = '') {
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
new CommonNavigation(currentPage);
});
} else {
new CommonNavigation(currentPage);
}
}
// 전역 변수 // 전역 변수
let jobData = []; let jobData = [];
@@ -744,40 +605,31 @@
const endDate = document.getElementById('endDate').value; const endDate = document.getElementById('endDate').value;
const selectedUser = document.getElementById('userFilter').value; const selectedUser = document.getElementById('userFilter').value;
// API URL 구성 - 새로운 GetJobData API 사용 console.log('Loading job data with params:', { startDate, endDate, selectedUser });
let url = '/Jobreport/GetJobData';
const params = new URLSearchParams();
if (startDate) params.append('startDate', startDate);
if (endDate) params.append('endDate', endDate);
if (selectedUser) params.append('user', selectedUser);
if (params.toString()) { // machine.Jobreport_GetList(sd, ed, uid, cate, doit) 호출
url += '?' + params.toString(); const jsonStr = await machine.Jobreport_GetList(
} startDate || '',
endDate || '',
selectedUser || '',
'', // cate (빈 값)
'' // doit (빈 값)
);
console.log('Fetching data from:', url); responseData = JSON.parse(jsonStr);
const response = await fetch(url);
console.log('Response status:', response.status);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: 데이터를 불러오는데 실패했습니다.`);
}
// JSON 응답 직접 파싱
const responseText = await response.text();
console.log('Raw response:', responseText);
responseData = JSON.parse(responseText);
console.log('Parsed response:', responseData); console.log('Parsed response:', responseData);
// 응답 데이터가 배열인지 확인 // MachineBridge 응답 형식: { Success: true, Data: [...] }
if (Array.isArray(responseData)) { if (responseData.Success && Array.isArray(responseData.Data)) {
jobData = responseData; jobData = responseData.Data;
} else if (responseData.error) { } else if (responseData.error) {
throw new Error(responseData.error); throw new Error(responseData.error);
} else if (Array.isArray(responseData)) {
// 하위 호환성: 배열이 직접 반환되는 경우
jobData = responseData;
} else { } else {
// 데이터가 배열이 아닌 경우 빈 배열로 초기화 // 데이터가 없는 경우 빈 배열로 초기화
console.warn('Response is not an array, initializing empty array'); console.warn('Response has no data, initializing empty array');
jobData = []; jobData = [];
} }
@@ -867,7 +719,7 @@
}); });
} }
function loadUserList() { async function loadUserList() {
const userSelect = document.getElementById('userFilter'); const userSelect = document.getElementById('userFilter');
// 기존 옵션 제거 (전체 옵션 제외) // 기존 옵션 제거 (전체 옵션 제외)
@@ -875,18 +727,18 @@
userSelect.removeChild(userSelect.lastChild); userSelect.removeChild(userSelect.lastChild);
} }
// 서버에서 사용자 목록 가져오기 try {
fetch('/Jobreport/GetUsers') // machine.Jobreport_GetUsers() 호출
.then(response => { const jsonStr = await machine.Jobreport_GetUsers();
if (!response.ok) { const data = JSON.parse(jsonStr);
throw new Error(`HTTP ${response.status}: 사용자 목록을 불러오는데 실패했습니다.`);
}
return response.json();
})
.then(data => {
console.log('사용자 목록 데이터:', data); console.log('사용자 목록 데이터:', data);
if (data && Array.isArray(data)) {
data.forEach(user => { // 응답 형식 처리: { Success: true, Data: [...] } 또는 배열 직접
const users = (data.Success && data.Data) ? data.Data : (Array.isArray(data) ? data : []);
if (users.length > 0) {
users.forEach(user => {
const option = document.createElement('option'); const option = document.createElement('option');
option.value = user.id; option.value = user.id;
option.textContent = `${user.name} [${user.id}] ${user.process}`; option.textContent = `${user.name} [${user.id}] ${user.process}`;
@@ -894,36 +746,35 @@
}); });
console.log('사용자 목록 로드 완료:', userSelect.children.length - 1, '명'); console.log('사용자 목록 로드 완료:', userSelect.children.length - 1, '명');
} else { } else {
console.warn('사용자 목록 데이터가 배열이 아닙니다:', data); console.warn('사용자 목록 데이터가 없습니다:', data);
} }
}) } catch (error) {
.catch(error => {
console.error('사용자 목록 로드 중 오류:', error); console.error('사용자 목록 로드 중 오류:', error);
// 에러 시 기본 사용자 옵션 추가 // 에러 시 기본 사용자 옵션 추가
const option = document.createElement('option'); const option = document.createElement('option');
option.value = FCOMMON?.info?.Login?.no || ''; option.value = FCOMMON?.info?.Login?.no || '';
option.textContent = '현재 사용자'; option.textContent = '현재 사용자';
userSelect.appendChild(option); userSelect.appendChild(option);
}); }
} }
// 공용코드 12번(상태코드) 로드 // 공용코드 12번(상태코드) 로드
function loadStatusCodes() { async function loadStatusCodes() {
fetch('/Common/GetList?grp=12') try {
.then(response => { const jsonStr = await machine.Common_GetList('12');
if (!response.ok) { const data = JSON.parse(jsonStr);
throw new Error(`HTTP ${response.status}: 상태코드를 불러오는데 실패했습니다.`);
}
return response.json();
})
.then(data => {
console.log('상태코드 원본 데이터:', data); console.log('상태코드 원본 데이터:', data);
if (data && Array.isArray(data)) {
statusCodes = data; // 응답 형식 처리: { Success: true, Data: [...] } 또는 배열 직접
const codes = (data.Success && data.Data) ? data.Data : (Array.isArray(data) ? data : []);
if (codes.length > 0) {
statusCodes = codes;
console.log('상태코드 로드 완료:', statusCodes.length, '개'); console.log('상태코드 로드 완료:', statusCodes.length, '개');
console.log('첫번째 상태코드 샘플:', statusCodes[0]); console.log('첫번째 상태코드 샘플:', statusCodes[0]);
} else { } else {
console.warn('상태코드 데이터가 배열이 아닙니다:', data); console.warn('상태코드 데이터가 없습니다:', data);
// 기본 상태코드 설정 // 기본 상태코드 설정
statusCodes = [ statusCodes = [
{ svalue: '진행 중' }, { svalue: '진행 중' },
@@ -932,8 +783,7 @@
{ svalue: '보류' } { svalue: '보류' }
]; ];
} }
}) } catch (error) {
.catch(error => {
console.error('상태코드 로드 중 오류:', error); console.error('상태코드 로드 중 오류:', error);
// 에러 시 기본 상태코드 사용 // 에러 시 기본 상태코드 사용
statusCodes = [ statusCodes = [
@@ -942,7 +792,7 @@
{ svalue: '대기' }, { svalue: '대기' },
{ svalue: '보류' } { svalue: '보류' }
]; ];
}); }
} }
// 상태코드 드롭다운 옵션 생성 헬퍼 함수 // 상태코드 드롭다운 옵션 생성 헬퍼 함수
@@ -962,78 +812,95 @@
} }
// 공용코드 13번(요청부서) 로드 // 공용코드 13번(요청부서) 로드
function loadRequestDeptCodes() { async function loadRequestDeptCodes() {
fetch('/Common/GetList?grp=13') try {
.then(response => response.json()) const jsonStr = await machine.Common_GetList('13');
.then(data => { const data = JSON.parse(jsonStr);
if (data && Array.isArray(data)) {
requestDeptCodes = data; // 응답 형식 처리: { Success: true, Data: [...] } 또는 배열 직접
const codes = (data.Success && data.Data) ? data.Data : (Array.isArray(data) ? data : []);
if (codes.length > 0) {
requestDeptCodes = codes;
console.log('요청부서 코드 로드 완료:', requestDeptCodes.length, '개'); console.log('요청부서 코드 로드 완료:', requestDeptCodes.length, '개');
} else {
requestDeptCodes = [];
} }
}) } catch (error) {
.catch(error => {
console.error('요청부서 코드 로드 중 오류:', error); console.error('요청부서 코드 로드 중 오류:', error);
requestDeptCodes = []; requestDeptCodes = [];
}); }
} }
// 공용코드 14번(패키지) 로드 // 공용코드 14번(패키지) 로드
function loadPackageCodes() { async function loadPackageCodes() {
fetch('/Common/GetList?grp=14') try {
.then(response => response.json()) const jsonStr = await machine.Common_GetList('14');
.then(data => { const data = JSON.parse(jsonStr);
if (data && Array.isArray(data)) {
packageCodes = data; // 응답 형식 처리: { Success: true, Data: [...] } 또는 배열 직접
const codes = (data.Success && data.Data) ? data.Data : (Array.isArray(data) ? data : []);
if (codes.length > 0) {
packageCodes = codes;
console.log('패키지 코드 로드 완료:', packageCodes.length, '개'); console.log('패키지 코드 로드 완료:', packageCodes.length, '개');
} else {
packageCodes = [];
} }
}) } catch (error) {
.catch(error => {
console.error('패키지 코드 로드 중 오류:', error); console.error('패키지 코드 로드 중 오류:', error);
packageCodes = []; packageCodes = [];
}); }
} }
// 공용코드 15번(업무형태 - 트리구조) 로드 // 공용코드 15번(업무형태 - 트리구조) 로드
function loadJobTypeCodes() { async function loadJobTypeCodes() {
fetch('/Common/GetList?grp=15') try {
.then(response => response.json()) const jsonStr = await machine.Common_GetList('15');
.then(data => { const data = JSON.parse(jsonStr);
console.log('업무형태 원본 데이터:', data); console.log('업무형태 원본 데이터:', data);
if (data && Array.isArray(data)) {
jobTypeCodes = data; // 응답 형식 처리: { Success: true, Data: [...] } 또는 배열 직접
const codes = (data.Success && data.Data) ? data.Data : (Array.isArray(data) ? data : []);
if (codes.length > 0) {
jobTypeCodes = codes;
console.log('업무형태 코드 로드 완료:', jobTypeCodes.length, '개'); console.log('업무형태 코드 로드 완료:', jobTypeCodes.length, '개');
if (jobTypeCodes.length > 0) {
console.log('업무형태 첫번째 샘플:', jobTypeCodes[0]); console.log('업무형태 첫번째 샘플:', jobTypeCodes[0]);
console.log('업무형태 데이터 구조 확인:'); console.log('업무형태 데이터 구조 확인:');
console.log(' - svalue2 (프로세스):', jobTypeCodes[0].svalue2); console.log(' - svalue2 (프로세스):', jobTypeCodes[0].svalue2);
console.log(' - svalue (분류):', jobTypeCodes[0].svalue); console.log(' - svalue (분류):', jobTypeCodes[0].svalue);
console.log(' - memo (항목):', jobTypeCodes[0].memo); console.log(' - memo (항목):', jobTypeCodes[0].memo);
}
} else { } else {
console.warn('업무형태 데이터가 배열이 아닙니다:', data); console.warn('업무형태 데이터가 없습니다:', data);
jobTypeCodes = []; jobTypeCodes = [];
} }
}) } catch (error) {
.catch(error => {
console.error('업무형태 코드 로드 중 오류:', error); console.error('업무형태 코드 로드 중 오류:', error);
jobTypeCodes = []; jobTypeCodes = [];
}); }
} }
// 공용코드 16번(프로세스) 로드 // 공용코드 16번(프로세스) 로드
function loadProcessCodes() { async function loadProcessCodes() {
fetch('/Common/GetList?grp=16') try {
.then(response => response.json()) const jsonStr = await machine.Common_GetList('16');
.then(data => { const data = JSON.parse(jsonStr);
if (data && Array.isArray(data)) {
processCodes = data; // 응답 형식 처리: { Success: true, Data: [...] } 또는 배열 직접
const codes = (data.Success && data.Data) ? data.Data : (Array.isArray(data) ? data : []);
if (codes.length > 0) {
processCodes = codes;
console.log('프로세스 코드 로드 완료:', processCodes.length, '개'); console.log('프로세스 코드 로드 완료:', processCodes.length, '개');
} else {
processCodes = [];
} }
}) } catch (error) {
.catch(error => {
console.error('프로세스 코드 로드 중 오류:', error); console.error('프로세스 코드 로드 중 오류:', error);
processCodes = []; processCodes = [];
}); }
} }
// 드롭다운 옵션 생성 헬퍼 함수들 // 드롭다운 옵션 생성 헬퍼 함수들
@@ -1395,12 +1262,17 @@
// 전체 상세 정보를 서버에서 가져오기 // 전체 상세 정보를 서버에서 가져오기
try { try {
const response = await fetch(`/Jobreport/GetJobDetail?id=${item.idx}`); const jsonStr = await machine.Jobreport_GetDetail(item.idx);
if (response.ok) { const data = JSON.parse(jsonStr);
const fullItem = await response.json();
// 전체 정보가 있으면 사용, 없으면 기존 item 사용 // 응답 형식 처리: { Success: true, Data: {...} } 또는 객체 직접
item = fullItem.error ? item : fullItem; if (data.Success && data.Data) {
item = data.Data;
} else if (!data.error && typeof data === 'object') {
// 하위 호환성: 객체가 직접 반환되는 경우
item = data;
} }
// error가 있으면 기존 item 유지
} catch (error) { } catch (error) {
console.warn('Failed to load full details, using truncated data:', error); console.warn('Failed to load full details, using truncated data:', error);
} }
@@ -1680,50 +1552,30 @@
async function handleAddSubmit(event) { async function handleAddSubmit(event) {
event.preventDefault(); event.preventDefault();
// URLSearchParams 사용 (application/x-www-form-urlencoded)
const formData = new URLSearchParams();
formData.append('pdate', document.getElementById('editPdate').value);
formData.append('status', document.getElementById('editStatus').value);
formData.append('projectName', document.getElementById('editProjectName').value);
formData.append('requestpart', document.getElementById('editRequestpart').value);
formData.append('package', document.getElementById('editPackage').value);
formData.append('jobprocess', document.getElementById('editJobProcess').value);
formData.append('jobtype', document.getElementById('editJobType').value);
formData.append('jobgrp', document.getElementById('editJobGrp').value);
formData.append('jobprocess2', document.getElementById('editJobProcess2').value);
formData.append('hrs', document.getElementById('editHrs').value);
formData.append('ot', document.getElementById('editOt').value);
formData.append('otStart', document.getElementById('editOtStart').value);
formData.append('otEnd', document.getElementById('editOtEnd').value);
formData.append('description', document.getElementById('editDescription').value);
try { try {
const response = await fetch('/Jobreport/Add', { // 폼 데이터 수집
method: 'POST', const jdate = document.getElementById('editPdate').value;
headers: { const cate = document.getElementById('editJobType').value || '';
'Content-Type': 'application/x-www-form-urlencoded' const title = document.getElementById('editProjectName').value;
}, const doit = document.getElementById('editDescription').value;
body: formData const remark = document.getElementById('editStatus').value || '';
}); const jfrom = document.getElementById('editOtStart').value || '';
const jto = document.getElementById('editOtEnd').value || '';
if (!response.ok) { console.log('Adding job with params:', { jdate, cate, title, doit, remark, jfrom, jto });
throw new Error(`HTTP ${response.status}: 추가에 실패했습니다.`);
} // machine.Jobreport_Add(jdate, cate, title, doit, remark, jfrom, jto) 호출
const jsonStr = await machine.Jobreport_Add(jdate, cate, title, doit, remark, jfrom, jto);
const result = JSON.parse(jsonStr);
const result = await response.text();
console.log('Add result:', result); console.log('Add result:', result);
// JSON 응답인지 확인 // 응답 형식 처리: { Success: true } 또는 { success: true }
try { if (result.Success === false || result.success === false) {
const jsonResult = JSON.parse(result); throw new Error(result.Message || result.message || '추가에 실패했습니다.');
if (!jsonResult.success) {
throw new Error(jsonResult.message || '추가에 실패했습니다.');
}
// 성공시 alert 없이 바로 진행
} catch (parseError) {
// JSON이 아닌 경우도 성공으로 처리 (alert 없음)
} }
// 성공시 alert 없이 바로 진행
// 모달 닫기 // 모달 닫기
closeModal(); closeModal();
@@ -1739,51 +1591,31 @@
async function handleEditSubmit(event) { async function handleEditSubmit(event) {
event.preventDefault(); event.preventDefault();
// URLSearchParams 사용 (application/x-www-form-urlencoded)
const formData = new URLSearchParams();
formData.append('idx', document.getElementById('editIdx').value);
formData.append('pdate', document.getElementById('editPdate').value);
formData.append('status', document.getElementById('editStatus').value);
formData.append('projectName', document.getElementById('editProjectName').value);
formData.append('requestpart', document.getElementById('editRequestpart').value);
formData.append('package', document.getElementById('editPackage').value);
formData.append('jobprocess', document.getElementById('editJobProcess').value);
formData.append('jobtype', document.getElementById('editJobType').value);
formData.append('jobgrp', document.getElementById('editJobGrp').value);
formData.append('jobprocess2', document.getElementById('editJobProcess2').value);
formData.append('hrs', document.getElementById('editHrs').value);
formData.append('ot', document.getElementById('editOt').value);
formData.append('otStart', document.getElementById('editOtStart').value);
formData.append('otEnd', document.getElementById('editOtEnd').value);
formData.append('description', document.getElementById('editDescription').value);
try { try {
const response = await fetch('/Jobreport/Edit', { // 폼 데이터 수집
method: 'POST', const idx = document.getElementById('editIdx').value;
headers: { const jdate = document.getElementById('editPdate').value;
'Content-Type': 'application/x-www-form-urlencoded' const cate = document.getElementById('editJobType').value || '';
}, const title = document.getElementById('editProjectName').value;
body: formData const doit = document.getElementById('editDescription').value;
}); const remark = document.getElementById('editStatus').value || '';
const jfrom = document.getElementById('editOtStart').value || '';
const jto = document.getElementById('editOtEnd').value || '';
if (!response.ok) { console.log('Editing job with params:', { idx, jdate, cate, title, doit, remark, jfrom, jto });
throw new Error(`HTTP ${response.status}: 업데이트에 실패했습니다.`);
} // machine.Jobreport_Edit(idx, jdate, cate, title, doit, remark, jfrom, jto) 호출
const jsonStr = await machine.Jobreport_Edit(idx, jdate, cate, title, doit, remark, jfrom, jto);
const result = JSON.parse(jsonStr);
const result = await response.text();
console.log('Edit result:', result); console.log('Edit result:', result);
// JSON 응답인지 확인 // 응답 형식 처리: { Success: true } 또는 { success: true }
try { if (result.Success === false || result.success === false) {
const jsonResult = JSON.parse(result); throw new Error(result.Message || result.message || '수정에 실패했습니다.');
if (!jsonResult.success) {
throw new Error(jsonResult.message || '수정에 실패했습니다.');
}
// 성공시 alert 없이 바로 진행
} catch (parseError) {
// JSON이 아닌 경우도 성공으로 처리 (alert 없음)
} }
// 성공시 alert 없이 바로 진행
// 모달 닫기 // 모달 닫기
closeModal(); closeModal();
@@ -1809,23 +1641,22 @@
} }
try { try {
const response = await fetch(`/Jobreport/Delete/${idx}`, { console.log('Deleting job with idx:', idx);
method: 'DELETE'
});
if (!response.ok) { // machine.Jobreport_Delete(idx) 호출
throw new Error('삭제에 실패했습니다.'); const jsonStr = await machine.Jobreport_Delete(idx);
const result = JSON.parse(jsonStr);
console.log('Delete result:', result);
// 응답 형식 처리: { Success: true } 또는 { success: true }
if (result.Success === false || result.success === false) {
throw new Error(result.Message || result.message || '삭제에 실패했습니다.');
} }
const result = await response.json();
if (result.success) {
// 성공시 alert 없이 바로 모달 닫기 // 성공시 alert 없이 바로 모달 닫기
closeModal(); closeModal();
await loadJobData(); await loadJobData();
} else {
throw new Error(result.message || '삭제에 실패했습니다.');
}
} catch (error) { } catch (error) {
console.error('Error deleting job:', error); console.error('Error deleting job:', error);

View File

@@ -473,6 +473,9 @@
</div> </div>
</div> </div>
<!-- 공통 네비게이션 -->
<script src="/js/common-navigation.js"></script>
<script> <script>
let projects = []; let projects = [];
let currentProjectIdx = null; let currentProjectIdx = null;
@@ -483,109 +486,14 @@
let itemsPerPage = 10; let itemsPerPage = 10;
let filteredProjects = []; let filteredProjects = [];
// machine HostObject 참조
const machine = window.chrome.webview.hostObjects.machine;
// 초기화 // 초기화
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
initializeApp(); initializeApp();
}); });
// 네비게이션 클래스
class CommonNavigation {
constructor(currentPage = '') {
this.currentPage = currentPage;
this.createNavigation();
}
async createNavigation() {
try {
const response = await fetch('/Common/GetNavigationMenu');
const data = await response.json();
if (data.Success && data.Data) {
this.createNavigationFromData(data.Data);
} else {
this.createFallbackNavigation();
}
} catch (error) {
console.error('Navigation initialization failed:', error);
this.createFallbackNavigation();
}
}
createNavigationFromData(menuItems) {
const nav = document.createElement('nav');
nav.className = 'glass-effect border-b border-white/10';
nav.innerHTML = this.getNavigationHTML(menuItems);
document.body.insertBefore(nav, document.body.firstChild);
}
createFallbackNavigation() {
this.createNavigation();
}
createNavigation() {
const nav = document.createElement('nav');
nav.className = 'glass-effect border-b border-white/10';
nav.innerHTML = this.getNavigationHTML();
document.body.insertBefore(nav, document.body.firstChild);
}
getNavigationHTML(menuItems = null) {
if (!menuItems) {
menuItems = [
{ key: 'dashboard', title: '대시보드', url: '/Dashboard/', 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' },
{ key: 'common', title: '공용코드', url: '/Common', 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' },
{ key: 'jobreport', title: '업무일지', url: '/Jobreport/', 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' },
{ key: 'kuntae', title: '근태관리', url: '/Kuntae/', icon: 'M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z' },
{ key: 'todo', title: '할일관리', url: '/Todo/', 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' },
{ key: 'project', title: '프로젝트', url: '/Project/', 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' }
];
}
return `
<div class="container mx-auto px-4">
<div class="flex items-center justify-between h-16">
<div class="flex items-center space-x-8">
<a href="/Dashboard/" class="flex items-center space-x-2 hover:opacity-80 transition-opacity cursor-pointer">
<svg class="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="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"></path>
</svg>
<span class="text-xl font-bold text-white">GroupWare</span>
</a>
<nav class="hidden md:flex space-x-1">
${menuItems.map(item => `
<a href="${item.url}" class="px-3 py-2 rounded-md text-sm font-medium transition-colors ${
this.currentPage === item.key
? 'bg-white/20 text-white'
: 'text-white/60 hover:text-white hover:bg-white/10'
}">
<svg class="w-4 h-4 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="${item.icon}"></path>
</svg>
${item.title}
</a>
`).join('')}
</nav>
</div>
<div class="flex items-center space-x-4">
<div class="text-sm text-white/60">
<span id="currentUser">사용자</span>
</div>
</div>
</div>
</div>
`;
}
}
function initNavigation(currentPage = '') {
try {
new CommonNavigation(currentPage);
} catch (error) {
console.error('Navigation initialization failed:', error);
new CommonNavigation(currentPage);
}
}
function initializeApp() { function initializeApp() {
initNavigation('project'); initNavigation('project');
getCurrentUser(); getCurrentUser();
@@ -593,11 +501,12 @@
setupEventListeners(); setupEventListeners();
} }
function getCurrentUser() { async function getCurrentUser() {
// 현재 로그인된 사용자 정보 가져오기 // 현재 로그인된 사용자 정보 가져오기
fetch('/Common/GetCurrentUser') try {
.then(response => response.json()) const jsonStr = await machine.GetCurrentUser();
.then(data => { const data = JSON.parse(jsonStr);
if (data.Success && data.Data) { if (data.Success && data.Data) {
currentUser = data.Data.userName || data.Data.name || '사용자'; currentUser = data.Data.userName || data.Data.name || '사용자';
document.getElementById('currentUser').textContent = currentUser; document.getElementById('currentUser').textContent = currentUser;
@@ -605,12 +514,11 @@
currentUser = '사용자'; currentUser = '사용자';
document.getElementById('currentUser').textContent = currentUser; document.getElementById('currentUser').textContent = currentUser;
} }
}) } catch (error) {
.catch(error => {
console.error('Error getting current user:', error); console.error('Error getting current user:', error);
currentUser = '사용자'; currentUser = '사용자';
document.getElementById('currentUser').textContent = currentUser; document.getElementById('currentUser').textContent = currentUser;
}); }
} }
function setupEventListeners() { function setupEventListeners() {
@@ -648,13 +556,14 @@
}); });
} }
function loadProjects() { async function loadProjects() {
const status = document.getElementById('statusFilter').value; const status = document.getElementById('statusFilter').value;
const userFilter = document.getElementById('managerFilter').value; const userFilter = document.getElementById('managerFilter').value;
fetch(`/Project/GetProjects?status=${status}&userFilter=${userFilter}`) try {
.then(response => response.json()) const jsonStr = await machine.Project_GetProjects(status, userFilter);
.then(data => { const data = JSON.parse(jsonStr);
if (!data.Success) { if (!data.Success) {
console.error('Error:', data.Message); console.error('Error:', data.Message);
document.getElementById('projectTableBody').innerHTML = '<tr><td colspan="23" class="px-6 py-4 text-center text-red-400">데이터를 불러오는데 실패했습니다.</td></tr>'; document.getElementById('projectTableBody').innerHTML = '<tr><td colspan="23" class="px-6 py-4 text-center text-red-400">데이터를 불러오는데 실패했습니다.</td></tr>';
@@ -668,11 +577,10 @@
currentPage = 1; currentPage = 1;
filterData(); filterData();
updateStatusCounts(); updateStatusCounts();
}) } catch (error) {
.catch(error => {
console.error('Error:', error); console.error('Error:', error);
document.getElementById('projectTableBody').innerHTML = '<tr><td colspan="23" class="px-6 py-4 text-center text-red-400">데이터를 불러오는데 실패했습니다.</td></tr>'; document.getElementById('projectTableBody').innerHTML = '<tr><td colspan="23" class="px-6 py-4 text-center text-red-400">데이터를 불러오는데 실패했습니다.</td></tr>';
}); }
} }
function updateStatusCounts() { function updateStatusCounts() {
@@ -830,12 +738,13 @@
document.getElementById('projectModal').classList.remove('hidden'); document.getElementById('projectModal').classList.remove('hidden');
} }
function editProject(idx) { async function editProject(idx) {
currentProjectIdx = idx; currentProjectIdx = idx;
fetch(`/Project/GetProject?id=${idx}`) try {
.then(response => response.json()) const jsonStr = await machine.Project_GetProject(idx);
.then(data => { const data = JSON.parse(jsonStr);
if (!data.Success) { if (!data.Success) {
alert('프로젝트 정보를 불러오는데 실패했습니다: ' + data.Message); alert('프로젝트 정보를 불러오는데 실패했습니다: ' + data.Message);
return; return;
@@ -856,11 +765,10 @@
document.getElementById('deleteProjectBtn').classList.remove('hidden'); document.getElementById('deleteProjectBtn').classList.remove('hidden');
document.getElementById('projectModal').classList.remove('hidden'); document.getElementById('projectModal').classList.remove('hidden');
}) } catch (error) {
.catch(error => {
console.error('Error:', error); console.error('Error:', error);
alert('프로젝트 정보를 불러오는데 실패했습니다.'); alert('프로젝트 정보를 불러오는데 실패했습니다.');
}); }
} }
function closeProjectModal() { function closeProjectModal() {
@@ -868,7 +776,7 @@
document.getElementById('deleteModal').classList.add('hidden'); document.getElementById('deleteModal').classList.add('hidden');
} }
function handleFormSubmit(e) { async function handleFormSubmit(e) {
e.preventDefault(); e.preventDefault();
const projectData = { const projectData = {
@@ -884,18 +792,15 @@
memo: document.getElementById('projectMemo').value memo: document.getElementById('projectMemo').value
}; };
const url = currentProjectIdx ? '/Project/UpdateProject' : '/Project/CreateProject'; try {
const method = currentProjectIdx ? 'PUT' : 'POST'; let jsonStr;
if (currentProjectIdx) {
jsonStr = await machine.Project_UpdateProject(JSON.stringify(projectData));
} else {
jsonStr = await machine.Project_CreateProject(JSON.stringify(projectData));
}
const data = JSON.parse(jsonStr);
fetch(url, {
method: method,
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(projectData)
})
.then(response => response.json())
.then(data => {
if (!data.Success) { if (!data.Success) {
alert('오류: ' + data.Message); alert('오류: ' + data.Message);
} else { } else {
@@ -903,11 +808,10 @@
closeProjectModal(); closeProjectModal();
loadProjects(); loadProjects();
} }
}) } catch (error) {
.catch(error => {
console.error('Error:', error); console.error('Error:', error);
alert('저장 중 오류가 발생했습니다.'); alert('저장 중 오류가 발생했습니다.');
}); }
} }
function deleteCurrentProject() { function deleteCurrentProject() {
@@ -918,14 +822,13 @@
document.getElementById('deleteModal').classList.add('hidden'); document.getElementById('deleteModal').classList.add('hidden');
} }
function confirmDelete() { async function confirmDelete() {
if (!currentProjectIdx) return; if (!currentProjectIdx) return;
fetch(`/Project/DeleteProject?id=${currentProjectIdx}`, { try {
method: 'DELETE' const jsonStr = await machine.Project_DeleteProject(currentProjectIdx);
}) const data = JSON.parse(jsonStr);
.then(response => response.json())
.then(data => {
if (!data.Success) { if (!data.Success) {
alert('오류: ' + data.Message); alert('오류: ' + data.Message);
} else { } else {
@@ -933,11 +836,10 @@
closeProjectModal(); closeProjectModal();
loadProjects(); loadProjects();
} }
}) } catch (error) {
.catch(error => {
console.error('Error:', error); console.error('Error:', error);
alert('삭제 중 오류가 발생했습니다.'); alert('삭제 중 오류가 발생했습니다.');
}); }
} }
function getStatusClass(status) { function getStatusClass(status) {

View File

@@ -1,158 +0,0 @@
<!-- 공통 네비게이션 -->
<nav id="main-navigation" class="glass-effect border-b border-white/10 relative z-40">
<div class="container mx-auto px-4">
<div class="flex items-center justify-between h-16">
<!-- 로고 및 브랜드 -->
<div class="flex items-center space-x-8">
<div class="flex items-center space-x-2">
<svg class="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="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"></path>
</svg>
<span class="text-xl font-bold text-white">GroupWare</span>
</div>
<!-- 데스크톱 메뉴 -->
<nav class="hidden md:flex space-x-1">
<a href="/DashBoard/"
class="nav-item px-3 py-2 rounded-md text-sm font-medium transition-colors text-white/60 hover:text-white hover:bg-white/10"
data-page="dashboard">
<svg class="w-4 h-4 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="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"></path>
</svg>
대시보드
</a>
<a href="/Common/"
class="nav-item px-3 py-2 rounded-md text-sm font-medium transition-colors text-white/60 hover:text-white hover:bg-white/10"
data-page="common">
<svg class="w-4 h-4 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="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"></path>
</svg>
공용코드
</a>
<a href="/Jobreport/"
class="nav-item px-3 py-2 rounded-md text-sm font-medium transition-colors text-white/60 hover:text-white hover:bg-white/10"
data-page="jobreport">
<svg class="w-4 h-4 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="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"></path>
</svg>
업무일지
</a>
<a href="/Kuntae/"
class="nav-item px-3 py-2 rounded-md text-sm font-medium transition-colors text-white/60 hover:text-white hover:bg-white/10"
data-page="kuntae">
<svg class="w-4 h-4 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
근태관리
</a>
<a href="/Todo/"
class="nav-item px-3 py-2 rounded-md text-sm font-medium transition-colors text-white/60 hover:text-white hover:bg-white/10"
data-page="todo">
<svg class="w-4 h-4 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="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"></path>
</svg>
할일관리
</a>
<a href="/Project/"
class="nav-item px-3 py-2 rounded-md text-sm font-medium transition-colors text-white/60 hover:text-white hover:bg-white/10"
data-page="project">
<svg class="w-4 h-4 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="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"></path>
</svg>
프로젝트
</a>
<a href="/Purchase/"
class="nav-item px-3 py-2 rounded-md text-sm font-medium transition-colors text-white/60 hover:text-white hover:bg-white/10"
data-page="purchase">
<svg class="w-4 h-4 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 11V7a4 4 0 00-8 0v4M5 9h14l1 12H4L5 9z"></path>
</svg>
구매관리
</a>
<a href="/Customer/"
class="nav-item px-3 py-2 rounded-md text-sm font-medium transition-colors text-white/60 hover:text-white hover:bg-white/10"
data-page="customer">
<svg class="w-4 h-4 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>
</svg>
고객관리
</a>
</nav>
</div>
<!-- 우측 메뉴 -->
<div class="flex items-center space-x-4">
<div class="text-sm text-white/60">
<span>사용자</span>
</div>
<!-- 모바일 메뉴 버튼 -->
<button id="mobile-menu-button"
class="md:hidden text-white/60 hover:text-white focus:outline-none focus:text-white">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
</svg>
</button>
</div>
</div>
<!-- 모바일 메뉴 -->
<div id="mobile-menu" class="md:hidden border-t border-white/10 py-2 hidden">
<a href="/DashBoard/" class="nav-item-mobile block px-3 py-2 text-sm font-medium transition-colors text-white/60 hover:text-white hover:bg-white/10" data-page="dashboard">
<svg class="w-4 h-4 inline mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="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"></path>
</svg>
대시보드
</a>
<a href="/Common/" class="nav-item-mobile block px-3 py-2 text-sm font-medium transition-colors text-white/60 hover:text-white hover:bg-white/10" data-page="common">
<svg class="w-4 h-4 inline mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="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"></path>
</svg>
공용코드
</a>
<a href="/Jobreport/" class="nav-item-mobile block px-3 py-2 text-sm font-medium transition-colors text-white/60 hover:text-white hover:bg-white/10" data-page="jobreport">
<svg class="w-4 h-4 inline mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="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"></path>
</svg>
업무일지
</a>
<a href="/Kuntae/" class="nav-item-mobile block px-3 py-2 text-sm font-medium transition-colors text-white/60 hover:text-white hover:bg-white/10" data-page="kuntae">
<svg class="w-4 h-4 inline mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
근태관리
</a>
<a href="/Todo/" class="nav-item-mobile block px-3 py-2 text-sm font-medium transition-colors text-white/60 hover:text-white hover:bg-white/10" data-page="todo">
<svg class="w-4 h-4 inline mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="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"></path>
</svg>
할일관리
</a>
<a href="/Project/" class="nav-item-mobile block px-3 py-2 text-sm font-medium transition-colors text-white/60 hover:text-white hover:bg-white/10" data-page="project">
<svg class="w-4 h-4 inline mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="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"></path>
</svg>
프로젝트
</a>
<a href="/Purchase/" class="nav-item-mobile block px-3 py-2 text-sm font-medium transition-colors text-white/60 hover:text-white hover:bg-white/10" data-page="purchase">
<svg class="w-4 h-4 inline mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 11V7a4 4 0 00-8 0v4M5 9h14l1 12H4L5 9z"></path>
</svg>
구매관리
</a>
<a href="/Customer/" class="nav-item-mobile block px-3 py-2 text-sm font-medium transition-colors text-white/60 hover:text-white hover:bg-white/10" data-page="customer">
<svg class="w-4 h-4 inline mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>
</svg>
고객관리
</a>
</div>
</div>
</nav>

View File

@@ -1,111 +0,0 @@
// 공통 네비게이션 컴포넌트
class CommonNavigation {
constructor(currentPage = '') {
this.currentPage = currentPage;
this.init();
}
init() {
this.createNavigation();
this.addEventListeners();
}
createNavigation() {
const nav = document.createElement('nav');
nav.className = 'glass-effect border-b border-white/10';
nav.innerHTML = this.getNavigationHTML();
// body의 첫 번째 자식으로 추가
document.body.insertBefore(nav, document.body.firstChild);
}
getNavigationHTML() {
return `
<div class="container mx-auto px-4">
<div class="flex items-center justify-between h-16">
<!-- 로고/타이틀 -->
<div class="flex items-center">
<h2 class="text-xl font-bold text-white">GroupWare</h2>
</div>
<!-- 메뉴 -->
<div class="hidden md:flex items-center space-x-8">
${this.getMenuItemHTML('dashboard', '/Dashboard/', '대시보드', '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')}
${this.getMenuItemHTML('common', '/Common', '공용코드', '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')}
${this.getMenuItemHTML('jobreport', '/Jobreport/', '업무일지', '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')}
${this.getMenuItemHTML('kuntae', '/Kuntae/', '근태관리', 'M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z')}
</div>
<!-- 모바일 메뉴 버튼 -->
<div class="md:hidden">
<button id="mobile-menu-button" class="text-white/80 hover:text-white transition-colors p-2">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
</svg>
</button>
</div>
</div>
<!-- 모바일 메뉴 -->
<div id="mobile-menu" class="md:hidden hidden border-t border-white/10 pt-4 pb-4">
${this.getMobileMenuItemHTML('dashboard', '/Dashboard/', '대시보드', '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')}
${this.getMobileMenuItemHTML('common', '/Common', '공용코드', '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')}
${this.getMobileMenuItemHTML('jobreport', '/Jobreport/', '업무일지', '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')}
${this.getMobileMenuItemHTML('kuntae', '/Kuntae/', '근태관리', 'M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z')}
</div>
</div>
`;
}
getMenuItemHTML(pageKey, href, text, svgPath) {
const isActive = this.currentPage === pageKey;
const activeClass = isActive ? 'text-white bg-white/20' : 'text-white/80 hover:text-white hover:bg-white/10';
return `
<a href="${href}" class="${activeClass} transition-colors px-3 py-2 rounded-lg">
<svg class="w-4 h-4 inline mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="${svgPath}"></path>
</svg>
${text}
</a>
`;
}
getMobileMenuItemHTML(pageKey, href, text, svgPath) {
const isActive = this.currentPage === pageKey;
const activeClass = isActive ? 'text-white bg-white/20' : 'text-white/80 hover:text-white hover:bg-white/10';
return `
<a href="${href}" class="block ${activeClass} transition-colors px-3 py-2 rounded-lg mb-2">
<svg class="w-4 h-4 inline mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="${svgPath}"></path>
</svg>
${text}
</a>
`;
}
addEventListeners() {
// 모바일 메뉴 토글
const mobileMenuButton = document.getElementById('mobile-menu-button');
const mobileMenu = document.getElementById('mobile-menu');
if (mobileMenuButton && mobileMenu) {
mobileMenuButton.addEventListener('click', function() {
mobileMenu.classList.toggle('hidden');
});
}
}
}
// 전역 함수로 내비게이션 초기화
function initNavigation(currentPage = '') {
// DOM이 로드된 후에 실행
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
new CommonNavigation(currentPage);
});
} else {
new CommonNavigation(currentPage);
}
}

View File

@@ -1,195 +0,0 @@
/**
* 공통 네비게이션 컴포넌트
* 서버에서 메뉴 정보를 받아와서 동적으로 네비게이션을 생성합니다.
*/
class CommonNavigation {
constructor(currentPage = '') {
this.currentPage = currentPage;
this.menuItems = [];
this.init();
}
async init() {
try {
await this.loadMenuItems();
this.createNavigation();
this.addEventListeners();
} catch (error) {
console.error('Navigation initialization failed:', error);
// 오류 발생 시 기본 메뉴로 폴백
this.createFallbackNavigation();
}
}
async loadMenuItems() {
try {
const response = await fetch('/api/Common/GetNavigationMenu');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
if (data.Success && data.Data) {
this.menuItems = data.Data;
} else {
throw new Error(data.Message || 'Failed to load menu items');
}
} catch (error) {
console.error('Failed to load navigation menu:', error);
// 기본 메뉴 항목으로 폴백
this.menuItems = this.getDefaultMenuItems();
}
}
getDefaultMenuItems() {
return [
{
key: 'dashboard',
title: '대시보드',
url: '/Dashboard/',
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
},
{
key: 'common',
title: '공용코드',
url: '/Common',
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
},
{
key: 'jobreport',
title: '업무일지',
url: '/Jobreport/',
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
},
{
key: 'kuntae',
title: '근태관리',
url: '/Kuntae/',
icon: 'M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z',
isVisible: true,
sortOrder: 4
},
{
key: 'todo',
title: '할일관리',
url: '/Todo/',
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
}
];
}
createNavigation() {
const nav = document.createElement('nav');
nav.className = 'glass-effect border-b border-white/10';
nav.innerHTML = this.getNavigationHTML();
// body의 첫 번째 자식으로 추가
document.body.insertBefore(nav, document.body.firstChild);
}
createFallbackNavigation() {
console.log('Creating fallback navigation...');
this.createNavigation();
}
getNavigationHTML() {
const visibleItems = this.menuItems
.filter(item => item.isVisible)
.sort((a, b) => a.sortOrder - b.sortOrder);
return `
<div class="container mx-auto px-4">
<div class="flex items-center justify-between h-16">
<!-- 로고/타이틀 -->
<div class="flex items-center">
<h2 class="text-xl font-bold text-white">GroupWare</h2>
</div>
<!-- 메뉴 -->
<div class="hidden md:flex items-center space-x-8">
${visibleItems.map(item => this.getMenuItemHTML(item)).join('')}
</div>
<!-- 모바일 메뉴 버튼 -->
<div class="md:hidden">
<button id="mobile-menu-button" class="text-white/80 hover:text-white transition-colors p-2">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
</svg>
</button>
</div>
</div>
<!-- 모바일 메뉴 -->
<div id="mobile-menu" class="md:hidden hidden border-t border-white/10 pt-4 pb-4">
${visibleItems.map(item => this.getMobileMenuItemHTML(item)).join('')}
</div>
</div>
`;
}
getMenuItemHTML(item) {
const isActive = this.currentPage === item.key;
const activeClass = isActive ? 'text-white bg-white/20' : 'text-white/80 hover:text-white hover:bg-white/10';
return `
<a href="${item.url}" class="${activeClass} transition-colors px-3 py-2 rounded-lg">
<svg class="w-4 h-4 inline mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="${item.icon}"></path>
</svg>
${item.title}
</a>
`;
}
getMobileMenuItemHTML(item) {
const isActive = this.currentPage === item.key;
const activeClass = isActive ? 'text-white bg-white/20' : 'text-white/80 hover:text-white hover:bg-white/10';
return `
<a href="${item.url}" class="block ${activeClass} transition-colors px-3 py-2 rounded-lg mb-2">
<svg class="w-4 h-4 inline mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="${item.icon}"></path>
</svg>
${item.title}
</a>
`;
}
addEventListeners() {
// 모바일 메뉴 토글
const mobileMenuButton = document.getElementById('mobile-menu-button');
const mobileMenu = document.getElementById('mobile-menu');
if (mobileMenuButton && mobileMenu) {
mobileMenuButton.addEventListener('click', function() {
mobileMenu.classList.toggle('hidden');
});
}
}
}
// 전역 함수로 내비게이션 초기화
function initNavigation(currentPage = '') {
// DOM이 로드된 후에 실행
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
new CommonNavigation(currentPage);
});
} else {
new CommonNavigation(currentPage);
}
}
// ES6 모듈로도 사용 가능하도록 export (필요시)
if (typeof module !== 'undefined' && module.exports) {
module.exports = { CommonNavigation, initNavigation };
}

View File

@@ -1,158 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
C# 프로젝트 파일 자동 업데이트 도구
새로운 파일을 EETGW.csproj에 자동으로 등록합니다.
"""
import re
import os
import sys
from typing import List, Tuple, Optional
class ProjectFileUpdater:
def __init__(self, project_path: str = "EETGW.csproj"):
self.project_path = project_path
self.content = ""
self.load_project()
def load_project(self) -> bool:
"""프로젝트 파일 로드"""
try:
with open(self.project_path, 'r', encoding='utf-8') as f:
self.content = f.read()
return True
except Exception as e:
print(f"❌ 프로젝트 파일을 읽을 수 없습니다: {e}")
return False
def save_project(self) -> bool:
"""프로젝트 파일 저장"""
try:
with open(self.project_path, 'w', encoding='utf-8') as f:
f.write(self.content)
print("✅ 프로젝트 파일이 업데이트되었습니다.")
return True
except Exception as e:
print(f"❌ 프로젝트 파일 저장 실패: {e}")
return False
def is_file_registered(self, file_path: str) -> bool:
"""파일이 이미 등록되어 있는지 확인"""
# Windows 스타일 경로로 변환
windows_path = file_path.replace('/', '\\')
unix_path = file_path.replace('\\', '/')
patterns = [
f'Include="{windows_path}"',
f"Include='{windows_path}'",
f'Include="{unix_path}"',
f"Include='{unix_path}'"
]
return any(pattern in self.content for pattern in patterns)
def find_last_wwwroot_entry(self) -> Optional[Tuple[int, int]]:
"""마지막 wwwroot 관련 항목의 위치 찾기"""
pattern = r'<(?:Content|None) Include="Web\\wwwroot.*?</(?:Content|None)>'
matches = list(re.finditer(pattern, self.content, re.DOTALL))
if matches:
last_match = matches[-1]
return (last_match.start(), last_match.end())
return None
def add_file_to_project(self, file_path: str, file_type: str = "None") -> bool:
"""파일을 프로젝트에 추가"""
# Windows 스타일 경로로 변환
windows_path = file_path.replace('/', '\\')
# 이미 등록된 파일인지 확인
if self.is_file_registered(windows_path):
print(f"⚠️ 파일이 이미 등록되어 있습니다: {windows_path}")
return False
# 새로운 항목 생성
new_entry = f''' <{file_type} Include="{windows_path}">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</{file_type}>'''
# 마지막 wwwroot 항목 찾기
last_entry_pos = self.find_last_wwwroot_entry()
if last_entry_pos:
start_pos, end_pos = last_entry_pos
# 마지막 항목 다음에 새 항목 삽입
self.content = (
self.content[:end_pos] +
'\n' + new_entry +
self.content[end_pos:]
)
print(f"✅ 파일이 프로젝트에 추가되었습니다: {windows_path}")
return True
else:
print("❌ wwwroot 섹션을 찾을 수 없습니다.")
return False
def add_multiple_files(self, files: List[Tuple[str, str]]) -> int:
"""여러 파일을 한 번에 추가"""
added_count = 0
for file_path, file_type in files:
if self.add_file_to_project(file_path, file_type):
added_count += 1
return added_count
def add_react_component_files(component_name: str) -> bool:
"""React 컴포넌트 관련 파일들을 프로젝트에 추가"""
updater = ProjectFileUpdater()
files_to_add = [
(f"Web\\wwwroot\\react\\{component_name}.jsx", "None"),
(f"Web\\wwwroot\\react-{component_name.lower()}.html", "None")
]
added_count = updater.add_multiple_files(files_to_add)
if added_count > 0:
updater.save_project()
print(f"🎉 {component_name} 컴포넌트 관련 {added_count}개 파일이 등록되었습니다!")
return True
else:
print("추가된 파일이 없습니다.")
return False
def add_single_file(file_path: str, file_type: str = "None") -> bool:
"""단일 파일을 프로젝트에 추가"""
updater = ProjectFileUpdater()
if updater.add_file_to_project(file_path, file_type):
updater.save_project()
return True
return False
def main():
"""CLI 메인 함수"""
if len(sys.argv) < 2:
print("사용법:")
print(" python _add_to_project.py <파일경로> [파일타입]")
print(" python _add_to_project.py --react <컴포넌트명>")
print("")
print("예시:")
print(" python _add_to_project.py 'Web\\wwwroot\\react\\MyComponent.jsx'")
print(" python _add_to_project.py --react Dashboard")
sys.exit(1)
if sys.argv[1] == "--react" and len(sys.argv) >= 3:
component_name = sys.argv[2]
add_react_component_files(component_name)
else:
file_path = sys.argv[1]
file_type = sys.argv[2] if len(sys.argv) > 2 else "None"
add_single_file(file_path, file_type)
if __name__ == "__main__":
main()

View File

@@ -1,177 +0,0 @@
// C# 프로젝트 파일 자동 업데이트 헬퍼
// 새로운 파일을 생성할 때 EETGW.csproj에 자동으로 등록하는 스크립트
const fs = require('fs');
const path = require('path');
class ProjectUpdater {
constructor(projectPath = 'EETGW.csproj') {
this.projectPath = projectPath;
this.projectContent = '';
this.loadProject();
}
loadProject() {
try {
this.projectContent = fs.readFileSync(this.projectPath, 'utf8');
} catch (error) {
console.error('프로젝트 파일을 읽을 수 없습니다:', error.message);
}
}
/**
* 새로운 파일을 프로젝트에 등록
* @param {string} filePath - Web/wwwroot로 시작하는 상대 경로
* @param {string} fileType - 'Content' 또는 'None' (기본값: 'None')
*/
addFile(filePath, fileType = 'None') {
// 파일 경로를 백슬래시로 변경 (Windows 형식)
const windowsPath = filePath.replace(/\//g, '\\');
// 이미 등록된 파일인지 확인
if (this.isFileAlreadyRegistered(windowsPath)) {
console.log(`파일이 이미 등록되어 있습니다: ${windowsPath}`);
return false;
}
// 새로운 항목 생성
const newEntry = ` <${fileType} Include="${windowsPath}">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</${fileType}>`;
// 마지막 wwwroot 관련 항목 찾기
const lastWwwrootMatch = this.findLastWwwrootEntry();
if (lastWwwrootMatch) {
// 마지막 wwwroot 항목 다음에 삽입
const insertPosition = lastWwwrootMatch.index + lastWwwrootMatch[0].length;
this.projectContent =
this.projectContent.slice(0, insertPosition) +
'\n' + newEntry +
this.projectContent.slice(insertPosition);
return true;
}
console.error('wwwroot 섹션을 찾을 수 없습니다.');
return false;
}
/**
* 파일이 이미 등록되어 있는지 확인
*/
isFileAlreadyRegistered(filePath) {
const patterns = [
`Include="${filePath}"`,
`Include='${filePath}'`,
filePath.replace(/\\/g, '/') // 슬래시 형태도 확인
];
return patterns.some(pattern => this.projectContent.includes(pattern));
}
/**
* 마지막 wwwroot 관련 항목 찾기
*/
findLastWwwrootEntry() {
const wwwrootPattern = /<(?:Content|None) Include="Web\\wwwroot.*?<\/(?:Content|None)>/gs;
let lastMatch = null;
let match;
while ((match = wwwrootPattern.exec(this.projectContent)) !== null) {
lastMatch = match;
}
return lastMatch;
}
/**
* 프로젝트 파일 저장
*/
saveProject() {
try {
fs.writeFileSync(this.projectPath, this.projectContent, 'utf8');
console.log('프로젝트 파일이 업데이트되었습니다.');
return true;
} catch (error) {
console.error('프로젝트 파일 저장 실패:', error.message);
return false;
}
}
/**
* 여러 파일을 한 번에 등록
*/
addFiles(files) {
let hasChanges = false;
files.forEach(({ path, type = 'None' }) => {
if (this.addFile(path, type)) {
hasChanges = true;
console.log(`추가됨: ${path}`);
}
});
return hasChanges;
}
/**
* React 관련 파일들 자동 등록
*/
addReactFiles(basePath) {
const reactFiles = [
{ path: `${basePath}.html`, type: 'None' },
{ path: `${basePath}.jsx`, type: 'None' }
];
return this.addFiles(reactFiles);
}
}
// 사용 예시
function addNewReactComponent(componentName) {
const updater = new ProjectUpdater();
const basePath = `Web\\wwwroot\\react\\${componentName}`;
const files = [
{ path: `${basePath}.jsx`, type: 'None' },
{ path: `Web\\wwwroot\\react-${componentName.toLowerCase()}.html`, type: 'None' }
];
if (updater.addFiles(files)) {
updater.saveProject();
console.log(`${componentName} 컴포넌트 파일들이 프로젝트에 등록되었습니다.`);
}
}
// 일반 파일 추가
function addNewFile(filePath, fileType = 'None') {
const updater = new ProjectUpdater();
if (updater.addFile(filePath, fileType)) {
updater.saveProject();
console.log(`✅ 파일이 프로젝트에 등록되었습니다: ${filePath}`);
}
}
module.exports = {
ProjectUpdater,
addNewReactComponent,
addNewFile
};
// CLI에서 직접 실행할 수 있도록
if (require.main === module) {
const args = process.argv.slice(2);
if (args.length === 0) {
console.log('사용법: node _project_updater.js <파일경로> [파일타입]');
console.log('예시: node _project_updater.js "Web\\wwwroot\\react\\NewComponent.jsx" None');
process.exit(1);
}
const filePath = args[0];
const fileType = args[1] || 'None';
addNewFile(filePath, fileType);
}

View File

@@ -241,6 +241,7 @@
// //
// menuStrip1 // menuStrip1
// //
this.menuStrip1.Enabled = false;
this.menuStrip1.Font = new System.Drawing.Font("맑은 고딕", 10F); this.menuStrip1.Font = new System.Drawing.Font("맑은 고딕", 10F);
this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.btSetting, this.btSetting,
@@ -1113,6 +1114,7 @@
// //
this.toolStrip1.AutoSize = false; this.toolStrip1.AutoSize = false;
this.toolStrip1.Dock = System.Windows.Forms.DockStyle.Right; this.toolStrip1.Dock = System.Windows.Forms.DockStyle.Right;
this.toolStrip1.Enabled = false;
this.toolStrip1.GripStyle = System.Windows.Forms.ToolStripGripStyle.Hidden; this.toolStrip1.GripStyle = System.Windows.Forms.ToolStripGripStyle.Hidden;
this.toolStrip1.ImageScalingSize = new System.Drawing.Size(24, 24); this.toolStrip1.ImageScalingSize = new System.Drawing.Size(24, 24);
this.toolStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { this.toolStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {

View File

@@ -206,10 +206,51 @@ namespace Project
Func_Login(); Func_Login();
// WebView2 로그인이 아닌 경우에만 여기서 후처리 실행
// WebView2 로그인의 경우 OnLoginCompleted()에서 호출됨
if (!(webok && Pub.InitWebView == 1))
{
OnLoginCompleted();
}
}
/// <summary>
/// 로그인 완료 후 실행되는 처리
/// WebView2 로그인의 경우 MachineBridge에서 호출됨
/// </summary>
public void OnLoginCompleted()
{
menuStrip1.Enabled = true;
toolStrip1.Enabled = true;
this.mn_purchase.Visible = FCOMMON.Util.getBit(FCOMMON.info.Login.gpermission, (int)FCOMMON.eGroupPermission.menu_purchase);
this.mn_project.Visible = FCOMMON.Util.getBit(FCOMMON.info.Login.gpermission, (int)FCOMMON.eGroupPermission.menu_project);
this.mn_dailyhistory.Visible = FCOMMON.Util.getBit(FCOMMON.info.Login.gpermission, (int)FCOMMON.eGroupPermission.menu_history);
this.mn_jago.Visible = FCOMMON.Util.getBit(FCOMMON.info.Login.gpermission, (int)FCOMMON.eGroupPermission.menu_jago);
//this.mn_eq.Visible = FCOMMON.Util.getBit(FCOMMON.info.Login.gpermission, (int)FCOMMON.eGroupPermission.menu_equipment);
this.mn_kuntae.Visible = FCOMMON.Util.getBit(FCOMMON.info.Login.gpermission, (int)FCOMMON.eGroupPermission.menu_workday);
this.mn_docu.Visible = FCOMMON.Util.getBit(FCOMMON.info.Login.gpermission, (int)FCOMMON.eGroupPermission.menu_docu);
//this.mn_logdata.Visible = FCOMMON.Util.getBit(FCOMMON.info.Login.gpermission, (int)FCOMMON.eGroupPermission.menu_logdata);
//220421
FCOMMON.info.Disable_8hourover = Pub.setting.Disable8HourOver;
if (FCOMMON.info.Login.level >= 10) btDev.Visible = true;
sbLogin.Text = string.Format("[{0}] ({1}-{2} T:{3}) - ({5}){4}",
FCOMMON.info.Login.title,
FCOMMON.info.Login.no,
FCOMMON.info.Login.nameK,
FCOMMON.info.Login.tel,
FCOMMON.info.Login.dept,
FCOMMON.info.Login.gcode);
FCOMMON.Pub.log.Add("Program Start");
sbLoginUseTime.Text = "접속시간:" + FCOMMON.info.Login.loginusetime.ToString("N1") + "ms";
// Start chat service after login // Start chat service after login
StartChatService(); StartChatService();
///즐겨찾기 목록 갱신 // 즐겨찾기 목록 갱신
Update_FavoriteSite(); Update_FavoriteSite();
UpdateControls(); UpdateControls();
@@ -247,9 +288,10 @@ namespace Project
this.sbWeb.Text = $"Host:{(webok ? "O" : "X")},WebView:{Pub.InitWebView},Server:{Pub.WebServiceURL}"; this.sbWeb.Text = $"Host:{(webok ? "O" : "X")},WebView:{Pub.InitWebView},Server:{Pub.WebServiceURL}";
if (webok && Pub.InitWebView == 1) if (webok && Pub.InitWebView == 1)
{ {
using (var f = new Dialog.fLogin_WB()) //using (var f = new Dialog.fLogin_WB())
if (f.ShowDialog() != System.Windows.Forms.DialogResult.OK) // if (f.ShowDialog() != System.Windows.Forms.DialogResult.OK)
Application.ExitThread(); // Application.ExitThread();
Menu_Dashboard();
} }
else else
{ {
@@ -259,32 +301,7 @@ namespace Project
} }
this.mn_purchase.Visible = FCOMMON.Util.getBit(FCOMMON.info.Login.gpermission, (int)FCOMMON.eGroupPermission.menu_purchase);
this.mn_project.Visible = FCOMMON.Util.getBit(FCOMMON.info.Login.gpermission, (int)FCOMMON.eGroupPermission.menu_project);
this.mn_dailyhistory.Visible = FCOMMON.Util.getBit(FCOMMON.info.Login.gpermission, (int)FCOMMON.eGroupPermission.menu_history);
this.mn_jago.Visible = FCOMMON.Util.getBit(FCOMMON.info.Login.gpermission, (int)FCOMMON.eGroupPermission.menu_jago);
//this.mn_eq.Visible = FCOMMON.Util.getBit(FCOMMON.info.Login.gpermission, (int)FCOMMON.eGroupPermission.menu_equipment);
this.mn_kuntae.Visible = FCOMMON.Util.getBit(FCOMMON.info.Login.gpermission, (int)FCOMMON.eGroupPermission.menu_workday);
this.mn_docu.Visible = FCOMMON.Util.getBit(FCOMMON.info.Login.gpermission, (int)FCOMMON.eGroupPermission.menu_docu);
//this.mn_logdata.Visible = FCOMMON.Util.getBit(FCOMMON.info.Login.gpermission, (int)FCOMMON.eGroupPermission.menu_logdata);
//220421
FCOMMON.info.Disable_8hourover = Pub.setting.Disable8HourOver;
if (FCOMMON.info.Login.level >= 10) btDev.Visible = true;
sbLogin.Text = string.Format("[{0}] ({1}-{2} T:{3}) - ({5}){4}",
FCOMMON.info.Login.title,
FCOMMON.info.Login.no,
FCOMMON.info.Login.nameK,
FCOMMON.info.Login.tel,
FCOMMON.info.Login.dept,
FCOMMON.info.Login.gcode);
FCOMMON.Pub.log.Add("Program Start");
sbLoginUseTime.Text = "접속시간:" + FCOMMON.info.Login.loginusetime.ToString("N1") + "ms";
Func_RunStartForm();
} }
void Func_RunStartForm() void Func_RunStartForm()
{ {
@@ -296,7 +313,6 @@ namespace Project
var menu_kuntaeVisible = FCOMMON.Util.getBit(FCOMMON.info.Login.gpermission, (int)FCOMMON.eGroupPermission.menu_workday); var menu_kuntaeVisible = FCOMMON.Util.getBit(FCOMMON.info.Login.gpermission, (int)FCOMMON.eGroupPermission.menu_workday);
var menu_logdata = FCOMMON.Util.getBit(FCOMMON.info.Login.gpermission, (int)FCOMMON.eGroupPermission.menu_logdata); var menu_logdata = FCOMMON.Util.getBit(FCOMMON.info.Login.gpermission, (int)FCOMMON.eGroupPermission.menu_logdata);
this.Menu_Dashboard();
} }
void Menu_WorkTable() void Menu_WorkTable()
{ {
@@ -1501,14 +1517,14 @@ namespace Project
} }
Dialog.fDashboard fdashboard = null; Dialog.fDashboardNew fdashboard = null;
void Menu_Dashboard() void Menu_Dashboard()
{ {
string formkey = "DASHBOARD"; string formkey = "DASHBOARD";
if (!ShowForm(formkey)) if (!ShowForm(formkey))
{ {
if (fdashboard == null || fdashboard.IsDisposed) if (fdashboard == null || fdashboard.IsDisposed)
fdashboard = new Dialog.fDashboard(); fdashboard = new Dialog.fDashboardNew();
AddForm(formkey, fdashboard); AddForm(formkey, fdashboard);
} }
} }

View File

@@ -452,16 +452,16 @@
<data name="toolStripButton3.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> <data name="toolStripButton3.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value> <value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAIHSURBVDhPY2CAgZnGrAxLtTMZluosYFim08KwWFcdLL5K YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAIGSURBVDhPlY9PaNNgGMbfi7iD+Oei4t3NphkeJogwETxI
i4dhmVYhwzLteQzLdFoZlqlLwfWggKU6qxmW6fyH48U6PxiWaHoyLNO8jiK+VOsJwyotCTTNWjYgSYG1 E8GDKGyCXqZidUO7r+06EONhhS75vuq8DbQKWyJUBb30ItK4g1odztlpTRgM2hwmiCiDoWj6yheWugTm
lv/9Tub8T7lc/z/3ZMX/eXNiP9S05/9LvdzwP/Jc2X/VbV5QQ7T7UA1YpjOBf435seQr9d+Kb/X8L7nZ nwceSJ73+T0kAL7Gu9aBHo2DLt4BQxyBic4OLy8KG8AQEmBEC2CIWTA6drSYgHTxHhgitjwhfoPJiARG
8//QzJz/jzrT/l+dlPkTJAbDJrtDTzAs0bmGasBSXcHiW911MEUdh+r/Xy+N+L+/1f7v9UKvzz3bCuEG pBbIdcGBorA9BAvd/Lj5wT48UunH01UFByoZLNw8+eVy9mLzTPUq9r5O486SvDISzQcHDPH6pvt7n/XN
FN/qugBSj2oAAwND8c3uuTBFlZc6lp0vDDl4Odnt28Ugtf/bKpz2Fl/s+AiSK7jZ8w5dLxjAXFB0s6cE KcvEppi0KE6N92N99Cy+uxH/zjPfex4ffwGT4vvggN65hdjaFb+Um1KwlurBcvaAW0vIS7SUaA0QW33D
xH/RGiH+uMzz/5UMrT9Psm3UCm/26hff6vpcdKvnPLpeMCi+02NcfKPrOIz/uNjT81Gpx38wLvHyAKu5 +8EBACCWdssvDb/NGTOJY0+rfYeWZ4+2Yylz8AmZzX3lt0sW/RxmPflfMGjRJH9fzPZsa6QlnDsn/HQu
2V1afKu7BUUjMii+0y0GYz+p8MqCGfCk0jsTJFZ/v54j99ZEPhRNuMCjCs++J+Ve/0H4cblXL7o8QfC4 dLcnLLab2OrSoE1nwqwnMk+7yAf1uf/eIJJUT8XQc1KOeR1LSxFbGwmAq0Xmta3+s5ORz/sDzvDhOM+U
zHMj3AulHhvQ5fGC//vrOV50RZ1+0xP3H4RfdkaeAomhq0MB/w70a/490Nv3/0DvmfsHen//P9D7HxlD BaVtwB7bGIDWUj0j5Z0hGbkbQzIL3/+qRlp61PqFVOxh+P5HYVlpW1RPvPpETyH3x9HelzwL9wJqmtci
xc6A1ezv0UBo3DaR/e+B3vmne+r/oWvChUFq/x7omQfSy/B3f28vugJiMUgvw78DfbZ/D/Qs/3+gdxUp rsnyaLLpBZP9QJPhaq9k016nTHf9Bktj612T3a6oSjMMrWXedU1a4Cy4ZcbChX81Z6Fp5ve7Jr2LJiv+
GKQHpBcAOYOqX5UHGvcAAAAASUVORK5CYII= jznD2V8tj6pV862slAAAAABJRU5ErkJggg==
</value> </value>
</data> </data>
<metadata name="$this.TrayHeight" type="System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> <metadata name="$this.TrayHeight" type="System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">