- README.md: 전체 개요, 5개 하위 메뉴 구조, 아키텍처 - construction-photos.md: 공사현장 사진대지 (GCS, 행 구조, 음성입력) - meeting-minutes.md: 회의록 (STT 화자분리, Gemini AI 요약, 오디오 녹음) - planning-views.md: 견적/프로젝트/워크플로우 화면 명세 - INDEX.md: 문서 인덱스에 planning 등록
8.1 KiB
8.1 KiB
공사현장 사진대지
작성일: 2026-03-06 상태: 운영 중 라우트:
/juil/construction-photos관련: README.md | 회의록 | 뷰 화면
1. 개요
건설/시공 현장의 작업 과정을 작업전/작업중/작업후 3단계 사진으로 기록하고 관리하는 기능. Google Cloud Storage에 사진을 저장하며, 음성 입력(Web Speech API)으로 현장명과 설명을 입력할 수 있다.
2. 라우트
/juil/construction-photos
├── GET / → index (목록 페이지)
├── GET /list → list (JSON 목록)
├── POST / → store (새 사진대지 등록)
├── POST /log-stt-usage → logSttUsage (STT 시간 기록)
├── GET /{id} → show (상세 조회)
├── PUT /{id} → update (메타데이터 수정)
├── DELETE /{id} → destroy (삭제)
├── POST /{id}/rows → addRow (행 추가)
├── DELETE /{id}/rows/{rowId} → deleteRow (행 삭제)
├── POST /{id}/rows/{rowId}/upload → uploadPhoto (사진 업로드)
├── DELETE /{id}/rows/{rowId}/photo/{type} → deletePhoto (사진 삭제)
└── GET /{id}/rows/{rowId}/download/{type} → downloadPhoto (다운로드)
3. 데이터베이스
3.1 construction_site_photos (사진대지)
| 컬럼 | 타입 | 설명 |
|---|---|---|
id |
BIGINT PK | |
tenant_id |
BIGINT FK | 테넌트 격리 |
user_id |
BIGINT FK | 등록자 |
site_name |
VARCHAR(200) | 현장명 (필수) |
work_date |
DATE | 작업일자 (필수) |
description |
TEXT NULL | 설명 |
deleted_at |
TIMESTAMP NULL | 소프트 삭제 |
인덱스: tenant_id, user_id, (tenant_id, work_date)
3.2 construction_site_photo_rows (사진 행)
각 사진대지는 1개 이상의 행을 가지며, 각 행에 3개 타입(before/during/after) 사진 저장.
| 컬럼 | 타입 | 설명 |
|---|---|---|
id |
BIGINT PK | |
construction_site_photo_id |
BIGINT FK | 부모 (cascade delete) |
sort_order |
INT | 정렬 순서 (0부터) |
before_photo_path |
VARCHAR(500) NULL | 작업전 GCS 경로 |
before_photo_gcs_uri |
VARCHAR(500) NULL | 작업전 GCS URI |
before_photo_size |
INT UNSIGNED NULL | 작업전 파일크기 (bytes) |
during_photo_path |
VARCHAR(500) NULL | 작업중 GCS 경로 |
during_photo_gcs_uri |
VARCHAR(500) NULL | 작업중 GCS URI |
during_photo_size |
INT UNSIGNED NULL | 작업중 파일크기 (bytes) |
after_photo_path |
VARCHAR(500) NULL | 작업후 GCS 경로 |
after_photo_gcs_uri |
VARCHAR(500) NULL | 작업후 GCS URI |
after_photo_size |
INT UNSIGNED NULL | 작업후 파일크기 (bytes) |
3.3 테이블 관계
construction_site_photos
│ 1:N
▼
construction_site_photo_rows (sort_order ASC)
├── before_photo_* (작업전)
├── during_photo_* (작업중)
└── after_photo_* (작업후)
4. API 명세
4.1 목록 조회
GET /juil/construction-photos/list
| 파라미터 | 타입 | 설명 |
|---|---|---|
search |
string | 현장명 검색 |
date_from |
date | 시작일 |
date_to |
date | 종료일 |
per_page |
int | 페이지당 건수 |
4.2 생성
POST /juil/construction-photos
| 필드 | 규칙 | 설명 |
|---|---|---|
site_name |
required, max:200 | 현장명 |
work_date |
required, date | 작업일자 |
description |
nullable, max:2000 | 설명 |
생성 시 빈 행 1개 자동 추가
4.3 사진 업로드
POST /juil/construction-photos/{id}/rows/{rowId}/upload
| 필드 | 규칙 | 설명 |
|---|---|---|
type |
required, in:before,during,after | 사진 타입 |
photo |
required, image, mimes:jpeg,jpg,png,webp, max:10240 | 최대 10MB |
4.4 사진 다운로드
GET /juil/construction-photos/{id}/rows/{rowId}/download/{type}?inline=1
| 파라미터 | 설명 |
|---|---|
inline=1 |
브라우저 표시 (미지정 시 다운로드) |
5. GCS 저장 구조
5.1 경로 패턴
construction-site-photos/{tenant_id}/{photo_id}/{row_id}_{timestamp}_{type}.{ext}
예시:
construction-site-photos/1/42/15_1709723456_before.jpg
construction-site-photos/1/42/15_1709723456_during.jpg
construction-site-photos/1/42/15_1709723456_after.png
5.2 업로드 흐름
클라이언트 (Canvas 이미지 압축: 1920px, quality 80%)
↓
FormData (multipart) 전송
↓
컨트롤러: uploadPhoto()
↓
서비스: uploadPhoto()
├── 기존 사진 있으면 GCS에서 삭제
├── GCS에 업로드
├── DB에 path + uri + size 저장
└── AiTokenHelper::saveGcsStorageUsage() 호출
↓
응답: { success, data: Photo with rows }
5.3 삭제 흐름
사진 삭제: GCS 파일 삭제 → DB 필드 null
행 삭제: 행 내 모든 사진 GCS 삭제 → 행 삭제 → sort_order 재정렬
사진대지 삭제: 모든 행의 모든 사진 GCS 삭제 → soft delete
6. 음성 입력 (Web Speech API)
6.1 VoiceInputButton 컴포넌트
현장명, 설명 필드에 음성으로 텍스트 입력 가능.
// Web Speech Recognition 설정
recognition.lang = 'ko-KR';
recognition.continuous = true;
recognition.interimResults = true;
recognition.maxAlternatives = 1;
6.2 인식 상태
| 상태 | 표시 | 설명 |
|---|---|---|
| interim (미확정) | 이탤릭 + 회색 | 인식 중간 결과, 2초 후 소실 |
| final (확정) | 일반체 + 진한색 | 확정 텍스트, 영구 저장 |
6.3 사용량 추적
STT 사용 종료 시:
duration = Math.max(1, (Date.now() - startTime) / 1000)
↓
POST /juil/construction-photos/log-stt-usage
body: { duration_seconds }
↓
AiTokenHelper::saveSttUsage('공사현장사진대지-음성입력', seconds)
7. UI 구성 (React)
7.1 사진 타입별 색상
| 타입 | 라벨 | 배경색 | 뱃지색 |
|---|---|---|---|
before |
작업전 | bg-blue-50 |
bg-blue-100 text-blue-800 |
during |
작업중 | bg-yellow-50 |
bg-yellow-100 text-yellow-800 |
after |
작업후 | bg-green-50 |
bg-green-100 text-green-800 |
7.2 행 관리
- 행 추가: sort_order 자동 계산 (마지막 + 1)
- 행 삭제: 최소 1개 행 유지 필수
- 행별 사진: 각 행에 3개 타입 사진 독립 업로드/삭제
8. 모델 메서드
8.1 ConstructionSitePhoto
user() # BelongsTo User (등록자)
rows() # HasMany Row (sort_order ASC)
getPhotoCount(): int # 전체 사진 개수 (모든 행의 사진 합계)
8.2 ConstructionSitePhotoRow
constructionSitePhoto() # BelongsTo 부모
hasPhoto(string $type): bool # 특정 타입 사진 존재 여부
getPhotoCount(): int # 이 행의 사진 개수 (0~3)
8.3 ConstructionSitePhotoService
getList(array $filters) # 검색/필터 목록 (페이지네이션)
create(array $data) # 생성 + 빈 행 1개 자동 추가
update(ConstructionSitePhoto, array $data) # 메타데이터만 수정
delete(ConstructionSitePhoto) # GCS 전체 삭제 → soft delete
uploadPhoto(Row, UploadedFile, string $type) # GCS 업로드 + DB 기록
deletePhotoByType(Row, string $type) # 특정 타입 GCS 삭제
addRow(ConstructionSitePhoto) # 행 추가 (sort_order 자동)
deleteRow(Row) # 행 내 GCS 삭제 → 행 삭제 → 재정렬
관련 문서
- README.md — 기획 메뉴 전체 개요
- 회의록 작성 — STT/AI 통합 회의 기록
- 견적/프로젝트/워크플로우 — 화면 명세
최종 업데이트: 2026-03-06