feat: [juil] 업무 Workflow 플로우차트 메뉴 추가
- 주일기업 기획 하위 '업무 Workflow' 메뉴 추가 - 11단계 업무처리과정 인터랙티브 플로우차트 구현 - 각 단계 클릭 시 상세정보(담당부서, 필요서류, SAM 연동) 표시
This commit is contained in:
@@ -26,4 +26,13 @@ public function project(Request $request): View|Response
|
||||
|
||||
return view('juil.project');
|
||||
}
|
||||
|
||||
public function workflow(Request $request): View|Response
|
||||
{
|
||||
if ($request->header('HX-Request')) {
|
||||
return response('', 200)->header('HX-Redirect', route('juil.workflow'));
|
||||
}
|
||||
|
||||
return view('juil.workflow');
|
||||
}
|
||||
}
|
||||
|
||||
570
resources/views/juil/workflow.blade.php
Normal file
570
resources/views/juil/workflow.blade.php
Normal file
@@ -0,0 +1,570 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title', '업무 Workflow')
|
||||
|
||||
@section('content')
|
||||
<div id="root"></div>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
@include('partials.react-cdn')
|
||||
<script type="text/babel">
|
||||
@verbatim
|
||||
const { useState, useRef, useEffect } = React;
|
||||
|
||||
// --- 업무 프로세스 데이터 ---
|
||||
const processes = [
|
||||
{
|
||||
id: 1,
|
||||
phase: 'sales',
|
||||
name: '영업/수주',
|
||||
icon: '📋',
|
||||
dept: '영업팀',
|
||||
color: '#3B82F6',
|
||||
bgColor: '#EFF6FF',
|
||||
description: '건설사/시행사로부터 프로젝트 정보 수집 및 수주 활동',
|
||||
details: [
|
||||
'건설사 입찰공고 모니터링',
|
||||
'현장 실측 및 요구사항 파악',
|
||||
'고객사 미팅 및 관계 관리',
|
||||
],
|
||||
documents: ['입찰공고문', '현장조사서', '고객 요구사항서'],
|
||||
samLink: null,
|
||||
samMenu: null,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
phase: 'estimate',
|
||||
name: '견적서 작성',
|
||||
icon: '🧮',
|
||||
dept: '견적팀',
|
||||
color: '#8B5CF6',
|
||||
bgColor: '#F5F3FF',
|
||||
description: '자재/인건비/경비를 산출하여 견적서 작성',
|
||||
details: [
|
||||
'자재 단가 산출 (블라인드, 스크린, 셔터)',
|
||||
'인건비/시공비 산정',
|
||||
'이윤율 적용 및 최종 견적가 확정',
|
||||
],
|
||||
documents: ['견적서', '단가산출서', '자재목록'],
|
||||
samLink: '/juil/estimate',
|
||||
samMenu: '견적/입찰/공사관리',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
phase: 'bid',
|
||||
name: '입찰 참여',
|
||||
icon: '🏷️',
|
||||
dept: '영업팀',
|
||||
color: '#EC4899',
|
||||
bgColor: '#FDF2F8',
|
||||
description: '견적서 기반으로 입찰에 참여',
|
||||
details: [
|
||||
'입찰서류 준비 및 제출',
|
||||
'기술제안서 작성',
|
||||
'가격 협상',
|
||||
],
|
||||
documents: ['입찰서', '기술제안서', '사업자등록증 사본'],
|
||||
samLink: '/juil/estimate',
|
||||
samMenu: '견적/입찰/공사관리',
|
||||
branch: true,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
phase: 'contract',
|
||||
name: '낙찰/계약',
|
||||
icon: '📝',
|
||||
dept: '영업팀',
|
||||
color: '#10B981',
|
||||
bgColor: '#ECFDF5',
|
||||
description: '낙찰 후 공사 계약 체결',
|
||||
details: [
|
||||
'계약서 작성 및 검토',
|
||||
'계약금 수령',
|
||||
'프로젝트 등록 및 담당자 배정',
|
||||
],
|
||||
documents: ['공사계약서', '착공계', '공정표'],
|
||||
samLink: '/juil/project',
|
||||
samMenu: '프로젝트관리/기성청구',
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
phase: 'order',
|
||||
name: '자재 발주',
|
||||
icon: '📦',
|
||||
dept: '구매팀',
|
||||
color: '#F59E0B',
|
||||
bgColor: '#FFFBEB',
|
||||
description: '시공에 필요한 자재를 발주',
|
||||
details: [
|
||||
'BOM(자재명세서) 기반 발주량 산정',
|
||||
'협력업체 발주서 발행',
|
||||
'납기일 관리',
|
||||
],
|
||||
documents: ['발주서', 'BOM', '납품요청서'],
|
||||
samLink: null,
|
||||
samMenu: null,
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
phase: 'receive',
|
||||
name: '자재 입고/검수',
|
||||
icon: '🔍',
|
||||
dept: '자재팀',
|
||||
color: '#06B6D4',
|
||||
bgColor: '#ECFEFF',
|
||||
description: '입고된 자재의 수량/품질 검수',
|
||||
details: [
|
||||
'입고 수량 확인',
|
||||
'품질 검사 (규격, 색상, 하자)',
|
||||
'불량 자재 반품 처리',
|
||||
],
|
||||
documents: ['입고검수서', '거래명세서', '반품요청서'],
|
||||
samLink: null,
|
||||
samMenu: null,
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
phase: 'construct',
|
||||
name: '현장 시공',
|
||||
icon: '🔧',
|
||||
dept: '시공팀',
|
||||
color: '#EF4444',
|
||||
bgColor: '#FEF2F2',
|
||||
description: '현장에서 블라인드/스크린/셔터 설치 시공',
|
||||
details: [
|
||||
'시공 일정 조율 (건설사와 협의)',
|
||||
'현장 설치 작업',
|
||||
'시공 사진 촬영 및 기록',
|
||||
],
|
||||
documents: ['시공계획서', '작업일보', '시공사진'],
|
||||
samLink: '/juil/construction-photos',
|
||||
samMenu: '공사현장 사진대지',
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
phase: 'inspect',
|
||||
name: '시공 검수',
|
||||
icon: '✅',
|
||||
dept: '현장팀',
|
||||
color: '#14B8A6',
|
||||
bgColor: '#F0FDFA',
|
||||
description: '시공 완료 후 품질 검수 및 하자 보수',
|
||||
details: [
|
||||
'건설사 합동 검수',
|
||||
'하자 사항 보수',
|
||||
'검수 완료 확인서 수령',
|
||||
],
|
||||
documents: ['검수확인서', '하자보수보고서', '준공사진'],
|
||||
samLink: '/juil/construction-photos',
|
||||
samMenu: '공사현장 사진대지',
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
phase: 'billing',
|
||||
name: '기성 청구',
|
||||
icon: '💰',
|
||||
dept: '경리팀',
|
||||
color: '#7C3AED',
|
||||
bgColor: '#F5F3FF',
|
||||
description: '시공 진행률에 따른 기성금 청구',
|
||||
details: [
|
||||
'기성 내역서 작성',
|
||||
'세금계산서 발행',
|
||||
'기성금 청구서 제출',
|
||||
],
|
||||
documents: ['기성내역서', '세금계산서', '기성청구서'],
|
||||
samLink: '/juil/project',
|
||||
samMenu: '프로젝트관리/기성청구',
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
phase: 'payment',
|
||||
name: '입금 확인',
|
||||
icon: '🏦',
|
||||
dept: '경리팀',
|
||||
color: '#059669',
|
||||
bgColor: '#ECFDF5',
|
||||
description: '기성금 입금 확인 및 매출 처리',
|
||||
details: [
|
||||
'입금 내역 대사',
|
||||
'미수금 관리',
|
||||
'매출 장부 기록',
|
||||
],
|
||||
documents: ['입금확인서', '매출대장'],
|
||||
samLink: null,
|
||||
samMenu: null,
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
phase: 'as',
|
||||
name: 'A/S 관리',
|
||||
icon: '🛠️',
|
||||
dept: '시공팀',
|
||||
color: '#6B7280',
|
||||
bgColor: '#F9FAFB',
|
||||
description: '하자보수기간 내 A/S 대응',
|
||||
details: [
|
||||
'A/S 접수 및 처리',
|
||||
'하자보수 비용 관리',
|
||||
'보증기간 만료 관리',
|
||||
],
|
||||
documents: ['A/S 접수대장', '하자보수 보고서'],
|
||||
samLink: null,
|
||||
samMenu: null,
|
||||
},
|
||||
];
|
||||
|
||||
// --- 화살표 컴포넌트 ---
|
||||
function Arrow({ direction = 'right', branch = false }) {
|
||||
if (direction === 'down') {
|
||||
return (
|
||||
<div style={{ display: 'flex', justifyContent: 'center', padding: '4px 0' }}>
|
||||
<div style={{
|
||||
width: 0, height: 0,
|
||||
borderLeft: '8px solid transparent',
|
||||
borderRight: '8px solid transparent',
|
||||
borderTop: '10px solid #9CA3AF',
|
||||
}}></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div style={{
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
minWidth: '40px', position: 'relative',
|
||||
}}>
|
||||
<div style={{
|
||||
width: '100%', height: '2px', backgroundColor: '#9CA3AF',
|
||||
}}></div>
|
||||
<div style={{
|
||||
position: 'absolute', right: 0,
|
||||
width: 0, height: 0,
|
||||
borderTop: '6px solid transparent',
|
||||
borderBottom: '6px solid transparent',
|
||||
borderLeft: '8px solid #9CA3AF',
|
||||
}}></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// --- 프로세스 노드 컴포넌트 ---
|
||||
function ProcessNode({ process, isActive, onClick }) {
|
||||
return (
|
||||
<div
|
||||
onClick={() => onClick(process)}
|
||||
style={{
|
||||
minWidth: '140px',
|
||||
maxWidth: '160px',
|
||||
padding: '12px',
|
||||
borderRadius: '12px',
|
||||
border: `2px solid ${isActive ? process.color : '#E5E7EB'}`,
|
||||
backgroundColor: isActive ? process.bgColor : '#FFFFFF',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.2s ease',
|
||||
boxShadow: isActive ? `0 4px 12px ${process.color}33` : '0 1px 3px rgba(0,0,0,0.1)',
|
||||
textAlign: 'center',
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
<div style={{ fontSize: '24px', marginBottom: '6px' }}>{process.icon}</div>
|
||||
<div style={{
|
||||
fontSize: '13px', fontWeight: 700, color: '#1F2937',
|
||||
marginBottom: '4px', whiteSpace: 'nowrap',
|
||||
}}>{process.name}</div>
|
||||
<div style={{
|
||||
fontSize: '11px',
|
||||
color: '#FFFFFF',
|
||||
backgroundColor: process.color,
|
||||
borderRadius: '10px',
|
||||
padding: '1px 8px',
|
||||
display: 'inline-block',
|
||||
}}>{process.dept}</div>
|
||||
{process.branch && (
|
||||
<div style={{
|
||||
position: 'absolute', top: '-8px', right: '-8px',
|
||||
width: '20px', height: '20px', borderRadius: '50%',
|
||||
backgroundColor: '#FEF3C7', border: '2px solid #F59E0B',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
fontSize: '10px',
|
||||
}}>⑂</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// --- 상세 패널 컴포넌트 ---
|
||||
function DetailPanel({ process, onClose }) {
|
||||
if (!process) return null;
|
||||
return (
|
||||
<div style={{
|
||||
backgroundColor: '#FFFFFF',
|
||||
borderRadius: '16px',
|
||||
border: `2px solid ${process.color}`,
|
||||
padding: '24px',
|
||||
boxShadow: '0 10px 25px rgba(0,0,0,0.1)',
|
||||
position: 'relative',
|
||||
}}>
|
||||
<button
|
||||
onClick={onClose}
|
||||
style={{
|
||||
position: 'absolute', top: '12px', right: '12px',
|
||||
width: '28px', height: '28px', borderRadius: '50%',
|
||||
border: '1px solid #E5E7EB', backgroundColor: '#F9FAFB',
|
||||
cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
fontSize: '14px', color: '#6B7280',
|
||||
}}
|
||||
>×</button>
|
||||
|
||||
{/* 헤더 */}
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '12px', marginBottom: '16px' }}>
|
||||
<div style={{
|
||||
width: '48px', height: '48px', borderRadius: '12px',
|
||||
backgroundColor: process.bgColor, display: 'flex',
|
||||
alignItems: 'center', justifyContent: 'center', fontSize: '24px',
|
||||
}}>{process.icon}</div>
|
||||
<div>
|
||||
<div style={{ fontSize: '18px', fontWeight: 700, color: '#1F2937' }}>
|
||||
<span style={{
|
||||
display: 'inline-block', width: '24px', height: '24px',
|
||||
borderRadius: '50%', backgroundColor: process.color, color: '#FFF',
|
||||
fontSize: '12px', textAlign: 'center', lineHeight: '24px',
|
||||
marginRight: '8px',
|
||||
}}>{process.id}</span>
|
||||
{process.name}
|
||||
</div>
|
||||
<div style={{ fontSize: '13px', color: '#6B7280', marginTop: '2px' }}>
|
||||
담당: <span style={{ color: process.color, fontWeight: 600 }}>{process.dept}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 설명 */}
|
||||
<div style={{
|
||||
fontSize: '14px', color: '#374151', marginBottom: '16px',
|
||||
padding: '12px', backgroundColor: process.bgColor, borderRadius: '8px',
|
||||
borderLeft: `4px solid ${process.color}`,
|
||||
}}>
|
||||
{process.description}
|
||||
</div>
|
||||
|
||||
{/* 3컬럼 그리드 */}
|
||||
<div style={{ display: 'flex', gap: '16px', flexWrap: 'wrap' }}>
|
||||
{/* 상세 업무 */}
|
||||
<div style={{ flex: '1 1 200px' }}>
|
||||
<div style={{
|
||||
fontSize: '13px', fontWeight: 700, color: '#374151',
|
||||
marginBottom: '8px', display: 'flex', alignItems: 'center', gap: '4px',
|
||||
}}>
|
||||
<span style={{ color: process.color }}>●</span> 상세 업무
|
||||
</div>
|
||||
<ul style={{ listStyle: 'none', padding: 0, margin: 0 }}>
|
||||
{process.details.map((d, i) => (
|
||||
<li key={i} style={{
|
||||
fontSize: '12px', color: '#4B5563', padding: '4px 0',
|
||||
borderBottom: '1px solid #F3F4F6',
|
||||
paddingLeft: '12px', position: 'relative',
|
||||
}}>
|
||||
<span style={{ position: 'absolute', left: 0, color: '#9CA3AF' }}>-</span>
|
||||
{d}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* 필요 서류 */}
|
||||
<div style={{ flex: '1 1 200px' }}>
|
||||
<div style={{
|
||||
fontSize: '13px', fontWeight: 700, color: '#374151',
|
||||
marginBottom: '8px', display: 'flex', alignItems: 'center', gap: '4px',
|
||||
}}>
|
||||
<span style={{ color: process.color }}>●</span> 필요 서류
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '4px' }}>
|
||||
{process.documents.map((doc, i) => (
|
||||
<span key={i} style={{
|
||||
fontSize: '11px', padding: '3px 10px',
|
||||
backgroundColor: '#F3F4F6', borderRadius: '12px',
|
||||
color: '#374151', whiteSpace: 'nowrap',
|
||||
}}>{doc}</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* SAM 연동 */}
|
||||
<div style={{ flex: '1 1 150px' }}>
|
||||
<div style={{
|
||||
fontSize: '13px', fontWeight: 700, color: '#374151',
|
||||
marginBottom: '8px', display: 'flex', alignItems: 'center', gap: '4px',
|
||||
}}>
|
||||
<span style={{ color: process.color }}>●</span> SAM 연동
|
||||
</div>
|
||||
{process.samLink ? (
|
||||
<a
|
||||
href={process.samLink}
|
||||
style={{
|
||||
display: 'inline-flex', alignItems: 'center', gap: '6px',
|
||||
fontSize: '12px', color: process.color, fontWeight: 600,
|
||||
padding: '6px 12px', borderRadius: '8px',
|
||||
backgroundColor: process.bgColor,
|
||||
border: `1px solid ${process.color}40`,
|
||||
textDecoration: 'none',
|
||||
}}
|
||||
>
|
||||
↗ {process.samMenu}
|
||||
</a>
|
||||
) : (
|
||||
<span style={{
|
||||
fontSize: '12px', color: '#9CA3AF', fontStyle: 'italic',
|
||||
}}>추후 연동 예정</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// --- 입찰 분기 표시 ---
|
||||
function BranchInfo() {
|
||||
return (
|
||||
<div style={{
|
||||
display: 'flex', gap: '8px', justifyContent: 'center',
|
||||
margin: '8px 0', fontSize: '11px',
|
||||
}}>
|
||||
<span style={{
|
||||
padding: '2px 10px', borderRadius: '10px',
|
||||
backgroundColor: '#DCFCE7', color: '#166534', fontWeight: 600,
|
||||
}}>낙찰 → 계약 진행</span>
|
||||
<span style={{
|
||||
padding: '2px 10px', borderRadius: '10px',
|
||||
backgroundColor: '#FEE2E2', color: '#991B1B', fontWeight: 600,
|
||||
}}>유찰 → 재입찰/종료</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// --- 메인 앱 ---
|
||||
function App() {
|
||||
const [selected, setSelected] = useState(null);
|
||||
|
||||
const topRow = processes.slice(0, 6); // 영업~입고검수
|
||||
const bottomRow = processes.slice(6); // 시공~A/S
|
||||
|
||||
return (
|
||||
<div style={{ padding: '20px' }}>
|
||||
{/* 헤더 */}
|
||||
<div style={{
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
|
||||
marginBottom: '24px',
|
||||
}}>
|
||||
<div>
|
||||
<h1 style={{
|
||||
fontSize: '22px', fontWeight: 800, color: '#111827', margin: 0,
|
||||
}}>업무 Workflow</h1>
|
||||
<p style={{ fontSize: '13px', color: '#6B7280', margin: '4px 0 0' }}>
|
||||
주일기업 업무처리과정 플로우차트 — 각 단계를 클릭하면 상세 정보를 확인할 수 있습니다
|
||||
</p>
|
||||
</div>
|
||||
<div style={{
|
||||
display: 'flex', gap: '6px', flexWrap: 'wrap', justifyContent: 'flex-end',
|
||||
}}>
|
||||
{['영업', '견적', '시공', '정산'].map((label, i) => (
|
||||
<span key={i} style={{
|
||||
fontSize: '11px', padding: '2px 10px',
|
||||
borderRadius: '10px', backgroundColor: '#F3F4F6',
|
||||
color: '#6B7280', fontWeight: 500,
|
||||
}}>{label}</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 플로우차트 영역 */}
|
||||
<div style={{
|
||||
backgroundColor: '#FAFBFC',
|
||||
borderRadius: '16px',
|
||||
border: '1px solid #E5E7EB',
|
||||
padding: '24px 16px',
|
||||
marginBottom: '24px',
|
||||
overflowX: 'auto',
|
||||
}}>
|
||||
{/* 상단 행: 영업 → 입고검수 */}
|
||||
<div style={{
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
gap: '4px', minWidth: 'fit-content', margin: '0 auto',
|
||||
}}>
|
||||
{topRow.map((p, i) => (
|
||||
<React.Fragment key={p.id}>
|
||||
<ProcessNode
|
||||
process={p}
|
||||
isActive={selected?.id === p.id}
|
||||
onClick={setSelected}
|
||||
/>
|
||||
{i < topRow.length - 1 && <Arrow />}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 입찰 분기 표시 (3번째 노드 아래) */}
|
||||
<BranchInfo />
|
||||
|
||||
{/* 연결 화살표 (상단 → 하단) */}
|
||||
<div style={{
|
||||
display: 'flex', justifyContent: 'center', padding: '4px 0',
|
||||
}}>
|
||||
<div style={{
|
||||
display: 'flex', flexDirection: 'column', alignItems: 'center',
|
||||
}}>
|
||||
<div style={{ width: '2px', height: '16px', backgroundColor: '#9CA3AF' }}></div>
|
||||
<div style={{
|
||||
width: 0, height: 0,
|
||||
borderLeft: '6px solid transparent',
|
||||
borderRight: '6px solid transparent',
|
||||
borderTop: '8px solid #9CA3AF',
|
||||
}}></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 하단 행: 시공 → A/S */}
|
||||
<div style={{
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
gap: '4px', minWidth: 'fit-content', margin: '0 auto',
|
||||
}}>
|
||||
{bottomRow.map((p, i) => (
|
||||
<React.Fragment key={p.id}>
|
||||
<ProcessNode
|
||||
process={p}
|
||||
isActive={selected?.id === p.id}
|
||||
onClick={setSelected}
|
||||
/>
|
||||
{i < bottomRow.length - 1 && <Arrow />}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 상세 패널 */}
|
||||
{selected && (
|
||||
<DetailPanel process={selected} onClose={() => setSelected(null)} />
|
||||
)}
|
||||
|
||||
{/* 범례 */}
|
||||
<div style={{
|
||||
display: 'flex', gap: '16px', flexWrap: 'wrap',
|
||||
padding: '12px 16px', backgroundColor: '#F9FAFB',
|
||||
borderRadius: '8px', border: '1px solid #E5E7EB',
|
||||
fontSize: '12px', color: '#6B7280',
|
||||
}}>
|
||||
<span><strong>범례:</strong></span>
|
||||
<span>클릭 → 상세 보기</span>
|
||||
<span>⑂ 분기점 (입찰 결과)</span>
|
||||
<span>↗ SAM 메뉴 바로가기</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
|
||||
@endverbatim
|
||||
</script>
|
||||
@endpush
|
||||
@@ -1641,6 +1641,7 @@
|
||||
Route::middleware('auth')->prefix('juil')->name('juil.')->group(function () {
|
||||
Route::get('/estimate', [PlanningController::class, 'estimate'])->name('estimate');
|
||||
Route::get('/project', [PlanningController::class, 'project'])->name('project');
|
||||
Route::get('/workflow', [PlanningController::class, 'workflow'])->name('workflow');
|
||||
|
||||
// 공사현장 사진대지
|
||||
Route::prefix('construction-photos')->name('construction-photos.')->group(function () {
|
||||
|
||||
Reference in New Issue
Block a user