프로젝트 시스템 통합 및 전반적인 개선사항
- 솔루션 설정 및 프로젝트 파일 업데이트 - BaseController 최적화 (HtmlAgilityPack 의존성 제거) - CommonController 네비게이션 메뉴에 프로젝트 추가 - JobreportController 사용자 조회 기능 및 필터링 개선 - 모든 웹 화면 UI/UX 통합 및 일관성 개선 - 프로그램 시작 시 중복 실행 감지 개선 - 각종 폼 및 데이터셋 디자이너 업데이트 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,13 @@
|
|||||||
{
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/claude-code-settings.json",
|
||||||
"permissions": {
|
"permissions": {
|
||||||
"allow": [
|
"allow": [
|
||||||
"Bash(curl:*)"
|
"Bash(curl:*)",
|
||||||
|
"Bash(mkdir:*)",
|
||||||
|
"Bash(copy \"C:\\Data\\Source\\(0014) GroupWare\\Source\\Project\\Web\\Controller\\BaseController.cs\" \"C:\\Data\\Source\\(0014) GroupWare\\Source\\EETGW.Shared\\Controllers\"\")",
|
||||||
|
"Bash(copy:*)",
|
||||||
|
"Bash(powershell:*)",
|
||||||
|
"Bash(git add:*)"
|
||||||
],
|
],
|
||||||
"deny": []
|
"deny": []
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio Express 15 for Windows Desktop
|
# Visual Studio Version 17
|
||||||
VisualStudioVersion = 15.0.36123.18
|
VisualStudioVersion = 17.14.36310.24 d17.14
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EETGW", "Project\EETGW.csproj", "{65F3E762-800C-499E-862F-A535642EC59F}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EETGW", "Project\EETGW.csproj", "{65F3E762-800C-499E-862F-A535642EC59F}"
|
||||||
EndProject
|
EndProject
|
||||||
|
|||||||
@@ -143,12 +143,6 @@
|
|||||||
<Reference Include="FarPoint.Win.Spread, Version=11.40.20177.0, Culture=neutral, PublicKeyToken=327c3516b1b18457, processorArchitecture=MSIL" />
|
<Reference Include="FarPoint.Win.Spread, Version=11.40.20177.0, Culture=neutral, PublicKeyToken=327c3516b1b18457, processorArchitecture=MSIL" />
|
||||||
<Reference Include="GrapeCity.Spreadsheet.Win, Version=11.40.20177.0, Culture=neutral, PublicKeyToken=327c3516b1b18457" />
|
<Reference Include="GrapeCity.Spreadsheet.Win, Version=11.40.20177.0, Culture=neutral, PublicKeyToken=327c3516b1b18457" />
|
||||||
<Reference Include="GrapeCity.Win.PluginInputMan, Version=11.40.20177.0, Culture=neutral, PublicKeyToken=327c3516b1b18457, processorArchitecture=MSIL" />
|
<Reference Include="GrapeCity.Win.PluginInputMan, Version=11.40.20177.0, Culture=neutral, PublicKeyToken=327c3516b1b18457, processorArchitecture=MSIL" />
|
||||||
<Reference Include="HtmlAgilityPack, Version=1.11.49.0, Culture=neutral, PublicKeyToken=bd319b19eaf3b43a, processorArchitecture=MSIL">
|
|
||||||
<HintPath>..\packages\HtmlAgilityPack.1.11.49\lib\Net45\HtmlAgilityPack.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="HtmlAgilityPack.CssSelectors, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
|
||||||
<HintPath>..\packages\HtmlAgilityPack.CssSelectors.1.0.2\lib\net45\HtmlAgilityPack.CssSelectors.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="libxl.net">
|
<Reference Include="libxl.net">
|
||||||
<HintPath>..\DLL\libxl.net.dll</HintPath>
|
<HintPath>..\DLL\libxl.net.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
@@ -426,6 +420,7 @@
|
|||||||
<Compile Include="MessageWindow.cs" />
|
<Compile Include="MessageWindow.cs" />
|
||||||
<Compile Include="MethodExtentions.cs" />
|
<Compile Include="MethodExtentions.cs" />
|
||||||
<Compile Include="Web\Model\PageModel.cs" />
|
<Compile Include="Web\Model\PageModel.cs" />
|
||||||
|
<Compile Include="Web\Model\ProjectModel.cs" />
|
||||||
<Compile Include="Web\Model\TodoModel.cs" />
|
<Compile Include="Web\Model\TodoModel.cs" />
|
||||||
<Compile Include="Web\Startup.cs" />
|
<Compile Include="Web\Startup.cs" />
|
||||||
<Compile Include="Program.cs" />
|
<Compile Include="Program.cs" />
|
||||||
@@ -672,6 +667,9 @@
|
|||||||
<Content Include="SqlServerTypes\x86\SqlServerSpatial140.dll">
|
<Content Include="SqlServerTypes\x86\SqlServerSpatial140.dll">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
|
<Content Include="Web\wwwroot\Project\index.html">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
<None Include="Web\wwwroot\css\common.css">
|
<None Include="Web\wwwroot\css\common.css">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
|
|||||||
@@ -54,8 +54,8 @@ namespace Project
|
|||||||
if (processes.Length > 1)
|
if (processes.Length > 1)
|
||||||
{
|
{
|
||||||
// 중복실행 감지
|
// 중복실행 감지
|
||||||
string message = "⚠️ GroupWare 프로그램이 이미 실행 중입니다!\n\n" +
|
string message = $"⚠️ {Application.ProductName} 프로그램이 이미 실행 중입니다!\n\n" +
|
||||||
"동시에 여러 개의 GroupWare를 실행할 수 없습니다.\n\n" +
|
"동시에 여러 개의 프로그램을 실행할 수 없습니다.\n\n" +
|
||||||
"해결방법을 선택하세요:";
|
"해결방법을 선택하세요:";
|
||||||
|
|
||||||
var result = MessageBox.Show(message + "\n\n예(Y): 기존 프로그램을 종료하고 새로 시작\n아니오(N): 현재 실행을 취소",
|
var result = MessageBox.Show(message + "\n\n예(Y): 기존 프로그램을 종료하고 새로 시작\n아니오(N): 현재 실행을 취소",
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Web.Http;
|
using System.Web.Http;
|
||||||
using agi = HtmlAgilityPack;
|
|
||||||
using Project.Web.Model;
|
using Project.Web.Model;
|
||||||
|
|
||||||
namespace Project.Web.Controllers
|
namespace Project.Web.Controllers
|
||||||
@@ -159,33 +158,7 @@ namespace Project.Web.Controllers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
agi.HtmlDocument doc = new agi.HtmlDocument();
|
|
||||||
doc.LoadHtml(contents);
|
|
||||||
|
|
||||||
//파일참조 태그를 모두 가져옴
|
|
||||||
var tags_include = doc.QuerySelectorAll("include");
|
|
||||||
foreach (var item in tags_include)
|
|
||||||
{
|
|
||||||
var filename = item.InnerText;
|
|
||||||
|
|
||||||
var load_file = String.Concat(AppDomain.CurrentDomain.BaseDirectory, "View", filename.Replace("/", "\\"));
|
|
||||||
load_file = load_file.Replace("\\\\", "\\");
|
|
||||||
String fileContents;// = String.Empty;
|
|
||||||
|
|
||||||
Console.WriteLine("## " + item.OuterHtml);
|
|
||||||
if (System.IO.File.Exists(load_file))
|
|
||||||
{
|
|
||||||
fileContents = System.IO.File.ReadAllText(load_file, System.Text.Encoding.UTF8);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
fileContents = string.Format("<div class=\"fg-red\">#include Error:nofile:{0}</div>",
|
|
||||||
filename); //파일이없다면 해당 부분은 오류 처리한다.
|
|
||||||
}
|
|
||||||
contents = contents.Replace(item.OuterHtml, fileContents);
|
|
||||||
}
|
|
||||||
|
|
||||||
//콘텐츠내의 file 을 찾아서 처리한다. ; 정규식의 처리속도가 느릴듯하여, 그냥 처리해본다
|
|
||||||
|
|
||||||
|
|
||||||
//시스템변수 replace
|
//시스템변수 replace
|
||||||
@@ -242,31 +215,7 @@ namespace Project.Web.Controllers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
agi.HtmlDocument doc = new agi.HtmlDocument();
|
|
||||||
doc.LoadHtml(contents);
|
|
||||||
|
|
||||||
//파일참조 태그를 모두 가져옴
|
|
||||||
var tags_include = doc.QuerySelectorAll("include");
|
|
||||||
foreach (var item in tags_include)
|
|
||||||
{
|
|
||||||
var filename = item.InnerText;
|
|
||||||
|
|
||||||
var load_file = String.Concat(AppDomain.CurrentDomain.BaseDirectory, "View", filename.Replace("/", "\\"));
|
|
||||||
load_file = load_file.Replace("\\\\", "\\");
|
|
||||||
String fileContents;// = String.Empty;
|
|
||||||
|
|
||||||
Console.WriteLine("## " + item.OuterHtml);
|
|
||||||
if (System.IO.File.Exists(load_file))
|
|
||||||
{
|
|
||||||
fileContents = System.IO.File.ReadAllText(load_file, System.Text.Encoding.UTF8);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
fileContents = string.Format("<div class=\"fg-red\">#include Error:nofile:{0}</div>",
|
|
||||||
filename); //파일이없다면 해당 부분은 오류 처리한다.
|
|
||||||
}
|
|
||||||
contents = contents.Replace(item.OuterHtml, fileContents);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -16,23 +16,16 @@ namespace Project.Web.Controllers
|
|||||||
{
|
{
|
||||||
var sql = string.Empty;
|
var sql = string.Empty;
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(grp))
|
//코드그룹이 없다면 전체 목록을 조회할 수 있도록 99를 조회한다
|
||||||
{
|
if (string.IsNullOrEmpty(grp)) grp = "99";
|
||||||
// grp가 없으면 모든 그룹의 데이터를 가져옴
|
|
||||||
sql = "select *" +
|
|
||||||
" from common" +
|
|
||||||
" where gcode = @gcode" +
|
|
||||||
" order by grp, code, svalue";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// 특정 그룹의 데이터만 가져옴
|
// 특정 그룹의 데이터만 가져옴
|
||||||
sql = "select *" +
|
sql = "select *" +
|
||||||
" from common" +
|
" from common" +
|
||||||
" where gcode = @gcode" +
|
" where gcode = @gcode" +
|
||||||
" and grp = @grp" +
|
" and grp = @grp" +
|
||||||
" order by code,svalue";
|
" order by code,svalue";
|
||||||
}
|
|
||||||
|
|
||||||
var cs = Properties.Settings.Default.gwcs;
|
var cs = Properties.Settings.Default.gwcs;
|
||||||
var cn = new System.Data.SqlClient.SqlConnection(cs);
|
var cn = new System.Data.SqlClient.SqlConnection(cs);
|
||||||
@@ -216,7 +209,7 @@ namespace Project.Web.Controllers
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var sql = "select code, svalue from common WITH (nolock) " +
|
var sql = "select code, svalue, memo from common WITH (nolock) " +
|
||||||
"where gcode = @gcode and grp = '99' " +
|
"where gcode = @gcode and grp = '99' " +
|
||||||
"order by code";
|
"order by code";
|
||||||
|
|
||||||
@@ -381,6 +374,14 @@ namespace Project.Web.Controllers
|
|||||||
icon = "M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2M12 12l2 2 4-4",
|
icon = "M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2M12 12l2 2 4-4",
|
||||||
isVisible = true,
|
isVisible = true,
|
||||||
sortOrder = 5
|
sortOrder = 5
|
||||||
|
},
|
||||||
|
new {
|
||||||
|
key = "project",
|
||||||
|
title = "프로젝트",
|
||||||
|
url = "/Project/",
|
||||||
|
icon = "M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10",
|
||||||
|
isVisible = true,
|
||||||
|
sortOrder = 6
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
using Microsoft.Owin;
|
using Microsoft.Owin;
|
||||||
|
using Project.Web.Controllers;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Web;
|
using System.Web;
|
||||||
using System.Web.Http;
|
using System.Web.Http;
|
||||||
|
using System.Data;
|
||||||
|
using System.Web.Http.Results;
|
||||||
|
using System.Data.SqlClient;
|
||||||
|
|
||||||
namespace Project.Web.Controllers
|
namespace Project.Web.Controllers
|
||||||
{
|
{
|
||||||
@@ -641,6 +645,90 @@ namespace Project.Web.Controllers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public HttpResponseMessage GetUsers()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string connectionString = Properties.Settings.Default.gwcs;
|
||||||
|
var users = new List<dynamic>();
|
||||||
|
|
||||||
|
using (var connection = new System.Data.SqlClient.SqlConnection(connectionString))
|
||||||
|
{
|
||||||
|
connection.Open();
|
||||||
|
|
||||||
|
string selectSql = @"
|
||||||
|
SELECT name, id, processs
|
||||||
|
FROM vGroupUser
|
||||||
|
WHERE gcode = @gcode AND useJobReport = 1 AND useUserState = 1
|
||||||
|
ORDER BY name";
|
||||||
|
|
||||||
|
using (var command = new System.Data.SqlClient.SqlCommand(selectSql, connection))
|
||||||
|
{
|
||||||
|
command.Parameters.AddWithValue("@gcode", FCOMMON.info.Login.gcode);
|
||||||
|
|
||||||
|
using (var reader = command.ExecuteReader())
|
||||||
|
{
|
||||||
|
while (reader.Read())
|
||||||
|
{
|
||||||
|
users.Add(new
|
||||||
|
{
|
||||||
|
name = reader["name"],
|
||||||
|
id = reader["id"],
|
||||||
|
process = reader["processs"]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 디버깅 로그 추가
|
||||||
|
System.Diagnostics.Debug.WriteLine($"GetUsers: Found {users.Count} users for gcode {FCOMMON.info.Login.gcode}");
|
||||||
|
|
||||||
|
// JSON 형태로 변환
|
||||||
|
var jsonData = "[";
|
||||||
|
bool first = true;
|
||||||
|
|
||||||
|
foreach (var user in users)
|
||||||
|
{
|
||||||
|
if (!first) jsonData += ",";
|
||||||
|
first = false;
|
||||||
|
|
||||||
|
var name = EscapeJsonString(user.name?.ToString() ?? "");
|
||||||
|
var id = EscapeJsonString(user.id?.ToString() ?? "");
|
||||||
|
var process = EscapeJsonString(user.process?.ToString() ?? "");
|
||||||
|
|
||||||
|
jsonData += "{";
|
||||||
|
jsonData += $"\"name\":\"{name}\",";
|
||||||
|
jsonData += $"\"id\":\"{id}\",";
|
||||||
|
jsonData += $"\"process\":\"{process}\"";
|
||||||
|
jsonData += "}";
|
||||||
|
}
|
||||||
|
jsonData += "]";
|
||||||
|
|
||||||
|
var resp = new HttpResponseMessage()
|
||||||
|
{
|
||||||
|
Content = new StringContent(
|
||||||
|
jsonData,
|
||||||
|
System.Text.Encoding.UTF8,
|
||||||
|
"application/json")
|
||||||
|
};
|
||||||
|
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
var errorResp = new HttpResponseMessage()
|
||||||
|
{
|
||||||
|
Content = new StringContent(
|
||||||
|
$"{{\"error\":\"{ex.Message}\"}}",
|
||||||
|
System.Text.Encoding.UTF8,
|
||||||
|
"application/json")
|
||||||
|
};
|
||||||
|
return errorResp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public HttpResponseMessage GetJobData()
|
public HttpResponseMessage GetJobData()
|
||||||
{
|
{
|
||||||
@@ -649,9 +737,11 @@ namespace Project.Web.Controllers
|
|||||||
var gets = Request.GetQueryNameValuePairs();
|
var gets = Request.GetQueryNameValuePairs();
|
||||||
var startDateParam = gets.Where(t => t.Key == "startDate").FirstOrDefault();
|
var startDateParam = gets.Where(t => t.Key == "startDate").FirstOrDefault();
|
||||||
var endDateParam = gets.Where(t => t.Key == "endDate").FirstOrDefault();
|
var endDateParam = gets.Where(t => t.Key == "endDate").FirstOrDefault();
|
||||||
|
var userParam = gets.Where(t => t.Key == "user").FirstOrDefault();
|
||||||
|
|
||||||
var startDate = startDateParam.Key != null ? startDateParam.Value : null;
|
var startDate = startDateParam.Key != null ? startDateParam.Value : null;
|
||||||
var endDate = endDateParam.Key != null ? endDateParam.Value : null;
|
var endDate = endDateParam.Key != null ? endDateParam.Value : null;
|
||||||
|
var selectedUser = userParam.Key != null ? userParam.Value : null;
|
||||||
|
|
||||||
// 날짜 파라미터 처리
|
// 날짜 파라미터 처리
|
||||||
string sd, ed;
|
string sd, ed;
|
||||||
@@ -683,13 +773,17 @@ namespace Project.Web.Controllers
|
|||||||
description, '' as ww, otStart, otEnd, ot as ot2, '' as otReason,
|
description, '' as ww, otStart, otEnd, ot as ot2, '' as otReason,
|
||||||
'' as grade, '' as indate, '' as outdate, pidx
|
'' as grade, '' as indate, '' as outdate, pidx
|
||||||
FROM JobReport WITH (NOLOCK)
|
FROM JobReport WITH (NOLOCK)
|
||||||
WHERE gcode = @gcode AND uid = @uid AND pdate BETWEEN @startDate AND @endDate
|
WHERE gcode = @gcode AND pdate BETWEEN @startDate AND @endDate";
|
||||||
ORDER BY pdate DESC";
|
|
||||||
|
// 사용자 필터가 있으면 해당 사용자, 없으면 로그인한 사용자
|
||||||
|
selectSql += " AND uid = @uid";
|
||||||
|
|
||||||
|
selectSql += " ORDER BY pdate DESC";
|
||||||
|
|
||||||
using (var command = new System.Data.SqlClient.SqlCommand(selectSql, connection))
|
using (var command = new System.Data.SqlClient.SqlCommand(selectSql, connection))
|
||||||
{
|
{
|
||||||
command.Parameters.AddWithValue("@gcode", FCOMMON.info.Login.gcode);
|
command.Parameters.AddWithValue("@gcode", FCOMMON.info.Login.gcode);
|
||||||
command.Parameters.AddWithValue("@uid", FCOMMON.info.Login.no);
|
command.Parameters.AddWithValue("@uid", !string.IsNullOrEmpty(selectedUser) ? selectedUser : FCOMMON.info.Login.no);
|
||||||
command.Parameters.AddWithValue("@startDate", sd);
|
command.Parameters.AddWithValue("@startDate", sd);
|
||||||
command.Parameters.AddWithValue("@endDate", ed);
|
command.Parameters.AddWithValue("@endDate", ed);
|
||||||
|
|
||||||
|
|||||||
@@ -92,71 +92,95 @@
|
|||||||
select option:checked {
|
select option:checked {
|
||||||
background-color: #6366F1 !important;
|
background-color: #6366F1 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 테이블 셀 텍스트 오버플로우 처리 */
|
||||||
|
.truncate {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 값(문자열) 열 최대 너비 제한 */
|
||||||
|
.svalue-cell {
|
||||||
|
max-width: 128px; /* w-32 = 128px */
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="gradient-bg min-h-screen">
|
<body class="bg-gradient-to-br from-blue-900 via-purple-900 to-indigo-900 min-h-screen text-white">
|
||||||
<div class="container mx-auto px-4 py-8">
|
<div class="container mx-auto px-4 py-8">
|
||||||
<!-- 개발중 경고 메시지 -->
|
|
||||||
<div class="bg-orange-500 rounded-lg p-4 mb-6 border-l-4 border-orange-700 animate-slide-up shadow-lg">
|
<!-- 2열 구조 메인 컨테이너 -->
|
||||||
<div class="flex items-center">
|
<div class="flex gap-6 h-[calc(100vh-200px)]">
|
||||||
<svg class="w-5 h-5 text-orange-900 mr-3" 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>
|
<div class="w-80">
|
||||||
</svg>
|
<div class="glass-effect rounded-2xl h-full card-hover animate-slide-up flex flex-col">
|
||||||
<div>
|
<div class="p-4 border-b border-white/10">
|
||||||
<p class="text-white font-bold text-base">🚧 개발중인 기능입니다</p>
|
<h3 class="text-lg font-semibold text-white flex items-center">
|
||||||
<p class="text-orange-100 text-sm font-medium">일부 기능이 정상적으로 동작하지 않을 수 있습니다.</p>
|
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14-7l-7 7-7-7M19 21l-7-7-7 7"></path>
|
||||||
|
</svg>
|
||||||
|
코드그룹 목록
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 overflow-y-auto custom-scrollbar p-2">
|
||||||
|
<div id="groupList" class="space-y-1">
|
||||||
|
<!-- 그룹 목록이 여기에 표시됩니다 -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 검색 및 필터 -->
|
<!-- 우측: 상세 데이터 -->
|
||||||
<div class="glass-effect rounded-2xl p-6 mb-6 card-hover animate-slide-up">
|
<div class="flex-1">
|
||||||
<div class="flex flex-wrap gap-4 items-end">
|
<div class="glass-effect rounded-2xl h-full card-hover animate-slide-up flex flex-col">
|
||||||
<div class="flex-1 min-w-0">
|
<!-- 상단 헤더 -->
|
||||||
<label class="block text-sm font-medium text-white/70 mb-2">코드그룹</label>
|
<div class="p-4 border-b border-white/10 flex items-center justify-between">
|
||||||
<select id="grpFilter" class="w-full px-3 py-2 bg-white/20 backdrop-blur-sm border border-white/30 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-primary-400 transition-all">
|
<div>
|
||||||
<!-- 동적으로 로드됩니다 -->
|
<h3 class="text-lg font-semibold text-white flex items-center">
|
||||||
</select>
|
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
|
||||||
|
</svg>
|
||||||
|
<span id="selectedGroupTitle">코드그룹을 선택하세요</span>
|
||||||
|
</h3>
|
||||||
|
<p class="text-white/70 text-sm mt-1">총 <span id="recordCount" class="text-white font-medium">0</span>건</p>
|
||||||
|
</div>
|
||||||
|
<button onclick="showAddModal()" class="bg-white/20 hover:bg-white/30 backdrop-blur-sm text-white px-4 py-2 rounded-lg transition-all border border-white/30 flex items-center text-sm">
|
||||||
|
<svg class="w-4 h-4 mr-1" 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="flex-1 overflow-x-auto overflow-y-auto custom-scrollbar">
|
||||||
|
<table class="w-full">
|
||||||
|
<thead class="bg-white/10 sticky top-0">
|
||||||
|
<tr>
|
||||||
|
<th class="w-24 px-4 py-3 text-left text-xs font-medium text-white/70 uppercase tracking-wider">코드</th>
|
||||||
|
<th class="px-4 py-3 text-left text-xs font-medium text-white/70 uppercase tracking-wider">비고</th>
|
||||||
|
<th class="w-32 px-4 py-3 text-left text-xs font-medium text-white/70 uppercase tracking-wider">값(문자열)</th>
|
||||||
|
<th class="w-20 px-4 py-3 text-left text-xs font-medium text-white/70 uppercase tracking-wider">값(숫자)</th>
|
||||||
|
<th class="w-20 px-4 py-3 text-left text-xs font-medium text-white/70 uppercase tracking-wider">값(실수)</th>
|
||||||
|
<th class="w-24 px-4 py-3 text-left text-xs font-medium text-white/70 uppercase tracking-wider">값2</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="dataTable" class="divide-y divide-white/10">
|
||||||
|
<tr>
|
||||||
|
<td colspan="6" class="px-4 py-8 text-center text-white/70">
|
||||||
|
<svg class="w-12 h-12 mx-auto mb-2 text-white/30" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
|
||||||
|
</svg>
|
||||||
|
좌측에서 코드그룹을 선택하세요
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-2">
|
|
||||||
<button onclick="loadData()" class="bg-white/20 hover:bg-white/30 backdrop-blur-sm text-white px-6 py-2 rounded-lg transition-all border border-white/30 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="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
|
|
||||||
</svg>
|
|
||||||
조회
|
|
||||||
</button>
|
|
||||||
<button onclick="showAddModal()" class="bg-white/20 hover:bg-white/30 backdrop-blur-sm text-white px-6 py-2 rounded-lg transition-all border border-white/30 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>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 데이터 테이블 -->
|
|
||||||
<div class="glass-effect rounded-2xl overflow-hidden card-hover animate-slide-up">
|
|
||||||
<div class="overflow-x-auto max-h-[calc(100vh-300px)] overflow-y-auto custom-scrollbar">
|
|
||||||
<table class="w-full">
|
|
||||||
<thead class="bg-white/10 sticky top-0">
|
|
||||||
<tr>
|
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-white/70 uppercase tracking-wider">코드</th>
|
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-white/70 uppercase tracking-wider">비고</th>
|
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-white/70 uppercase tracking-wider">값(문자열)</th>
|
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-white/70 uppercase tracking-wider">값(숫자)</th>
|
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-white/70 uppercase tracking-wider">값(실수)</th>
|
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-white/70 uppercase tracking-wider">값2</th>
|
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-white/70 uppercase tracking-wider">작업</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="dataTable" class="divide-y divide-white/10">
|
|
||||||
<!-- 데이터가 여기에 표시됩니다 -->
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<div class="px-6 py-3 border-t border-white/10 bg-white/5">
|
|
||||||
<p class="text-white/70 text-sm text-center">총 <span id="recordCount" class="text-white font-medium">0</span>건</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -224,13 +248,21 @@
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
<!-- 모달 푸터 -->
|
<!-- 모달 푸터 -->
|
||||||
<div class="px-6 py-4 border-t border-white/10 flex justify-end gap-2">
|
<div class="px-6 py-4 border-t border-white/10 flex justify-between">
|
||||||
<button onclick="hideEditModal()" class="bg-white/20 hover:bg-white/30 text-white px-4 py-2 rounded-lg transition-colors">
|
<button onclick="deleteCurrentItem()" class="bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded-lg transition-colors flex items-center">
|
||||||
취소
|
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
</button>
|
<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>
|
||||||
<button onclick="saveData()" class="bg-white/20 hover:bg-white/30 text-white px-4 py-2 rounded-lg transition-colors">
|
</svg>
|
||||||
저장
|
삭제
|
||||||
</button>
|
</button>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<button onclick="hideEditModal()" class="bg-white/20 hover:bg-white/30 text-white px-4 py-2 rounded-lg transition-colors">
|
||||||
|
취소
|
||||||
|
</button>
|
||||||
|
<button onclick="saveData()" class="bg-white/20 hover:bg-white/30 text-white px-4 py-2 rounded-lg transition-colors">
|
||||||
|
저장
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -252,7 +284,7 @@
|
|||||||
<p class="text-sm text-gray-500">이 작업은 되돌릴 수 없습니다.</p>
|
<p class="text-sm text-gray-500">이 작업은 되돌릴 수 없습니다.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-gray-700 mb-6">선택한 공용코드를 삭제하시겠습니까?</p>
|
<p class="text-gray-700 mb-6">선택한 공용코드를 삭제하시겠습니까?<br><span class="text-sm text-gray-500">이 작업은 되돌릴 수 없습니다.</span></p>
|
||||||
<div class="flex justify-end gap-2">
|
<div class="flex justify-end gap-2">
|
||||||
<button onclick="hideDeleteModal()" class="bg-gray-300 hover:bg-gray-400 text-gray-700 px-4 py-2 rounded-lg transition-colors">
|
<button onclick="hideDeleteModal()" class="bg-gray-300 hover:bg-gray-400 text-gray-700 px-4 py-2 rounded-lg transition-colors">
|
||||||
취소
|
취소
|
||||||
@@ -331,22 +363,33 @@
|
|||||||
return `
|
return `
|
||||||
<div class="container mx-auto px-4">
|
<div class="container mx-auto px-4">
|
||||||
<div class="flex items-center justify-between h-16">
|
<div class="flex items-center justify-between h-16">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center space-x-8">
|
||||||
<h2 class="text-xl font-bold text-white">GroupWare</h2>
|
<div class="flex items-center space-x-2">
|
||||||
</div>
|
<svg class="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<div class="hidden md:flex items-center space-x-8">
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
|
||||||
${visibleItems.map(item => this.getMenuItemHTML(item)).join('')}
|
|
||||||
</div>
|
|
||||||
<div class="md:hidden">
|
|
||||||
<button id="mobile-menu-button" class="text-white/80 hover:text-white transition-colors p-2">
|
|
||||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
|
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
<span class="text-xl font-bold text-white">GroupWare</span>
|
||||||
|
</div>
|
||||||
|
<nav class="hidden md:flex space-x-1">
|
||||||
|
${visibleItems.map(item => `
|
||||||
|
<a href="${item.url}" class="px-3 py-2 rounded-md text-sm font-medium transition-colors ${
|
||||||
|
this.currentPage === item.key
|
||||||
|
? 'bg-white/20 text-white'
|
||||||
|
: 'text-white/60 hover:text-white hover:bg-white/10'
|
||||||
|
}">
|
||||||
|
<svg class="w-4 h-4 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="${item.icon}"></path>
|
||||||
|
</svg>
|
||||||
|
${item.title}
|
||||||
|
</a>
|
||||||
|
`).join('')}
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center space-x-4">
|
||||||
|
<div class="text-sm text-white/60">
|
||||||
|
<span id="currentUser">사용자</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div id="mobile-menu" class="md:hidden hidden border-t border-white/10 pt-4 pb-4">
|
|
||||||
${visibleItems.map(item => this.getMobileMenuItemHTML(item)).join('')}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@@ -396,6 +439,7 @@
|
|||||||
let currentData = [];
|
let currentData = [];
|
||||||
let deleteTargetIdx = null;
|
let deleteTargetIdx = null;
|
||||||
let groupData = [];
|
let groupData = [];
|
||||||
|
let selectedGroupCode = null;
|
||||||
|
|
||||||
// 페이지 로드시 초기 데이터 로드
|
// 페이지 로드시 초기 데이터 로드
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
@@ -410,11 +454,8 @@
|
|||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
groupData = data || [];
|
groupData = data || [];
|
||||||
renderGroupSelects();
|
renderGroupList();
|
||||||
|
renderEditGroupSelect();
|
||||||
// 기본적으로 "99-전체" 선택
|
|
||||||
document.getElementById('grpFilter').value = '99';
|
|
||||||
loadData();
|
|
||||||
hideLoading();
|
hideLoading();
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
@@ -424,38 +465,64 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 셀렉트 박스들에 그룹 옵션 렌더링
|
// 좌측 그룹 리스트 렌더링
|
||||||
function renderGroupSelects() {
|
function renderGroupList() {
|
||||||
const grpFilter = document.getElementById('grpFilter');
|
const groupList = document.getElementById('groupList');
|
||||||
|
|
||||||
|
if (groupData.length === 0) {
|
||||||
|
groupList.innerHTML = '<div class="text-white/70 text-center py-4">그룹 데이터가 없습니다.</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
groupList.innerHTML = groupData.map(group => `
|
||||||
|
<div class="group-item cursor-pointer p-3 rounded-lg border border-white/20 hover:bg-white/10 transition-all ${selectedGroupCode === group.code ? 'bg-white/20' : ''}"
|
||||||
|
onclick="selectGroup('${group.code}', '${group.memo}')">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="w-8 h-8 bg-white/20 rounded-full flex items-center justify-center mr-3">
|
||||||
|
<span class="text-white text-sm font-medium">${group.code}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="text-white font-medium">${group.memo}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 그룹 선택 처리
|
||||||
|
function selectGroup(code, name) {
|
||||||
|
selectedGroupCode = code;
|
||||||
|
|
||||||
|
// 선택된 그룹 하이라이트 업데이트
|
||||||
|
document.querySelectorAll('.group-item').forEach(item => {
|
||||||
|
item.classList.remove('bg-white/20');
|
||||||
|
});
|
||||||
|
event.target.closest('.group-item').classList.add('bg-white/20');
|
||||||
|
|
||||||
|
// 우측 제목 업데이트
|
||||||
|
document.getElementById('selectedGroupTitle').textContent = `${code} - ${name}`;
|
||||||
|
|
||||||
|
// 해당 그룹의 데이터 로드
|
||||||
|
loadDataByGroup(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 편집 모달용 그룹 셀렉트 렌더링
|
||||||
|
function renderEditGroupSelect() {
|
||||||
const editGrp = document.getElementById('editGrp');
|
const editGrp = document.getElementById('editGrp');
|
||||||
|
|
||||||
// 필터 셀렉트박스 - "99-전체" 옵션 추가
|
|
||||||
const filterOptions = [
|
|
||||||
'<option value="99" class="bg-gray-800 text-white">99-전체</option>'
|
|
||||||
].concat(
|
|
||||||
groupData.map(group =>
|
|
||||||
`<option value="${group.code}" class="bg-gray-800 text-white">${group.code}-${group.svalue}</option>`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
grpFilter.innerHTML = filterOptions.join('');
|
|
||||||
|
|
||||||
// 편집 모달 셀렉트박스
|
|
||||||
editGrp.innerHTML = '<option value="" class="bg-gray-800 text-white">선택하세요</option>' +
|
editGrp.innerHTML = '<option value="" class="bg-gray-800 text-white">선택하세요</option>' +
|
||||||
groupData.map(group =>
|
groupData.map(group =>
|
||||||
`<option value="${group.code}" class="bg-gray-800 text-white">${group.code}-${group.svalue}</option>`
|
`<option value="${group.code}" class="bg-gray-800 text-white">${group.code}-${group.memo}</option>`
|
||||||
).join('');
|
).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 데이터 로드
|
// 특정 그룹의 데이터 로드
|
||||||
function loadData() {
|
function loadDataByGroup(grp) {
|
||||||
const grp = document.getElementById('grpFilter').value;
|
|
||||||
showLoading();
|
showLoading();
|
||||||
|
|
||||||
let url = 'http://127.0.0.1:7979/Common/GetList';
|
let url = 'http://127.0.0.1:7979/Common/GetList';
|
||||||
if (grp && grp !== '99') {
|
if (grp) {
|
||||||
url += '?grp=' + encodeURIComponent(grp);
|
url += '?grp=' + encodeURIComponent(grp);
|
||||||
}
|
}
|
||||||
// grp가 '99'이면 모든 그룹의 데이터를 가져옴 (파라미터 없음)
|
|
||||||
|
|
||||||
fetch(url)
|
fetch(url)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
@@ -475,11 +542,18 @@
|
|||||||
function renderTable() {
|
function renderTable() {
|
||||||
const tbody = document.getElementById('dataTable');
|
const tbody = document.getElementById('dataTable');
|
||||||
const recordCount = document.getElementById('recordCount');
|
const recordCount = document.getElementById('recordCount');
|
||||||
const selectedGrp = document.getElementById('grpFilter').value;
|
|
||||||
const showAllGroups = selectedGrp === '99';
|
|
||||||
|
|
||||||
if (currentData.length === 0) {
|
if (currentData.length === 0) {
|
||||||
tbody.innerHTML = '<tr><td colspan="7" class="px-6 py-4 text-center text-white/70">데이터가 없습니다.</td></tr>';
|
tbody.innerHTML = `
|
||||||
|
<tr>
|
||||||
|
<td colspan="6" class="px-4 py-8 text-center text-white/70">
|
||||||
|
<svg class="w-12 h-12 mx-auto mb-2 text-white/30" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
|
||||||
|
</svg>
|
||||||
|
${selectedGroupCode ? '데이터가 없습니다.' : '좌측에서 코드그룹을 선택하세요'}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
recordCount.textContent = '0';
|
recordCount.textContent = '0';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -487,35 +561,14 @@
|
|||||||
recordCount.textContent = currentData.length;
|
recordCount.textContent = currentData.length;
|
||||||
|
|
||||||
tbody.innerHTML = currentData.map(item => {
|
tbody.innerHTML = currentData.map(item => {
|
||||||
// 전체 보기일 때는 코드에 그룹 정보도 표시
|
|
||||||
let codeDisplay = item.code || '-';
|
|
||||||
if (showAllGroups && item.grp) {
|
|
||||||
// 그룹 데이터에서 해당 그룹의 이름 찾기
|
|
||||||
const groupInfo = groupData.find(g => g.code === item.grp);
|
|
||||||
const groupName = groupInfo ? groupInfo.svalue : item.grp;
|
|
||||||
codeDisplay = `${item.grp}-${groupName}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<tr class="hover:bg-white/5 transition-colors">
|
<tr class="hover:bg-white/5 transition-colors cursor-pointer" onclick="editItem(${item.idx})">
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-white">${codeDisplay}</td>
|
<td class="px-4 py-4 whitespace-nowrap text-sm text-white">${item.code || '-'}</td>
|
||||||
<td class="px-6 py-4 text-sm text-white">${item.memo || '-'}</td>
|
<td class="px-4 py-4 text-sm text-white">${item.memo || '-'}</td>
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-white">${item.svalue || '-'}</td>
|
<td class="px-4 py-4 text-sm text-white svalue-cell" title="${item.svalue || '-'}">${item.svalue || '-'}</td>
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-white">${item.ivalue || '0'}</td>
|
<td class="px-4 py-4 whitespace-nowrap text-sm text-white">${item.ivalue || '0'}</td>
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-white">${item.fvalue || '0.0'}</td>
|
<td class="px-4 py-4 whitespace-nowrap text-sm text-white">${item.fvalue || '0.0'}</td>
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-white">${item.svalue2 || '-'}</td>
|
<td class="px-4 py-4 whitespace-nowrap text-sm text-white">${item.svalue2 || '-'}</td>
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
|
|
||||||
<button onclick="editItem(${item.idx})" class="text-blue-400 hover:text-blue-300 mr-2 transition-colors">
|
|
||||||
<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 onclick="deleteItem(${item.idx})" class="text-red-400 hover:text-red-300 transition-colors">
|
|
||||||
<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>
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
`;
|
`;
|
||||||
}).join('');
|
}).join('');
|
||||||
@@ -523,13 +576,24 @@
|
|||||||
|
|
||||||
// 추가 모달 표시
|
// 추가 모달 표시
|
||||||
function showAddModal() {
|
function showAddModal() {
|
||||||
|
if (!selectedGroupCode) {
|
||||||
|
showNotification('먼저 코드그룹을 선택하세요.', 'warning');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById('modalTitle').textContent = '공용코드 추가';
|
document.getElementById('modalTitle').textContent = '공용코드 추가';
|
||||||
document.getElementById('editForm').reset();
|
document.getElementById('editForm').reset();
|
||||||
document.getElementById('editIdx').value = '';
|
document.getElementById('editIdx').value = '';
|
||||||
|
|
||||||
// 현재 선택된 코드그룹을 자동 입력
|
// 현재 선택된 코드그룹을 자동 입력
|
||||||
const selectedGrp = document.getElementById('grpFilter').value;
|
document.getElementById('editGrp').value = selectedGroupCode;
|
||||||
document.getElementById('editGrp').value = selectedGrp;
|
|
||||||
|
// 삭제 버튼 비활성화 (새로 추가하는 항목이므로)
|
||||||
|
const deleteBtn = document.querySelector('#editModal button[onclick="deleteCurrentItem()"]');
|
||||||
|
if (deleteBtn) {
|
||||||
|
deleteBtn.disabled = true;
|
||||||
|
deleteBtn.classList.add('opacity-50', 'cursor-not-allowed');
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById('editModal').classList.remove('hidden');
|
document.getElementById('editModal').classList.remove('hidden');
|
||||||
}
|
}
|
||||||
@@ -549,6 +613,13 @@
|
|||||||
document.getElementById('editSvalue2').value = item.svalue2 || '';
|
document.getElementById('editSvalue2').value = item.svalue2 || '';
|
||||||
document.getElementById('editMemo').value = item.memo || '';
|
document.getElementById('editMemo').value = item.memo || '';
|
||||||
|
|
||||||
|
// 삭제 버튼 활성화 (기존 항목 편집이므로)
|
||||||
|
const deleteBtn = document.querySelector('#editModal button[onclick="deleteCurrentItem()"]');
|
||||||
|
if (deleteBtn) {
|
||||||
|
deleteBtn.disabled = false;
|
||||||
|
deleteBtn.classList.remove('opacity-50', 'cursor-not-allowed');
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById('editModal').classList.remove('hidden');
|
document.getElementById('editModal').classList.remove('hidden');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -588,7 +659,10 @@
|
|||||||
if (data.Success) {
|
if (data.Success) {
|
||||||
showNotification(data.Message, 'success');
|
showNotification(data.Message, 'success');
|
||||||
hideDeleteModal();
|
hideDeleteModal();
|
||||||
loadData(); // 데이터 새로고침
|
// 현재 선택된 그룹의 데이터 새로고침
|
||||||
|
if (selectedGroupCode) {
|
||||||
|
loadDataByGroup(selectedGroupCode);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
showNotification(data.Message, 'error');
|
showNotification(data.Message, 'error');
|
||||||
}
|
}
|
||||||
@@ -600,6 +674,19 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 편집 다이얼로그에서 현재 항목 삭제
|
||||||
|
function deleteCurrentItem() {
|
||||||
|
const editIdx = document.getElementById('editIdx').value;
|
||||||
|
if (!editIdx) {
|
||||||
|
showNotification('삭제할 항목이 없습니다.', 'warning');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteTargetIdx = parseInt(editIdx);
|
||||||
|
hideEditModal();
|
||||||
|
document.getElementById('deleteModal').classList.remove('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
// 데이터 저장
|
// 데이터 저장
|
||||||
function saveData() {
|
function saveData() {
|
||||||
const form = document.getElementById('editForm');
|
const form = document.getElementById('editForm');
|
||||||
@@ -634,7 +721,10 @@
|
|||||||
if (result.Success) {
|
if (result.Success) {
|
||||||
showNotification(result.Message, 'success');
|
showNotification(result.Message, 'success');
|
||||||
hideEditModal();
|
hideEditModal();
|
||||||
loadData(); // 데이터 새로고침
|
// 현재 선택된 그룹의 데이터 새로고침
|
||||||
|
if (selectedGroupCode) {
|
||||||
|
loadDataByGroup(selectedGroupCode);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
showNotification(result.Message, 'error');
|
showNotification(result.Message, 'error');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -121,7 +121,7 @@
|
|||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="gradient-bg min-h-screen">
|
<body class="bg-gradient-to-br from-blue-900 via-purple-900 to-indigo-900 min-h-screen text-white">
|
||||||
<div class="container mx-auto px-4 py-8">
|
<div class="container mx-auto px-4 py-8">
|
||||||
<!-- 헤더 -->
|
<!-- 헤더 -->
|
||||||
<div class="text-center mb-8 animate-fade-in">
|
<div class="text-center mb-8 animate-fade-in">
|
||||||
@@ -224,7 +224,7 @@
|
|||||||
휴가/기타 현황
|
휴가/기타 현황
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="overflow-x-auto max-h-96 custom-scrollbar">
|
<div class="overflow-x-auto max-h-[460px] custom-scrollbar">
|
||||||
<table class="w-full">
|
<table class="w-full">
|
||||||
<thead class="bg-white/10 sticky top-0">
|
<thead class="bg-white/10 sticky top-0">
|
||||||
<tr>
|
<tr>
|
||||||
@@ -266,7 +266,7 @@
|
|||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
<div id="urgentTodoList" class="space-y-3 max-h-80 overflow-y-auto custom-scrollbar">
|
<div id="urgentTodoList" class="space-y-3 max-h-[384px] overflow-y-auto custom-scrollbar">
|
||||||
<!-- 할일이 여기에 표시됩니다 -->
|
<!-- 할일이 여기에 표시됩니다 -->
|
||||||
<div class="text-center text-white/50 py-8">
|
<div class="text-center text-white/50 py-8">
|
||||||
<svg class="w-8 h-8 mx-auto mb-2 opacity-50" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="w-8 h-8 mx-auto mb-2 opacity-50" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
@@ -720,7 +720,8 @@
|
|||||||
{ key: 'common', title: '공용코드', url: '/Common', icon: 'M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z', isVisible: true, sortOrder: 2 },
|
{ key: 'common', title: '공용코드', url: '/Common', icon: 'M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z', isVisible: true, sortOrder: 2 },
|
||||||
{ key: 'jobreport', title: '업무일지', url: '/Jobreport/', icon: 'M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2', isVisible: true, sortOrder: 3 },
|
{ key: 'jobreport', title: '업무일지', url: '/Jobreport/', icon: 'M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2', isVisible: true, sortOrder: 3 },
|
||||||
{ key: 'kuntae', title: '근태관리', url: '/Kuntae/', icon: 'M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z', isVisible: true, sortOrder: 4 },
|
{ key: 'kuntae', title: '근태관리', url: '/Kuntae/', icon: 'M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z', isVisible: true, sortOrder: 4 },
|
||||||
{ key: 'todo', title: '할일관리', url: '/Todo/', icon: 'M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2M12 12l2 2 4-4', isVisible: true, sortOrder: 5 }
|
{ key: 'todo', title: '할일관리', url: '/Todo/', icon: 'M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2M12 12l2 2 4-4', isVisible: true, sortOrder: 5 },
|
||||||
|
{ key: 'project', title: '프로젝트', url: '/Project/', icon: 'M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10', isVisible: true, sortOrder: 6 },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -742,22 +743,33 @@
|
|||||||
return `
|
return `
|
||||||
<div class="container mx-auto px-4">
|
<div class="container mx-auto px-4">
|
||||||
<div class="flex items-center justify-between h-16">
|
<div class="flex items-center justify-between h-16">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center space-x-8">
|
||||||
<h2 class="text-xl font-bold text-white">GroupWare</h2>
|
<div class="flex items-center space-x-2">
|
||||||
</div>
|
<svg class="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<div class="hidden md:flex items-center space-x-8">
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2H5a2 2 0 00-2-2z M8 5a2 2 0 012-2h4a2 2 0 012 2v2H8V5z"></path>
|
||||||
${visibleItems.map(item => this.getMenuItemHTML(item)).join('')}
|
|
||||||
</div>
|
|
||||||
<div class="md:hidden">
|
|
||||||
<button id="mobile-menu-button" class="text-white/80 hover:text-white transition-colors p-2">
|
|
||||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
|
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
<span class="text-xl font-bold text-white">GroupWare</span>
|
||||||
|
</div>
|
||||||
|
<nav class="hidden md:flex space-x-1">
|
||||||
|
${visibleItems.map(item => `
|
||||||
|
<a href="${item.url}" class="px-3 py-2 rounded-md text-sm font-medium transition-colors ${
|
||||||
|
this.currentPage === item.key
|
||||||
|
? 'bg-white/20 text-white'
|
||||||
|
: 'text-white/60 hover:text-white hover:bg-white/10'
|
||||||
|
}">
|
||||||
|
<svg class="w-4 h-4 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="${item.icon}"></path>
|
||||||
|
</svg>
|
||||||
|
${item.title}
|
||||||
|
</a>
|
||||||
|
`).join('')}
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center space-x-4">
|
||||||
|
<div class="text-sm text-white/60">
|
||||||
|
<span id="currentUser">사용자</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div id="mobile-menu" class="md:hidden hidden border-t border-white/10 pt-4 pb-4">
|
|
||||||
${visibleItems.map(item => this.getMobileMenuItemHTML(item)).join('')}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -119,9 +119,28 @@
|
|||||||
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
|
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
|
||||||
background: rgba(255, 255, 255, 0.5);
|
background: rgba(255, 255, 255, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 셀렉트 박스 옵션 스타일링 */
|
||||||
|
select option {
|
||||||
|
background-color: #1f2937 !important;
|
||||||
|
color: #f9fafb !important;
|
||||||
|
padding: 8px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
select option:hover {
|
||||||
|
background-color: #374151 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
select option:checked {
|
||||||
|
background-color: #3b82f6 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
select option:focus {
|
||||||
|
background-color: #374151 !important;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="gradient-bg min-h-screen">
|
<body class="bg-gradient-to-br from-blue-900 via-purple-900 to-indigo-900 min-h-screen text-white">
|
||||||
<!-- 네비게이션 메뉴 (동적으로 추가됨) -->
|
<!-- 네비게이션 메뉴 (동적으로 추가됨) -->
|
||||||
|
|
||||||
<div class="container mx-auto px-4 py-8">
|
<div class="container mx-auto px-4 py-8">
|
||||||
@@ -189,46 +208,78 @@
|
|||||||
|
|
||||||
<!-- 필터 및 검색 -->
|
<!-- 필터 및 검색 -->
|
||||||
<div class="glass-effect rounded-lg mb-6 animate-slide-up">
|
<div class="glass-effect rounded-lg mb-6 animate-slide-up">
|
||||||
<div class="p-6">
|
<div class="p-4">
|
||||||
<div class="flex flex-col md:flex-row md:items-center md:justify-between space-y-4 md:space-y-0">
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-4">
|
||||||
<div class="flex flex-col sm:flex-row space-y-2 sm:space-y-0 sm:space-x-4">
|
<!-- 좌측: 필터 컨트롤 -->
|
||||||
<div>
|
<div class="lg:col-span-2 space-y-3">
|
||||||
<label class="block text-sm font-medium text-white/80 mb-1">조회기간</label>
|
<div class="grid grid-cols-1 sm:grid-cols-3 gap-3">
|
||||||
<div class="flex space-x-2">
|
<div>
|
||||||
<input type="date" id="startDate" class="bg-white/20 border border-white/30 rounded-md px-3 py-2 text-white placeholder-white/60 focus:outline-none focus:ring-2 focus:ring-white/50 focus:border-transparent text-sm">
|
<label class="block text-xs font-medium text-white/80 mb-1">조회기간</label>
|
||||||
<span class="flex items-center text-white/60">~</span>
|
<div class="flex space-x-1">
|
||||||
<input type="date" id="endDate" class="bg-white/20 border border-white/30 rounded-md px-3 py-2 text-white placeholder-white/60 focus:outline-none focus:ring-2 focus:ring-white/50 focus:border-transparent text-sm">
|
<input type="date" id="startDate" class="flex-1 bg-white/20 border border-white/30 rounded-md px-2 py-1 text-white placeholder-white/60 focus:outline-none focus:ring-1 focus:ring-white/50 focus:border-transparent text-xs">
|
||||||
|
<span class="flex items-center text-white/60 text-xs">~</span>
|
||||||
|
<input type="date" id="endDate" class="flex-1 bg-white/20 border border-white/30 rounded-md px-2 py-1 text-white placeholder-white/60 focus:outline-none focus:ring-1 focus:ring-white/50 focus:border-transparent text-xs">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-xs font-medium text-white/80 mb-1">상태</label>
|
||||||
|
<select id="statusFilter" class="w-full bg-white/20 border border-white/30 rounded-md px-2 py-1 text-white focus:outline-none focus:ring-1 focus:ring-white/50 focus:border-transparent text-xs">
|
||||||
|
<option value="">전체</option>
|
||||||
|
<option value="진행중">진행중</option>
|
||||||
|
<option value="완료">완료</option>
|
||||||
|
<option value="대기">대기</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-xs font-medium text-white/80 mb-1">타입</label>
|
||||||
|
<select id="typeFilter" class="w-full bg-white/20 border border-white/30 rounded-md px-2 py-1 text-white focus:outline-none focus:ring-1 focus:ring-white/50 focus:border-transparent text-xs">
|
||||||
|
<option value="">전체</option>
|
||||||
|
<option value="개발">개발</option>
|
||||||
|
<option value="유지보수">유지보수</option>
|
||||||
|
<option value="분석">분석</option>
|
||||||
|
<option value="테스트">테스트</option>
|
||||||
|
<option value="문서작업">문서작업</option>
|
||||||
|
<option value="회의">회의</option>
|
||||||
|
<option value="기타">기타</option>
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||||
<label class="block text-sm font-medium text-white/80 mb-1">상태</label>
|
<div>
|
||||||
<select id="statusFilter" class="bg-white/20 border border-white/30 rounded-md px-3 py-2 text-white focus:outline-none focus:ring-2 focus:ring-white/50 focus:border-transparent">
|
<label class="block text-xs font-medium text-white/80 mb-1">사용자</label>
|
||||||
<option value="">전체</option>
|
<select id="userFilter" class="w-full bg-white/20 border border-white/30 rounded-md px-2 py-1 text-white focus:outline-none focus:ring-1 focus:ring-white/50 focus:border-transparent text-xs">
|
||||||
<option value="진행중">진행중</option>
|
<option value="">전체</option>
|
||||||
<option value="완료">완료</option>
|
</select>
|
||||||
<option value="대기">대기</option>
|
</div>
|
||||||
</select>
|
<div>
|
||||||
|
<label class="block text-xs font-medium text-white/80 mb-1">프로젝트</label>
|
||||||
|
<select id="projectFilter" class="w-full bg-white/20 border border-white/30 rounded-md px-2 py-1 text-white focus:outline-none focus:ring-1 focus:ring-white/50 focus:border-transparent text-xs">
|
||||||
|
<option value="">전체</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||||
<label class="block text-sm font-medium text-white/80 mb-1">프로젝트</label>
|
<div>
|
||||||
<select id="projectFilter" class="bg-white/20 border border-white/30 rounded-md px-3 py-2 text-white focus:outline-none focus:ring-2 focus:ring-white/50 focus:border-transparent">
|
<label class="block text-xs font-medium text-white/80 mb-1">검색</label>
|
||||||
<option value="">전체</option>
|
<input type="text" id="searchInput" placeholder="업무 내용 검색..." class="w-full bg-white/20 border border-white/30 rounded-md px-2 py-1 text-white placeholder-white/60 focus:outline-none focus:ring-1 focus:ring-white/50 focus:border-transparent text-xs">
|
||||||
</select>
|
</div>
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label class="block text-sm font-medium text-white/80 mb-1">검색</label>
|
|
||||||
<input type="text" id="searchInput" placeholder="업무 내용 검색..." class="bg-white/20 border border-white/30 rounded-md px-3 py-2 text-white placeholder-white/60 focus:outline-none focus:ring-2 focus:ring-white/50 focus:border-transparent">
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex space-x-2">
|
|
||||||
<button id="clearFilterBtn" class="glass-effect hover:bg-white/30 text-white px-4 py-2 rounded-md flex items-center transition-colors">
|
<!-- 우측: 액션 버튼들 -->
|
||||||
<i data-feather="x" class="w-4 h-4 mr-2"></i>
|
<div class="flex flex-col space-y-2 justify-center">
|
||||||
필터 초기화
|
<button id="addJobBtn" class="bg-primary-500 hover:bg-primary-600 text-white px-4 py-2 rounded-md flex items-center justify-center transition-colors" title="업무일지 추가">
|
||||||
|
<i data-feather="plus" class="w-4 h-4 mr-2"></i>
|
||||||
|
업무일지 추가
|
||||||
</button>
|
</button>
|
||||||
<button id="exportBtn" class="glass-effect hover:bg-white/30 text-white px-4 py-2 rounded-md flex items-center transition-colors">
|
<button id="exportBtn" class="bg-green-500 hover:bg-green-600 text-white px-4 py-2 rounded-md flex items-center justify-center transition-colors" title="엑셀 다운로드">
|
||||||
<i data-feather="download" class="w-4 h-4 mr-2"></i>
|
<i data-feather="download" class="w-4 h-4 mr-2"></i>
|
||||||
엑셀 다운로드
|
엑셀 다운로드
|
||||||
</button>
|
</button>
|
||||||
|
<button id="clearFilterBtn" class="bg-white/20 hover:bg-white/30 text-white px-4 py-2 rounded-md flex items-center justify-center transition-colors" title="필터 초기화">
|
||||||
|
<i data-feather="refresh-cw" class="w-4 h-4 mr-2"></i>
|
||||||
|
필터 초기화
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -406,22 +457,33 @@
|
|||||||
return `
|
return `
|
||||||
<div class="container mx-auto px-4">
|
<div class="container mx-auto px-4">
|
||||||
<div class="flex items-center justify-between h-16">
|
<div class="flex items-center justify-between h-16">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center space-x-8">
|
||||||
<h2 class="text-xl font-bold text-white">GroupWare</h2>
|
<div class="flex items-center space-x-2">
|
||||||
</div>
|
<svg class="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<div class="hidden md:flex items-center space-x-8">
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path>
|
||||||
${visibleItems.map(item => this.getMenuItemHTML(item)).join('')}
|
|
||||||
</div>
|
|
||||||
<div class="md:hidden">
|
|
||||||
<button id="mobile-menu-button" class="text-white/80 hover:text-white transition-colors p-2">
|
|
||||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
|
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
<span class="text-xl font-bold text-white">GroupWare</span>
|
||||||
|
</div>
|
||||||
|
<nav class="hidden md:flex space-x-1">
|
||||||
|
${visibleItems.map(item => `
|
||||||
|
<a href="${item.url}" class="px-3 py-2 rounded-md text-sm font-medium transition-colors ${
|
||||||
|
this.currentPage === item.key
|
||||||
|
? 'bg-white/20 text-white'
|
||||||
|
: 'text-white/60 hover:text-white hover:bg-white/10'
|
||||||
|
}">
|
||||||
|
<svg class="w-4 h-4 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="${item.icon}"></path>
|
||||||
|
</svg>
|
||||||
|
${item.title}
|
||||||
|
</a>
|
||||||
|
`).join('')}
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center space-x-4">
|
||||||
|
<div class="text-sm text-white/60">
|
||||||
|
<span id="currentUser">사용자</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div id="mobile-menu" class="md:hidden hidden border-t border-white/10 pt-4 pb-4">
|
|
||||||
${visibleItems.map(item => this.getMobileMenuItemHTML(item)).join('')}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@@ -488,6 +550,19 @@
|
|||||||
initNavigation('jobreport');
|
initNavigation('jobreport');
|
||||||
initializeApp();
|
initializeApp();
|
||||||
loadJobData();
|
loadJobData();
|
||||||
|
|
||||||
|
// 새로운 위치의 버튼들에 이벤트 리스너 추가
|
||||||
|
setTimeout(() => {
|
||||||
|
const addJobBtn = document.getElementById('addJobBtn');
|
||||||
|
const exportBtn = document.getElementById('exportBtn');
|
||||||
|
|
||||||
|
if (addJobBtn) {
|
||||||
|
addJobBtn.addEventListener('click', showAddJobModal);
|
||||||
|
}
|
||||||
|
if (exportBtn) {
|
||||||
|
exportBtn.addEventListener('click', exportToExcel);
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
});
|
});
|
||||||
|
|
||||||
function initializeApp() {
|
function initializeApp() {
|
||||||
@@ -499,10 +574,16 @@
|
|||||||
document.getElementById('startDate').value = twoWeeksAgo;
|
document.getElementById('startDate').value = twoWeeksAgo;
|
||||||
document.getElementById('endDate').value = today;
|
document.getElementById('endDate').value = today;
|
||||||
|
|
||||||
|
// 사용자 목록 로드
|
||||||
|
console.log('사용자 목록 로드 시작...');
|
||||||
|
loadUserList();
|
||||||
|
|
||||||
// 이벤트 리스너 등록
|
// 이벤트 리스너 등록
|
||||||
document.getElementById('startDate').addEventListener('change', loadJobData);
|
document.getElementById('startDate').addEventListener('change', loadJobData);
|
||||||
document.getElementById('endDate').addEventListener('change', loadJobData);
|
document.getElementById('endDate').addEventListener('change', loadJobData);
|
||||||
document.getElementById('statusFilter').addEventListener('change', filterData);
|
document.getElementById('statusFilter').addEventListener('change', filterData);
|
||||||
|
document.getElementById('typeFilter').addEventListener('change', filterData);
|
||||||
|
document.getElementById('userFilter').addEventListener('change', loadJobData);
|
||||||
document.getElementById('projectFilter').addEventListener('change', filterData);
|
document.getElementById('projectFilter').addEventListener('change', filterData);
|
||||||
document.getElementById('searchInput').addEventListener('input', filterData);
|
document.getElementById('searchInput').addEventListener('input', filterData);
|
||||||
document.getElementById('clearFilterBtn').addEventListener('click', clearFilters);
|
document.getElementById('clearFilterBtn').addEventListener('click', clearFilters);
|
||||||
@@ -524,7 +605,6 @@
|
|||||||
renderTable();
|
renderTable();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
document.getElementById('exportBtn').addEventListener('click', exportToExcel);
|
|
||||||
document.getElementById('closeModal').addEventListener('click', closeModal);
|
document.getElementById('closeModal').addEventListener('click', closeModal);
|
||||||
|
|
||||||
// 정렬 이벤트 리스너
|
// 정렬 이벤트 리스너
|
||||||
@@ -561,12 +641,14 @@
|
|||||||
// 조회기간 파라미터 가져오기
|
// 조회기간 파라미터 가져오기
|
||||||
const startDate = document.getElementById('startDate').value;
|
const startDate = document.getElementById('startDate').value;
|
||||||
const endDate = document.getElementById('endDate').value;
|
const endDate = document.getElementById('endDate').value;
|
||||||
|
const selectedUser = document.getElementById('userFilter').value;
|
||||||
|
|
||||||
// API URL 구성 - 새로운 GetJobData API 사용
|
// API URL 구성 - 새로운 GetJobData API 사용
|
||||||
let url = '/Jobreport/GetJobData';
|
let url = '/Jobreport/GetJobData';
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
if (startDate) params.append('startDate', startDate);
|
if (startDate) params.append('startDate', startDate);
|
||||||
if (endDate) params.append('endDate', endDate);
|
if (endDate) params.append('endDate', endDate);
|
||||||
|
if (selectedUser) params.append('user', selectedUser);
|
||||||
|
|
||||||
if (params.toString()) {
|
if (params.toString()) {
|
||||||
url += '?' + params.toString();
|
url += '?' + params.toString();
|
||||||
@@ -684,20 +766,62 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function loadUserList() {
|
||||||
|
const userSelect = document.getElementById('userFilter');
|
||||||
|
|
||||||
|
// 기존 옵션 제거 (전체 옵션 제외)
|
||||||
|
while (userSelect.children.length > 1) {
|
||||||
|
userSelect.removeChild(userSelect.lastChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 서버에서 사용자 목록 가져오기
|
||||||
|
fetch('/Jobreport/GetUsers')
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP ${response.status}: 사용자 목록을 불러오는데 실패했습니다.`);
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
console.log('사용자 목록 데이터:', data);
|
||||||
|
if (data && Array.isArray(data)) {
|
||||||
|
data.forEach(user => {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = user.id;
|
||||||
|
option.textContent = `${user.name} [${user.id}] ${user.process}`;
|
||||||
|
userSelect.appendChild(option);
|
||||||
|
});
|
||||||
|
console.log('사용자 목록 로드 완료:', userSelect.children.length - 1, '명');
|
||||||
|
} else {
|
||||||
|
console.warn('사용자 목록 데이터가 배열이 아닙니다:', data);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('사용자 목록 로드 중 오류:', error);
|
||||||
|
// 에러 시 기본 사용자 옵션 추가
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = FCOMMON?.info?.Login?.no || '';
|
||||||
|
option.textContent = '현재 사용자';
|
||||||
|
userSelect.appendChild(option);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function filterData() {
|
function filterData() {
|
||||||
const statusFilter = document.getElementById('statusFilter').value;
|
const statusFilter = document.getElementById('statusFilter').value;
|
||||||
|
const typeFilter = document.getElementById('typeFilter').value;
|
||||||
const projectFilter = document.getElementById('projectFilter').value;
|
const projectFilter = document.getElementById('projectFilter').value;
|
||||||
const searchTerm = document.getElementById('searchInput').value.toLowerCase();
|
const searchTerm = document.getElementById('searchInput').value.toLowerCase();
|
||||||
|
|
||||||
filteredData = jobData.filter(item => {
|
filteredData = jobData.filter(item => {
|
||||||
const statusMatch = !statusFilter || item.status === statusFilter;
|
const statusMatch = !statusFilter || item.status === statusFilter;
|
||||||
|
const typeMatch = !typeFilter || item.type === typeFilter;
|
||||||
const projectMatch = !projectFilter || item.projectName === projectFilter;
|
const projectMatch = !projectFilter || item.projectName === projectFilter;
|
||||||
const searchMatch = !searchTerm ||
|
const searchMatch = !searchTerm ||
|
||||||
(item.description && item.description.toLowerCase().includes(searchTerm)) ||
|
(item.description && item.description.toLowerCase().includes(searchTerm)) ||
|
||||||
(item.projectName && item.projectName.toLowerCase().includes(searchTerm)) ||
|
(item.projectName && item.projectName.toLowerCase().includes(searchTerm)) ||
|
||||||
(item.requestpart && item.requestpart.toLowerCase().includes(searchTerm));
|
(item.requestpart && item.requestpart.toLowerCase().includes(searchTerm));
|
||||||
|
|
||||||
return statusMatch && projectMatch && searchMatch;
|
return statusMatch && typeMatch && projectMatch && searchMatch;
|
||||||
});
|
});
|
||||||
|
|
||||||
currentPage = 1;
|
currentPage = 1;
|
||||||
@@ -862,6 +986,112 @@
|
|||||||
document.getElementById('nextPage').disabled = currentPage >= maxPage;
|
document.getElementById('nextPage').disabled = currentPage >= maxPage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function showAddJobModal() {
|
||||||
|
const modal = document.getElementById('detailModal');
|
||||||
|
const content = document.getElementById('modalContent');
|
||||||
|
|
||||||
|
// 오늘 날짜를 기본값으로 설정
|
||||||
|
const today = new Date().toISOString().split('T')[0];
|
||||||
|
|
||||||
|
content.innerHTML = `
|
||||||
|
<form id="editJobForm" class="space-y-6">
|
||||||
|
<input type="hidden" id="editIdx" value="">
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 lg:grid-cols-5 gap-8">
|
||||||
|
<!-- 좌측: 기본 정보 (2열) -->
|
||||||
|
<div class="lg:col-span-2">
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<label class="block text-white/70 text-sm font-medium mb-2">날짜 *</label>
|
||||||
|
<input type="date" id="editPdate" value="${today}"
|
||||||
|
class="w-full bg-white/20 backdrop-blur-sm border border-white/30 rounded-lg px-4 py-2 text-white focus:outline-none focus:ring-2 focus:ring-primary-400 transition-all" required>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-white/70 text-sm font-medium mb-2">상태 *</label>
|
||||||
|
<select id="editStatus" class="w-full bg-white/20 backdrop-blur-sm border border-white/30 rounded-lg px-4 py-2 text-white focus:outline-none focus:ring-2 focus:ring-primary-400 transition-all" required>
|
||||||
|
<option value="">선택하세요</option>
|
||||||
|
<option value="진행 중" selected>진행 중</option>
|
||||||
|
<option value="완료">완료</option>
|
||||||
|
<option value="대기">대기</option>
|
||||||
|
<option value="보류">보류</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-white/70 text-sm font-medium mb-2">요청부서</label>
|
||||||
|
<input type="text" id="editRequestpart" value=""
|
||||||
|
class="w-full bg-white/20 backdrop-blur-sm border border-white/30 rounded-lg px-4 py-2 text-white placeholder-white/50 focus:outline-none focus:ring-2 focus:ring-primary-400 transition-all">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-white/70 text-sm font-medium mb-2">타입</label>
|
||||||
|
<select id="editType" class="w-full bg-white/20 backdrop-blur-sm border border-white/30 rounded-lg px-4 py-2 text-white focus:outline-none focus:ring-2 focus:ring-primary-400 transition-all">
|
||||||
|
<option value="">선택하세요</option>
|
||||||
|
<option value="개발">개발</option>
|
||||||
|
<option value="유지보수">유지보수</option>
|
||||||
|
<option value="분석">분석</option>
|
||||||
|
<option value="테스트">테스트</option>
|
||||||
|
<option value="문서작업">문서작업</option>
|
||||||
|
<option value="회의">회의</option>
|
||||||
|
<option value="기타">기타</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-white/70 text-sm font-medium mb-2">근무시간 (시간) *</label>
|
||||||
|
<input type="number" id="editHrs" value="8" step="any"
|
||||||
|
class="w-full bg-white/20 backdrop-blur-sm border border-white/30 rounded-lg px-4 py-2 text-white focus:outline-none focus:ring-2 focus:ring-primary-400 transition-all" required>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-white/70 text-sm font-medium mb-2">초과근무 (시간)</label>
|
||||||
|
<input type="number" id="editOt" value="" step="any"
|
||||||
|
class="w-full bg-white/20 backdrop-blur-sm border border-white/30 rounded-lg px-4 py-2 text-white focus:outline-none focus:ring-2 focus:ring-primary-400 transition-all">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-white/70 text-sm font-medium mb-2">초과근무 시작시간</label>
|
||||||
|
<input type="time" id="editOtStart" value=""
|
||||||
|
class="w-full bg-white/20 backdrop-blur-sm border border-white/30 rounded-lg px-4 py-2 text-white focus:outline-none focus:ring-2 focus:ring-primary-400 transition-all">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-white/70 text-sm font-medium mb-2">초과근무 종료시간</label>
|
||||||
|
<input type="time" id="editOtEnd" value=""
|
||||||
|
class="w-full bg-white/20 backdrop-blur-sm border border-white/30 rounded-lg px-4 py-2 text-white focus:outline-none focus:ring-2 focus:ring-primary-400 transition-all">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 우측: 프로젝트명과 업무내용 -->
|
||||||
|
<div class="lg:col-span-3 space-y-4">
|
||||||
|
<div>
|
||||||
|
<label class="block text-white/70 text-sm font-medium mb-2">프로젝트명 *</label>
|
||||||
|
<input type="text" id="editProjectName" value=""
|
||||||
|
class="w-full bg-white/20 backdrop-blur-sm border border-white/30 rounded-lg px-4 py-2 text-white placeholder-white/50 focus:outline-none focus:ring-2 focus:ring-primary-400 transition-all" required>
|
||||||
|
</div>
|
||||||
|
<div class="flex-grow">
|
||||||
|
<label class="block text-white/70 text-sm font-medium mb-2">업무내용 *</label>
|
||||||
|
<textarea id="editDescription" rows="15" required
|
||||||
|
class="w-full h-full min-h-[360px] bg-white/20 backdrop-blur-sm border border-white/30 rounded-lg px-4 py-2 text-white placeholder-white/50 focus:outline-none focus:ring-2 focus:ring-primary-400 transition-all resize-vertical"
|
||||||
|
placeholder="상세한 업무 내용을 입력하세요..."></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<!-- 모달 푸터 -->
|
||||||
|
<div class="px-6 py-4 border-t border-white/10 flex justify-end space-x-3 bg-black/10 rounded-b-2xl">
|
||||||
|
<button type="button" onclick="closeModal()" class="bg-white/20 hover:bg-white/30 text-white px-4 py-2 rounded-lg transition-colors">
|
||||||
|
취소
|
||||||
|
</button>
|
||||||
|
<button type="submit" form="editJobForm" class="bg-primary-500 hover:bg-primary-600 text-white px-6 py-2 rounded-lg transition-colors">
|
||||||
|
저장
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
modal.classList.remove('hidden');
|
||||||
|
feather.replace();
|
||||||
|
|
||||||
|
// 폼 제출 이벤트 리스너 추가
|
||||||
|
document.getElementById('editJobForm').addEventListener('submit', handleAddSubmit);
|
||||||
|
}
|
||||||
|
|
||||||
async function showDetailModal(item) {
|
async function showDetailModal(item) {
|
||||||
// 문자열로 전달된 경우 JSON 파싱
|
// 문자열로 전달된 경우 JSON 파싱
|
||||||
if (typeof item === 'string') {
|
if (typeof item === 'string') {
|
||||||
@@ -970,16 +1200,18 @@
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
<!-- 모달 푸터 -->
|
<!-- 모달 푸터 -->
|
||||||
<div class="px-6 py-4 border-t border-white/10 flex justify-end space-x-3 bg-black/10 rounded-b-2xl">
|
<div class="px-6 py-4 border-t border-white/10 flex justify-between items-center bg-black/10 rounded-b-2xl">
|
||||||
<button type="button" id="deleteJobBtn" class="bg-danger-500 hover:bg-danger-600 text-white px-4 py-2 rounded-lg transition-colors">
|
<button type="button" id="deleteJobBtn" class="bg-danger-500 hover:bg-danger-600 text-white px-4 py-2 rounded-lg transition-colors">
|
||||||
삭제
|
삭제
|
||||||
</button>
|
</button>
|
||||||
<button type="button" onclick="closeModal()" class="bg-white/20 hover:bg-white/30 text-white px-4 py-2 rounded-lg transition-colors">
|
<div class="flex space-x-3">
|
||||||
취소
|
<button type="button" onclick="closeModal()" class="bg-white/20 hover:bg-white/30 text-white px-4 py-2 rounded-lg transition-colors">
|
||||||
</button>
|
취소
|
||||||
<button type="submit" form="editJobForm" class="bg-primary-500 hover:bg-primary-600 text-white px-6 py-2 rounded-lg transition-colors">
|
</button>
|
||||||
저장
|
<button type="submit" form="editJobForm" class="bg-primary-500 hover:bg-primary-600 text-white px-6 py-2 rounded-lg transition-colors">
|
||||||
</button>
|
저장
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -995,6 +1227,61 @@
|
|||||||
document.getElementById('detailModal').classList.add('hidden');
|
document.getElementById('detailModal').classList.add('hidden');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleAddSubmit(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
// URLSearchParams 사용 (application/x-www-form-urlencoded)
|
||||||
|
const formData = new URLSearchParams();
|
||||||
|
formData.append('pdate', document.getElementById('editPdate').value);
|
||||||
|
formData.append('status', document.getElementById('editStatus').value);
|
||||||
|
formData.append('projectName', document.getElementById('editProjectName').value);
|
||||||
|
formData.append('requestpart', document.getElementById('editRequestpart').value);
|
||||||
|
formData.append('type', document.getElementById('editType').value);
|
||||||
|
formData.append('hrs', document.getElementById('editHrs').value);
|
||||||
|
formData.append('ot', document.getElementById('editOt').value);
|
||||||
|
formData.append('otStart', document.getElementById('editOtStart').value);
|
||||||
|
formData.append('otEnd', document.getElementById('editOtEnd').value);
|
||||||
|
formData.append('description', document.getElementById('editDescription').value);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/Jobreport/Add', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded'
|
||||||
|
},
|
||||||
|
body: formData
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP ${response.status}: 추가에 실패했습니다.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await response.text();
|
||||||
|
console.log('Add result:', result);
|
||||||
|
|
||||||
|
// JSON 응답인지 확인
|
||||||
|
try {
|
||||||
|
const jsonResult = JSON.parse(result);
|
||||||
|
if (!jsonResult.success) {
|
||||||
|
throw new Error(jsonResult.message || '추가에 실패했습니다.');
|
||||||
|
}
|
||||||
|
// 성공시 alert 없이 바로 진행
|
||||||
|
} catch (parseError) {
|
||||||
|
// JSON이 아닌 경우도 성공으로 처리 (alert 없음)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 모달 닫기
|
||||||
|
closeModal();
|
||||||
|
|
||||||
|
// 데이터 새로고침
|
||||||
|
await loadJobData();
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error adding job:', error);
|
||||||
|
alert('업무일지 추가 중 오류가 발생했습니다: ' + error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function handleEditSubmit(event) {
|
async function handleEditSubmit(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
@@ -1202,6 +1489,8 @@
|
|||||||
|
|
||||||
// 다른 필터들 초기화
|
// 다른 필터들 초기화
|
||||||
document.getElementById('statusFilter').value = '';
|
document.getElementById('statusFilter').value = '';
|
||||||
|
document.getElementById('typeFilter').value = '';
|
||||||
|
document.getElementById('userFilter').value = '';
|
||||||
document.getElementById('projectFilter').value = '';
|
document.getElementById('projectFilter').value = '';
|
||||||
document.getElementById('searchInput').value = '';
|
document.getElementById('searchInput').value = '';
|
||||||
|
|
||||||
|
|||||||
@@ -140,7 +140,7 @@
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="gradient-bg min-h-screen">
|
<body class="bg-gradient-to-br from-blue-900 via-purple-900 to-indigo-900 min-h-screen text-white">
|
||||||
<div class="container mx-auto px-4 py-8">
|
<div class="container mx-auto px-4 py-8">
|
||||||
<!-- 개발중 경고 메시지 -->
|
<!-- 개발중 경고 메시지 -->
|
||||||
<div class="bg-orange-500 rounded-lg p-4 mb-6 border-l-4 border-orange-700 animate-slide-up shadow-lg">
|
<div class="bg-orange-500 rounded-lg p-4 mb-6 border-l-4 border-orange-700 animate-slide-up shadow-lg">
|
||||||
@@ -489,29 +489,33 @@
|
|||||||
return `
|
return `
|
||||||
<div class="container mx-auto px-4">
|
<div class="container mx-auto px-4">
|
||||||
<div class="flex items-center justify-between h-16">
|
<div class="flex items-center justify-between h-16">
|
||||||
<!-- 로고/타이틀 -->
|
<div class="flex items-center space-x-8">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center space-x-2">
|
||||||
<h2 class="text-xl font-bold text-white">GroupWare</h2>
|
<svg class="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
</div>
|
<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>
|
||||||
|
|
||||||
<!-- 메뉴 -->
|
|
||||||
<div class="hidden md:flex items-center space-x-8">
|
|
||||||
${visibleItems.map(item => this.getMenuItemHTML(item)).join('')}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 모바일 메뉴 버튼 -->
|
|
||||||
<div class="md:hidden">
|
|
||||||
<button id="mobile-menu-button" class="text-white/80 hover:text-white transition-colors p-2">
|
|
||||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
|
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
<span class="text-xl font-bold text-white">GroupWare</span>
|
||||||
|
</div>
|
||||||
|
<nav class="hidden md:flex space-x-1">
|
||||||
|
${visibleItems.map(item => `
|
||||||
|
<a href="${item.url}" class="px-3 py-2 rounded-md text-sm font-medium transition-colors ${
|
||||||
|
this.currentPage === item.key
|
||||||
|
? 'bg-white/20 text-white'
|
||||||
|
: 'text-white/60 hover:text-white hover:bg-white/10'
|
||||||
|
}">
|
||||||
|
<svg class="w-4 h-4 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="${item.icon}"></path>
|
||||||
|
</svg>
|
||||||
|
${item.title}
|
||||||
|
</a>
|
||||||
|
`).join('')}
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center space-x-4">
|
||||||
|
<div class="text-sm text-white/60">
|
||||||
|
<span id="currentUser">사용자</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 모바일 메뉴 -->
|
|
||||||
<div id="mobile-menu" class="md:hidden hidden border-t border-white/10 pt-4 pb-4">
|
|
||||||
${visibleItems.map(item => this.getMobileMenuItemHTML(item)).join('')}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -119,7 +119,7 @@
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="gradient-bg min-h-screen">
|
<body class="bg-gradient-to-br from-blue-900 via-purple-900 to-indigo-900 min-h-screen text-white">
|
||||||
<div class="container mx-auto px-4 py-8">
|
<div class="container mx-auto px-4 py-8">
|
||||||
<!-- 할일 목록 -->
|
<!-- 할일 목록 -->
|
||||||
<div class="glass-effect rounded-2xl overflow-hidden animate-slide-up">
|
<div class="glass-effect rounded-2xl overflow-hidden animate-slide-up">
|
||||||
@@ -410,40 +410,45 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
getNavigationHTML() {
|
getNavigationHTML() {
|
||||||
|
const menuItems = [
|
||||||
|
{ key: 'dashboard', title: '대시보드', url: '/Dashboard/', icon: 'M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2H5a2 2 0 00-2-2z M8 5a2 2 0 012-2h4a2 2 0 012 2v2H8V5z' },
|
||||||
|
{ key: 'common', title: '공용코드', url: '/Common', icon: 'M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z' },
|
||||||
|
{ key: 'jobreport', title: '업무일지', url: '/Jobreport/', icon: 'M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2' },
|
||||||
|
{ key: 'kuntae', title: '근태관리', url: '/Kuntae/', icon: 'M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z' },
|
||||||
|
{ key: 'todo', title: '할일관리', url: '/Todo/', icon: 'M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2M12 12l2 2 4-4' },
|
||||||
|
{ key: 'project', title: '프로젝트', url: '/Project/', icon: 'M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10' }
|
||||||
|
];
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="container mx-auto px-4">
|
<div class="container mx-auto px-4">
|
||||||
<div class="flex items-center justify-between h-16">
|
<div class="flex items-center justify-between h-16">
|
||||||
<!-- 로고/타이틀 -->
|
<div class="flex items-center space-x-8">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center space-x-2">
|
||||||
<h2 class="text-xl font-bold text-white">GroupWare</h2>
|
<svg class="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
</div>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2M12 12l2 2 4-4"></path>
|
||||||
|
|
||||||
<!-- 메뉴 -->
|
|
||||||
<div class="hidden md:flex items-center space-x-8">
|
|
||||||
${this.getMenuItemHTML('dashboard', '/Dashboard/', '대시보드', 'M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2H5a2 2 0 00-2-2z M8 5a2 2 0 012-2h4a2 2 0 012 2v2H8V5z')}
|
|
||||||
${this.getMenuItemHTML('common', '/Common', '공용코드', 'M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z')}
|
|
||||||
${this.getMenuItemHTML('jobreport', '/Jobreport/', '업무일지', 'M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2')}
|
|
||||||
${this.getMenuItemHTML('kuntae', '/Kuntae/', '근태관리', 'M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z')}
|
|
||||||
${this.getMenuItemHTML('todo', '/Todo/', '할일관리', 'M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2M12 12l2 2 4-4')}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 모바일 메뉴 버튼 -->
|
|
||||||
<div class="md:hidden">
|
|
||||||
<button id="mobile-menu-button" class="text-white/80 hover:text-white transition-colors p-2">
|
|
||||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
|
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
<span class="text-xl font-bold text-white">GroupWare</span>
|
||||||
|
</div>
|
||||||
|
<nav class="hidden md:flex space-x-1">
|
||||||
|
${menuItems.map(item => `
|
||||||
|
<a href="${item.url}" class="px-3 py-2 rounded-md text-sm font-medium transition-colors ${
|
||||||
|
this.currentPage === item.key
|
||||||
|
? 'bg-white/20 text-white'
|
||||||
|
: 'text-white/60 hover:text-white hover:bg-white/10'
|
||||||
|
}">
|
||||||
|
<svg class="w-4 h-4 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="${item.icon}"></path>
|
||||||
|
</svg>
|
||||||
|
${item.title}
|
||||||
|
</a>
|
||||||
|
`).join('')}
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center space-x-4">
|
||||||
|
<div class="text-sm text-white/60">
|
||||||
|
<span id="currentUser">사용자</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 모바일 메뉴 -->
|
|
||||||
<div id="mobile-menu" class="md:hidden hidden border-t border-white/10 pt-4 pb-4">
|
|
||||||
${this.getMobileMenuItemHTML('dashboard', '/Dashboard/', '대시보드', 'M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2H5a2 2 0 00-2-2z M8 5a2 2 0 012-2h4a2 2 0 012 2v2H8V5z')}
|
|
||||||
${this.getMobileMenuItemHTML('common', '/Common', '공용코드', 'M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z')}
|
|
||||||
${this.getMobileMenuItemHTML('jobreport', '/Jobreport/', '업무일지', 'M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2')}
|
|
||||||
${this.getMobileMenuItemHTML('kuntae', '/Kuntae/', '근태관리', 'M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z')}
|
|
||||||
${this.getMobileMenuItemHTML('todo', '/Todo/', '할일관리', 'M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2M12 12l2 2 4-4')}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -351,7 +351,6 @@ namespace Project
|
|||||||
Menu_Log();
|
Menu_Log();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void listToolStripMenuItem_Click(object sender, EventArgs e)
|
private void listToolStripMenuItem_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
menu_projecT_list();
|
menu_projecT_list();
|
||||||
@@ -458,12 +457,12 @@ namespace Project
|
|||||||
|
|
||||||
private void codesToolStripMenuItem_Click(object sender, EventArgs e)
|
private void codesToolStripMenuItem_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (Pub.InitWebView > 0 && System.Diagnostics.Debugger.IsAttached)
|
//if (Pub.InitWebView > 0 && System.Diagnostics.Debugger.IsAttached)
|
||||||
{
|
//{
|
||||||
var f = new Dialog.fCommon();
|
// var f = new Dialog.fCommon();
|
||||||
f.ShowDialog();
|
// f.ShowDialog();
|
||||||
}
|
//}
|
||||||
else
|
//else
|
||||||
{
|
{
|
||||||
var f = new FCM0000.fCode();
|
var f = new FCM0000.fCode();
|
||||||
f.ShowDialog();
|
f.ShowDialog();
|
||||||
|
|||||||
@@ -622,7 +622,7 @@
|
|||||||
this.Controls.Add(this.bn);
|
this.Controls.Add(this.bn);
|
||||||
this.Name = "fJobReportAI";
|
this.Name = "fJobReportAI";
|
||||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||||
this.Text = "업무일지 - 자동입력 양식";
|
this.Text = "기타업무등록";
|
||||||
this.Load += new System.EventHandler(this.@__Load);
|
this.Load += new System.EventHandler(this.@__Load);
|
||||||
((System.ComponentModel.ISupportInitialize)(this.bn)).EndInit();
|
((System.ComponentModel.ISupportInitialize)(this.bn)).EndInit();
|
||||||
this.bn.ResumeLayout(false);
|
this.bn.ResumeLayout(false);
|
||||||
|
|||||||
5936
SubProject/FPJ0000/dsPRJ.Designer.cs
generated
5936
SubProject/FPJ0000/dsPRJ.Designer.cs
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user