..
This commit is contained in:
46
Project/Dialog/fJobReport.Designer.cs
generated
Normal file
46
Project/Dialog/fJobReport.Designer.cs
generated
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
namespace Project.Dialog
|
||||||
|
{
|
||||||
|
partial class fJobReport
|
||||||
|
{
|
||||||
|
/// <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();
|
||||||
|
//
|
||||||
|
// fJobReport
|
||||||
|
//
|
||||||
|
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 = "fJobReport";
|
||||||
|
this.Text = "업무일지";
|
||||||
|
this.ResumeLayout(false);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
86
Project/Dialog/fJobReport.cs
Normal file
86
Project/Dialog/fJobReport.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 fJobReport : fBase
|
||||||
|
{
|
||||||
|
private WebView2 webView21;
|
||||||
|
public fJobReport()
|
||||||
|
{
|
||||||
|
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}/Jobreport");
|
||||||
|
}
|
||||||
|
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/fJobReport.resx
Normal file
120
Project/Dialog/fJobReport.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>
|
</Reference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Compile Include="Dialog\fJobReport.cs">
|
||||||
|
<SubType>Form</SubType>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="Dialog\fJobReport.Designer.cs">
|
||||||
|
<DependentUpon>fJobReport.cs</DependentUpon>
|
||||||
|
</Compile>
|
||||||
<Compile Include="Dialog\fLogin_WB.cs">
|
<Compile Include="Dialog\fLogin_WB.cs">
|
||||||
<SubType>Form</SubType>
|
<SubType>Form</SubType>
|
||||||
</Compile>
|
</Compile>
|
||||||
@@ -469,6 +475,9 @@
|
|||||||
<EmbeddedResource Include="Dev\fDisableItem.resx">
|
<EmbeddedResource Include="Dev\fDisableItem.resx">
|
||||||
<DependentUpon>fDisableItem.cs</DependentUpon>
|
<DependentUpon>fDisableItem.cs</DependentUpon>
|
||||||
</EmbeddedResource>
|
</EmbeddedResource>
|
||||||
|
<EmbeddedResource Include="Dialog\fJobReport.resx">
|
||||||
|
<DependentUpon>fJobReport.cs</DependentUpon>
|
||||||
|
</EmbeddedResource>
|
||||||
<EmbeddedResource Include="Dialog\fDashboard.resx">
|
<EmbeddedResource Include="Dialog\fDashboard.resx">
|
||||||
<DependentUpon>fDashboard.cs</DependentUpon>
|
<DependentUpon>fDashboard.cs</DependentUpon>
|
||||||
</EmbeddedResource>
|
</EmbeddedResource>
|
||||||
@@ -645,6 +654,9 @@
|
|||||||
<Content Include="Web\wwwroot\DashBoard\index.html">
|
<Content Include="Web\wwwroot\DashBoard\index.html">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
|
<Content Include="Web\wwwroot\Jobreport\index.html">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
<Content Include="Web\wwwroot\login.html">
|
<Content Include="Web\wwwroot\login.html">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ namespace Project.Web.Controllers
|
|||||||
{
|
{
|
||||||
|
|
||||||
var sql = "select count(*) from EETGW_HolydayRequest " +
|
var sql = "select count(*) from EETGW_HolydayRequest " +
|
||||||
" where gcode = @gcode and isnull(conf,0) = 1 "+
|
" where gcode = @gcode and isnull(conf,0) = 1 " +
|
||||||
" and sdate <= convert(varchar(10),GETDATE(),120) and edate >= convert(varchar(10),GETDATE(),120)";
|
" and sdate <= convert(varchar(10),GETDATE(),120) and edate >= convert(varchar(10),GETDATE(),120)";
|
||||||
|
|
||||||
var cn = DBM.getCn();
|
var cn = DBM.getCn();
|
||||||
@@ -84,7 +84,7 @@ namespace Project.Web.Controllers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public HttpResponseMessage GetholyRequestUser()
|
public HttpResponseMessage GetholyRequestUser()
|
||||||
{
|
{
|
||||||
@@ -93,8 +93,8 @@ namespace Project.Web.Controllers
|
|||||||
$" from EETGW_HolydayRequest INNER JOIN " +
|
$" from EETGW_HolydayRequest INNER JOIN " +
|
||||||
$" Users ON EETGW_HolydayRequest.uid = Users.id " +
|
$" Users ON EETGW_HolydayRequest.uid = Users.id " +
|
||||||
$" where EETGW_HolydayRequest.gcode = @gcode" +
|
$" where EETGW_HolydayRequest.gcode = @gcode" +
|
||||||
$" and isnull(conf,0) = 0 ";
|
$" and isnull(conf,0) = 0 ";
|
||||||
//" and sdate <= convert(varchar(10),GETDATE(),120) and edate >= convert(varchar(10),GETDATE(),120)";
|
//" and sdate <= convert(varchar(10),GETDATE(),120) and edate >= convert(varchar(10),GETDATE(),120)";
|
||||||
|
|
||||||
//sql = sql.Replace("{gcode}", FCOMMON.info.Login.gcode);
|
//sql = sql.Replace("{gcode}", FCOMMON.info.Login.gcode);
|
||||||
|
|
||||||
@@ -125,6 +125,60 @@ namespace Project.Web.Controllers
|
|||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public HttpResponseMessage GetJobData(string startDate = "", string endDate = "")
|
||||||
|
{
|
||||||
|
var sql = string.Empty;
|
||||||
|
|
||||||
|
// 기본값 설정 (이번 달)
|
||||||
|
if (string.IsNullOrEmpty(startDate) || string.IsNullOrEmpty(endDate))
|
||||||
|
{
|
||||||
|
var now = DateTime.Now;
|
||||||
|
var firstDayOfMonth = new DateTime(now.Year, now.Month, 1);
|
||||||
|
var lastDayOfMonth = firstDayOfMonth.AddMonths(1).AddDays(-1);
|
||||||
|
startDate = firstDayOfMonth.ToString("yyyy-MM-dd");
|
||||||
|
endDate = lastDayOfMonth.ToString("yyyy-MM-dd");
|
||||||
|
}
|
||||||
|
|
||||||
|
sql = $" select idx,pdate,status,projectName, uid, requestpart, package,type,process,description," +
|
||||||
|
" hrs,ot,otStart,otEnd" +
|
||||||
|
" from JobReport" +
|
||||||
|
" where gcode = @gcode and uid = @uid" +
|
||||||
|
" and pdate between @startDate and @endDate" +
|
||||||
|
" order by pdate desc, wdate desc";
|
||||||
|
|
||||||
|
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("startDate", startDate);
|
||||||
|
cmd.Parameters.AddWithValue("endDate", 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]
|
[HttpGet]
|
||||||
public HttpResponseMessage GetCurrentUserCount()
|
public HttpResponseMessage GetCurrentUserCount()
|
||||||
|
|||||||
@@ -288,93 +288,32 @@ namespace Project.Web.Controllers
|
|||||||
[HttpGet]
|
[HttpGet]
|
||||||
public HttpResponseMessage Index()
|
public HttpResponseMessage Index()
|
||||||
{
|
{
|
||||||
//로그인이 되어있지않다면 로그인을 가져온다
|
// 직접 파일을 읽어서 반환
|
||||||
MethodResult result;
|
var filePath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Web", "wwwroot", "Jobreport", "index.html");
|
||||||
result = View();
|
var contents = string.Empty;
|
||||||
|
|
||||||
|
if (System.IO.File.Exists(filePath))
|
||||||
var gets = Request.GetQueryNameValuePairs();// GetParameters(data);
|
|
||||||
|
|
||||||
|
|
||||||
var key_search = gets.Where(t => t.Key == "search").FirstOrDefault();
|
|
||||||
var model = GetGlobalModel();
|
|
||||||
var getParams = Request.GetQueryNameValuePairs();// GetParameters(data);
|
|
||||||
|
|
||||||
//기본값을 찾아서 없애줘야한다
|
|
||||||
var searchkey = string.Empty;
|
|
||||||
if (key_search.Key != null && key_search.Value.isEmpty() == false) searchkey = key_search.Value.Trim();
|
|
||||||
|
|
||||||
var tbody = new System.Text.StringBuilder();
|
|
||||||
|
|
||||||
//테이블데이터생성
|
|
||||||
var itemcnt = 0;
|
|
||||||
//if (searchkey.isEmpty() == false)
|
|
||||||
{
|
{
|
||||||
var db = new dsMSSQLTableAdapters.vJobReportForUserTableAdapter();// EEEntitiesJobreport();
|
contents = System.IO.File.ReadAllText(filePath, System.Text.Encoding.UTF8);
|
||||||
var sd = DateTime.Now.ToShortDateString();
|
|
||||||
var ed = DateTime.Now.ToShortDateString();
|
|
||||||
var rows = db.GetByDate(FCOMMON.info.Login.gcode, FCOMMON.info.Login.no, sd, ed);
|
|
||||||
//.vJobReportForUser.AsNoTracking().Where(t => t.gcode == FCOMMON.info.Login.gcode && t.id == FCOMMON.info.Login.no && t.pdate.CompareTo(sd) >= 0 && t.pdate.CompareTo(ed) <= 1).OrderByDescending(t => t.pdate);
|
|
||||||
itemcnt = rows.Count();
|
|
||||||
foreach (var item in rows)
|
|
||||||
{
|
|
||||||
tbody.AppendLine("<tr>");
|
|
||||||
|
|
||||||
tbody.AppendLine($"<th scope='row'>{item.pdate.Substring(5)}</th>");
|
|
||||||
tbody.AppendLine($"<td>{item.ww}</td>");
|
|
||||||
tbody.AppendLine($"<td>{item.name}</td>");
|
|
||||||
|
|
||||||
if (item.status == "진행 중" || item.status.EndsWith("%"))
|
|
||||||
tbody.AppendLine($"<td class='table-info text-center'>{item.status}</td>");
|
|
||||||
else
|
|
||||||
tbody.AppendLine($"<td class='text-center'>{item.status}</td>");
|
|
||||||
|
|
||||||
tbody.AppendLine($"<td>{item.type}</td>");
|
|
||||||
tbody.AppendLine($"<td><a href='/jobreport/edit/{item.idx}'>{item.projectName}</a></td>");
|
|
||||||
tbody.AppendLine($"<td>{item.hrs}</td>");
|
|
||||||
tbody.AppendLine($"<td>{item.ot}</td>");
|
|
||||||
|
|
||||||
tbody.AppendLine("<td><span class='d-inline-block text-truncate' style='max-width: 150px;'>");
|
|
||||||
tbody.AppendLine(item.description);
|
|
||||||
tbody.AppendLine("</span></td>");
|
|
||||||
|
|
||||||
tbody.AppendLine("</tr>");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
|
||||||
|
|
||||||
//아잍쳄이 없는경우
|
|
||||||
if (itemcnt == 0)
|
|
||||||
{
|
{
|
||||||
tbody.AppendLine("<tr>");
|
// 파일이 없으면 404 에러 페이지 또는 기본 메시지
|
||||||
tbody.AppendLine("<th scope='row'>1</th>");
|
contents = "<html><body><h1>404 - File Not Found</h1><p>The requested file was not found: " + filePath + "</p></body></html>";
|
||||||
tbody.AppendLine("<td colspan='6'>자료가 없습니다</td>");
|
|
||||||
tbody.AppendLine("</tr>");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var contents = result.Content.Replace("{search}", searchkey);
|
|
||||||
contents = contents.Replace("{tabledata}", tbody.ToString());
|
|
||||||
contents = contents.Replace("{cnt}", itemcnt.ToString());
|
|
||||||
|
|
||||||
|
|
||||||
//공용값 적용
|
//공용값 적용
|
||||||
ApplyCommonValue(ref contents);
|
ApplyCommonValue(ref contents);
|
||||||
|
|
||||||
//최종문자 적용
|
|
||||||
result.Content = contents;
|
|
||||||
|
|
||||||
var resp = new HttpResponseMessage()
|
var resp = new HttpResponseMessage()
|
||||||
{
|
{
|
||||||
Content = new StringContent(
|
Content = new StringContent(
|
||||||
result.Content,
|
contents,
|
||||||
System.Text.Encoding.UTF8,
|
System.Text.Encoding.UTF8,
|
||||||
"text/html")
|
"text/html")
|
||||||
};
|
};
|
||||||
|
|
||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
863
Project/Web/wwwroot/Jobreport/index.html
Normal file
863
Project/Web/wwwroot/Jobreport/index.html
Normal file
@@ -0,0 +1,863 @@
|
|||||||
|
<!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/chart.js"></script>
|
||||||
|
<script src="https://unpkg.com/feather-icons"></script>
|
||||||
|
<script>
|
||||||
|
tailwind.config = {
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
primary: '#3B82F6',
|
||||||
|
secondary: '#6B7280',
|
||||||
|
success: '#10B981',
|
||||||
|
danger: '#EF4444',
|
||||||
|
warning: '#F59E0B'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body class="bg-gray-50 min-h-screen">
|
||||||
|
<!-- 헤더 -->
|
||||||
|
<header class="bg-white shadow-sm border-b">
|
||||||
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div class="flex justify-between items-center py-4">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<i data-feather="file-text" class="w-8 h-8 text-primary mr-3"></i>
|
||||||
|
<h1 class="text-2xl font-bold text-gray-900">업무일지</h1>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center space-x-4">
|
||||||
|
<button id="refreshBtn" class="bg-primary hover:bg-blue-700 text-white px-4 py-2 rounded-lg flex items-center">
|
||||||
|
<i data-feather="refresh-cw" class="w-4 h-4 mr-2"></i>
|
||||||
|
새로고침
|
||||||
|
</button>
|
||||||
|
<div class="text-sm text-gray-600">
|
||||||
|
<span id="currentDate"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<!-- 메인 컨텐츠 -->
|
||||||
|
<main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||||
|
<!-- 통계 카드 -->
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-8">
|
||||||
|
<div class="bg-white rounded-lg shadow p-6">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="p-2 bg-blue-100 rounded-lg">
|
||||||
|
<i data-feather="calendar" class="w-6 h-6 text-primary"></i>
|
||||||
|
</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 p-6">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="p-2 bg-green-100 rounded-lg">
|
||||||
|
<i data-feather="clock" class="w-6 h-6 text-success"></i>
|
||||||
|
</div>
|
||||||
|
<div class="ml-4">
|
||||||
|
<p class="text-sm font-medium text-gray-600">총 근무시간</p>
|
||||||
|
<p id="totalHours" class="text-2xl font-bold text-gray-900">0h</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bg-white rounded-lg shadow p-6">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="p-2 bg-orange-100 rounded-lg">
|
||||||
|
<i data-feather="zap" class="w-6 h-6 text-warning"></i>
|
||||||
|
</div>
|
||||||
|
<div class="ml-4">
|
||||||
|
<p class="text-sm font-medium text-gray-600">총 초과근무</p>
|
||||||
|
<p id="totalOT" class="text-2xl font-bold text-gray-900">0h</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bg-white rounded-lg shadow p-6">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="p-2 bg-purple-100 rounded-lg">
|
||||||
|
<i data-feather="folder" class="w-6 h-6 text-purple-600"></i>
|
||||||
|
</div>
|
||||||
|
<div class="ml-4">
|
||||||
|
<p class="text-sm font-medium text-gray-600">진행중 프로젝트</p>
|
||||||
|
<p id="activeProjects" class="text-2xl font-bold text-gray-900">0</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 필터 및 검색 -->
|
||||||
|
<div class="bg-white rounded-lg shadow mb-6">
|
||||||
|
<div class="p-6 border-b border-gray-200">
|
||||||
|
<div class="flex flex-col md:flex-row md:items-center md:justify-between space-y-4 md:space-y-0">
|
||||||
|
<div class="flex flex-col sm:flex-row space-y-2 sm:space-y-0 sm:space-x-4">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">조회기간</label>
|
||||||
|
<div class="flex space-x-2">
|
||||||
|
<input type="date" id="startDate" class="border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent text-sm">
|
||||||
|
<span class="flex items-center text-gray-500">~</span>
|
||||||
|
<input type="date" id="endDate" class="border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent text-sm">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">상태</label>
|
||||||
|
<select id="statusFilter" class="border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent">
|
||||||
|
<option value="">전체</option>
|
||||||
|
<option value="진행중">진행중</option>
|
||||||
|
<option value="완료">완료</option>
|
||||||
|
<option value="대기">대기</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">프로젝트</label>
|
||||||
|
<select id="projectFilter" class="border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent">
|
||||||
|
<option value="">전체</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">검색</label>
|
||||||
|
<input type="text" id="searchInput" placeholder="업무 내용 검색..." class="border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex space-x-2">
|
||||||
|
<button id="clearFilterBtn" class="bg-gray-500 hover:bg-gray-600 text-white px-4 py-2 rounded-md flex items-center">
|
||||||
|
<i data-feather="x" class="w-4 h-4 mr-2"></i>
|
||||||
|
필터 초기화
|
||||||
|
</button>
|
||||||
|
<button id="exportBtn" class="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded-md flex items-center">
|
||||||
|
<i data-feather="download" class="w-4 h-4 mr-2"></i>
|
||||||
|
엑셀 다운로드
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 데이터 테이블 -->
|
||||||
|
<div class="bg-white rounded-lg shadow overflow-hidden">
|
||||||
|
<div class="overflow-x-auto">
|
||||||
|
<table class="min-w-full divide-y divide-gray-200">
|
||||||
|
<thead class="bg-gray-50">
|
||||||
|
<tr>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100" data-sort="pdate">
|
||||||
|
날짜 <i data-feather="chevron-down" class="w-4 h-4 inline ml-1"></i>
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100" data-sort="status">
|
||||||
|
상태 <i data-feather="chevron-down" class="w-4 h-4 inline ml-1"></i>
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100" data-sort="projectName">
|
||||||
|
프로젝트명 <i data-feather="chevron-down" class="w-4 h-4 inline ml-1"></i>
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100" data-sort="requestpart">
|
||||||
|
요청부서 <i data-feather="chevron-down" class="w-4 h-4 inline ml-1"></i>
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100" data-sort="package">
|
||||||
|
패키지 <i data-feather="chevron-down" class="w-4 h-4 inline ml-1"></i>
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100" data-sort="type">
|
||||||
|
타입 <i data-feather="chevron-down" class="w-4 h-4 inline ml-1"></i>
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100" data-sort="process">
|
||||||
|
프로세스 <i data-feather="chevron-down" class="w-4 h-4 inline ml-1"></i>
|
||||||
|
</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 cursor-pointer hover:bg-gray-100" data-sort="hrs">
|
||||||
|
근무시간 <i data-feather="chevron-down" class="w-4 h-4 inline ml-1"></i>
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100" data-sort="ot">
|
||||||
|
초과근무 <i data-feather="chevron-down" class="w-4 h-4 inline ml-1"></i>
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">초과근무 시간</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="jobTableBody" class="bg-white divide-y divide-gray-200">
|
||||||
|
<!-- 데이터가 여기에 동적으로 로드됩니다 -->
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!-- 로딩 상태 -->
|
||||||
|
<div id="loadingState" class="hidden p-8 text-center">
|
||||||
|
<div class="inline-flex items-center">
|
||||||
|
<svg class="animate-spin -ml-1 mr-3 h-5 w-5 text-primary" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||||
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||||
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||||
|
</svg>
|
||||||
|
<span class="text-gray-600">데이터를 불러오는 중...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 빈 상태 -->
|
||||||
|
<div id="emptyState" class="hidden p-8 text-center">
|
||||||
|
<i data-feather="inbox" class="w-12 h-12 text-gray-400 mx-auto mb-4"></i>
|
||||||
|
<p class="text-gray-500">업무일지 데이터가 없습니다.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!-- 페이지네이션 -->
|
||||||
|
<div id="pagination" class="mt-6 flex items-center justify-between">
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<span class="text-sm text-gray-700">페이지당 행 수:</span>
|
||||||
|
<select id="pageSize" class="border border-gray-300 rounded-md px-2 py-1 text-sm">
|
||||||
|
<option value="10">10</option>
|
||||||
|
<option value="25" selected>25</option>
|
||||||
|
<option value="50">50</option>
|
||||||
|
<option value="100">100</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<button id="prevPage" class="px-3 py-1 border border-gray-300 rounded-md text-sm disabled:opacity-50 disabled:cursor-not-allowed">
|
||||||
|
이전
|
||||||
|
</button>
|
||||||
|
<span id="pageInfo" class="text-sm text-gray-700">1 / 1</span>
|
||||||
|
<button id="nextPage" class="px-3 py-1 border border-gray-300 rounded-md text-sm disabled:opacity-50 disabled:cursor-not-allowed">
|
||||||
|
다음
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<!-- 상세 모달 -->
|
||||||
|
<div id="detailModal" 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-11/12 md:w-3/4 lg:w-1/2 shadow-lg rounded-md bg-white">
|
||||||
|
<div class="mt-3">
|
||||||
|
<div class="flex items-center justify-between mb-4">
|
||||||
|
<h3 class="text-lg font-medium text-gray-900">업무 상세정보</h3>
|
||||||
|
<button id="closeModal" class="text-gray-400 hover:text-gray-600">
|
||||||
|
<i data-feather="x" class="w-6 h-6"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div id="modalContent" class="space-y-4">
|
||||||
|
<!-- 모달 내용이 여기에 동적으로 로드됩니다 -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// 전역 변수
|
||||||
|
let jobData = [];
|
||||||
|
let filteredData = [];
|
||||||
|
let currentPage = 1;
|
||||||
|
let pageSize = 25;
|
||||||
|
let sortColumn = 'pdate';
|
||||||
|
let sortDirection = 'desc';
|
||||||
|
|
||||||
|
// 초기화
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
initializeApp();
|
||||||
|
loadJobData();
|
||||||
|
});
|
||||||
|
|
||||||
|
function initializeApp() {
|
||||||
|
// 현재 날짜 표시
|
||||||
|
const now = new Date();
|
||||||
|
document.getElementById('currentDate').textContent = now.toLocaleDateString('ko-KR', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric',
|
||||||
|
weekday: 'long'
|
||||||
|
});
|
||||||
|
|
||||||
|
// 조회기간 기본값 설정 (이번 달)
|
||||||
|
const currentMonth = new Date(now.getFullYear(), now.getMonth(), 1);
|
||||||
|
const lastDayOfMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0);
|
||||||
|
|
||||||
|
document.getElementById('startDate').value = currentMonth.toISOString().split('T')[0];
|
||||||
|
document.getElementById('endDate').value = lastDayOfMonth.toISOString().split('T')[0];
|
||||||
|
|
||||||
|
// 이벤트 리스너 등록
|
||||||
|
document.getElementById('refreshBtn').addEventListener('click', loadJobData);
|
||||||
|
document.getElementById('startDate').addEventListener('change', loadJobData);
|
||||||
|
document.getElementById('endDate').addEventListener('change', loadJobData);
|
||||||
|
document.getElementById('statusFilter').addEventListener('change', filterData);
|
||||||
|
document.getElementById('projectFilter').addEventListener('change', filterData);
|
||||||
|
document.getElementById('searchInput').addEventListener('input', filterData);
|
||||||
|
document.getElementById('clearFilterBtn').addEventListener('click', clearFilters);
|
||||||
|
document.getElementById('pageSize').addEventListener('change', function() {
|
||||||
|
pageSize = parseInt(this.value);
|
||||||
|
currentPage = 1;
|
||||||
|
renderTable();
|
||||||
|
});
|
||||||
|
document.getElementById('prevPage').addEventListener('click', function() {
|
||||||
|
if (currentPage > 1) {
|
||||||
|
currentPage--;
|
||||||
|
renderTable();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
document.getElementById('nextPage').addEventListener('click', function() {
|
||||||
|
const maxPage = Math.ceil(filteredData.length / pageSize);
|
||||||
|
if (currentPage < maxPage) {
|
||||||
|
currentPage++;
|
||||||
|
renderTable();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
document.getElementById('exportBtn').addEventListener('click', exportToExcel);
|
||||||
|
document.getElementById('closeModal').addEventListener('click', closeModal);
|
||||||
|
|
||||||
|
// 정렬 이벤트 리스너
|
||||||
|
document.querySelectorAll('[data-sort]').forEach(th => {
|
||||||
|
th.addEventListener('click', function() {
|
||||||
|
const column = this.getAttribute('data-sort');
|
||||||
|
if (sortColumn === column) {
|
||||||
|
sortDirection = sortDirection === 'asc' ? 'desc' : 'asc';
|
||||||
|
} else {
|
||||||
|
sortColumn = column;
|
||||||
|
sortDirection = 'asc';
|
||||||
|
}
|
||||||
|
sortData();
|
||||||
|
renderTable();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 모달 외부 클릭 시 닫기
|
||||||
|
document.getElementById('detailModal').addEventListener('click', function(e) {
|
||||||
|
if (e.target === this) {
|
||||||
|
closeModal();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Feather 아이콘 초기화
|
||||||
|
feather.replace();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadJobData() {
|
||||||
|
showLoading(true);
|
||||||
|
try {
|
||||||
|
// 조회기간 파라미터 가져오기
|
||||||
|
const startDate = document.getElementById('startDate').value;
|
||||||
|
const endDate = document.getElementById('endDate').value;
|
||||||
|
|
||||||
|
// API URL 구성
|
||||||
|
let url = '/DashBoard/GetJobData';
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
if (startDate) params.append('startDate', startDate);
|
||||||
|
if (endDate) params.append('endDate', endDate);
|
||||||
|
|
||||||
|
if (params.toString()) {
|
||||||
|
url += '?' + params.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(url);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('데이터를 불러오는데 실패했습니다.');
|
||||||
|
}
|
||||||
|
jobData = await response.json();
|
||||||
|
filteredData = [...jobData];
|
||||||
|
|
||||||
|
updateStatistics();
|
||||||
|
updateProjectFilter();
|
||||||
|
sortData();
|
||||||
|
renderTable();
|
||||||
|
// 확장된 행들 닫기
|
||||||
|
closeAllExpandedRows();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading job data:', error);
|
||||||
|
showError('데이터를 불러오는데 실패했습니다: ' + error.message);
|
||||||
|
} finally {
|
||||||
|
showLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateStatistics() {
|
||||||
|
const totalDays = new Set(jobData.map(item => item.pdate)).size;
|
||||||
|
const totalHours = jobData.reduce((sum, item) => sum + (parseFloat(item.hrs) || 0), 0);
|
||||||
|
const totalOT = jobData.reduce((sum, item) => sum + (parseFloat(item.ot) || 0), 0);
|
||||||
|
const activeProjects = new Set(jobData.filter(item => item.status === '진행중').map(item => item.projectName)).size;
|
||||||
|
|
||||||
|
document.getElementById('totalDays').textContent = totalDays;
|
||||||
|
document.getElementById('totalHours').textContent = totalHours.toFixed(1) + 'h';
|
||||||
|
document.getElementById('totalOT').textContent = totalOT.toFixed(1) + 'h';
|
||||||
|
document.getElementById('activeProjects').textContent = activeProjects;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateProjectFilter() {
|
||||||
|
const projectSelect = document.getElementById('projectFilter');
|
||||||
|
const projects = [...new Set(jobData.map(item => item.projectName).filter(Boolean))];
|
||||||
|
|
||||||
|
// 기존 옵션 제거 (전체 옵션 제외)
|
||||||
|
while (projectSelect.children.length > 1) {
|
||||||
|
projectSelect.removeChild(projectSelect.lastChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 새 옵션 추가
|
||||||
|
projects.forEach(project => {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = project;
|
||||||
|
option.textContent = project;
|
||||||
|
projectSelect.appendChild(option);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterData() {
|
||||||
|
const statusFilter = document.getElementById('statusFilter').value;
|
||||||
|
const projectFilter = document.getElementById('projectFilter').value;
|
||||||
|
const searchTerm = document.getElementById('searchInput').value.toLowerCase();
|
||||||
|
|
||||||
|
filteredData = jobData.filter(item => {
|
||||||
|
const statusMatch = !statusFilter || item.status === statusFilter;
|
||||||
|
const projectMatch = !projectFilter || item.projectName === projectFilter;
|
||||||
|
const searchMatch = !searchTerm ||
|
||||||
|
(item.description && item.description.toLowerCase().includes(searchTerm)) ||
|
||||||
|
(item.projectName && item.projectName.toLowerCase().includes(searchTerm)) ||
|
||||||
|
(item.requestpart && item.requestpart.toLowerCase().includes(searchTerm));
|
||||||
|
|
||||||
|
return statusMatch && projectMatch && searchMatch;
|
||||||
|
});
|
||||||
|
|
||||||
|
currentPage = 1;
|
||||||
|
sortData();
|
||||||
|
renderTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortData() {
|
||||||
|
filteredData.sort((a, b) => {
|
||||||
|
let aVal = a[sortColumn];
|
||||||
|
let bVal = b[sortColumn];
|
||||||
|
|
||||||
|
// 날짜 정렬
|
||||||
|
if (sortColumn === 'pdate') {
|
||||||
|
aVal = new Date(aVal);
|
||||||
|
bVal = new Date(bVal);
|
||||||
|
}
|
||||||
|
// 숫자 정렬
|
||||||
|
else if (['hrs', 'ot'].includes(sortColumn)) {
|
||||||
|
aVal = parseFloat(aVal) || 0;
|
||||||
|
bVal = parseFloat(bVal) || 0;
|
||||||
|
}
|
||||||
|
// 문자열 정렬
|
||||||
|
else {
|
||||||
|
aVal = (aVal || '').toString().toLowerCase();
|
||||||
|
bVal = (bVal || '').toString().toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aVal < bVal) return sortDirection === 'asc' ? -1 : 1;
|
||||||
|
if (aVal > bVal) return sortDirection === 'asc' ? 1 : -1;
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderTable() {
|
||||||
|
const tbody = document.getElementById('jobTableBody');
|
||||||
|
const startIndex = (currentPage - 1) * pageSize;
|
||||||
|
const endIndex = startIndex + pageSize;
|
||||||
|
const pageData = filteredData.slice(startIndex, endIndex);
|
||||||
|
|
||||||
|
if (pageData.length === 0) {
|
||||||
|
showEmptyState();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
hideEmptyState();
|
||||||
|
tbody.innerHTML = '';
|
||||||
|
|
||||||
|
pageData.forEach((item, index) => {
|
||||||
|
const row = document.createElement('tr');
|
||||||
|
row.className = 'hover:bg-gray-50 cursor-pointer';
|
||||||
|
row.setAttribute('data-item-id', item.idx || index);
|
||||||
|
row.addEventListener('click', () => toggleRowDetail(item, row));
|
||||||
|
|
||||||
|
const statusColor = getStatusColor(item.status);
|
||||||
|
const otTime = item.otStart && item.otEnd ?
|
||||||
|
`${item.otStart} ~ ${item.otEnd}` : '-';
|
||||||
|
|
||||||
|
row.innerHTML = `
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<i data-feather="chevron-right" class="w-4 h-4 mr-2 text-gray-400 expand-icon"></i>
|
||||||
|
${formatDate(item.pdate)}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap">
|
||||||
|
<span class="inline-flex px-2 py-1 text-xs font-semibold rounded-full ${statusColor}">
|
||||||
|
${item.status || '-'}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||||
|
${item.projectName || '-'}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||||
|
${item.requestpart || '-'}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||||
|
${item.package || '-'}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||||
|
${item.type || '-'}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||||
|
${item.process || '-'}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 text-sm text-gray-900">
|
||||||
|
<div class="max-w-xs truncate cursor-pointer hover:text-primary hover:underline"
|
||||||
|
title="${item.description || ''}"
|
||||||
|
onclick="event.stopPropagation(); showDetailModal(${JSON.stringify(item).replace(/"/g, '"')})">
|
||||||
|
${item.description || '-'}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||||
|
${item.hrs ? parseFloat(item.hrs).toFixed(1) + 'h' : '-'}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||||
|
${item.ot ? parseFloat(item.ot).toFixed(1) + 'h' : '-'}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||||
|
${otTime}
|
||||||
|
</td>
|
||||||
|
`;
|
||||||
|
|
||||||
|
tbody.appendChild(row);
|
||||||
|
|
||||||
|
// 확장 행 추가 (숨겨진 상태)
|
||||||
|
const expandRow = document.createElement('tr');
|
||||||
|
expandRow.className = 'hidden expand-row';
|
||||||
|
expandRow.setAttribute('data-parent-id', item.idx || index);
|
||||||
|
expandRow.innerHTML = `
|
||||||
|
<td colspan="11" class="px-6 py-4 bg-gray-50 border-t border-gray-200">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-2">업무내용</label>
|
||||||
|
<div class="bg-white rounded-lg p-4 cursor-pointer hover:bg-gray-100 transition-colors border"
|
||||||
|
onclick="showDetailModal(${JSON.stringify(item).replace(/"/g, '"')})">
|
||||||
|
<p class="text-sm text-gray-900 whitespace-pre-wrap">${item.description || '-'}</p>
|
||||||
|
<div class="mt-2 text-xs text-gray-500 flex items-center">
|
||||||
|
<i data-feather="maximize-2" class="w-4 h-4 mr-1"></i>
|
||||||
|
클릭하여 전체 내용 보기
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
`;
|
||||||
|
|
||||||
|
tbody.appendChild(expandRow);
|
||||||
|
});
|
||||||
|
|
||||||
|
updatePagination();
|
||||||
|
feather.replace();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStatusColor(status) {
|
||||||
|
switch (status) {
|
||||||
|
case '진행 중': return 'bg-blue-100 text-blue-800';
|
||||||
|
case '진행 완료': return 'bg-green-100 text-green-800';
|
||||||
|
case '대기': return 'bg-yellow-100 text-yellow-800';
|
||||||
|
default: return 'bg-gray-100 text-gray-800';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDate(dateString) {
|
||||||
|
if (!dateString) return '-';
|
||||||
|
const date = new Date(dateString);
|
||||||
|
return date.toLocaleDateString('ko-KR');
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatePagination() {
|
||||||
|
const maxPage = Math.ceil(filteredData.length / pageSize);
|
||||||
|
const startItem = (currentPage - 1) * pageSize + 1;
|
||||||
|
const endItem = Math.min(currentPage * pageSize, filteredData.length);
|
||||||
|
|
||||||
|
document.getElementById('pageInfo').textContent = `${currentPage} / ${maxPage}`;
|
||||||
|
document.getElementById('prevPage').disabled = currentPage <= 1;
|
||||||
|
document.getElementById('nextPage').disabled = currentPage >= maxPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
function showDetailModal(item) {
|
||||||
|
// 문자열로 전달된 경우 JSON 파싱
|
||||||
|
if (typeof item === 'string') {
|
||||||
|
try {
|
||||||
|
item = JSON.parse(item);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error parsing item:', e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const modal = document.getElementById('detailModal');
|
||||||
|
const content = document.getElementById('modalContent');
|
||||||
|
|
||||||
|
content.innerHTML = `
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700">날짜</label>
|
||||||
|
<p class="mt-1 text-sm text-gray-900">${formatDate(item.pdate)}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700">상태</label>
|
||||||
|
<p class="mt-1">
|
||||||
|
<span class="inline-flex px-2 py-1 text-xs font-semibold rounded-full ${getStatusColor(item.status)}">
|
||||||
|
${item.status || '-'}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700">프로젝트명</label>
|
||||||
|
<p class="mt-1 text-sm text-gray-900">${item.projectName || '-'}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700">요청부서</label>
|
||||||
|
<p class="mt-1 text-sm text-gray-900">${item.requestpart || '-'}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700">패키지</label>
|
||||||
|
<p class="mt-1 text-sm text-gray-900">${item.package || '-'}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700">타입</label>
|
||||||
|
<p class="mt-1 text-sm text-gray-900">${item.type || '-'}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700">프로세스</label>
|
||||||
|
<p class="mt-1 text-sm text-gray-900">${item.process || '-'}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700">근무시간</label>
|
||||||
|
<p class="mt-1 text-sm text-gray-900">${item.hrs ? parseFloat(item.hrs).toFixed(1) + 'h' : '-'}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700">초과근무</label>
|
||||||
|
<p class="mt-1 text-sm text-gray-900">${item.ot ? parseFloat(item.ot).toFixed(1) + 'h' : '-'}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700">초과근무 시간</label>
|
||||||
|
<p class="mt-1 text-sm text-gray-900">${item.otStart && item.otEnd ? `${item.otStart} ~ ${item.otEnd}` : '-'}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-4">
|
||||||
|
<label class="block text-sm font-medium text-gray-700">업무내용</label>
|
||||||
|
<p class="mt-1 text-sm text-gray-900 whitespace-pre-wrap">${item.description || '-'}</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
modal.classList.remove('hidden');
|
||||||
|
feather.replace();
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeModal() {
|
||||||
|
document.getElementById('detailModal').classList.add('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
function showSelectedJobDetail(item) {
|
||||||
|
const detailContainer = document.getElementById('selectedJobDetail');
|
||||||
|
const contentContainer = document.getElementById('selectedJobContent');
|
||||||
|
|
||||||
|
// 기존 선택된 행의 스타일 제거
|
||||||
|
document.querySelectorAll('#jobTableBody tr').forEach(row => {
|
||||||
|
row.classList.remove('bg-blue-50', 'border-l-4', 'border-blue-500');
|
||||||
|
});
|
||||||
|
|
||||||
|
// 현재 행에 선택 스타일 적용
|
||||||
|
const currentRow = event.target.closest('tr');
|
||||||
|
if (currentRow) {
|
||||||
|
currentRow.classList.add('bg-blue-50', 'border-l-4', 'border-blue-500');
|
||||||
|
}
|
||||||
|
|
||||||
|
contentContainer.innerHTML = `
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700">날짜</label>
|
||||||
|
<p class="mt-1 text-sm text-gray-900">${formatDate(item.pdate)}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700">상태</label>
|
||||||
|
<p class="mt-1">
|
||||||
|
<span class="inline-flex px-2 py-1 text-xs font-semibold rounded-full ${getStatusColor(item.status)}">
|
||||||
|
${item.status || '-'}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700">프로젝트명</label>
|
||||||
|
<p class="mt-1 text-sm text-gray-900">${item.projectName || '-'}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700">요청부서</label>
|
||||||
|
<p class="mt-1 text-sm text-gray-900">${item.requestpart || '-'}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700">패키지</label>
|
||||||
|
<p class="mt-1 text-sm text-gray-900">${item.package || '-'}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700">타입</label>
|
||||||
|
<p class="mt-1 text-sm text-gray-900">${item.type || '-'}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700">프로세스</label>
|
||||||
|
<p class="mt-1 text-sm text-gray-900">${item.process || '-'}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700">근무시간</label>
|
||||||
|
<p class="mt-1 text-sm text-gray-900">${item.hrs ? parseFloat(item.hrs).toFixed(1) + 'h' : '-'}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700">초과근무</label>
|
||||||
|
<p class="mt-1 text-sm text-gray-900">${item.ot ? parseFloat(item.ot).toFixed(1) + 'h' : '-'}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700">초과근무 시간</label>
|
||||||
|
<p class="mt-1 text-sm text-gray-900">${item.otStart && item.otEnd ? `${item.otStart} ~ ${item.otEnd}` : '-'}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-2">업무내용</label>
|
||||||
|
<div class="bg-gray-50 rounded-lg p-4 cursor-pointer hover:bg-gray-100 transition-colors"
|
||||||
|
onclick="showDetailModal(${JSON.stringify(item).replace(/"/g, '"')})">
|
||||||
|
<p class="text-sm text-gray-900 whitespace-pre-wrap">${item.description || '-'}</p>
|
||||||
|
<div class="mt-2 text-xs text-gray-500 flex items-center">
|
||||||
|
<i data-feather="maximize-2" class="w-4 h-4 mr-1"></i>
|
||||||
|
클릭하여 전체 내용 보기
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
detailContainer.classList.remove('hidden');
|
||||||
|
feather.replace();
|
||||||
|
|
||||||
|
// 부드러운 스크롤로 상세 내용으로 이동
|
||||||
|
detailContainer.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleRowDetail(item, row) {
|
||||||
|
const itemId = item.idx || row.getAttribute('data-item-id');
|
||||||
|
const expandRow = document.querySelector(`tr[data-parent-id="${itemId}"]`);
|
||||||
|
const expandIcon = row.querySelector('.expand-icon');
|
||||||
|
|
||||||
|
if (expandRow.classList.contains('hidden')) {
|
||||||
|
// 확장
|
||||||
|
expandRow.classList.remove('hidden');
|
||||||
|
expandIcon.setAttribute('data-feather', 'chevron-down');
|
||||||
|
row.classList.add('bg-blue-50');
|
||||||
|
} else {
|
||||||
|
// 축소
|
||||||
|
expandRow.classList.add('hidden');
|
||||||
|
expandIcon.setAttribute('data-feather', 'chevron-right');
|
||||||
|
row.classList.remove('bg-blue-50');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 아이콘 업데이트
|
||||||
|
feather.replace();
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeAllExpandedRows() {
|
||||||
|
document.querySelectorAll('.expand-row').forEach(row => {
|
||||||
|
row.classList.add('hidden');
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll('#jobTableBody tr').forEach(row => {
|
||||||
|
if (!row.classList.contains('expand-row')) {
|
||||||
|
row.classList.remove('bg-blue-50');
|
||||||
|
const icon = row.querySelector('.expand-icon');
|
||||||
|
if (icon) {
|
||||||
|
icon.setAttribute('data-feather', 'chevron-right');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
feather.replace();
|
||||||
|
}
|
||||||
|
|
||||||
|
function showLoading(show) {
|
||||||
|
const loadingState = document.getElementById('loadingState');
|
||||||
|
const tableBody = document.getElementById('jobTableBody');
|
||||||
|
|
||||||
|
if (show) {
|
||||||
|
loadingState.classList.remove('hidden');
|
||||||
|
tableBody.style.display = 'none';
|
||||||
|
} else {
|
||||||
|
loadingState.classList.add('hidden');
|
||||||
|
tableBody.style.display = 'table-row-group';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showEmptyState() {
|
||||||
|
document.getElementById('emptyState').classList.remove('hidden');
|
||||||
|
document.getElementById('jobTableBody').style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideEmptyState() {
|
||||||
|
document.getElementById('emptyState').classList.add('hidden');
|
||||||
|
document.getElementById('jobTableBody').style.display = 'table-row-group';
|
||||||
|
}
|
||||||
|
|
||||||
|
function showError(message) {
|
||||||
|
// 간단한 에러 알림 (실제 구현에서는 더 나은 알림 시스템 사용)
|
||||||
|
alert(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearFilters() {
|
||||||
|
// 조회기간을 이번 달로 초기화
|
||||||
|
const now = new Date();
|
||||||
|
const currentMonth = new Date(now.getFullYear(), now.getMonth(), 1);
|
||||||
|
const lastDayOfMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0);
|
||||||
|
|
||||||
|
document.getElementById('startDate').value = currentMonth.toISOString().split('T')[0];
|
||||||
|
document.getElementById('endDate').value = lastDayOfMonth.toISOString().split('T')[0];
|
||||||
|
|
||||||
|
// 다른 필터들 초기화
|
||||||
|
document.getElementById('statusFilter').value = '';
|
||||||
|
document.getElementById('projectFilter').value = '';
|
||||||
|
document.getElementById('searchInput').value = '';
|
||||||
|
|
||||||
|
// 서버에서 새로운 데이터 가져오기
|
||||||
|
loadJobData();
|
||||||
|
// 확장된 행들 닫기
|
||||||
|
closeAllExpandedRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
function exportToExcel() {
|
||||||
|
if (filteredData.length === 0) {
|
||||||
|
alert('내보낼 데이터가 없습니다.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 조회기간 정보 가져오기
|
||||||
|
const startDate = document.getElementById('startDate').value;
|
||||||
|
const endDate = document.getElementById('endDate').value;
|
||||||
|
const periodText = startDate && endDate ? `_${startDate}_${endDate}` : '';
|
||||||
|
|
||||||
|
// CSV 형식으로 데이터 변환
|
||||||
|
const headers = ['날짜', '상태', '프로젝트명', '요청부서', '패키지', '타입', '프로세스', '업무내용', '근무시간', '초과근무', '초과근무시작', '초과근무종료'];
|
||||||
|
const csvContent = [
|
||||||
|
headers.join(','),
|
||||||
|
...filteredData.map(item => [
|
||||||
|
formatDate(item.pdate),
|
||||||
|
item.status || '',
|
||||||
|
item.projectName || '',
|
||||||
|
item.requestpart || '',
|
||||||
|
item.package || '',
|
||||||
|
item.type || '',
|
||||||
|
item.process || '',
|
||||||
|
`"${(item.description || '').replace(/"/g, '""')}"`,
|
||||||
|
item.hrs || '',
|
||||||
|
item.ot || '',
|
||||||
|
item.otStart || '',
|
||||||
|
item.otEnd || ''
|
||||||
|
].join(','))
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
// 파일 다운로드
|
||||||
|
const blob = new Blob(['\ufeff' + csvContent], { type: 'text/csv;charset=utf-8;' });
|
||||||
|
const link = document.createElement('a');
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
link.setAttribute('href', url);
|
||||||
|
link.setAttribute('download', `업무일지${periodText}_${new Date().toISOString().split('T')[0]}.csv`);
|
||||||
|
link.style.visibility = 'hidden';
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -374,7 +374,13 @@ namespace Project
|
|||||||
{
|
{
|
||||||
string formkey = "WORKBOOK";
|
string formkey = "WORKBOOK";
|
||||||
if (!ShowForm(formkey))
|
if (!ShowForm(formkey))
|
||||||
AddForm(formkey, new FPJ0000.fJobReport());
|
{
|
||||||
|
if (this.webok && Pub.InitWebView == 1 && System.Diagnostics.Debugger.IsAttached)
|
||||||
|
AddForm(formkey, new Dialog.fJobReport());
|
||||||
|
else
|
||||||
|
AddForm(formkey, new FPJ0000.fJobReport());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
void menu_save_cost()
|
void menu_save_cost()
|
||||||
|
|||||||
Reference in New Issue
Block a user