근태입력화면 관련 작업

This commit is contained in:
ChiKyun Kim
2025-07-16 11:05:55 +09:00
parent 2b322542f1
commit 2806fb76ba
8 changed files with 1261 additions and 2 deletions

46
Project/Dialog/fHolyday.Designer.cs generated Normal file
View 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
}
}

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

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 @@
</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>

View File

@@ -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>

View 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; } // 데이터를기록한일시
}
}

View 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">&nbsp;</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">&nbsp;</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">&nbsp;</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">&nbsp;</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>

View File

@@ -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()