근태입력화면 관련 작업
This commit is contained in:
46
Project/Dialog/fHolyday.Designer.cs
generated
Normal file
46
Project/Dialog/fHolyday.Designer.cs
generated
Normal file
@@ -0,0 +1,46 @@
|
||||
namespace Project.Dialog
|
||||
{
|
||||
partial class fHolyday
|
||||
{
|
||||
/// <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.SuspendLayout();
|
||||
//
|
||||
// fHolyday
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 12F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.ClientSize = new System.Drawing.Size(1063, 567);
|
||||
this.Name = "fHolyday";
|
||||
this.Text = "근태입력";
|
||||
this.ResumeLayout(false);
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
86
Project/Dialog/fHolyday.cs
Normal file
86
Project/Dialog/fHolyday.cs
Normal file
@@ -0,0 +1,86 @@
|
||||
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 fHolyday : fBase
|
||||
{
|
||||
private WebView2 webView21;
|
||||
public fHolyday()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
|
||||
InitializeWebView2();
|
||||
|
||||
|
||||
}
|
||||
private void InitializeWebView2()
|
||||
{
|
||||
// 수동으로 WebView2 컨트롤 생성
|
||||
this.webView21 = new WebView2();
|
||||
|
||||
// 기본 속성 설정
|
||||
this.webView21.CreationProperties = null;
|
||||
this.webView21.DefaultBackgroundColor = Color.White;
|
||||
this.webView21.Dock = DockStyle.Fill;
|
||||
this.webView21.Location = new Point(0, 0);
|
||||
this.webView21.Name = "webView21";
|
||||
this.webView21.Size = new Size(800, 600);
|
||||
this.webView21.TabIndex = 0;
|
||||
this.webView21.ZoomFactor = 1D;
|
||||
|
||||
// 폼에 추가
|
||||
this.Controls.Add(this.webView21);
|
||||
|
||||
// 비동기 초기화
|
||||
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.webView21.EnsureCoreWebView2Async(env);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 시스템에 설치된 WebView2 사용
|
||||
await this.webView21.EnsureCoreWebView2Async();
|
||||
}
|
||||
// OWIN 서버의 DashBoard 페이지로 연결
|
||||
webView21.Source = new Uri($"{Pub.setting.WebServiceURL}/kuntae");
|
||||
}
|
||||
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)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
120
Project/Dialog/fHolyday.resx
Normal file
120
Project/Dialog/fHolyday.resx
Normal 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>
|
||||
@@ -259,6 +259,12 @@
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Dialog\fHolyday.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Dialog\fHolyday.Designer.cs">
|
||||
<DependentUpon>fHolyday.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Dialog\fJobReport.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
@@ -280,6 +286,7 @@
|
||||
<Compile Include="Web\Controller\BaseController.cs" />
|
||||
<Compile Include="Web\Controller\APIController.cs" />
|
||||
<Compile Include="Web\Controller\DashBoardController.cs" />
|
||||
<Compile Include="Web\Controller\KuntaeController.cs" />
|
||||
<Compile Include="Web\Controller\ManualController.cs" />
|
||||
<Compile Include="Web\Controller\ProjectController.cs" />
|
||||
<Compile Include="Web\Controller\JobreportController.cs" />
|
||||
@@ -474,6 +481,9 @@
|
||||
<EmbeddedResource Include="Dev\fDisableItem.resx">
|
||||
<DependentUpon>fDisableItem.cs</DependentUpon>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="Dialog\fHolyday.resx">
|
||||
<DependentUpon>fHolyday.cs</DependentUpon>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="Dialog\fJobReport.resx">
|
||||
<DependentUpon>fJobReport.cs</DependentUpon>
|
||||
</EmbeddedResource>
|
||||
@@ -656,6 +666,9 @@
|
||||
<Content Include="Web\wwwroot\Jobreport\index.html">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Web\wwwroot\Kuntae\index.html">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Web\wwwroot\login.html">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<PublishUrlHistory>ftp://10.131.36.205:2121/Install/GroupWare/|ftp://10.131.36.205:2121/Install/|ftp://10.131.36.205/Install/|게시\</PublishUrlHistory>
|
||||
|
||||
293
Project/Web/Controller/KuntaeController.cs
Normal file
293
Project/Web/Controller/KuntaeController.cs
Normal file
@@ -0,0 +1,293 @@
|
||||
using FCM0000;
|
||||
using Microsoft.Owin;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Web;
|
||||
using System.Web.Http;
|
||||
|
||||
namespace Project.Web.Controllers
|
||||
{
|
||||
public class KuntaeController : BaseController
|
||||
{
|
||||
|
||||
[HttpGet]
|
||||
public HttpResponseMessage GetList(string sd = null, string ed = null)
|
||||
{
|
||||
var sql = string.Empty;
|
||||
sql = "select idx,gcode,uid,dbo.getUserName(uid) as uname,cate,sdate,edate,term,termdr,drtime,DrTimePMS,crtime,title,contents, tag, extcate,extidx, wuid,wdate" +
|
||||
" from Holyday" +
|
||||
" where gcode = @gcode" +
|
||||
" and uid = @uid" +
|
||||
" and sdate between @sd and @ed" +
|
||||
" order by wdate desc";
|
||||
|
||||
|
||||
var cs = Properties.Settings.Default.gwcs;// "Data Source=K4FASQL.kr.ds.amkor.com,50150;Initial Catalog=EE;Persist Security Info=True;User ID=eeadm;Password=uJnU8a8q&DJ+ug-D!";
|
||||
var cn = new System.Data.SqlClient.SqlConnection(cs);
|
||||
var cmd = new System.Data.SqlClient.SqlCommand(sql, cn);
|
||||
cmd.Parameters.AddWithValue("gcode", FCOMMON.info.Login.gcode);
|
||||
cmd.Parameters.AddWithValue("uid", FCOMMON.info.Login.no);
|
||||
|
||||
// 날짜 파라미터가 없으면 기본값 사용 (현재 월)
|
||||
var startDate = !string.IsNullOrEmpty(sd) ? sd : DateTime.Now.AddDays(-7).ToString("yyyy-MM-dd");
|
||||
var endDate = !string.IsNullOrEmpty(ed) ? ed : DateTime.Now.ToString("yyyy-MM-dd");
|
||||
|
||||
cmd.Parameters.AddWithValue("sd", startDate);
|
||||
cmd.Parameters.AddWithValue("ed", endDate);
|
||||
var da = new System.Data.SqlClient.SqlDataAdapter(cmd);
|
||||
var dt = new System.Data.DataTable();
|
||||
da.Fill(dt);
|
||||
da.Dispose();
|
||||
cmd.Dispose();
|
||||
cn.Dispose();
|
||||
|
||||
var txtjson = JsonConvert.SerializeObject(dt, new JsonSerializerSettings
|
||||
{
|
||||
NullValueHandling = NullValueHandling.Ignore
|
||||
});
|
||||
|
||||
var resp = new HttpResponseMessage()
|
||||
{
|
||||
Content = new StringContent(
|
||||
txtjson,
|
||||
System.Text.Encoding.UTF8,
|
||||
"application/json")
|
||||
};
|
||||
|
||||
return resp;
|
||||
}
|
||||
|
||||
|
||||
[HttpGet]
|
||||
public HttpResponseMessage Index()
|
||||
{
|
||||
// 직접 파일을 읽어서 반환
|
||||
var filePath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Web", "wwwroot", "kuntae", "index.html");
|
||||
var contents = string.Empty;
|
||||
|
||||
if (System.IO.File.Exists(filePath))
|
||||
{
|
||||
contents = System.IO.File.ReadAllText(filePath, System.Text.Encoding.UTF8);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 파일이 없으면 404 에러 페이지 또는 기본 메시지
|
||||
contents = "<html><body><h1>404 - File Not Found</h1><p>The requested file was not found: " + filePath + "</p></body></html>";
|
||||
}
|
||||
|
||||
|
||||
var resp = new HttpResponseMessage()
|
||||
{
|
||||
Content = new StringContent(
|
||||
contents,
|
||||
System.Text.Encoding.UTF8,
|
||||
"text/html")
|
||||
};
|
||||
|
||||
return resp;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public HttpResponseMessage Insert([FromBody] KuntaeModel model)
|
||||
{
|
||||
try
|
||||
{
|
||||
var sql = @"INSERT INTO Holyday (gcode, uid, cate, sdate, edate, term, termdr, drtime, DrTimePMS, crtime, title, contents, tag, extcate, extidx, wuid, wdate)
|
||||
VALUES (@gcode, @uid, @cate, @sdate, @edate, @term, @termdr, @drtime, @DrTimePMS, @crtime, @title, @contents, @tag, @extcate, @extidx, @wuid, @wdate)";
|
||||
|
||||
var cs = Properties.Settings.Default.gwcs;
|
||||
var cn = new System.Data.SqlClient.SqlConnection(cs);
|
||||
var cmd = new System.Data.SqlClient.SqlCommand(sql, cn);
|
||||
|
||||
cmd.Parameters.AddWithValue("gcode", FCOMMON.info.Login.gcode);
|
||||
cmd.Parameters.AddWithValue("uid", FCOMMON.info.Login.no);
|
||||
cmd.Parameters.AddWithValue("cate", (object)model.cate ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("sdate", model.sdate);
|
||||
cmd.Parameters.AddWithValue("edate", (object)model.edate ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("term", (object)model.term ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("termdr", (object)model.termdr ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("drtime", (object)model.drtime ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("DrTimePMS", (object)model.DrTimePMS ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("crtime", (object)model.crtime ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("title", (object)model.title ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("contents", (object)model.contents ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("tag", (object)model.tag ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("extcate", (object)model.extcate ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("extidx", (object)model.extidx ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("wuid", FCOMMON.info.Login.no);
|
||||
cmd.Parameters.AddWithValue("wdate", DateTime.Now);
|
||||
|
||||
cn.Open();
|
||||
var result = cmd.ExecuteNonQuery();
|
||||
cn.Close();
|
||||
cmd.Dispose();
|
||||
cn.Dispose();
|
||||
|
||||
var response = new { success = true, message = "근태가 추가되었습니다." };
|
||||
var json = JsonConvert.SerializeObject(response);
|
||||
|
||||
return new HttpResponseMessage()
|
||||
{
|
||||
Content = new StringContent(json, System.Text.Encoding.UTF8, "application/json")
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var response = new { success = false, message = "근태 추가 중 오류가 발생했습니다: " + ex.Message };
|
||||
var json = JsonConvert.SerializeObject(response);
|
||||
|
||||
return new HttpResponseMessage()
|
||||
{
|
||||
Content = new StringContent(json, System.Text.Encoding.UTF8, "application/json")
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPut]
|
||||
public HttpResponseMessage Update([FromBody] KuntaeModel model)
|
||||
{
|
||||
try
|
||||
{
|
||||
var sql = @"UPDATE Holyday SET cate = @cate, sdate = @sdate, edate = @edate, term = @term, termdr = @termdr,
|
||||
drtime = @drtime, DrTimePMS = @DrTimePMS, crtime = @crtime, title = @title, contents = @contents,
|
||||
tag = @tag, extcate = @extcate, extidx = @extidx
|
||||
WHERE gcode = @gcode AND uid = @uid AND idx = @idx";
|
||||
|
||||
var cs = Properties.Settings.Default.gwcs;
|
||||
var cn = new System.Data.SqlClient.SqlConnection(cs);
|
||||
var cmd = new System.Data.SqlClient.SqlCommand(sql, cn);
|
||||
|
||||
cmd.Parameters.AddWithValue("gcode", FCOMMON.info.Login.gcode);
|
||||
cmd.Parameters.AddWithValue("uid", FCOMMON.info.Login.no);
|
||||
cmd.Parameters.AddWithValue("cate", (object)model.cate ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("sdate", model.sdate);
|
||||
cmd.Parameters.AddWithValue("edate", (object)model.edate ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("term", (object)model.term ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("termdr", (object)model.termdr ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("drtime", (object)model.drtime ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("DrTimePMS", (object)model.DrTimePMS ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("crtime", (object)model.crtime ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("title", (object)model.title ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("contents", (object)model.contents ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("tag", (object)model.tag ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("extcate", (object)model.extcate ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("extidx", (object)model.extidx ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("idx", model.idx);
|
||||
|
||||
cn.Open();
|
||||
var result = cmd.ExecuteNonQuery();
|
||||
cn.Close();
|
||||
cmd.Dispose();
|
||||
cn.Dispose();
|
||||
|
||||
var response = new { success = true, message = "근태가 수정되었습니다." };
|
||||
var json = JsonConvert.SerializeObject(response);
|
||||
|
||||
return new HttpResponseMessage()
|
||||
{
|
||||
Content = new StringContent(json, System.Text.Encoding.UTF8, "application/json")
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var response = new { success = false, message = "근태 수정 중 오류가 발생했습니다: " + ex.Message };
|
||||
var json = JsonConvert.SerializeObject(response);
|
||||
|
||||
return new HttpResponseMessage()
|
||||
{
|
||||
Content = new StringContent(json, System.Text.Encoding.UTF8, "application/json")
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[HttpDelete]
|
||||
public HttpResponseMessage Delete(string id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var sql = "DELETE FROM Holyday WHERE gcode = @gcode AND uid = @uid AND idx = @idx";
|
||||
|
||||
var cs = Properties.Settings.Default.gwcs;
|
||||
var cn = new System.Data.SqlClient.SqlConnection(cs);
|
||||
var cmd = new System.Data.SqlClient.SqlCommand(sql, cn);
|
||||
|
||||
cmd.Parameters.AddWithValue("gcode", FCOMMON.info.Login.gcode);
|
||||
cmd.Parameters.AddWithValue("uid", FCOMMON.info.Login.no);
|
||||
cmd.Parameters.AddWithValue("idx", id);
|
||||
|
||||
cn.Open();
|
||||
var result = cmd.ExecuteNonQuery();
|
||||
cn.Close();
|
||||
cmd.Dispose();
|
||||
cn.Dispose();
|
||||
|
||||
var response = new { success = true, message = "근태가 삭제되었습니다." };
|
||||
var json = JsonConvert.SerializeObject(response);
|
||||
|
||||
return new HttpResponseMessage()
|
||||
{
|
||||
Content = new StringContent(json, System.Text.Encoding.UTF8, "application/json")
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var response = new { success = false, message = "근태 삭제 중 오류가 발생했습니다: " + ex.Message };
|
||||
var json = JsonConvert.SerializeObject(response);
|
||||
|
||||
return new HttpResponseMessage()
|
||||
{
|
||||
Content = new StringContent(json, System.Text.Encoding.UTF8, "application/json")
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class KuntaeModel
|
||||
{
|
||||
/*
|
||||
idx : 데이터고유번호
|
||||
gcode : 그룹코드(데이터 그룹간 식별)
|
||||
uid : 사원번호
|
||||
cate : 근태구분
|
||||
sdate : 시작일
|
||||
edate : 종료일
|
||||
term : 사용일
|
||||
termdr : 발생일
|
||||
drtime : 발생시간,
|
||||
crtime : 사용시간
|
||||
DrTimePMS : PMS등록시간
|
||||
title : 제목
|
||||
contents : 내용
|
||||
tag : 입력방식특이사항(clipboard=클립보드에서붙여넣었다)
|
||||
extcate : 외부에서생성된 경우 외부 출처
|
||||
extidx : 외부출처인경우 데이터고유번호
|
||||
wuid : 데이터기록자 사원번호
|
||||
wdate : 데이터를기록한일시
|
||||
*/
|
||||
|
||||
public int idx { get; set; } // 데이터고유번호
|
||||
public string gcode { get; set; } // 그룹코드(데이터 그룹간 식별)
|
||||
public string uid { get; set; } // 사원번호
|
||||
public string uname { get; set; } // 성명
|
||||
public string cate { get; set; } // 근태구분
|
||||
public string sdate { get; set; } // 시작일
|
||||
public string edate { get; set; } // 종료일
|
||||
public string term { get; set; } // 사용일
|
||||
public string termdr { get; set; } // 발생일
|
||||
public string drtime { get; set; } // 발생시간
|
||||
public string DrTimePMS { get; set; } // PMS등록시간
|
||||
public string crtime { get; set; } // 사용시간
|
||||
public string title { get; set; } // 제목
|
||||
public string contents { get; set; } // 내용
|
||||
public string tag { get; set; } // 입력방식특이사항
|
||||
public string extcate { get; set; } // 외부에서생성된 경우 외부 출처
|
||||
public string extidx { get; set; } // 외부출처인경우 데이터고유번호
|
||||
public string wuid { get; set; } // 데이터기록자 사원번호
|
||||
public string wdate { get; set; } // 데이터를기록한일시
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
692
Project/Web/wwwroot/Kuntae/index.html
Normal file
692
Project/Web/wwwroot/Kuntae/index.html
Normal file
@@ -0,0 +1,692 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
||||
<meta http-equiv="Pragma" content="no-cache">
|
||||
<meta http-equiv="Expires" content="0">
|
||||
<title>근태입력 조회</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
primary: '#3B82F6',
|
||||
secondary: '#6B7280',
|
||||
success: '#10B981',
|
||||
danger: '#EF4444',
|
||||
warning: '#F59E0B'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.loading {
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 3px solid #f3f3f3;
|
||||
border-top: 3px solid #3B82F6;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.table-container {
|
||||
max-height: 600px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.table-container::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.table-container::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.table-container::-webkit-scrollbar-thumb {
|
||||
background: #c1c1c1;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.table-container::-webkit-scrollbar-thumb:hover {
|
||||
background: #a8a8a8;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-gray-50 min-h-screen">
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
|
||||
<!-- 검색 및 필터 섹션 -->
|
||||
<div class="bg-white rounded-lg shadow-md p-6 mb-6">
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label for="startDate" class="block text-sm font-medium text-gray-700 mb-2">시작일</label>
|
||||
<input type="date" id="startDate" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent">
|
||||
</div>
|
||||
<div>
|
||||
<label for="endDate" class="block text-sm font-medium text-gray-700 mb-2">종료일</label>
|
||||
<input type="date" id="endDate" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent">
|
||||
</div>
|
||||
<div class="flex items-end">
|
||||
<button id="searchBtn" class="w-full bg-primary text-white px-4 py-2 rounded-md hover:bg-blue-600 transition-colors duration-200 flex items-center justify-center">
|
||||
<span id="searchBtnText">조회</span>
|
||||
<div id="searchBtnLoading" class="loading ml-2 hidden"></div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 통계 카드 -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-6">
|
||||
<div class="bg-white rounded-lg shadow-md p-6">
|
||||
<div class="flex items-center">
|
||||
<div class="p-2 bg-blue-100 rounded-lg">
|
||||
<svg class="w-6 h-6 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<p class="text-sm font-medium text-gray-600">휴가사용</p>
|
||||
<p id="totalDays" class="text-2xl font-bold text-gray-900">0</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-white rounded-lg shadow-md p-6">
|
||||
<div class="flex items-center">
|
||||
<div class="p-2 bg-green-100 rounded-lg">
|
||||
<svg class="w-6 h-6 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<p class="text-sm font-medium text-gray-600">대체사용</p>
|
||||
<p id="normalDays" class="text-2xl font-bold text-gray-900">0</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-white rounded-lg shadow-md p-6">
|
||||
<div class="flex items-center">
|
||||
<div class="p-2 bg-yellow-100 rounded-lg">
|
||||
<svg class="w-6 h-6 text-yellow-600" 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>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<p class="text-sm font-medium text-gray-600">잔량(년차)</p>
|
||||
<p id="lateDays" class="text-2xl font-bold text-gray-900">0</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-white rounded-lg shadow-md p-6">
|
||||
<div class="flex items-center">
|
||||
<div class="p-2 bg-red-100 rounded-lg">
|
||||
<svg class="w-6 h-6 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<p class="text-sm font-medium text-gray-600">잔량(대체)</p>
|
||||
<p id="absentDays" class="text-2xl font-bold text-gray-900">0</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 데이터 테이블 -->
|
||||
<div class="bg-white rounded-lg shadow-md">
|
||||
<div class="px-6 py-4 border-b border-gray-200 flex justify-between items-center">
|
||||
<h3 class="text-lg font-medium text-gray-900">근태 상세 내역</h3>
|
||||
<button id="addBtn" class="bg-primary text-white px-4 py-2 rounded-md hover:bg-blue-600 transition-colors duration-200 flex items-center">
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
|
||||
</svg>
|
||||
근태 추가
|
||||
</button>
|
||||
</div>
|
||||
<div class="table-container">
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50 sticky top-0">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">구분</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">시작일</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">종료일</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">사번</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">성명</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">사용(일)</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">사용(H)</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">발생(일)</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">발생(H)</th>
|
||||
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">내용</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">#</th>
|
||||
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">잔량(일)</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">잔량(H)</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">전일(일)</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">전일(H)</th>
|
||||
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">소스</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">등록자</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">등록일</th>
|
||||
|
||||
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="dataTableBody" class="bg-white divide-y divide-gray-200">
|
||||
<tr id="loadingRow" class="hidden">
|
||||
<td colspan="7" class="px-6 py-4 text-center">
|
||||
<div class="flex items-center justify-center">
|
||||
<div class="loading mr-2"></div>
|
||||
<span class="text-gray-500">데이터를 불러오는 중...</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr id="noDataRow" class="hidden">
|
||||
<td colspan="7" class="px-6 py-4 text-center text-gray-500">
|
||||
조회된 데이터가 없습니다.
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 근태 추가/편집 모달 -->
|
||||
<div id="kuntaeModal" class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full hidden z-50">
|
||||
<div class="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white">
|
||||
<div class="mt-3">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 id="modalTitle" class="text-lg font-medium text-gray-900">근태 추가</h3>
|
||||
<button id="closeModal" class="text-gray-400 hover:text-gray-600">
|
||||
<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="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form id="kuntaeForm">
|
||||
<input type="hidden" id="editId" name="id">
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="modalDate" class="block text-sm font-medium text-gray-700 mb-2">날짜</label>
|
||||
<input type="date" id="modalDate" name="pdate" required
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent">
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4 mb-4">
|
||||
<div>
|
||||
<label for="modalInTime" class="block text-sm font-medium text-gray-700 mb-2">출근시간</label>
|
||||
<input type="time" id="modalInTime" name="intime"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent">
|
||||
</div>
|
||||
<div>
|
||||
<label for="modalOutTime" class="block text-sm font-medium text-gray-700 mb-2">퇴근시간</label>
|
||||
<input type="time" id="modalOutTime" name="outtime"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="modalMemo" class="block text-sm font-medium text-gray-700 mb-2">비고</label>
|
||||
<textarea id="modalMemo" name="memo" rows="3"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent"
|
||||
placeholder="비고사항을 입력하세요"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end space-x-3">
|
||||
<button type="button" id="cancelBtn"
|
||||
class="px-4 py-2 text-gray-700 bg-gray-200 rounded-md hover:bg-gray-300 transition-colors duration-200">
|
||||
취소
|
||||
</button>
|
||||
<button type="submit" id="saveBtn"
|
||||
class="px-4 py-2 bg-primary text-white rounded-md hover:bg-blue-600 transition-colors duration-200">
|
||||
저장
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 삭제 확인 모달 -->
|
||||
<div id="deleteModal" class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full hidden z-50">
|
||||
<div class="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white">
|
||||
<div class="mt-3">
|
||||
<div class="flex items-center mb-4">
|
||||
<div class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-red-100">
|
||||
<svg class="h-6 w-6 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-2">근태 삭제</h3>
|
||||
<p class="text-sm text-gray-500 mb-4">선택한 근태 데이터를 삭제하시겠습니까?</p>
|
||||
<p id="deleteConfirmText" class="text-sm text-gray-700 mb-6"></p>
|
||||
<div class="flex justify-center space-x-3">
|
||||
<button id="cancelDeleteBtn"
|
||||
class="px-4 py-2 text-gray-700 bg-gray-200 rounded-md hover:bg-gray-300 transition-colors duration-200">
|
||||
취소
|
||||
</button>
|
||||
<button id="confirmDeleteBtn"
|
||||
class="px-4 py-2 bg-red-600 text-white rounded-md hover:bg-red-700 transition-colors duration-200">
|
||||
삭제
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 전역 변수
|
||||
let currentData = [];
|
||||
let currentEditId = null;
|
||||
|
||||
// 페이지 로드 시 초기화
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
initializeDates();
|
||||
loadData();
|
||||
setupEventListeners();
|
||||
setupModalEvents();
|
||||
});
|
||||
|
||||
// 날짜 초기화 (현재 월)
|
||||
function initializeDates() {
|
||||
const now = new Date();
|
||||
const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
|
||||
const endOfMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0);
|
||||
|
||||
document.getElementById('startDate').value = startOfMonth.toISOString().split('T')[0];
|
||||
document.getElementById('endDate').value = endOfMonth.toISOString().split('T')[0];
|
||||
}
|
||||
|
||||
// 이벤트 리스너 설정
|
||||
function setupEventListeners() {
|
||||
document.getElementById('searchBtn').addEventListener('click', loadData);
|
||||
document.getElementById('addBtn').addEventListener('click', showAddModal);
|
||||
|
||||
// Enter 키로 검색
|
||||
document.getElementById('startDate').addEventListener('keypress', function(e) {
|
||||
if (e.key === 'Enter') loadData();
|
||||
});
|
||||
document.getElementById('endDate').addEventListener('keypress', function(e) {
|
||||
if (e.key === 'Enter') loadData();
|
||||
});
|
||||
}
|
||||
|
||||
// 모달 이벤트 설정
|
||||
function setupModalEvents() {
|
||||
// 근태 모달 이벤트
|
||||
document.getElementById('closeModal').addEventListener('click', hideKuntaeModal);
|
||||
document.getElementById('cancelBtn').addEventListener('click', hideKuntaeModal);
|
||||
document.getElementById('kuntaeForm').addEventListener('submit', saveKuntae);
|
||||
|
||||
// 삭제 모달 이벤트
|
||||
document.getElementById('cancelDeleteBtn').addEventListener('click', hideDeleteModal);
|
||||
document.getElementById('confirmDeleteBtn').addEventListener('click', confirmDelete);
|
||||
|
||||
// 모달 외부 클릭 시 닫기
|
||||
document.getElementById('kuntaeModal').addEventListener('click', function(e) {
|
||||
if (e.target === this) hideKuntaeModal();
|
||||
});
|
||||
document.getElementById('deleteModal').addEventListener('click', function(e) {
|
||||
if (e.target === this) hideDeleteModal();
|
||||
});
|
||||
}
|
||||
|
||||
// 데이터 로드
|
||||
async function loadData() {
|
||||
const startDate = document.getElementById('startDate').value;
|
||||
const endDate = document.getElementById('endDate').value;
|
||||
|
||||
if (!startDate || !endDate) {
|
||||
alert('시작일과 종료일을 모두 입력해주세요.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (new Date(startDate) > new Date(endDate)) {
|
||||
alert('시작일은 종료일보다 늦을 수 없습니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
showLoading(true);
|
||||
|
||||
try {
|
||||
const response = await axios.get(`/Kuntae/GetList?sd=${startDate}&ed=${endDate}`);
|
||||
|
||||
if (response.data) {
|
||||
currentData = response.data;
|
||||
renderTable();
|
||||
updateStatistics();
|
||||
} else {
|
||||
currentData = [];
|
||||
renderTable();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('데이터 로드 중 오류 발생:', error);
|
||||
alert('데이터를 불러오는 중 오류가 발생했습니다.');
|
||||
currentData = [];
|
||||
renderTable();
|
||||
} finally {
|
||||
showLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
// 로딩 상태 표시
|
||||
function showLoading(show) {
|
||||
const searchBtn = document.getElementById('searchBtn');
|
||||
const searchBtnText = document.getElementById('searchBtnText');
|
||||
const searchBtnLoading = document.getElementById('searchBtnLoading');
|
||||
const loadingRow = document.getElementById('loadingRow');
|
||||
|
||||
if (show) {
|
||||
searchBtn.disabled = true;
|
||||
searchBtnText.textContent = '조회 중...';
|
||||
searchBtnLoading.classList.remove('hidden');
|
||||
loadingRow.classList.remove('hidden');
|
||||
} else {
|
||||
searchBtn.disabled = false;
|
||||
searchBtnText.textContent = '조회';
|
||||
searchBtnLoading.classList.add('hidden');
|
||||
loadingRow.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
// 테이블 렌더링
|
||||
function renderTable() {
|
||||
const tbody = document.getElementById('dataTableBody');
|
||||
const noDataRow = document.getElementById('noDataRow');
|
||||
|
||||
// 기존 데이터 행 제거 (로딩, 노데이터 행 제외)
|
||||
const existingRows = tbody.querySelectorAll('tr:not(#loadingRow):not(#noDataRow)');
|
||||
existingRows.forEach(row => row.remove());
|
||||
|
||||
if (currentData.length === 0) {
|
||||
noDataRow.classList.remove('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
noDataRow.classList.add('hidden');
|
||||
|
||||
currentData.forEach(item => {
|
||||
const row = document.createElement('tr');
|
||||
row.className = 'hover:bg-gray-50 cursor-pointer';
|
||||
row.setAttribute('data-id', item.idx);
|
||||
|
||||
const startDate = item.sdate ? new Date(item.sdate) : null;
|
||||
const endDate = item.edate ? new Date(item.edate) : null;
|
||||
|
||||
row.innerHTML = `
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">${item.cate || '-'}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">${startDate ? formatDate(startDate) : '-'}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">${endDate ? formatDate(endDate) : '-'}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">${item.uid || '-'}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">${item.uname || '-'}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">${item.term || '-'}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">${item.termdr || '-'}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">${item.drtime || '-'}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">${item.crtime || '-'}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 max-w-xs truncate" title="${item.contents || ''}">${item.contents || '-'}</td>
|
||||
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">${item.tag || '-'}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900"> </td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900"> </td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900"> </td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900"> </td>
|
||||
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">${item.extcate || '-'}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">${item.wuid || '-'}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">${item.wdate || '-'}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||
<div class="flex space-x-2">
|
||||
<button class="text-blue-600 hover:text-blue-800 edit-btn" data-id="${item.idx}">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="text-red-600 hover:text-red-800 delete-btn" data-id="${item.idx}">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
`;
|
||||
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
|
||||
// 행 클릭 이벤트 추가
|
||||
tbody.querySelectorAll('tr[data-id]').forEach(row => {
|
||||
row.addEventListener('click', function(e) {
|
||||
if (!e.target.closest('.edit-btn') && !e.target.closest('.delete-btn')) {
|
||||
const id = this.getAttribute('data-id');
|
||||
showEditModal(id);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 편집 버튼 이벤트
|
||||
tbody.querySelectorAll('.edit-btn').forEach(btn => {
|
||||
btn.addEventListener('click', function(e) {
|
||||
e.stopPropagation();
|
||||
const id = this.getAttribute('data-id');
|
||||
showEditModal(id);
|
||||
});
|
||||
});
|
||||
|
||||
// 삭제 버튼 이벤트
|
||||
tbody.querySelectorAll('.delete-btn').forEach(btn => {
|
||||
btn.addEventListener('click', function(e) {
|
||||
e.stopPropagation();
|
||||
const id = this.getAttribute('data-id');
|
||||
showDeleteModal(id);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 날짜 포맷팅
|
||||
function formatDate(date) {
|
||||
return date.toLocaleDateString('ko-KR', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit'
|
||||
});
|
||||
}
|
||||
|
||||
// 근무시간 계산
|
||||
function calculateWorkHours(item) {
|
||||
if (!item.intime || !item.outtime) return '-';
|
||||
|
||||
try {
|
||||
const inTime = new Date(`2000-01-01 ${item.intime}`);
|
||||
const outTime = new Date(`2000-01-01 ${item.outtime}`);
|
||||
|
||||
if (outTime < inTime) {
|
||||
outTime.setDate(outTime.getDate() + 1);
|
||||
}
|
||||
|
||||
const diffMs = outTime - inTime;
|
||||
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
|
||||
const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
|
||||
|
||||
return `${diffHours}시간 ${diffMinutes}분`;
|
||||
} catch (error) {
|
||||
return '-';
|
||||
}
|
||||
}
|
||||
|
||||
// 상태 판단
|
||||
function getStatus(item) {
|
||||
if (!item.intime) return '결근';
|
||||
if (!item.outtime) return '출근';
|
||||
|
||||
// 지각 판단 (예: 9시 이후 출근)
|
||||
const inTime = new Date(`2000-01-01 ${item.intime}`);
|
||||
const lateThreshold = new Date(`2000-01-01 09:00:00`);
|
||||
|
||||
if (inTime > lateThreshold) return '지각';
|
||||
return '정상';
|
||||
}
|
||||
|
||||
// 상태별 CSS 클래스
|
||||
function getStatusClass(status) {
|
||||
switch (status) {
|
||||
case '정상': return 'bg-green-100 text-green-800';
|
||||
case '지각': return 'bg-yellow-100 text-yellow-800';
|
||||
case '결근': return 'bg-red-100 text-red-800';
|
||||
case '출근': return 'bg-blue-100 text-blue-800';
|
||||
default: return 'bg-gray-100 text-gray-800';
|
||||
}
|
||||
}
|
||||
|
||||
// 통계 업데이트
|
||||
function updateStatistics() {
|
||||
const totalDays = currentData.length;
|
||||
const normalDays = currentData.filter(item => getStatus(item) === '정상').length;
|
||||
const lateDays = currentData.filter(item => getStatus(item) === '지각').length;
|
||||
const absentDays = currentData.filter(item => getStatus(item) === '결근').length;
|
||||
|
||||
document.getElementById('totalDays').textContent = totalDays;
|
||||
document.getElementById('normalDays').textContent = normalDays;
|
||||
document.getElementById('lateDays').textContent = lateDays;
|
||||
document.getElementById('absentDays').textContent = absentDays;
|
||||
}
|
||||
|
||||
// 근태 추가 모달 표시
|
||||
function showAddModal() {
|
||||
currentEditId = null;
|
||||
document.getElementById('modalTitle').textContent = '근태 추가';
|
||||
document.getElementById('editId').value = '';
|
||||
document.getElementById('modalDate').value = new Date().toISOString().split('T')[0];
|
||||
document.getElementById('modalInTime').value = '';
|
||||
document.getElementById('modalOutTime').value = '';
|
||||
document.getElementById('modalMemo').value = '';
|
||||
document.getElementById('kuntaeModal').classList.remove('hidden');
|
||||
}
|
||||
|
||||
// 근태 편집 모달 표시
|
||||
function showEditModal(id) {
|
||||
const item = currentData.find(data => (data.id || data.wdate) == id);
|
||||
if (!item) {
|
||||
alert('데이터를 찾을 수 없습니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
currentEditId = id;
|
||||
document.getElementById('modalTitle').textContent = '근태 편집';
|
||||
document.getElementById('editId').value = id;
|
||||
document.getElementById('modalDate').value = item.pdate ? new Date(item.pdate).toISOString().split('T')[0] : '';
|
||||
document.getElementById('modalInTime').value = item.intime || '';
|
||||
document.getElementById('modalOutTime').value = item.outtime || '';
|
||||
document.getElementById('modalMemo').value = item.memo || '';
|
||||
document.getElementById('kuntaeModal').classList.remove('hidden');
|
||||
}
|
||||
|
||||
// 근태 모달 숨기기
|
||||
function hideKuntaeModal() {
|
||||
document.getElementById('kuntaeModal').classList.add('hidden');
|
||||
currentEditId = null;
|
||||
}
|
||||
|
||||
// 근태 저장
|
||||
async function saveKuntae(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const formData = new FormData(e.target);
|
||||
const data = {
|
||||
id: formData.get('id'),
|
||||
pdate: formData.get('pdate'),
|
||||
intime: formData.get('intime'),
|
||||
outtime: formData.get('outtime'),
|
||||
memo: formData.get('memo')
|
||||
};
|
||||
|
||||
if (!data.pdate) {
|
||||
alert('날짜를 입력해주세요.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const url = currentEditId ? '/Kuntae/Update' : '/Kuntae/Insert';
|
||||
const method = currentEditId ? 'PUT' : 'POST';
|
||||
|
||||
const response = await axios({
|
||||
method: method,
|
||||
url: url,
|
||||
data: data,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
if (response.data && response.data.success) {
|
||||
alert(currentEditId ? '근태가 수정되었습니다.' : '근태가 추가되었습니다.');
|
||||
hideKuntaeModal();
|
||||
loadData(); // 목록 새로고침
|
||||
} else {
|
||||
alert(response.data?.message || '저장 중 오류가 발생했습니다.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('저장 중 오류 발생:', error);
|
||||
alert('저장 중 오류가 발생했습니다.');
|
||||
}
|
||||
}
|
||||
|
||||
// 삭제 모달 표시
|
||||
function showDeleteModal(id) {
|
||||
const item = currentData.find(data => (data.id || data.wdate) == id);
|
||||
if (!item) {
|
||||
alert('데이터를 찾을 수 없습니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
const date = new Date(item.pdate);
|
||||
document.getElementById('deleteConfirmText').textContent =
|
||||
`${formatDate(date)} 근태 데이터를 삭제하시겠습니까?`;
|
||||
|
||||
document.getElementById('confirmDeleteBtn').setAttribute('data-id', id);
|
||||
document.getElementById('deleteModal').classList.remove('hidden');
|
||||
}
|
||||
|
||||
// 삭제 모달 숨기기
|
||||
function hideDeleteModal() {
|
||||
document.getElementById('deleteModal').classList.add('hidden');
|
||||
}
|
||||
|
||||
// 삭제 확인
|
||||
async function confirmDelete() {
|
||||
const id = document.getElementById('confirmDeleteBtn').getAttribute('data-id');
|
||||
|
||||
try {
|
||||
const response = await axios.delete(`/Kuntae/Delete/${id}`);
|
||||
|
||||
if (response.data && response.data.success) {
|
||||
alert('근태가 삭제되었습니다.');
|
||||
hideDeleteModal();
|
||||
loadData(); // 목록 새로고침
|
||||
} else {
|
||||
alert(response.data?.message || '삭제 중 오류가 발생했습니다.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('삭제 중 오류 발생:', error);
|
||||
alert('삭제 중 오류가 발생했습니다.');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -274,7 +274,16 @@ namespace Project
|
||||
{
|
||||
string formkey = "HOLY";
|
||||
if (!ShowForm(formkey))
|
||||
AddForm(formkey, new FBS0000.fHolyday());
|
||||
{
|
||||
Form f;
|
||||
if (webok && Pub.InitWebView == 1 && System.Diagnostics.Debugger.IsAttached)
|
||||
f = new FBS0000.fHolyday();
|
||||
else
|
||||
f = new FBS0000.fHolyday();
|
||||
|
||||
AddForm(formkey, f);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void _SetLang()
|
||||
|
||||
Reference in New Issue
Block a user