feat: [equipment] 다중 점검주기 + 정/부 담당자 체계 구현
- InspectionCycle enum: 6종 점검주기 상수, 열 라벨, check_date 계산 - Equipment 모델: subManager 관계, canInspect() 권한 체크 - Template/Inspection 모델: inspection_cycle fillable 추가 - EquipmentInspectionService: 주기별 점검 조회/토글/권한 체크 - 점검표 UI: 주기 탭, 동적 필터(월/연도), 주기별 그리드 열 - 점검항목 템플릿: 주기별 탭 그룹핑, 모달에 주기 선택 - 설비 등록/수정/상세: 부 담당자 필드 추가 - 권한 없는 장비 셀 비활성(cursor-not-allowed, opacity-50)
This commit is contained in:
221
app/Enums/InspectionCycle.php
Normal file
221
app/Enums/InspectionCycle.php
Normal file
@@ -0,0 +1,221 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use Carbon\Carbon;
|
||||
|
||||
class InspectionCycle
|
||||
{
|
||||
const DAILY = 'daily';
|
||||
|
||||
const WEEKLY = 'weekly';
|
||||
|
||||
const MONTHLY = 'monthly';
|
||||
|
||||
const BIMONTHLY = 'bimonthly';
|
||||
|
||||
const QUARTERLY = 'quarterly';
|
||||
|
||||
const SEMIANNUAL = 'semiannual';
|
||||
|
||||
/**
|
||||
* 전체 주기 목록 (코드 → 라벨)
|
||||
*/
|
||||
public static function all(): array
|
||||
{
|
||||
return [
|
||||
self::DAILY => '일일',
|
||||
self::WEEKLY => '주간',
|
||||
self::MONTHLY => '월간',
|
||||
self::BIMONTHLY => '2개월',
|
||||
self::QUARTERLY => '분기',
|
||||
self::SEMIANNUAL => '반년',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 주기별 라벨 반환
|
||||
*/
|
||||
public static function label(string $cycle): string
|
||||
{
|
||||
return self::all()[$cycle] ?? $cycle;
|
||||
}
|
||||
|
||||
/**
|
||||
* 주기별 기간 필터 타입 반환
|
||||
*/
|
||||
public static function periodType(string $cycle): string
|
||||
{
|
||||
return $cycle === self::DAILY ? 'month' : 'year';
|
||||
}
|
||||
|
||||
/**
|
||||
* 주기별 그리드 열 라벨 반환
|
||||
*
|
||||
* @return array<int, string> [colIndex => label]
|
||||
*/
|
||||
public static function columnLabels(string $cycle, ?string $period = null): array
|
||||
{
|
||||
return match ($cycle) {
|
||||
self::DAILY => self::dailyLabels($period),
|
||||
self::WEEKLY => self::weeklyLabels(),
|
||||
self::MONTHLY => self::monthlyLabels(),
|
||||
self::BIMONTHLY => self::bimonthlyLabels(),
|
||||
self::QUARTERLY => self::quarterlyLabels(),
|
||||
self::SEMIANNUAL => self::semiannualLabels(),
|
||||
default => self::dailyLabels($period),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 열 인덱스 → check_date 변환
|
||||
*/
|
||||
public static function resolveCheckDate(string $cycle, string $period, int $colIndex): string
|
||||
{
|
||||
return match ($cycle) {
|
||||
self::DAILY => self::dailyCheckDate($period, $colIndex),
|
||||
self::WEEKLY => self::weeklyCheckDate($period, $colIndex),
|
||||
self::MONTHLY => self::monthlyCheckDate($period, $colIndex),
|
||||
self::BIMONTHLY => self::bimonthlyCheckDate($period, $colIndex),
|
||||
self::QUARTERLY => self::quarterlyCheckDate($period, $colIndex),
|
||||
self::SEMIANNUAL => self::semiannualCheckDate($period, $colIndex),
|
||||
default => self::dailyCheckDate($period, $colIndex),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* check_date → year_month(period) 역산
|
||||
*/
|
||||
public static function resolvePeriod(string $cycle, string $checkDate): string
|
||||
{
|
||||
$date = Carbon::parse($checkDate);
|
||||
|
||||
return match ($cycle) {
|
||||
self::DAILY => $date->format('Y-m'),
|
||||
default => $date->format('Y'),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 주기별 그리드 열 수
|
||||
*/
|
||||
public static function columnCount(string $cycle, ?string $period = null): int
|
||||
{
|
||||
return count(self::columnLabels($cycle, $period));
|
||||
}
|
||||
|
||||
/**
|
||||
* 주말 여부 (daily 전용)
|
||||
*/
|
||||
public static function isWeekend(string $period, int $colIndex): bool
|
||||
{
|
||||
$date = Carbon::createFromFormat('Y-m', $period)->startOfMonth()->addDays($colIndex - 1);
|
||||
|
||||
return in_array($date->dayOfWeek, [0, 6]);
|
||||
}
|
||||
|
||||
// --- Daily ---
|
||||
private static function dailyLabels(?string $period): array
|
||||
{
|
||||
$date = Carbon::createFromFormat('Y-m', $period ?? now()->format('Y-m'));
|
||||
$days = $date->daysInMonth;
|
||||
$labels = [];
|
||||
for ($d = 1; $d <= $days; $d++) {
|
||||
$labels[$d] = (string) $d;
|
||||
}
|
||||
|
||||
return $labels;
|
||||
}
|
||||
|
||||
private static function dailyCheckDate(string $period, int $colIndex): string
|
||||
{
|
||||
return Carbon::createFromFormat('Y-m', $period)->startOfMonth()->addDays($colIndex - 1)->format('Y-m-d');
|
||||
}
|
||||
|
||||
// --- Weekly ---
|
||||
private static function weeklyLabels(): array
|
||||
{
|
||||
$labels = [];
|
||||
for ($w = 1; $w <= 52; $w++) {
|
||||
$labels[$w] = $w.'주';
|
||||
}
|
||||
|
||||
return $labels;
|
||||
}
|
||||
|
||||
private static function weeklyCheckDate(string $year, int $colIndex): string
|
||||
{
|
||||
// ISO 주차의 월요일
|
||||
return Carbon::create((int) $year)->setISODate((int) $year, $colIndex, 1)->format('Y-m-d');
|
||||
}
|
||||
|
||||
// --- Monthly ---
|
||||
private static function monthlyLabels(): array
|
||||
{
|
||||
$labels = [];
|
||||
for ($m = 1; $m <= 12; $m++) {
|
||||
$labels[$m] = $m.'월';
|
||||
}
|
||||
|
||||
return $labels;
|
||||
}
|
||||
|
||||
private static function monthlyCheckDate(string $year, int $colIndex): string
|
||||
{
|
||||
return Carbon::create((int) $year, $colIndex, 1)->format('Y-m-d');
|
||||
}
|
||||
|
||||
// --- Bimonthly ---
|
||||
private static function bimonthlyLabels(): array
|
||||
{
|
||||
return [
|
||||
1 => '1~2월',
|
||||
2 => '3~4월',
|
||||
3 => '5~6월',
|
||||
4 => '7~8월',
|
||||
5 => '9~10월',
|
||||
6 => '11~12월',
|
||||
];
|
||||
}
|
||||
|
||||
private static function bimonthlyCheckDate(string $year, int $colIndex): string
|
||||
{
|
||||
$month = ($colIndex - 1) * 2 + 1;
|
||||
|
||||
return Carbon::create((int) $year, $month, 1)->format('Y-m-d');
|
||||
}
|
||||
|
||||
// --- Quarterly ---
|
||||
private static function quarterlyLabels(): array
|
||||
{
|
||||
return [
|
||||
1 => '1분기',
|
||||
2 => '2분기',
|
||||
3 => '3분기',
|
||||
4 => '4분기',
|
||||
];
|
||||
}
|
||||
|
||||
private static function quarterlyCheckDate(string $year, int $colIndex): string
|
||||
{
|
||||
$month = ($colIndex - 1) * 3 + 1;
|
||||
|
||||
return Carbon::create((int) $year, $month, 1)->format('Y-m-d');
|
||||
}
|
||||
|
||||
// --- Semiannual ---
|
||||
private static function semiannualLabels(): array
|
||||
{
|
||||
return [
|
||||
1 => '상반기',
|
||||
2 => '하반기',
|
||||
];
|
||||
}
|
||||
|
||||
private static function semiannualCheckDate(string $year, int $colIndex): string
|
||||
{
|
||||
$month = $colIndex === 1 ? 1 : 7;
|
||||
|
||||
return Carbon::create((int) $year, $month, 1)->format('Y-m-d');
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Http\Controllers\Api\Admin;
|
||||
|
||||
use App\Enums\InspectionCycle;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\EquipmentInspectionService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
@@ -15,12 +16,20 @@ public function __construct(
|
||||
|
||||
public function index(Request $request)
|
||||
{
|
||||
$yearMonth = $request->input('year_month', now()->format('Y-m'));
|
||||
$cycle = $request->input('cycle', InspectionCycle::DAILY);
|
||||
$period = $request->input('period') ?? $request->input('year_month', now()->format('Y-m'));
|
||||
|
||||
// daily가 아닌 주기에서 period가 없으면 현재 연도
|
||||
if ($cycle !== InspectionCycle::DAILY && ! $request->input('period')) {
|
||||
$period = now()->format('Y');
|
||||
}
|
||||
|
||||
$productionLine = $request->input('production_line');
|
||||
$equipmentId = $request->input('equipment_id');
|
||||
|
||||
$inspections = $this->inspectionService->getMonthlyInspections(
|
||||
$yearMonth,
|
||||
$inspections = $this->inspectionService->getInspections(
|
||||
$cycle,
|
||||
$period,
|
||||
$productionLine,
|
||||
$equipmentId ? (int) $equipmentId : null
|
||||
);
|
||||
@@ -28,7 +37,8 @@ public function index(Request $request)
|
||||
if ($request->header('HX-Request')) {
|
||||
return view('equipment.partials.inspection-grid', [
|
||||
'inspections' => $inspections,
|
||||
'yearMonth' => $yearMonth,
|
||||
'cycle' => $cycle,
|
||||
'period' => $period,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -44,13 +54,15 @@ public function toggleDetail(Request $request): JsonResponse
|
||||
'equipment_id' => 'required|integer',
|
||||
'template_item_id' => 'required|integer',
|
||||
'check_date' => 'required|date',
|
||||
'cycle' => 'nullable|string',
|
||||
]);
|
||||
|
||||
try {
|
||||
$result = $this->inspectionService->toggleDetail(
|
||||
$request->input('equipment_id'),
|
||||
$request->input('template_item_id'),
|
||||
$request->input('check_date')
|
||||
$request->input('check_date'),
|
||||
$request->input('cycle', InspectionCycle::DAILY)
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
@@ -58,10 +70,12 @@ public function toggleDetail(Request $request): JsonResponse
|
||||
'data' => $result,
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
$status = $e->getMessage() === '점검 권한이 없습니다.' ? 403 : 400;
|
||||
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => $e->getMessage(),
|
||||
], 400);
|
||||
], $status);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,6 +84,7 @@ public function updateNotes(Request $request): JsonResponse
|
||||
$request->validate([
|
||||
'equipment_id' => 'required|integer',
|
||||
'year_month' => 'required|string',
|
||||
'cycle' => 'nullable|string',
|
||||
'overall_judgment' => 'nullable|in:OK,NG',
|
||||
'repair_note' => 'nullable|string',
|
||||
'issue_note' => 'nullable|string',
|
||||
@@ -80,7 +95,8 @@ public function updateNotes(Request $request): JsonResponse
|
||||
$inspection = $this->inspectionService->updateInspectionNotes(
|
||||
$request->input('equipment_id'),
|
||||
$request->input('year_month'),
|
||||
$request->only(['overall_judgment', 'repair_note', 'issue_note', 'inspector_id'])
|
||||
$request->only(['overall_judgment', 'repair_note', 'issue_note', 'inspector_id']),
|
||||
$request->input('cycle', InspectionCycle::DAILY)
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
@@ -100,6 +116,7 @@ public function storeTemplate(Request $request, int $equipmentId): JsonResponse
|
||||
{
|
||||
$request->validate([
|
||||
'item_no' => 'required|integer',
|
||||
'inspection_cycle' => 'nullable|string',
|
||||
'check_point' => 'required|string|max:50',
|
||||
'check_item' => 'required|string|max:100',
|
||||
'check_timing' => 'nullable|in:operating,stopped',
|
||||
@@ -109,7 +126,12 @@ public function storeTemplate(Request $request, int $equipmentId): JsonResponse
|
||||
]);
|
||||
|
||||
try {
|
||||
$template = $this->inspectionService->saveTemplate($equipmentId, $request->all());
|
||||
$data = $request->all();
|
||||
if (empty($data['inspection_cycle'])) {
|
||||
$data['inspection_cycle'] = InspectionCycle::DAILY;
|
||||
}
|
||||
|
||||
$template = $this->inspectionService->saveTemplate($equipmentId, $data);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Enums\InspectionCycle;
|
||||
use App\Services\EquipmentInspectionService;
|
||||
use App\Services\EquipmentRepairService;
|
||||
use App\Services\EquipmentService;
|
||||
@@ -72,8 +73,9 @@ public function inspections(Request $request): View|Response
|
||||
}
|
||||
|
||||
$equipmentList = $this->equipmentService->getEquipmentList();
|
||||
$cycles = InspectionCycle::all();
|
||||
|
||||
return view('equipment.inspections.index', compact('equipmentList'));
|
||||
return view('equipment.inspections.index', compact('equipmentList', 'cycles'));
|
||||
}
|
||||
|
||||
public function repairs(Request $request): View|Response
|
||||
|
||||
@@ -34,6 +34,7 @@ class Equipment extends Model
|
||||
'status',
|
||||
'disposed_date',
|
||||
'manager_id',
|
||||
'sub_manager_id',
|
||||
'photo_path',
|
||||
'memo',
|
||||
'is_active',
|
||||
@@ -56,6 +57,24 @@ public function manager(): BelongsTo
|
||||
return $this->belongsTo(\App\Models\User::class, 'manager_id');
|
||||
}
|
||||
|
||||
public function subManager(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(\App\Models\User::class, 'sub_manager_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* 해당 유저가 이 설비의 점검 권한이 있는지 확인
|
||||
*/
|
||||
public function canInspect(?int $userId = null): bool
|
||||
{
|
||||
$userId = $userId ?? auth()->id();
|
||||
if (auth()->user()?->isAdmin()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this->manager_id === $userId || $this->sub_manager_id === $userId;
|
||||
}
|
||||
|
||||
public function inspectionTemplates(): HasMany
|
||||
{
|
||||
return $this->hasMany(EquipmentInspectionTemplate::class, 'equipment_id')->orderBy('sort_order');
|
||||
|
||||
@@ -14,6 +14,7 @@ class EquipmentInspection extends Model
|
||||
protected $fillable = [
|
||||
'tenant_id',
|
||||
'equipment_id',
|
||||
'inspection_cycle',
|
||||
'year_month',
|
||||
'overall_judgment',
|
||||
'inspector_id',
|
||||
|
||||
@@ -13,6 +13,7 @@ class EquipmentInspectionTemplate extends Model
|
||||
protected $fillable = [
|
||||
'tenant_id',
|
||||
'equipment_id',
|
||||
'inspection_cycle',
|
||||
'item_no',
|
||||
'check_point',
|
||||
'check_item',
|
||||
@@ -37,6 +38,11 @@ public function scopeActive($query)
|
||||
return $query->where('is_active', true);
|
||||
}
|
||||
|
||||
public function scopeByCycle($query, string $cycle)
|
||||
{
|
||||
return $query->where('inspection_cycle', $cycle);
|
||||
}
|
||||
|
||||
public function getTimingLabelAttribute(): string
|
||||
{
|
||||
return match ($this->check_timing) {
|
||||
|
||||
@@ -2,19 +2,25 @@
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Enums\InspectionCycle;
|
||||
use App\Models\Equipment\Equipment;
|
||||
use App\Models\Equipment\EquipmentInspection;
|
||||
use App\Models\Equipment\EquipmentInspectionDetail;
|
||||
use App\Models\Equipment\EquipmentInspectionTemplate;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class EquipmentInspectionService
|
||||
{
|
||||
public function getMonthlyInspections(string $yearMonth, ?string $productionLine = null, ?int $equipmentId = null): array
|
||||
/**
|
||||
* 주기별 점검 데이터 조회
|
||||
*
|
||||
* @param string $cycle 점검주기 (daily/weekly/monthly/bimonthly/quarterly/semiannual)
|
||||
* @param string $period 기간 (daily: 2026-02, 그 외: 2026)
|
||||
*/
|
||||
public function getInspections(string $cycle, string $period, ?string $productionLine = null, ?int $equipmentId = null): array
|
||||
{
|
||||
$tenantId = session('selected_tenant_id', 1);
|
||||
|
||||
$equipmentQuery = Equipment::where('is_active', true)->where('status', '!=', 'disposed');
|
||||
$equipmentQuery = Equipment::where('is_active', true)
|
||||
->where('status', '!=', 'disposed')
|
||||
->with(['manager', 'subManager']);
|
||||
|
||||
if ($productionLine) {
|
||||
$equipmentQuery->where('production_line', $productionLine);
|
||||
@@ -26,13 +32,13 @@ public function getMonthlyInspections(string $yearMonth, ?string $productionLine
|
||||
|
||||
$equipments = $equipmentQuery->orderBy('sort_order')->orderBy('name')->get();
|
||||
|
||||
$date = Carbon::createFromFormat('Y-m', $yearMonth);
|
||||
$daysInMonth = $date->daysInMonth;
|
||||
$labels = InspectionCycle::columnLabels($cycle, $period);
|
||||
|
||||
$result = [];
|
||||
|
||||
foreach ($equipments as $equipment) {
|
||||
$templates = EquipmentInspectionTemplate::where('equipment_id', $equipment->id)
|
||||
->where('inspection_cycle', $cycle)
|
||||
->where('is_active', true)
|
||||
->orderBy('sort_order')
|
||||
->get();
|
||||
@@ -42,7 +48,8 @@ public function getMonthlyInspections(string $yearMonth, ?string $productionLine
|
||||
}
|
||||
|
||||
$inspection = EquipmentInspection::where('equipment_id', $equipment->id)
|
||||
->where('year_month', $yearMonth)
|
||||
->where('inspection_cycle', $cycle)
|
||||
->where('year_month', $period)
|
||||
->first();
|
||||
|
||||
$details = [];
|
||||
@@ -59,23 +66,41 @@ public function getMonthlyInspections(string $yearMonth, ?string $productionLine
|
||||
'templates' => $templates,
|
||||
'inspection' => $inspection,
|
||||
'details' => $details,
|
||||
'days_in_month' => $daysInMonth,
|
||||
'labels' => $labels,
|
||||
'can_inspect' => $equipment->canInspect(),
|
||||
];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function toggleDetail(int $equipmentId, int $templateItemId, string $checkDate): array
|
||||
/**
|
||||
* 하위호환: 기존 getMonthlyInspections 유지
|
||||
*/
|
||||
public function getMonthlyInspections(string $yearMonth, ?string $productionLine = null, ?int $equipmentId = null): array
|
||||
{
|
||||
return $this->getInspections(InspectionCycle::DAILY, $yearMonth, $productionLine, $equipmentId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 셀 토글 (점검 결과 순환)
|
||||
*/
|
||||
public function toggleDetail(int $equipmentId, int $templateItemId, string $checkDate, string $cycle = 'daily'): array
|
||||
{
|
||||
$equipment = Equipment::findOrFail($equipmentId);
|
||||
if (! $equipment->canInspect()) {
|
||||
throw new \Exception('점검 권한이 없습니다.');
|
||||
}
|
||||
|
||||
$tenantId = session('selected_tenant_id', 1);
|
||||
$yearMonth = Carbon::parse($checkDate)->format('Y-m');
|
||||
$period = InspectionCycle::resolvePeriod($cycle, $checkDate);
|
||||
|
||||
$inspection = EquipmentInspection::firstOrCreate(
|
||||
[
|
||||
'tenant_id' => $tenantId,
|
||||
'equipment_id' => $equipmentId,
|
||||
'year_month' => $yearMonth,
|
||||
'inspection_cycle' => $cycle,
|
||||
'year_month' => $period,
|
||||
],
|
||||
[
|
||||
'created_by' => auth()->id(),
|
||||
@@ -112,7 +137,7 @@ public function toggleDetail(int $equipmentId, int $templateItemId, string $chec
|
||||
];
|
||||
}
|
||||
|
||||
public function updateInspectionNotes(int $equipmentId, string $yearMonth, array $data): EquipmentInspection
|
||||
public function updateInspectionNotes(int $equipmentId, string $yearMonth, array $data, string $cycle = 'daily'): EquipmentInspection
|
||||
{
|
||||
$tenantId = session('selected_tenant_id', 1);
|
||||
|
||||
@@ -120,6 +145,7 @@ public function updateInspectionNotes(int $equipmentId, string $yearMonth, array
|
||||
[
|
||||
'tenant_id' => $tenantId,
|
||||
'equipment_id' => $equipmentId,
|
||||
'inspection_cycle' => $cycle,
|
||||
'year_month' => $yearMonth,
|
||||
],
|
||||
[
|
||||
@@ -134,8 +160,6 @@ public function updateInspectionNotes(int $equipmentId, string $yearMonth, array
|
||||
|
||||
public function getMonthlyStats(string $yearMonth): array
|
||||
{
|
||||
$tenantId = session('selected_tenant_id', 1);
|
||||
|
||||
$totalEquipments = Equipment::where('is_active', true)
|
||||
->where('status', '!=', 'disposed')
|
||||
->count();
|
||||
@@ -175,4 +199,16 @@ public function deleteTemplate(int $id): bool
|
||||
{
|
||||
return EquipmentInspectionTemplate::findOrFail($id)->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* 설비에 등록된 점검주기 목록 반환 (항목이 있는 주기만)
|
||||
*/
|
||||
public function getActiveCycles(int $equipmentId): array
|
||||
{
|
||||
return EquipmentInspectionTemplate::where('equipment_id', $equipmentId)
|
||||
->where('is_active', true)
|
||||
->distinct()
|
||||
->pluck('inspection_cycle')
|
||||
->toArray();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ class EquipmentService
|
||||
{
|
||||
public function getEquipments(array $filters = [], int $perPage = 20): LengthAwarePaginator
|
||||
{
|
||||
$query = Equipment::query()->with('manager');
|
||||
$query = Equipment::query()->with(['manager', 'subManager']);
|
||||
|
||||
if (! empty($filters['search'])) {
|
||||
$search = $filters['search'];
|
||||
@@ -40,7 +40,7 @@ public function getEquipments(array $filters = [], int $perPage = 20): LengthAwa
|
||||
|
||||
public function getEquipmentById(int $id): ?Equipment
|
||||
{
|
||||
return Equipment::with(['manager', 'inspectionTemplates', 'repairs', 'processes', 'photos'])->find($id);
|
||||
return Equipment::with(['manager', 'subManager', 'inspectionTemplates', 'repairs', 'processes', 'photos'])->find($id);
|
||||
}
|
||||
|
||||
public function createEquipment(array $data): Equipment
|
||||
|
||||
@@ -125,7 +125,7 @@ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none foc
|
||||
<h2 class="text-lg font-semibold text-gray-800 mb-4 pb-2 border-b">담당자 / 비고</h2>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-2">담당자</label>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-2">정 담당자</label>
|
||||
<select name="manager_id"
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
<option value="">선택</option>
|
||||
@@ -134,6 +134,16 @@ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none foc
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-2">부 담당자</label>
|
||||
<select name="sub_manager_id"
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
<option value="">선택</option>
|
||||
@foreach($users as $user)
|
||||
<option value="{{ $user->id }}">{{ $user->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-2">상태</label>
|
||||
<select name="status"
|
||||
|
||||
@@ -133,7 +133,7 @@ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none foc
|
||||
<h2 class="text-lg font-semibold text-gray-800 mb-4 pb-2 border-b">담당자 / 비고</h2>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-2">담당자</label>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-2">정 담당자</label>
|
||||
<select name="manager_id" id="manager_id"
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
<option value="">선택</option>
|
||||
@@ -142,6 +142,16 @@ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none foc
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-2">부 담당자</label>
|
||||
<select name="sub_manager_id" id="sub_manager_id"
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
<option value="">선택</option>
|
||||
@foreach($users as $user)
|
||||
<option value="{{ $user->id }}">{{ $user->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-2">상태</label>
|
||||
<select name="status" id="status"
|
||||
@@ -203,7 +213,7 @@ class="bg-gray-200 hover:bg-gray-300 text-gray-800 px-6 py-2 rounded-lg transiti
|
||||
const csrfToken = '{{ csrf_token() }}';
|
||||
const fields = ['equipment_code', 'name', 'equipment_type', 'specification', 'manufacturer',
|
||||
'model_name', 'serial_no', 'location', 'production_line', 'purchase_date', 'install_date',
|
||||
'purchase_price', 'useful_life', 'status', 'manager_id', 'memo'];
|
||||
'purchase_price', 'useful_life', 'status', 'manager_id', 'sub_manager_id', 'memo'];
|
||||
|
||||
// 설비 데이터 로드
|
||||
fetch(`/api/admin/equipment/${equipmentId}`, {
|
||||
|
||||
@@ -1,20 +1,47 @@
|
||||
@extends('layouts.app')
|
||||
@section('title', '일상점검표')
|
||||
@section('title', '설비 점검표')
|
||||
@section('content')
|
||||
|
||||
<!-- 헤더 -->
|
||||
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4 mb-6">
|
||||
<h1 class="text-2xl font-bold text-gray-800">일상점검표</h1>
|
||||
<h1 class="text-2xl font-bold text-gray-800">설비 점검표</h1>
|
||||
</div>
|
||||
|
||||
<!-- 주기 탭 -->
|
||||
<div class="flex gap-1 mb-4 bg-gray-100 rounded-lg p-1" style="width: fit-content;">
|
||||
@foreach($cycles as $code => $label)
|
||||
<button type="button"
|
||||
class="cycle-tab px-4 py-2 rounded-lg text-sm font-medium transition {{ $code === 'daily' ? 'bg-white text-blue-700 shadow-sm' : 'text-gray-600 hover:text-gray-800 hover:bg-gray-50' }}"
|
||||
data-cycle="{{ $code }}"
|
||||
onclick="switchCycle('{{ $code }}')">
|
||||
{{ $label }}
|
||||
</button>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
<!-- 필터 -->
|
||||
<x-filter-collapsible id="inspectionFilter" :defaultOpen="true">
|
||||
<x-filter-collapsible id="inspectionFilterWrap" :defaultOpen="true">
|
||||
<form id="inspectionFilter" class="flex flex-wrap gap-2 sm:gap-4">
|
||||
<div class="shrink-0" style="width: 160px;">
|
||||
<input type="hidden" name="cycle" id="filterCycle" value="daily">
|
||||
|
||||
<!-- daily 전용: 점검년월 -->
|
||||
<div class="shrink-0 filter-daily" style="width: 160px;">
|
||||
<label class="block text-xs text-gray-500 mb-1">점검년월</label>
|
||||
<input type="month" name="year_month" id="yearMonth" value="{{ now()->format('Y-m') }}"
|
||||
<input type="month" name="period" id="periodMonth" value="{{ now()->format('Y-m') }}"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
</div>
|
||||
|
||||
<!-- 그 외 주기: 점검연도 -->
|
||||
<div class="shrink-0 filter-year" style="width: 120px; display: none;">
|
||||
<label class="block text-xs text-gray-500 mb-1">점검연도</label>
|
||||
<select name="period_year" id="periodYear"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
@for($y = now()->year; $y >= now()->year - 3; $y--)
|
||||
<option value="{{ $y }}">{{ $y }}년</option>
|
||||
@endfor
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="shrink-0" style="width: 140px;">
|
||||
<label class="block text-xs text-gray-500 mb-1">생산라인</label>
|
||||
<select name="production_line"
|
||||
@@ -59,11 +86,47 @@ class="bg-white rounded-lg shadow-sm">
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
let currentCycle = 'daily';
|
||||
|
||||
document.getElementById('inspectionFilter').addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
updatePeriodValue();
|
||||
htmx.trigger('#inspection-grid', 'filterSubmit');
|
||||
});
|
||||
|
||||
function switchCycle(cycle) {
|
||||
currentCycle = cycle;
|
||||
document.getElementById('filterCycle').value = cycle;
|
||||
|
||||
// 탭 UI 전환
|
||||
document.querySelectorAll('.cycle-tab').forEach(btn => {
|
||||
if (btn.dataset.cycle === cycle) {
|
||||
btn.className = 'cycle-tab px-4 py-2 rounded-lg text-sm font-medium transition bg-white text-blue-700 shadow-sm';
|
||||
} else {
|
||||
btn.className = 'cycle-tab px-4 py-2 rounded-lg text-sm font-medium transition text-gray-600 hover:text-gray-800 hover:bg-gray-50';
|
||||
}
|
||||
});
|
||||
|
||||
// 필터 전환
|
||||
const isDaily = cycle === 'daily';
|
||||
document.querySelectorAll('.filter-daily').forEach(el => el.style.display = isDaily ? '' : 'none');
|
||||
document.querySelectorAll('.filter-year').forEach(el => el.style.display = isDaily ? 'none' : '');
|
||||
|
||||
updatePeriodValue();
|
||||
htmx.trigger('#inspection-grid', 'filterSubmit');
|
||||
}
|
||||
|
||||
function updatePeriodValue() {
|
||||
// hidden period 필드에 올바른 값 설정
|
||||
if (currentCycle === 'daily') {
|
||||
document.getElementById('periodMonth').name = 'period';
|
||||
document.getElementById('periodYear').name = 'period_year';
|
||||
} else {
|
||||
document.getElementById('periodMonth').name = 'period_month_unused';
|
||||
document.getElementById('periodYear').name = 'period';
|
||||
}
|
||||
}
|
||||
|
||||
function toggleCell(equipmentId, templateItemId, checkDate, cell) {
|
||||
fetch('/api/admin/equipment/inspections/detail', {
|
||||
method: 'PATCH',
|
||||
@@ -76,13 +139,17 @@ function toggleCell(equipmentId, templateItemId, checkDate, cell) {
|
||||
equipment_id: equipmentId,
|
||||
template_item_id: templateItemId,
|
||||
check_date: checkDate,
|
||||
cycle: currentCycle,
|
||||
})
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
cell.textContent = data.data.symbol;
|
||||
cell.className = 'inspection-cell cursor-pointer text-center text-lg font-bold select-none ' + data.data.color;
|
||||
const span = cell.querySelector('.inspection-cell') || cell;
|
||||
span.textContent = data.data.symbol;
|
||||
span.className = 'inspection-cell text-lg font-bold select-none ' + data.data.color;
|
||||
} else if (data.message) {
|
||||
showToast(data.message, 'error');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
@if(empty($inspections))
|
||||
<div class="p-12 text-center text-gray-500">
|
||||
<p>점검 가능한 설비가 없습니다.</p>
|
||||
<p class="text-sm mt-2">설비 등록대장에서 점검항목을 추가해주세요.</p>
|
||||
<p class="text-sm mt-2">설비 등록대장에서 해당 주기의 점검항목을 추가해주세요.</p>
|
||||
</div>
|
||||
@else
|
||||
@php
|
||||
$date = \Carbon\Carbon::createFromFormat('Y-m', $yearMonth);
|
||||
$daysInMonth = $date->daysInMonth;
|
||||
$cycle = $cycle ?? 'daily';
|
||||
$isDaily = $cycle === 'daily';
|
||||
// 첫 번째 설비의 labels 사용
|
||||
$labels = $inspections[0]['labels'] ?? [];
|
||||
@endphp
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
@@ -15,16 +17,14 @@
|
||||
<tr class="bg-gray-100">
|
||||
<th class="border border-gray-300 px-2 py-1 text-center whitespace-nowrap sticky left-0 bg-gray-100 z-10" style="min-width: 80px;">설비</th>
|
||||
<th class="border border-gray-300 px-2 py-1 text-center whitespace-nowrap sticky bg-gray-100 z-10" style="left: 80px; min-width: 80px;">점검항목</th>
|
||||
@for($d = 1; $d <= $daysInMonth; $d++)
|
||||
@foreach($labels as $colIndex => $label)
|
||||
@php
|
||||
$dayDate = $date->copy()->day($d);
|
||||
$dayOfWeek = $dayDate->dayOfWeek;
|
||||
$isWeekend = in_array($dayOfWeek, [0, 6]);
|
||||
$isWeekend = $isDaily && \App\Enums\InspectionCycle::isWeekend($period, $colIndex);
|
||||
@endphp
|
||||
<th class="border border-gray-300 px-1 py-1 text-center {{ $isWeekend ? 'bg-red-50 text-red-600' : '' }}" style="min-width: 32px;">
|
||||
{{ $d }}
|
||||
{{ $label }}
|
||||
</th>
|
||||
@endfor
|
||||
@endforeach
|
||||
<th class="border border-gray-300 px-2 py-1 text-center whitespace-nowrap" style="min-width: 60px;">판정</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -35,6 +35,7 @@
|
||||
$templates = $item['templates'];
|
||||
$inspection = $item['inspection'];
|
||||
$details = $item['details'];
|
||||
$canInspect = $item['can_inspect'] ?? true;
|
||||
$rowCount = $templates->count();
|
||||
@endphp
|
||||
|
||||
@@ -51,22 +52,28 @@
|
||||
<span class="text-gray-600">{{ $tmpl->check_point }}</span>
|
||||
</td>
|
||||
|
||||
@for($d = 1; $d <= $daysInMonth; $d++)
|
||||
@foreach($labels as $colIndex => $label)
|
||||
@php
|
||||
$checkDate = $date->copy()->day($d)->format('Y-m-d');
|
||||
$checkDate = \App\Enums\InspectionCycle::resolveCheckDate($cycle, $period, $colIndex);
|
||||
$key = $tmpl->id . '_' . $checkDate;
|
||||
$detail = isset($details[$key]) ? $details[$key]->first() : null;
|
||||
$symbol = $detail ? $detail->result_symbol : '';
|
||||
$color = $detail ? $detail->result_color : 'text-gray-400';
|
||||
$dayDate = $date->copy()->day($d);
|
||||
$isWeekend = in_array($dayDate->dayOfWeek, [0, 6]);
|
||||
$isWeekend = $isDaily && \App\Enums\InspectionCycle::isWeekend($period, $colIndex);
|
||||
@endphp
|
||||
<td class="border border-gray-300 text-center cursor-pointer select-none {{ $isWeekend ? 'bg-red-50' : '' }}"
|
||||
style="min-width: 32px; padding: 2px;"
|
||||
onclick="toggleCell({{ $equipment->id }}, {{ $tmpl->id }}, '{{ $checkDate }}', this)">
|
||||
<span class="inspection-cell text-lg font-bold {{ $color }}">{{ $symbol }}</span>
|
||||
</td>
|
||||
@endfor
|
||||
@if($canInspect)
|
||||
<td class="border border-gray-300 text-center cursor-pointer select-none {{ $isWeekend ? 'bg-red-50' : '' }}"
|
||||
style="min-width: 32px; padding: 2px;"
|
||||
onclick="toggleCell({{ $equipment->id }}, {{ $tmpl->id }}, '{{ $checkDate }}', this)">
|
||||
<span class="inspection-cell text-lg font-bold {{ $color }}">{{ $symbol }}</span>
|
||||
</td>
|
||||
@else
|
||||
<td class="border border-gray-300 text-center select-none cursor-not-allowed opacity-50 {{ $isWeekend ? 'bg-red-50' : '' }}"
|
||||
style="min-width: 32px; padding: 2px;">
|
||||
<span class="inspection-cell text-lg font-bold {{ $color }}">{{ $symbol }}</span>
|
||||
</td>
|
||||
@endif
|
||||
@endforeach
|
||||
|
||||
@if($idx === 0)
|
||||
<td class="border border-gray-300 px-2 py-1 text-center whitespace-nowrap"
|
||||
|
||||
@@ -67,9 +67,13 @@
|
||||
<p class="text-gray-900">{{ $equipment->useful_life ? $equipment->useful_life . '년' : '-' }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm text-gray-500 mb-1">담당자</label>
|
||||
<label class="block text-sm text-gray-500 mb-1">정 담당자</label>
|
||||
<p class="text-gray-900">{{ $equipment->manager?->name ?? '-' }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm text-gray-500 mb-1">부 담당자</label>
|
||||
<p class="text-gray-900">{{ $equipment->subManager?->name ?? '-' }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm text-gray-500 mb-1">상태</label>
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium {{ $equipment->status_color }}">
|
||||
|
||||
@@ -8,37 +8,67 @@ class="bg-blue-600 hover:bg-blue-700 text-white px-3 py-1.5 rounded-lg text-sm t
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@php
|
||||
$allCycles = \App\Enums\InspectionCycle::all();
|
||||
$templatesByCycle = $equipment->inspectionTemplates->groupBy('inspection_cycle');
|
||||
@endphp
|
||||
|
||||
@if($equipment->inspectionTemplates->isNotEmpty())
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th class="px-3 py-2 text-center text-sm font-semibold text-gray-700">번호</th>
|
||||
<th class="px-3 py-2 text-left text-sm font-semibold text-gray-700">점검개소</th>
|
||||
<th class="px-3 py-2 text-left text-sm font-semibold text-gray-700">점검항목</th>
|
||||
<th class="px-3 py-2 text-center text-sm font-semibold text-gray-700">시기</th>
|
||||
<th class="px-3 py-2 text-center text-sm font-semibold text-gray-700">주기</th>
|
||||
<th class="px-3 py-2 text-left text-sm font-semibold text-gray-700">점검방법</th>
|
||||
<th class="px-3 py-2 text-center text-sm font-semibold text-gray-700">액션</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200">
|
||||
@foreach($equipment->inspectionTemplates as $tmpl)
|
||||
<tr>
|
||||
<td class="px-3 py-2 text-sm text-center">{{ $tmpl->item_no }}</td>
|
||||
<td class="px-3 py-2 text-sm">{{ $tmpl->check_point }}</td>
|
||||
<td class="px-3 py-2 text-sm">{{ $tmpl->check_item }}</td>
|
||||
<td class="px-3 py-2 text-sm text-center">{{ $tmpl->timing_label }}</td>
|
||||
<td class="px-3 py-2 text-sm text-center">{{ $tmpl->check_frequency ?? '-' }}</td>
|
||||
<td class="px-3 py-2 text-sm text-gray-600">{{ $tmpl->check_method ?? '-' }}</td>
|
||||
<td class="px-3 py-2 text-sm text-center">
|
||||
<button onclick="deleteTemplate({{ $tmpl->id }})" class="text-red-600 hover:text-red-900">삭제</button>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
<!-- 주기별 탭 -->
|
||||
<div class="flex gap-1 mb-4 bg-gray-50 rounded-lg p-1" style="width: fit-content;">
|
||||
@foreach($allCycles as $code => $label)
|
||||
@php $count = isset($templatesByCycle[$code]) ? $templatesByCycle[$code]->count() : 0; @endphp
|
||||
<button type="button"
|
||||
class="tmpl-cycle-tab px-3 py-1.5 rounded-md text-xs font-medium transition {{ $loop->first ? 'bg-white text-blue-700 shadow-sm' : 'text-gray-500 hover:text-gray-700' }}"
|
||||
data-cycle="{{ $code }}"
|
||||
onclick="switchTemplateTab('{{ $code }}')">
|
||||
{{ $label }}
|
||||
@if($count > 0)
|
||||
<span class="ml-1 inline-flex items-center justify-center w-4 h-4 text-[10px] font-bold bg-blue-100 text-blue-700 rounded-full">{{ $count }}</span>
|
||||
@endif
|
||||
</button>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
<!-- 주기별 테이블 -->
|
||||
@foreach($allCycles as $code => $label)
|
||||
<div class="tmpl-cycle-content {{ $loop->first ? '' : 'hidden' }}" data-cycle="{{ $code }}">
|
||||
@if(isset($templatesByCycle[$code]) && $templatesByCycle[$code]->isNotEmpty())
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th class="px-3 py-2 text-center text-sm font-semibold text-gray-700">번호</th>
|
||||
<th class="px-3 py-2 text-left text-sm font-semibold text-gray-700">점검개소</th>
|
||||
<th class="px-3 py-2 text-left text-sm font-semibold text-gray-700">점검항목</th>
|
||||
<th class="px-3 py-2 text-center text-sm font-semibold text-gray-700">시기</th>
|
||||
<th class="px-3 py-2 text-center text-sm font-semibold text-gray-700">주기</th>
|
||||
<th class="px-3 py-2 text-left text-sm font-semibold text-gray-700">점검방법</th>
|
||||
<th class="px-3 py-2 text-center text-sm font-semibold text-gray-700">액션</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200">
|
||||
@foreach($templatesByCycle[$code] as $tmpl)
|
||||
<tr>
|
||||
<td class="px-3 py-2 text-sm text-center">{{ $tmpl->item_no }}</td>
|
||||
<td class="px-3 py-2 text-sm">{{ $tmpl->check_point }}</td>
|
||||
<td class="px-3 py-2 text-sm">{{ $tmpl->check_item }}</td>
|
||||
<td class="px-3 py-2 text-sm text-center">{{ $tmpl->timing_label }}</td>
|
||||
<td class="px-3 py-2 text-sm text-center">{{ $tmpl->check_frequency ?? '-' }}</td>
|
||||
<td class="px-3 py-2 text-sm text-gray-600">{{ $tmpl->check_method ?? '-' }}</td>
|
||||
<td class="px-3 py-2 text-sm text-center">
|
||||
<button onclick="deleteTemplate({{ $tmpl->id }})" class="text-red-600 hover:text-red-900">삭제</button>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@else
|
||||
<p class="text-gray-400 text-center py-4 text-sm">{{ $label }} 점검항목이 없습니다.</p>
|
||||
@endif
|
||||
</div>
|
||||
@endforeach
|
||||
@else
|
||||
<p class="text-gray-500 text-center py-8">등록된 점검항목이 없습니다.</p>
|
||||
@endif
|
||||
@@ -55,21 +85,32 @@ class="bg-blue-600 hover:bg-blue-700 text-white px-3 py-1.5 rounded-lg text-sm t
|
||||
</div>
|
||||
<form id="templateForm" class="p-6 space-y-4">
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-1">점검주기 <span class="text-red-500">*</span></label>
|
||||
<select name="inspection_cycle" id="tmplInspectionCycle"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
@foreach($allCycles as $code => $label)
|
||||
<option value="{{ $code }}">{{ $label }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-1">항목번호 <span class="text-red-500">*</span></label>
|
||||
<input type="number" name="item_no" required min="1"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-1">점검개소 <span class="text-red-500">*</span></label>
|
||||
<input type="text" name="check_point" required placeholder="겉모양"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-1">점검항목 <span class="text-red-500">*</span></label>
|
||||
<input type="text" name="check_item" required placeholder="청결상태"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-1">점검항목 <span class="text-red-500">*</span></label>
|
||||
<input type="text" name="check_item" required placeholder="청결상태"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
@@ -102,3 +143,21 @@ class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg">추가</bu
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function switchTemplateTab(cycle) {
|
||||
document.querySelectorAll('.tmpl-cycle-tab').forEach(btn => {
|
||||
if (btn.dataset.cycle === cycle) {
|
||||
btn.className = 'tmpl-cycle-tab px-3 py-1.5 rounded-md text-xs font-medium transition bg-white text-blue-700 shadow-sm';
|
||||
} else {
|
||||
btn.className = 'tmpl-cycle-tab px-3 py-1.5 rounded-md text-xs font-medium transition text-gray-500 hover:text-gray-700';
|
||||
}
|
||||
});
|
||||
document.querySelectorAll('.tmpl-cycle-content').forEach(el => {
|
||||
el.classList.toggle('hidden', el.dataset.cycle !== cycle);
|
||||
});
|
||||
// 모달의 주기 기본값도 전환
|
||||
const cycleSelect = document.getElementById('tmplInspectionCycle');
|
||||
if (cycleSelect) cycleSelect.value = cycle;
|
||||
}
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user