프로젝트 시스템 통합 및 전반적인 개선사항

- 솔루션 설정 및 프로젝트 파일 업데이트
- 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:
ChiKyun Kim
2025-08-04 15:23:41 +09:00
parent a11780f725
commit 81f91f0897
15 changed files with 3766 additions and 3319 deletions

View File

@@ -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": []
} }

View File

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

View File

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

View File

@@ -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): 현재 실행을 취소",

View File

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

View File

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

View File

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

View File

@@ -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">
<div class="glass-effect rounded-2xl h-full card-hover animate-slide-up flex flex-col">
<div class="p-4 border-b border-white/10">
<h3 class="text-lg font-semibold text-white flex items-center">
<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> </svg>
<div> 코드그룹 목록
<p class="text-white font-bold text-base">🚧 개발중인 기능입니다</p> </h3>
<p class="text-orange-100 text-sm font-medium">일부 기능이 정상적으로 동작하지 않을 수 있습니다.</p> </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">
</div> <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>
<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> </svg>
조회 <span id="selectedGroupTitle">코드그룹을 선택하세요</span>
</button> </h3>
<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"> <p class="text-white/70 text-sm mt-1"><span id="recordCount" class="text-white font-medium">0</span></p>
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"> </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> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
</svg> </svg>
추가 추가
</button> </button>
</div> </div>
</div>
</div>
<!-- 데이터 테이블 --> <!-- 데이터 테이블 -->
<div class="glass-effect rounded-2xl overflow-hidden card-hover animate-slide-up"> <div class="flex-1 overflow-x-auto overflow-y-auto custom-scrollbar">
<div class="overflow-x-auto max-h-[calc(100vh-300px)] overflow-y-auto 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>
<th class="px-6 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">코드</th>
<th class="px-6 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="px-6 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="px-6 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="px-6 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="px-6 py-3 text-left text-xs font-medium text-white/70 uppercase tracking-wider">값2</th> <th class="w-24 px-4 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> </tr>
</thead> </thead>
<tbody id="dataTable" class="divide-y divide-white/10"> <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> </tbody>
</table> </table>
</div> </div>
<div class="px-6 py-3 border-t border-white/10 bg-white/5"> </div>
<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,7 +248,14 @@
</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="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">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
</svg>
삭제
</button>
<div 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 onclick="hideEditModal()" class="bg-white/20 hover:bg-white/30 text-white px-4 py-2 rounded-lg transition-colors">
취소 취소
</button> </button>
@@ -235,6 +266,7 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<!-- 삭제 확인 모달 --> <!-- 삭제 확인 모달 -->
<div id="deleteModal" class="fixed inset-0 bg-black/50 backdrop-blur-sm hidden z-50"> <div id="deleteModal" class="fixed inset-0 bg-black/50 backdrop-blur-sm hidden z-50">
@@ -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');
} }

View File

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

View File

@@ -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,20 +208,22 @@
<!-- 필터 및 검색 --> <!-- 필터 및 검색 -->
<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 class="lg:col-span-2 space-y-3">
<div class="grid grid-cols-1 sm:grid-cols-3 gap-3">
<div> <div>
<label class="block text-sm font-medium text-white/80 mb-1">조회기간</label> <label class="block text-xs font-medium text-white/80 mb-1">조회기간</label>
<div class="flex space-x-2"> <div class="flex space-x-1">
<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"> <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">~</span> <span class="flex items-center text-white/60 text-xs">~</span>
<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="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> </div>
<div> <div>
<label class="block text-sm font-medium text-white/80 mb-1">상태</label> <label class="block text-xs font-medium text-white/80 mb-1">상태</label>
<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"> <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>
<option value="완료">완료</option> <option value="완료">완료</option>
@@ -210,25 +231,55 @@
</select> </select>
</div> </div>
<div> <div>
<label class="block text-sm font-medium text-white/80 mb-1">프로젝트</label> <label class="block text-xs font-medium text-white/80 mb-1">타입</label>
<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"> <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 class="grid grid-cols-1 sm:grid-cols-2 gap-3">
<div>
<label class="block text-xs font-medium text-white/80 mb-1">사용자</label>
<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>
</select> </select>
</div> </div>
<div> <div>
<label class="block text-sm font-medium text-white/80 mb-1">검색</label> <label class="block text-xs 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"> <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="flex space-x-2"> <div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
<button id="clearFilterBtn" class="glass-effect hover:bg-white/30 text-white px-4 py-2 rounded-md flex items-center transition-colors"> <div>
<i data-feather="x" class="w-4 h-4 mr-2"></i> <label class="block text-xs font-medium text-white/80 mb-1">검색</label>
필터 초기화 <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">
</div>
</div>
</div>
<!-- 우측: 액션 버튼들 -->
<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,10 +1200,11 @@
</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>
<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 type="button" onclick="closeModal()" class="bg-white/20 hover:bg-white/30 text-white px-4 py-2 rounded-lg transition-colors">
취소 취소
</button> </button>
@@ -981,6 +1212,7 @@
저장 저장
</button> </button>
</div> </div>
</div>
`; `;
modal.classList.remove('hidden'); modal.classList.remove('hidden');
@@ -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 = '';

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff