fix : Front Page [Ver 0.1] - 견적 기능 작업 외 기타
- estimate.php - estimate_form.php
This commit is contained in:
@@ -1,32 +1,265 @@
|
||||
<?php $CURRENT_SECTION='approval'; include '../inc/header.php'; ?>
|
||||
<div class="container" style="max-width:1280px; margin-top:24px;">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<strong>결재권자 풀 관리</strong>
|
||||
<div class="d-flex gap-2">
|
||||
<select class="form-select form-select-sm" id="type" style="width:140px;">
|
||||
<option value="user">개인</option><option value="department">부서</option><option value="role">역할</option>
|
||||
</select>
|
||||
<input class="form-control form-control-sm" id="keyword" placeholder="이름/코드">
|
||||
<button class="btn btn-sm btn-outline-secondary" id="find">검색</button>
|
||||
<button class="btn btn-sm btn-primary" id="add">추가</button>
|
||||
<!-- SAM RULES: include=../inc/header.php; base=/tenant; width=1280; js=jQuery+BS5 -->
|
||||
<?php
|
||||
$CURRENT_SECTION = 'approval';
|
||||
include '../inc/header.php';
|
||||
?>
|
||||
<div class="container" style="max-width:1280px; margin-top:24px;">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<strong>결재권자 풀 관리</strong>
|
||||
|
||||
<!-- 한 줄 툴바 -->
|
||||
<form id="toolbar" class="sam-toolbar d-flex align-items-center gap-2 flex-nowrap" role="search" onsubmit="return false;">
|
||||
<select class="form-select form-select-sm" id="type" style="width:140px;">
|
||||
<option value="user">개인</option>
|
||||
<option value="department">부서</option>
|
||||
<option value="role">역할</option>
|
||||
</select>
|
||||
<input class="form-control form-control-sm" id="keyword" placeholder="이름/코드" style="width:260px;">
|
||||
<div class="sam-actions d-flex flex-nowrap gap-2">
|
||||
<button class="btn btn-sm btn-outline-secondary" id="find" type="button">검색</button>
|
||||
<button class="btn btn-sm btn-primary" id="add" type="button">추가</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<table class="table table-hover align-middle">
|
||||
<thead class="table-light"><tr><th>유형</th><th>대상</th><th style="width:120px;">관리</th></tr></thead>
|
||||
<tbody id="poolRows">
|
||||
<tr><td>부서</td><td>개발팀</td><td><button class="btn btn-sm btn-outline-danger">삭제</button></td></tr>
|
||||
<tr><td>역할</td><td>일반관리자</td><td><button class="btn btn-sm btn-outline-danger">삭제</button></td></tr>
|
||||
<tr><td>개인</td><td>kevin</td><td><button class="btn btn-sm btn-outline-danger">삭제</button></td></tr>
|
||||
</tbody>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle text-center m-0" id="poolTable">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th style="width:120px;">유형</th>
|
||||
<th>대상</th>
|
||||
<th style="width:140px;">관리</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="poolRows"><!-- JS 렌더 --></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
$(function(){
|
||||
$('#add').on('click', ()=>alert('추가(샘플) /api/approval/pool_add.php'));
|
||||
</div>
|
||||
|
||||
<!-- 검색/추가 모달 -->
|
||||
<div class="modal fade" id="pickModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg modal-dialog-centered modal-dialog-scrollable">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">대상 선택</h5>
|
||||
<button class="btn-close" data-bs-dismiss="modal" aria-label="닫기"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<!-- 검색바(모달 내부에서도 재검색 가능) -->
|
||||
<div class="d-flex align-items-center gap-2 mb-2">
|
||||
<select class="form-select form-select-sm" id="pm_type" style="width:140px;"></select>
|
||||
<input class="form-control form-control-sm" id="pm_keyword" placeholder="이름/코드">
|
||||
<button class="btn btn-sm btn-outline-secondary" id="pm_find" type="button">검색</button>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle text-center m-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th style="width:100px;">유형</th>
|
||||
<th>코드</th>
|
||||
<th>이름</th>
|
||||
<th style="width:120px;">선택</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="pm_rows"><!-- JS 렌더 --></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="text-muted small">선택 즉시 풀에 추가됩니다.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
/* 툴바 전체를 한 줄로 고정 */
|
||||
.sam-toolbar { display:flex; flex-wrap:nowrap; gap:.5rem; }
|
||||
.sam-toolbar > * { flex:0 0 auto; min-width:0; } /* 넓이 강제분배 방지 */
|
||||
|
||||
/* 버튼 한글 줄바꿈 방지 (전역 break-all 무력화) */
|
||||
.sam-toolbar .btn,
|
||||
.sam-actions .btn {
|
||||
display:inline-flex;
|
||||
align-items:center;
|
||||
justify-content:center;
|
||||
white-space:nowrap !important;
|
||||
word-break:normal !important; /* ← 핵심 */
|
||||
overflow-wrap:normal !important;/* ← 핵심 */
|
||||
line-height:1.5;
|
||||
padding:.25rem .6rem; /* btn-sm 기본과 유사 */
|
||||
flex:0 0 auto;
|
||||
}
|
||||
|
||||
/* 입력 폭이 너무 좁아 버튼을 밀어내지 않도록 적당한 폭 부여 */
|
||||
#keyword { width:260px; min-width:160px; }
|
||||
|
||||
/* 매우 좁은 화면일 때만 버튼 폭/패딩을 더 줄임 */
|
||||
@media (max-width: 576px){
|
||||
#keyword{ width:140px; }
|
||||
.sam-toolbar .btn{ padding:.2rem .45rem; }
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
$(function(){
|
||||
// -----------------------------
|
||||
// 1) 샘플 데이터 (실서버에선 API로 대체)
|
||||
// -----------------------------
|
||||
const SAMPLE = {
|
||||
user: [
|
||||
{ code:'u001', name:'권혁성(kevin)' },
|
||||
{ code:'u002', name:'김슬기(sally)' },
|
||||
{ code:'u003', name:'홍길동(hong)' },
|
||||
],
|
||||
department: [
|
||||
{ code:'d001', name:'개발팀' },
|
||||
{ code:'d002', name:'영업팀' },
|
||||
{ code:'d003', name:'생산팀' },
|
||||
],
|
||||
role: [
|
||||
{ code:'r001', name:'최고관리자' },
|
||||
{ code:'r002', name:'일반관리자' },
|
||||
{ code:'r003', name:'일반직원' },
|
||||
]
|
||||
};
|
||||
|
||||
// 결재권자 풀 (메모리) : {type, code, name}
|
||||
const pool = [
|
||||
{type:'department', code:'d001', name:'개발팀'},
|
||||
{type:'role', code:'r002', name:'일반관리자'},
|
||||
{type:'user', code:'u001', name:'권혁성(kevin)'},
|
||||
];
|
||||
|
||||
// -----------------------------
|
||||
// 2) 렌더 함수
|
||||
// -----------------------------
|
||||
function renderPool(){
|
||||
if(pool.length===0){
|
||||
$('#poolRows').html('<tr><td colspan="3" class="text-muted py-4">등록된 결재권자가 없습니다.</td></tr>');
|
||||
return;
|
||||
}
|
||||
const html = pool.map((p,idx)=>`
|
||||
<tr>
|
||||
<td>${labelType(p.type)}</td>
|
||||
<td class="text-start">${escapeHtml(p.name)} <span class="text-muted">/ ${escapeHtml(p.code)}</span></td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-outline-danger" data-remove="${idx}">삭제</button>
|
||||
</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
$('#poolRows').html(html);
|
||||
}
|
||||
|
||||
function renderPickRows(list, type){
|
||||
if(list.length===0){
|
||||
$('#pm_rows').html('<tr><td colspan="4" class="text-muted py-4">검색 결과가 없습니다.</td></tr>');
|
||||
return;
|
||||
}
|
||||
const html = list.map(x=>`
|
||||
<tr>
|
||||
<td>${labelType(type)}</td>
|
||||
<td>${escapeHtml(x.code)}</td>
|
||||
<td class="text-start">${escapeHtml(x.name)}</td>
|
||||
<td><button class="btn btn-sm btn-primary" data-pick="${type}|${x.code}">선택</button></td>
|
||||
</tr>
|
||||
`).join('');
|
||||
$('#pm_rows').html(html);
|
||||
}
|
||||
|
||||
// -----------------------------
|
||||
// 3) 유틸
|
||||
// -----------------------------
|
||||
function labelType(t){
|
||||
switch(t){
|
||||
case 'user': return '개인';
|
||||
case 'department': return '부서';
|
||||
case 'role': return '역할';
|
||||
default: return t;
|
||||
}
|
||||
}
|
||||
function escapeHtml(s){return String(s??'').replace(/[&<>"']/g,m=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[m]));}
|
||||
function uniquePush(arr, item){
|
||||
if(!arr.some(x=>x.type===item.type && x.code===item.code)) arr.push(item);
|
||||
}
|
||||
|
||||
// -----------------------------
|
||||
// 4) 이벤트: 삭제
|
||||
// -----------------------------
|
||||
$(document).on('click','[data-remove]',function(){
|
||||
const i = +$(this).data('remove');
|
||||
if(i>=0){ pool.splice(i,1); renderPool(); }
|
||||
// 실제: $.post('/api/approval/pool_delete.php', { type: pool[i].type, code: pool[i].code })
|
||||
});
|
||||
</script>
|
||||
|
||||
// -----------------------------
|
||||
// 5) 검색 & 추가 (툴바)
|
||||
// -----------------------------
|
||||
$('#find').on('click', function(){
|
||||
openPicker(false); // 조회만
|
||||
});
|
||||
$('#add').on('click', function(){
|
||||
openPicker(true); // 선택 시 추가
|
||||
});
|
||||
|
||||
// -----------------------------
|
||||
// 6) 모달 열기/검색/선택
|
||||
// -----------------------------
|
||||
function openPicker(isAddMode){
|
||||
const type = $('#type').val();
|
||||
const kw = $('#keyword').val().trim().toLowerCase();
|
||||
|
||||
// 모달 상단 타입/키워드 동기화
|
||||
$('#pm_type').html($('#type').html()).val(type);
|
||||
$('#pm_keyword').val($('#keyword').val());
|
||||
|
||||
// 첫 렌더
|
||||
const list = filterList(type, kw);
|
||||
renderPickRows(list, type);
|
||||
|
||||
// 모달 띄우기
|
||||
const m = new bootstrap.Modal('#pickModal');
|
||||
m.show();
|
||||
|
||||
// 모달 내 검색
|
||||
$('#pm_find').off('click').on('click', function(){
|
||||
const t2 = $('#pm_type').val();
|
||||
const k2 = $('#pm_keyword').val().trim().toLowerCase();
|
||||
renderPickRows(filterList(t2, k2), t2);
|
||||
});
|
||||
|
||||
// 선택 → 추가
|
||||
$(document).off('click.pmPick').on('click.pmPick','[data-pick]', function(){
|
||||
const [t,c] = String($(this).data('pick')).split('|');
|
||||
const item = (SAMPLE[t]||[]).find(x=>x.code===c);
|
||||
if(!item) return;
|
||||
|
||||
// 추가 모드일 때만 풀에 반영
|
||||
uniquePush(pool, {type:t, code:item.code, name:item.name});
|
||||
renderPool();
|
||||
bootstrap.Modal.getInstance(document.getElementById('pickModal')).hide();
|
||||
|
||||
// 실제: $.post('/api/approval/pool_add.php', { type:t, code:item.code })
|
||||
});
|
||||
}
|
||||
|
||||
function filterList(type, kw){
|
||||
const base = SAMPLE[type] || [];
|
||||
if(!kw) return base;
|
||||
return base.filter(x =>
|
||||
String(x.code).toLowerCase().includes(kw) ||
|
||||
String(x.name).toLowerCase().includes(kw)
|
||||
);
|
||||
}
|
||||
|
||||
// 최초 렌더
|
||||
renderPool();
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php include '../inc/footer.php'; ?>
|
||||
|
||||
@@ -126,18 +126,21 @@
|
||||
// 카테고리 목록 (id, name)
|
||||
const categories = [
|
||||
{id:1, name:'제품군'},
|
||||
{id:2, name:'서비스'}
|
||||
{id:2, name:'자재군'}
|
||||
];
|
||||
// 분류 목록 (카테고리별 배열)
|
||||
let SEQ = 300;
|
||||
const subsByCat = {
|
||||
1: [
|
||||
{id:101, name:'노트북', desc:'휴대용 컴퓨터'},
|
||||
{id:102, name:'데스크탑', desc:'사무/게임용 컴퓨터'},
|
||||
{id:101, name:'주자재', desc:'본체에 사용되는 자재'},
|
||||
{id:102, name:'부자재', desc:'BOM에 사용되는 자재'},
|
||||
{id:102, name:'소모품', desc:'소모성 자재'},
|
||||
],
|
||||
2: [
|
||||
{id:201, name:'호스팅', desc:'웹 호스팅 서비스'},
|
||||
{id:202, name:'유지보수',desc:'운영/AS 지원'},
|
||||
{id:201, name:'주자재', desc:'메인자재'},
|
||||
{id:202, name:'BOM',desc:'결합제품'},
|
||||
{id:202, name:'반자재',desc:'일부결합제품'},
|
||||
{id:202, name:'제품',desc:'판매제품'},
|
||||
]
|
||||
};
|
||||
|
||||
|
||||
@@ -7,14 +7,12 @@
|
||||
<title>SAM::테넌트페이지</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" />
|
||||
<style>
|
||||
html, body {
|
||||
height: 100%;
|
||||
}
|
||||
html, body { height: 100%; }
|
||||
body {
|
||||
background-color: #fefefe; /* #f8f9fa */
|
||||
background-color: #fefefe;
|
||||
min-height: 100vh;
|
||||
position: relative;
|
||||
padding-bottom: 80px; /* 푸터 높이만큼 */
|
||||
padding-bottom: 80px; /* 푸터 높이만큼 */
|
||||
}
|
||||
header, footer {
|
||||
background-color: #2c4a85;
|
||||
@@ -22,54 +20,64 @@
|
||||
padding: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
.container {
|
||||
max-width: 1280px;
|
||||
margin: auto;
|
||||
}
|
||||
.container { max-width: 1280px; margin: auto; }
|
||||
.card {
|
||||
margin-bottom: 1rem;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 16px rgba(44,74,133,0.07);
|
||||
border: 1.5px solid #e3eaf3;
|
||||
}
|
||||
nav.container {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
nav a {
|
||||
color: white;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
nav a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
nav.container { max-width: 1280px; margin: 0 auto; }
|
||||
nav a { color: white; margin-right: 1rem; }
|
||||
nav a:hover { text-decoration: underline; }
|
||||
/* 푸터 하단 고정 */
|
||||
footer {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
left: 0; bottom: 0;
|
||||
width: 100%; height: 60px;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
/* 2단 메뉴(서브드롭다운) 오른쪽에 뜨게 */
|
||||
.dropdown-submenu {
|
||||
position: relative;
|
||||
}
|
||||
.dropdown-submenu { position: relative; }
|
||||
.dropdown-submenu > .dropdown-menu {
|
||||
top: 0;
|
||||
left: 100%;
|
||||
top: 0; left: 100%;
|
||||
margin-top: -1px;
|
||||
margin-left: 0;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
/* SAM Toolbar: title left, actions right (항상 1줄 유지) */
|
||||
.sam-toolbar {
|
||||
display:flex; align-items:center; justify-content:space-between;
|
||||
gap:12px; padding:.5rem 1rem; border-bottom:1px solid #e9ecef; background:#fff;
|
||||
}
|
||||
.sam-title { font-weight:600; font-size:1rem; }
|
||||
.sam-actions {
|
||||
display:flex; align-items:center; gap:8px; flex-wrap:nowrap; /* 줄바꿈 금지 */
|
||||
}
|
||||
.sam-actions .form-select,
|
||||
.sam-actions .form-control { height:32px; padding:.25rem .5rem; }
|
||||
.sam-actions .form-select { width:120px; min-width:110px; }
|
||||
.sam-actions .form-control { width:200px; min-width:180px; }
|
||||
.sam-actions .btn { white-space:nowrap; }
|
||||
/* 아주 좁은 화면에서만(<=420px) 2줄 허용 */
|
||||
@media (max-width: 420px){
|
||||
.sam-toolbar { row-gap:8px; flex-wrap:wrap; }
|
||||
.sam-actions { flex:1 1 100%; justify-content:flex-end; flex-wrap:wrap; }
|
||||
}
|
||||
/* 공용 테이블 톤앤매너 */
|
||||
.table-sam.table > :not(caption) > * > * {
|
||||
vertical-align: middle;
|
||||
text-align: center;
|
||||
padding: .55rem .6rem;
|
||||
}
|
||||
.table-sam thead th { white-space: nowrap; }
|
||||
.table-sam tbody tr:hover { background: #f7f9ff; }
|
||||
.table-sam thead { background: #f0f4fb; }
|
||||
</style>
|
||||
<script src="https://code.jquery.com/jquery-3.7.0.min.js"></script>
|
||||
<!-- FullCalendar CSS/JS (jQuery, Bootstrap 5용) -->
|
||||
<!-- FullCalendar CSS/JS -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/fullcalendar@6.1.11/index.global.min.css" rel="stylesheet" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/fullcalendar@6.1.11/index.global.min.js"></script>
|
||||
<!-- FullCalendar 한국어 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/fullcalendar@6.1.11/locales-all.global.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
'order' => [
|
||||
'title'=>'수주',
|
||||
'items'=>[
|
||||
['라벨'=>'견적 관리','href'=>'/tenant/order/manage.php'],
|
||||
['라벨'=>'견적 관리','href'=>'/tenant/order/estimate.php'],
|
||||
['라벨'=>'수주 관리','href'=>'/tenant/order/manage.php'],
|
||||
['라벨'=>'수주 등록/수정','href'=>'/tenant/order/edit.php'],
|
||||
['라벨'=>'상태 관리','href'=>'/tenant/order/status.php'],
|
||||
|
||||
176
public/tenant/order/estimate.php
Normal file
176
public/tenant/order/estimate.php
Normal file
@@ -0,0 +1,176 @@
|
||||
<?php
|
||||
$CURRENT_SECTION='order';
|
||||
include '../inc/header.php';
|
||||
?>
|
||||
<div class="container" style="max-width:1280px; margin-top:18px;">
|
||||
|
||||
<!-- 상단 툴바 -->
|
||||
<div class="sam-toolbar mb-2">
|
||||
<div class="sam-title">견적 리스트</div>
|
||||
<div class="sam-actions">
|
||||
<select class="form-select form-select-sm" id="selStatus">
|
||||
<option value="">상태(전체)</option>
|
||||
<option>접수</option><option>견적</option><option>발주</option>
|
||||
<option>생산</option><option>출고</option>
|
||||
</select>
|
||||
<select class="form-select form-select-sm" id="selType">
|
||||
<option value="">구분(전체)</option>
|
||||
<option>스크린</option><option>철재</option>
|
||||
</select>
|
||||
<input type="date" class="form-control form-control-sm" id="fromDate" />
|
||||
<span class="text-muted">~</span>
|
||||
<input type="date" class="form-control form-control-sm" id="toDate" />
|
||||
<input type="text" class="form-control form-control-sm" id="keyword" placeholder="발주처/현장/모델 등" />
|
||||
<button class="btn btn-sm btn-outline-secondary" id="btnSearch">검색</button>
|
||||
<button class="btn btn-sm btn-primary" id="btnNew">+ 신규 견적</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body p-2">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover table-sam m-0" id="estTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:60px;">NO</th>
|
||||
<th style="width:110px;">접수일</th>
|
||||
<th style="width:80px;">상태</th>
|
||||
<th style="width:160px;">생산로트</th>
|
||||
<th style="width:80px;">구분</th>
|
||||
<th style="width:140px;">발주처</th>
|
||||
<th style="width:110px;">제품모델명</th>
|
||||
<th>현장명</th>
|
||||
<th style="width:80px;">수량</th>
|
||||
<th style="width:110px;">출고일</th>
|
||||
<th style="width:90px;">운송현황</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="estTbody"><!-- JS 렌더 --></tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- 페이지네이션 -->
|
||||
<div class="d-flex justify-content-between align-items-center mt-2">
|
||||
<div class="text-muted small" id="resultInfo"></div>
|
||||
<nav aria-label="pagination">
|
||||
<ul class="pagination pagination-sm mb-0" id="pager"><!-- JS --></ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
/* 상태 배지 */
|
||||
.q-badge{display:inline-block; padding:.25rem .45rem; border-radius:.5rem; font-weight:600;}
|
||||
.q-접수{background:#e8f0ff; color:#204a9a;}
|
||||
.q-견적{background:#fff1bf; color:#6a5300;}
|
||||
.q-발주{background:#d7f5e6; color:#0b6b3e;}
|
||||
.q-생산{background:#e6f0ff; color:#164f96;}
|
||||
.q-출고{background:#e9ecef; color:#495057;}
|
||||
/* 현장명 말줄임 */
|
||||
#estTable td:nth-child(8){max-width:320px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
$(function(){
|
||||
// ---- 샘플 데이터 ----
|
||||
const rows = [
|
||||
{no:1, recv:'2025-07-15', status:'접수', lot:'KD-PR-250715-11', type:'철재', buyer:'SK텔레콤', model:'KWE01', site:'판교센터', qty:3, ship:'2025-07-25', shipstat:'택배'},
|
||||
{no:2, recv:'2025-07-14', status:'견적', lot:'KD-PR-250714-12', type:'스크린', buyer:'현대건설', model:'KQTS01', site:'서울현장', qty:2, ship:'2025-07-28', shipstat:'화물차'},
|
||||
{no:3, recv:'2025-07-13', status:'발주', lot:'KD-PR-250713-13', type:'철재', buyer:'KT', model:'KTE01', site:'강남센터', qty:5, ship:'2025-07-30', shipstat:'트럭'},
|
||||
{no:4, recv:'2025-07-12', status:'생산', lot:'KD-PR-250712-14', type:'스크린', buyer:'LG전자', model:'KDSS01', site:'평택공장', qty:1, ship:'2025-08-01', shipstat:'택배'},
|
||||
{no:5, recv:'2025-07-11', status:'출고', lot:'KD-PR-250711-15', type:'철재', buyer:'삼성SDI', model:'KSS01', site:'천안사업장', qty:5, ship:'2025-08-01', shipstat:'화물차'},
|
||||
{no:6, recv:'2025-07-10', status:'출고', lot:'KD-PR-250710-01', type:'스크린', buyer:'롯데쇼핑', model:'KSS01', site:'서울물류센터', qty:3, ship:'2025-08-02', shipstat:'택배'},
|
||||
{no:7, recv:'2025-07-09', status:'생산', lot:'KD-PR-250709-02', type:'스크린', buyer:'두산중공업',model:'KSS02', site:'울산공장', qty:1, ship:'2025-08-05', shipstat:'트럭'},
|
||||
{no:8, recv:'2025-07-08', status:'발주', lot:'KD-PR-250708-03', type:'철재', buyer:'CJ대한통운',model:'KSS02', site:'인천사업장', qty:3, ship:'2025-08-02', shipstat:'화물차'},
|
||||
{no:9, recv:'2025-07-07', status:'견적', lot:'KD-PR-250707-04', type:'스크린', buyer:'포스코', model:'KSS01', site:'포항공장', qty:2, ship:'2025-07-28', shipstat:'택배'},
|
||||
{no:10, recv:'2025-07-06', status:'접수', lot:'KD-PR-250706-05', type:'철재', buyer:'현대자동차',model:'KSS02', site:'울산공장', qty:8, ship:'2025-08-03', shipstat:'화물차'},
|
||||
{no:6, recv:'2025-07-10', status:'출고', lot:'KD-PR-250710-01', type:'스크린', buyer:'롯데쇼핑', model:'KSS01', site:'서울물류센터', qty:3, ship:'2025-08-02', shipstat:'택배'},
|
||||
{no:7, recv:'2025-07-09', status:'생산', lot:'KD-PR-250709-02', type:'스크린', buyer:'두산중공업',model:'KSS02', site:'울산공장', qty:1, ship:'2025-08-05', shipstat:'트럭'},
|
||||
{no:8, recv:'2025-07-08', status:'발주', lot:'KD-PR-250708-03', type:'철재', buyer:'CJ대한통운',model:'KSS02', site:'인천사업장', qty:3, ship:'2025-08-02', shipstat:'화물차'},
|
||||
{no:9, recv:'2025-07-07', status:'견적', lot:'KD-PR-250707-04', type:'스크린', buyer:'포스코', model:'KSS01', site:'포항공장', qty:2, ship:'2025-07-28', shipstat:'택배'},
|
||||
{no:10, recv:'2025-07-06', status:'접수', lot:'KD-PR-250706-05', type:'철재', buyer:'현대자동차',model:'KSS02', site:'울산공장', qty:8, ship:'2025-08-03', shipstat:'화물차'},
|
||||
];
|
||||
|
||||
// ---- 상태 배지 ----
|
||||
const badge = s => `<span class="q-badge q-${s}">${s}</span>`;
|
||||
|
||||
// ---- 페이징 ----
|
||||
const pageSize = 10;
|
||||
let curPage = 1, filtered = [...rows];
|
||||
|
||||
function render(){
|
||||
const start = (curPage-1)*pageSize;
|
||||
const slice = filtered.slice(start, start+pageSize);
|
||||
$('#estTbody').html(slice.map(r=>`
|
||||
<tr data-no="${r.no}" class="row-open">
|
||||
<td>${r.no}</td>
|
||||
<td>${r.recv}</td>
|
||||
<td>${badge(r.status)}</td>
|
||||
<td>${r.lot}</td>
|
||||
<td>${r.type}</td>
|
||||
<td>${r.buyer}</td>
|
||||
<td>${r.model}</td>
|
||||
<td title="${r.site}">${r.site}</td>
|
||||
<td>${r.qty}</td>
|
||||
<td>${r.ship}</td>
|
||||
<td>${r.shipstat}</td>
|
||||
</tr>
|
||||
`).join(''));
|
||||
|
||||
const total = Math.max(1, Math.ceil(filtered.length/pageSize));
|
||||
let p = `
|
||||
<li class="page-item ${curPage===1?'disabled':''}"><a class="page-link" href="#" data-p="prev">«</a></li>`;
|
||||
for(let i=1;i<=total;i++){
|
||||
p += `<li class="page-item ${i===curPage?'active':''}"><a class="page-link" href="#" data-p="${i}">${i}</a></li>`;
|
||||
}
|
||||
p += `<li class="page-item ${curPage===total?'disabled':''}"><a class="page-link" href="#" data-p="next">»</a></li>`;
|
||||
$('#pager').html(p);
|
||||
$('#resultInfo').text(`총 ${filtered.length.toLocaleString()}건`);
|
||||
}
|
||||
|
||||
// 검색
|
||||
$('#btnSearch').on('click', function(){
|
||||
const st = $('#selStatus').val();
|
||||
const tp = $('#selType').val();
|
||||
const kw = ($('#keyword').val()||'').toLowerCase();
|
||||
const from = $('#fromDate').val();
|
||||
const to = $('#toDate').val();
|
||||
filtered = rows.filter(r=>{
|
||||
const okSt = !st || r.status===st;
|
||||
const okTp = !tp || r.type===tp;
|
||||
const okKw = !kw || [r.buyer,r.site,r.model,r.lot].some(v=>String(v).toLowerCase().includes(kw));
|
||||
const okDt = (!from || r.recv>=from) && (!to || r.recv<=to);
|
||||
return okSt && okTp && okKw && okDt;
|
||||
});
|
||||
curPage=1; render();
|
||||
});
|
||||
|
||||
// 페이징
|
||||
$(document).on('click', '#pager .page-link', function(e){
|
||||
e.preventDefault();
|
||||
const v = $(this).data('p');
|
||||
const total = Math.max(1, Math.ceil(filtered.length/pageSize));
|
||||
if(v==='prev'){ if(curPage>1) curPage--; }
|
||||
else if(v==='next'){ if(curPage<total) curPage++; }
|
||||
else curPage = parseInt(v,10);
|
||||
render();
|
||||
});
|
||||
|
||||
// 행 클릭 (상세로 이동/모달 호출 등)
|
||||
$(document).on('click', 'tr.row-open', function(){
|
||||
const no = $(this).data('no');
|
||||
// 예시: 상세 페이지로 이동
|
||||
// location.href = '/tenant/estimate/estimate_view.php?no='+no;
|
||||
alert('견적 상세 열기 (NO: '+no+')');
|
||||
});
|
||||
|
||||
// 버튼
|
||||
$('#btnNew').on('click', ()=>location.href='estimate_form.php');
|
||||
|
||||
render();
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php include '../inc/footer.php'; ?>
|
||||
471
public/tenant/order/estimate_form.php
Normal file
471
public/tenant/order/estimate_form.php
Normal file
@@ -0,0 +1,471 @@
|
||||
<!-- SAM RULES: include=../inc/header.php; base=/tenant; width=1280; js=jQuery+BS5 -->
|
||||
<?php
|
||||
$CURRENT_SECTION='estimate';
|
||||
include '../inc/header.php';
|
||||
?>
|
||||
<div class="container" style="max-width:1280px; margin-top:18px;">
|
||||
|
||||
<!-- ========== 기본정보 입력 ========== -->
|
||||
<div class="card mb-3">
|
||||
<div class="card-header"><strong>기본정보 입력</strong></div>
|
||||
<div class="card-body">
|
||||
<form id="baseForm" class="row g-2 align-items-center">
|
||||
<div class="col-sm-3">
|
||||
<label class="form-label mb-1">접수일</label>
|
||||
<input type="date" class="form-control" id="recvDate">
|
||||
</div>
|
||||
<div class="col-sm-3">
|
||||
<label class="form-label mb-1">작성자</label>
|
||||
<input type="text" class="form-control" id="writer" placeholder="작성자">
|
||||
</div>
|
||||
<div class="col-sm-3">
|
||||
<label class="form-label mb-1">발주처</label>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" id="customer" placeholder="검색">
|
||||
<button class="btn btn-outline-secondary" type="button" id="btnFindCust">🔍</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-3">
|
||||
<label class="form-label mb-1">업체담당자</label>
|
||||
<input type="text" class="form-control" id="custManager" placeholder="담당자명">
|
||||
</div>
|
||||
|
||||
<div class="col-sm-3">
|
||||
<label class="form-label mb-1">연락처</label>
|
||||
<input type="text" class="form-control" id="phone" placeholder="연락처">
|
||||
</div>
|
||||
<div class="col-sm-9">
|
||||
<label class="form-label mb-1">현장</label>
|
||||
<input type="text" class="form-control" id="site" placeholder="현장명">
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<label class="form-label mb-1">메모</label>
|
||||
<input type="text" class="form-control" id="memo" placeholder="메모입력">
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<label class="form-label mb-1">첨부파일 <span class="text-muted">(파일당 20MB, 총 100MB, 여러개 가능)</span></label>
|
||||
<input type="file" class="form-control" id="files" multiple>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ========== 제품구성 설정 ========== -->
|
||||
<div class="card mb-3" id="productCard">
|
||||
<div class="card-header d-flex align-items-center justify-content-between">
|
||||
<strong>제품구성 설정</strong>
|
||||
<div class="d-flex gap-2">
|
||||
<select id="modelPreset" class="form-select form-select-sm" style="width:220px;">
|
||||
<option value="">모델 프리셋 선택(임시)</option>
|
||||
<option value="KSS01">KSS01 (인정)</option>
|
||||
<option value="KSS02">KSS02 (인정)</option>
|
||||
</select>
|
||||
<button class="btn btn-sm btn-outline-secondary" id="btnAddSet">구성추가</button>
|
||||
<button class="btn btn-sm btn-primary" id="btnSaveSet">저장</button>
|
||||
<button class="btn btn-sm btn-outline-primary" id="btnCalc">계산하기</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 기본 설정 바 (항상 위로 붙음 / 세트 생성 시 복사 원본) -->
|
||||
<div class="sam-sticky p-3 border-bottom bg-white">
|
||||
<div class="row g-2">
|
||||
<div class="col-md-2">
|
||||
<label class="form-label mb-1">층수</label>
|
||||
<select id="g_floor" class="form-select">
|
||||
<option>1</option><option>2</option><option>3</option><option>4</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label mb-1">부호(호수)</label>
|
||||
<input id="g_room" class="form-control" placeholder="예: 1205">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label mb-1">본체종류</label>
|
||||
<select id="g_body" class="form-select">
|
||||
<option value="일반">일반</option>
|
||||
<option value="내화">내화</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label mb-1">마감유형</label>
|
||||
<select id="g_finish" class="form-select">
|
||||
<option value="EGI">EGI</option>
|
||||
<option value="SUS">SUS</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label class="form-label mb-1">오픈사이즈</label>
|
||||
<div class="input-group">
|
||||
<input id="g_open_w" class="form-control" placeholder="가로(mm)" inputmode="numeric">
|
||||
<span class="input-group-text">×</span>
|
||||
<input id="g_open_h" class="form-control" placeholder="세로(mm)" inputmode="numeric">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label mb-1">제작사이즈</label>
|
||||
<div class="input-group">
|
||||
<input id="g_make_w" class="form-control" placeholder="가로(mm)" inputmode="numeric">
|
||||
<span class="input-group-text">×</span>
|
||||
<input id="g_make_h" class="form-control" placeholder="세로(mm)" inputmode="numeric">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<label class="form-label mb-1">제작치수</label>
|
||||
<div class="row g-2">
|
||||
<div class="col"><input id="g_mk_w" class="form-control" placeholder="가로" inputmode="numeric"></div>
|
||||
<div class="col"><input id="g_mk_h" class="form-control" placeholder="높이" inputmode="numeric"></div>
|
||||
<div class="col"><input id="g_mk_edge" class="form-control" placeholder="마구리" inputmode="numeric"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 세트 컨테이너 -->
|
||||
<div class="card-body" id="setsBox">
|
||||
<!-- JS가 세트 카드를 여기 추가 -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ========== 세부 항목 (계산 결과) ========== -->
|
||||
<div class="card">
|
||||
<div class="card-header"><strong>세부 항목 (읽기전용)</strong></div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sam table-hover align-middle m-0" id="detailTable">
|
||||
<thead><tr>
|
||||
<th style="width:60px;">#</th>
|
||||
<th style="width:120px;">품목코드</th>
|
||||
<th>품목명</th>
|
||||
<th style="width:180px;">규격</th>
|
||||
<th style="width:80px;">수량</th>
|
||||
<th style="width:100px;">단위</th>
|
||||
<th style="width:140px;">단가</th>
|
||||
<th style="width:140px;">공급가액</th>
|
||||
<th style="width:100px;">부가세</th>
|
||||
</tr></thead>
|
||||
<tbody></tbody>
|
||||
<tfoot><tr>
|
||||
<th colspan="7" class="text-end">합계</th>
|
||||
<th id="sumAmt" class="text-end">0</th>
|
||||
<th id="sumTax" class="text-end">0</th>
|
||||
</tr></tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
/* sticky bar */
|
||||
.sam-sticky{ position: sticky; top: 70px; z-index: 30; }
|
||||
/* 좌측/우측 리스트 */
|
||||
.pick-col{border:1px solid #e5e9f2; border-radius:10px; overflow:hidden;}
|
||||
.pick-head{background:#f7f9ff; padding:.6rem .9rem; font-weight:600;}
|
||||
.pick-body{max-height:260px; overflow:auto;}
|
||||
.pick-row{display:flex; align-items:center; gap:8px; padding:.5rem .75rem; border-top:1px solid #f1f3f8;}
|
||||
.pick-row:hover{background:#fafcff;}
|
||||
.chip{display:inline-block; padding:.1rem .45rem; border:1px solid #cbd6ee; border-radius:10rem; font-size:.8rem; color:#2c4a85; background:#edf3ff;}
|
||||
/* 공용 테이블 톤 */
|
||||
.table-sam.table > :not(caption) > * > * { vertical-align: middle; text-align: center; padding:.55rem .6rem; }
|
||||
.table-sam thead th{ white-space:nowrap; }
|
||||
.table-sam tbody tr:hover{ background:#f7f9ff; }
|
||||
.table-sam thead{ background:#f0f4fb; }
|
||||
</style>
|
||||
|
||||
<script>
|
||||
(function($){
|
||||
// ===== 샘플 마스터 데이터 =====
|
||||
const MASTER = {
|
||||
screen: [
|
||||
{code:'SC-001', name:'스크린 베이스', unit:'EA', price:30000},
|
||||
{code:'SC-002', name:'스크린 레일', unit:'M', price:15000},
|
||||
{code:'SC-003', name:'실리카원단', unit:'㎡', price:12000},
|
||||
],
|
||||
steel: [
|
||||
{code:'ST-101', name:'가이드레일(앞판재)', unit:'EA', price:26892},
|
||||
{code:'ST-102', name:'보강판철', unit:'EA', price:21547},
|
||||
]
|
||||
};
|
||||
|
||||
// ===== 세트 카드 템플릿 =====
|
||||
function setCardTpl(idx, presetLabel, base){
|
||||
const id = `set${idx}`;
|
||||
const v = base || {};
|
||||
return `
|
||||
<div class="border rounded-3 p-3 mb-3 set-card" id="${id}" data-index="${idx}">
|
||||
<div class="d-flex align-items-center justify-content-between mb-3">
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<span class="badge bg-secondary-subtle text-dark border">세트 #${idx}</span>
|
||||
<span class="badge ${presetLabel? 'bg-success' : 'bg-dark-subtle text-dark'}">${presetLabel||'비인정'}</span>
|
||||
</div>
|
||||
<button type="button" class="btn btn-sm btn-outline-danger btnDelSet">삭제</button>
|
||||
</div>
|
||||
|
||||
<!-- 세트별 설정 (기본설정 복사본) -->
|
||||
<div class="row g-2 mb-2">
|
||||
<div class="col-md-2">
|
||||
<label class="form-label mb-1">층수</label>
|
||||
<select class="form-select s_floor">
|
||||
${[1,2,3,4].map(n=>`<option ${v.floor==n?'selected':''}>${n}</option>`).join('')}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label mb-1">부호(호수)</label>
|
||||
<input class="form-control s_room" placeholder="예: 1205" value="${v.room||''}">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label mb-1">본체종류</label>
|
||||
<select class="form-select s_body">
|
||||
<option ${v.body==='일반'?'selected':''}>일반</option>
|
||||
<option ${v.body==='내화'?'selected':''}>내화</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label mb-1">마감유형</label>
|
||||
<select class="form-select s_finish">
|
||||
<option ${v.finish==='EGI'?'selected':''} value="EGI">EGI</option>
|
||||
<option ${v.finish==='SUS'?'selected':''} value="SUS">SUS</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label class="form-label mb-1">오픈사이즈</label>
|
||||
<div class="input-group">
|
||||
<input class="form-control s_open_w" placeholder="가로(mm)" value="${v.open_w||''}">
|
||||
<span class="input-group-text">×</span>
|
||||
<input class="form-control s_open_h" placeholder="세로(mm)" value="${v.open_h||''}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label mb-1">제작사이즈</label>
|
||||
<div class="input-group">
|
||||
<input class="form-control s_make_w" placeholder="가로(mm)" value="${v.make_w||''}">
|
||||
<span class="input-group-text">×</span>
|
||||
<input class="form-control s_make_h" placeholder="세로(mm)" value="${v.make_h||''}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<label class="form-label mb-1">제작치수</label>
|
||||
<div class="row g-2">
|
||||
<div class="col"><input class="form-control s_mk_w" placeholder="가로" value="${v.mk_w||''}"></div>
|
||||
<div class="col"><input class="form-control s_mk_h" placeholder="높이" value="${v.mk_h||''}"></div>
|
||||
<div class="col"><input class="form-control s_mk_edge" placeholder="마구리" value="${v.mk_edge||''}"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 탭 -->
|
||||
<ul class="nav nav-tabs small mb-2">
|
||||
<li class="nav-item"><a class="nav-link active" data-bs-toggle="tab" href="#${id}_scr">스크린</a></li>
|
||||
<li class="nav-item"><a class="nav-link" data-bs-toggle="tab" href="#${id}_steel">철재</a></li>
|
||||
<li class="nav-item"><a class="nav-link" data-bs-toggle="tab" href="#${id}_slat">슬랫</a></li>
|
||||
<li class="nav-item"><a class="nav-link" data-bs-toggle="tab" href="#${id}_motor">전동개폐기</a></li>
|
||||
<li class="nav-item"><a class="nav-link" data-bs-toggle="tab" href="#${id}_safe">연동 폐쇄기구</a></li>
|
||||
<li class="nav-item"><a class="nav-link" data-bs-toggle="tab" href="#${id}_bend">절곡품</a></li>
|
||||
<li class="nav-item"><a class="nav-link" data-bs-toggle="tab" href="#${id}_sub">부자재</a></li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content">
|
||||
${pickPane(id,'scr','선택 가능한 스크린 제품', MASTER.screen)}
|
||||
${pickPane(id,'steel','선택 가능한 철재 제품', MASTER.steel)}
|
||||
${emptyPane(id,'slat')}
|
||||
${emptyPane(id,'motor')}
|
||||
${emptyPane(id,'safe')}
|
||||
${emptyPane(id,'bend')}
|
||||
${emptyPane(id,'sub')}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function pickPane(id, key, title, items){
|
||||
return `
|
||||
<div class="tab-pane fade ${key==='scr'?'show active':''}" id="${id}_${key}">
|
||||
<div class="row g-3">
|
||||
<div class="col-lg-6">
|
||||
<div class="pick-col">
|
||||
<div class="pick-head">${title}</div>
|
||||
<div class="pick-body">
|
||||
${items.map(it=>`
|
||||
<div class="pick-row">
|
||||
<div class="flex-grow-1">
|
||||
<div><strong>${it.code}</strong> <span class="text-muted">· ${it.name}</span></div>
|
||||
<div class="small text-muted">단위 ${it.unit} / 기준단가 ${num(it.price)}</div>
|
||||
</div>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary btnPick"
|
||||
data-code="${it.code}" data-name="${it.name}" data-unit="${it.unit}" data-price="${it.price}">선택</button>
|
||||
</div>`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<div class="pick-col">
|
||||
<div class="pick-head">계산식(선택 결과)</div>
|
||||
<div class="pick-body selectedBox"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
function emptyPane(id,key){
|
||||
return `<div class="tab-pane fade" id="${id}_${key}">
|
||||
<div class="text-muted small p-3">※ 이 탭은 나중에 마스터 연동 시 채워집니다.</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function selectedRowTpl(code,name,unit){
|
||||
return `<div class="pick-row">
|
||||
<span class="chip">${code}</span>
|
||||
<div class="flex-grow-1">${name}</div>
|
||||
<div style="width:70px;"><span class="badge bg-light text-dark w-100">${unit}</span></div>
|
||||
<input type="number" class="form-control form-control-sm selQty" value="1" style="width:80px;">
|
||||
<button class="btn btn-sm btn-outline-danger btnSelDel" type="button">삭제</button>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
// ===== 상태 =====
|
||||
let setSeq = 0;
|
||||
|
||||
// ===== 유틸 =====
|
||||
function num(v){ return (+v).toLocaleString(); }
|
||||
function readGlobal(){
|
||||
return {
|
||||
floor: $('#g_floor').val(),
|
||||
room: $('#g_room').val().trim(),
|
||||
body: $('#g_body').val(),
|
||||
finish:$('#g_finish').val(),
|
||||
open_w:$('#g_open_w').val(), open_h:$('#g_open_h').val(),
|
||||
make_w:$('#g_make_w').val(), make_h:$('#g_make_h').val(),
|
||||
mk_w: $('#g_mk_w').val(), mk_h: $('#g_mk_h').val(), mk_edge: $('#g_mk_edge').val()
|
||||
};
|
||||
}
|
||||
function validSize(v){
|
||||
const ok = (x)=> x!=='' && !isNaN(+x);
|
||||
return ok(v.open_w)&&ok(v.open_h)&&ok(v.make_w)&&ok(v.make_h);
|
||||
}
|
||||
|
||||
// ===== 세트 추가 =====
|
||||
$('#btnAddSet').on('click', function(){
|
||||
const g = readGlobal();
|
||||
const preset = $('#modelPreset').val();
|
||||
const label = preset ? preset : '비인정';
|
||||
setSeq++;
|
||||
$('#setsBox').append( setCardTpl(setSeq, label, g) );
|
||||
});
|
||||
|
||||
// 세트 삭제
|
||||
$(document).on('click', '.btnDelSet', function(){
|
||||
$(this).closest('.set-card').remove();
|
||||
});
|
||||
|
||||
// 품목 선택 → 오른쪽 박스 추가(중복 시 수량 +1)
|
||||
$(document).on('click', '.btnPick', function(){
|
||||
const $set = $(this).closest('.set-card');
|
||||
const code = $(this).data('code');
|
||||
const name = $(this).data('name');
|
||||
const unit = $(this).data('unit');
|
||||
const $box = $set.find('.tab-pane.active .selectedBox');
|
||||
|
||||
// 중복 찾아 수량 +1
|
||||
const $exists = $box.find('.pick-row').filter(function(){
|
||||
return $(this).find('.chip').text()===code;
|
||||
});
|
||||
if($exists.length){
|
||||
const $qty = $exists.find('.selQty');
|
||||
$qty.val( (+$qty.val()||0) + 1 );
|
||||
}else{
|
||||
$box.append( selectedRowTpl(code,name,unit) );
|
||||
}
|
||||
});
|
||||
// 선택 삭제
|
||||
$(document).on('click', '.btnSelDel', function(){
|
||||
$(this).closest('.pick-row').remove();
|
||||
});
|
||||
|
||||
// ===== 저장 (임시) =====
|
||||
$('#btnSaveSet').on('click', function(){
|
||||
alert('세트 저장(임시). 실제로는 /tenant/api/estimate/save_set.php 로 전송');
|
||||
});
|
||||
|
||||
// ===== 계산하기 =====
|
||||
$('#btnCalc').on('click', function(){
|
||||
try{
|
||||
const result = []; // [{code,name,unit,qty,price}]
|
||||
let lineNo = 0;
|
||||
|
||||
// 모든 세트 순회
|
||||
$('.set-card').each(function(){
|
||||
const gset = {
|
||||
floor: $('.s_floor',this).val(),
|
||||
room: $('.s_room',this).val(),
|
||||
body: $('.s_body',this).val(),
|
||||
finish:$('.s_finish',this).val(),
|
||||
open_w:$('.s_open_w',this).val(), open_h:$('.s_open_h',this).val(),
|
||||
make_w:$('.s_make_w',this).val(), make_h:$('.s_make_h',this).val()
|
||||
};
|
||||
if(!validSize(gset)){
|
||||
throw new Error('세트의 치수(오픈/제작)가 비었습니다.');
|
||||
}
|
||||
|
||||
// 활성 탭들에서 선택 결과 취합
|
||||
$(this).find('.selectedBox').each(function(){
|
||||
$(this).find('.pick-row').each(function(){
|
||||
const code = $(this).find('.chip').text();
|
||||
const name = $(this).find('.flex-grow-1').text().trim();
|
||||
const unit = $(this).find('.badge').text().trim();
|
||||
const qty = +($(this).find('.selQty').val()||0);
|
||||
if(!qty) return;
|
||||
|
||||
// 샘플 단가 소스: MASTER 에서 조회
|
||||
const price = (MASTER.screen.concat(MASTER.steel).find(x=>x.code===code)?.price) || 0;
|
||||
|
||||
// 간단 산식(샘플): 제작 면적(m²) = (make_w * make_h)/1e6
|
||||
// 단위가 ㎡이면 면적×단가, 그 외는 수량×단가
|
||||
let amount = 0;
|
||||
if(unit==='㎡'){
|
||||
const area = (+gset.make_w||0) * (+gset.make_h||0) / 1_000_000;
|
||||
amount = Math.round(area * price * qty);
|
||||
}else{
|
||||
amount = Math.round(price * qty);
|
||||
}
|
||||
|
||||
result.push({line: ++lineNo, code, name, unit, qty, price, amount});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// 테이블 렌더
|
||||
const $tb = $('#detailTable tbody').empty();
|
||||
let sumAmt = 0, sumTax = 0;
|
||||
result.forEach(r=>{
|
||||
sumAmt += r.amount;
|
||||
const tax = Math.round(r.amount * 0.1); // 부가세
|
||||
sumTax += tax;
|
||||
$tb.append(`<tr>
|
||||
<td>${r.line}</td>
|
||||
<td>${r.code}</td>
|
||||
<td class="text-start">${r.name}</td>
|
||||
<td>-</td>
|
||||
<td>${r.qty}</td>
|
||||
<td>${r.unit}</td>
|
||||
<td class="text-end">${num(r.price)}</td>
|
||||
<td class="text-end">${num(r.amount)}</td>
|
||||
<td class="text-end">${num(tax)}</td>
|
||||
</tr>`);
|
||||
});
|
||||
$('#sumAmt').text(num(sumAmt));
|
||||
$('#sumTax').text(num(sumTax));
|
||||
if(result.length===0) alert('선택된 품목이 없습니다.');
|
||||
}catch(err){
|
||||
console.error(err);
|
||||
alert('계산 중 오류: '+ err.message);
|
||||
}
|
||||
});
|
||||
})(jQuery);
|
||||
</script>
|
||||
|
||||
<?php include '../inc/footer.php'; ?>
|
||||
Reference in New Issue
Block a user