mirror of
https://github.com/cna-bootcamp/phonebill.git
synced 2025-12-05 23:56:23 +00:00
first
This commit is contained in:
commit
db7d66a9fc
14
.claude/commands/design-api.md
Normal file
14
.claude/commands/design-api.md
Normal file
@ -0,0 +1,14 @@
|
||||
# API 설계
|
||||
|
||||
**Command**: `/design-api`
|
||||
|
||||
## 설명
|
||||
API를 설계합니다. 공통설계원칙과 API설계가이드를 준용하여 설계합니다.
|
||||
|
||||
## 프롬프트
|
||||
```
|
||||
@architecture
|
||||
API를 설계해 주세요:
|
||||
- '공통설계원칙'과 'API설계가이드'를 준용하여 설계
|
||||
|
||||
```
|
||||
22
.claude/commands/design-class.md
Normal file
22
.claude/commands/design-class.md
Normal file
@ -0,0 +1,22 @@
|
||||
# 클래스 설계
|
||||
|
||||
**Command**: `/design-class`
|
||||
|
||||
## 설명
|
||||
클래스를 설계합니다. 공통설계원칙과 클래스설계가이드를 준용하여 설계합니다. 클래스설계 정보가 제공되어야 합니다.
|
||||
|
||||
## 프롬프트
|
||||
```
|
||||
@architecture
|
||||
'공통설계원칙'과 '클래스설계가이드'를 준용하여 클래스를 설계해 주세요.
|
||||
프롬프트에 '[클래스설계 정보]'항목이 없으면 수행을 중단하고 안내 메시지를 표시합니다.
|
||||
{안내메시지}
|
||||
'[클래스설계 정보]' 섹션에 아래 예와 같은 정보를 제공해 주십시오.
|
||||
[클래스설계 정보]
|
||||
- 패키지 그룹: com.unicorn.tripgen
|
||||
- 설계 아키텍처 패턴
|
||||
- User: Layered
|
||||
- Trip: Clean
|
||||
- Location: Layered
|
||||
- AI: Layered
|
||||
```
|
||||
13
.claude/commands/design-data.md
Normal file
13
.claude/commands/design-data.md
Normal file
@ -0,0 +1,13 @@
|
||||
# 데이터 설계
|
||||
|
||||
**Command**: `/design-data`
|
||||
|
||||
## 설명
|
||||
데이터 설계를 수행합니다. 공통설계원칙과 데이터설계가이드를 준용하여 설계합니다.
|
||||
|
||||
## 프롬프트
|
||||
```
|
||||
@architecture
|
||||
데이터 설계를 해주세요:
|
||||
- '공통설계원칙'과 '데이터설계가이드'를 준용하여 설계
|
||||
```
|
||||
15
.claude/commands/design-fix-prototype.md
Normal file
15
.claude/commands/design-fix-prototype.md
Normal file
@ -0,0 +1,15 @@
|
||||
# 프로토타입 오류수정
|
||||
|
||||
**Command**: `/design-fix-prototype`
|
||||
|
||||
## 설명
|
||||
프로토타입에서 발생한 오류를 해결합니다. 오류내용이 제공되어야 수행됩니다.
|
||||
|
||||
## 프롬프트
|
||||
```
|
||||
@fix as @front
|
||||
'[오류내용]'섹션에 제공된 오류를 해결해 주세요.
|
||||
프롬프트에 '[오류내용]'섹션이 없으면 수행 중단하고 안내 메시지 표시
|
||||
{안내메시지}
|
||||
'[오류내용]'섹션 하위에 오류 내용을 제공
|
||||
```
|
||||
26
.claude/commands/design-front.md
Normal file
26
.claude/commands/design-front.md
Normal file
@ -0,0 +1,26 @@
|
||||
# 프론트엔드 설계
|
||||
|
||||
**Command**: `/design-front`
|
||||
|
||||
## 설명
|
||||
프론트엔드설계서를 작성합니다. 프론트엔드설계가이드를 준용하여 작성합니다. 백엔드시스템 정보가 제공되어야 합니다.
|
||||
|
||||
## 프롬프트
|
||||
```
|
||||
@plan as @front
|
||||
'프론트엔드설계가이드'를 준용하여 **프론트엔드설계서**를 작성해 주세요.
|
||||
프롬프트에 '[백엔드시스템]'항목이 없으면 수행을 중단하고 안내 메시지를 표시합니다.
|
||||
{안내메시지}
|
||||
'[백엔드시스템]' 섹션에 아래 예와 같은 정보를 제공해 주십시오.
|
||||
[백엔드시스템]
|
||||
- 시스템: tripgen
|
||||
- 마이크로서비스: user-service, location-service, trip-service, ai-service
|
||||
- API문서
|
||||
- user service: http://localhost:8081/v3/api-docs
|
||||
- location service: http://localhost:8082/v3/api-docs
|
||||
- trip service: http://localhost:8083/v3/api-docs
|
||||
- ai service: http://localhost:8084/v3/api-docs
|
||||
[요구사항]
|
||||
- 각 화면에 Back 아이콘 버튼과 화면 타이틀 표시
|
||||
- 하단 네비게이션 바 아이콘화: 홈, 새여행, 주변장소검색, 여행보기
|
||||
```
|
||||
44
.claude/commands/design-help.md
Normal file
44
.claude/commands/design-help.md
Normal file
@ -0,0 +1,44 @@
|
||||
# Design Work Type Commands
|
||||
|
||||
**Command**: `/design-help`
|
||||
|
||||
## 설명
|
||||
Design 워크타입의 명령어들과 작업 순서를 표시합니다.
|
||||
|
||||
## Design 작업 순서
|
||||
|
||||
Design 워크타입은 UI/UX 설계부터 물리 아키텍처까지 전체 시스템 설계를 담당합니다.
|
||||
|
||||
### Phase 1: UI/UX 설계 및 프로토타입
|
||||
1. **UI/UX 설계**: `/design-uiux`
|
||||
2. **프로토타입 작성**: `/design-prototype`
|
||||
3. **프로토타입 검증**: `/design-test-prototype`
|
||||
4. **프로토타입 오류수정**: `/design-fix-prototype` (필요시)
|
||||
5. **프로토타입 개선**: `/design-improve-prototype` (필요시)
|
||||
6. **유저스토리 품질 높이기**: `/design-improve-userstory`
|
||||
7. **설계서 업데이트**: `/design-update-uiux`
|
||||
|
||||
### Phase 2: 아키텍처 설계
|
||||
8. **클라우드 아키텍처 패턴 선정**: `/design-pattern`
|
||||
9. **논리아키텍처 설계**: `/design-logical`
|
||||
10. **외부 시퀀스 설계**: `/design-seq-outer`
|
||||
11. **내부 시퀀스 설계**: `/design-seq-inner`
|
||||
12. **API 설계**: `/design-api`
|
||||
13. **클래스 설계**: `/design-class`
|
||||
14. **데이터 설계**: `/design-data`
|
||||
|
||||
### Phase 3: 물리 설계
|
||||
15. **High Level 아키텍처 정의서**: `/design-high-level`
|
||||
16. **물리 아키텍처 설계**: `/design-physical`
|
||||
17. **프론트엔드 설계**: `/design-front`
|
||||
|
||||
## 작업 흐름
|
||||
```
|
||||
UI/UX → 프로토타입 → 아키텍처 → 물리설계
|
||||
```
|
||||
|
||||
## 필수 사전 요구사항
|
||||
- **클래스 설계**: `[클래스설계 정보]` 섹션 필요
|
||||
- **High Level 아키텍처**: `CLOUD` 정보 필요
|
||||
- **물리 아키텍처**: `CLOUD` 정보 필요
|
||||
- **프론트엔드 설계**: `[백엔드시스템]` 정보 필요
|
||||
16
.claude/commands/design-high-level.md
Normal file
16
.claude/commands/design-high-level.md
Normal file
@ -0,0 +1,16 @@
|
||||
# High Level 아키텍처 정의서 작성
|
||||
|
||||
**Command**: `/design-high-level`
|
||||
|
||||
## 설명
|
||||
High Level 아키텍처 정의서를 작성합니다. HighLevel아키텍처정의가이드를 준용하여 작성합니다. CLOUD 정보가 제공되어야 합니다.
|
||||
|
||||
## 프롬프트
|
||||
```
|
||||
@architecture
|
||||
'HighLevel아키텍처정의가이드'를 준용하여 High Level 아키텍처 정의서를 작성해 주세요.
|
||||
'CLOUD' 정보가 없으면 수행을 중단하고 안내메시지를 표시하세요.
|
||||
{안내메시지}
|
||||
아래 예와 같이 CLOUD 제공자를 Azure, AWS, Google과 같이 제공하세요.
|
||||
- CLOUD: Azure
|
||||
```
|
||||
15
.claude/commands/design-improve-prototype.md
Normal file
15
.claude/commands/design-improve-prototype.md
Normal file
@ -0,0 +1,15 @@
|
||||
# 프로토타입 개선
|
||||
|
||||
**Command**: `/design-improve-prototype`
|
||||
|
||||
## 설명
|
||||
프로토타입을 개선합니다. 개선내용이 제공되어야 수행됩니다.
|
||||
|
||||
## 프롬프트
|
||||
```
|
||||
@improve as @front
|
||||
'[개선내용]'섹션에 있는 내용을 개선해 주세요.
|
||||
프롬프트에 '[개선내용]'항목이 없으면 수행을 중단하고 안내 메시지 표시
|
||||
{안내메시지}
|
||||
'[개선내용]'섹션 하위에 개선할 내용을 제공
|
||||
```
|
||||
12
.claude/commands/design-improve-userstory.md
Normal file
12
.claude/commands/design-improve-userstory.md
Normal file
@ -0,0 +1,12 @@
|
||||
# 유저스토리 품질 높이기
|
||||
|
||||
**Command**: `/design-improve-userstory`
|
||||
|
||||
## 설명
|
||||
프로토타입을 웹브라우저에서 분석한 후 수정된 프로토타입에 따라 유저스토리를 업데이트합니다.
|
||||
|
||||
## 프롬프트
|
||||
```
|
||||
@analyze as @front 프로토타입을 웹브라우저에서 분석한 후,
|
||||
@document as @scribe 수정된 프로토타입에 따라 유저스토리를 업데이트 해주십시오.
|
||||
```
|
||||
14
.claude/commands/design-logical.md
Normal file
14
.claude/commands/design-logical.md
Normal file
@ -0,0 +1,14 @@
|
||||
# 논리아키텍처 설계
|
||||
|
||||
**Command**: `/design-logical`
|
||||
|
||||
## 설명
|
||||
논리 아키텍처를 설계합니다. 공통설계원칙과 논리아키텍처 설계 가이드를 준용하여 설계합니다.
|
||||
|
||||
## 프롬프트
|
||||
```
|
||||
@architecture
|
||||
논리 아키텍처를 설계해 주세요:
|
||||
- '공통설계원칙'과 '논리아키텍처 설계 가이드'를 준용하여 설계
|
||||
|
||||
```
|
||||
13
.claude/commands/design-pattern.md
Normal file
13
.claude/commands/design-pattern.md
Normal file
@ -0,0 +1,13 @@
|
||||
# 클라우드 아키텍처 패턴 선정
|
||||
|
||||
**Command**: `/design-pattern`
|
||||
|
||||
## 설명
|
||||
클라우드 아키텍처 패턴 적용 방안을 작성합니다. 클라우드아키텍처패턴선정가이드를 준용하여 작성합니다.
|
||||
|
||||
## 프롬프트
|
||||
```
|
||||
@design-pattern
|
||||
클라우드 아키텍처 패턴 적용 방안을 작성해 주세요:
|
||||
- '클라우드아키텍처패턴선정가이드'를 준용하여 작성
|
||||
```
|
||||
16
.claude/commands/design-physical.md
Normal file
16
.claude/commands/design-physical.md
Normal file
@ -0,0 +1,16 @@
|
||||
# 물리 아키텍처 설계
|
||||
|
||||
**Command**: `/design-physical`
|
||||
|
||||
## 설명
|
||||
물리아키텍처를 설계합니다. 물리아키텍처설계가이드를 준용하여 설계합니다. CLOUD 정보가 제공되어야 합니다.
|
||||
|
||||
## 프롬프트
|
||||
```
|
||||
@architecture
|
||||
'물리아키텍처설계가이드'를 준용하여 물리아키텍처를 설계해 주세요.
|
||||
'CLOUD' 정보가 없으면 수행을 중단하고 안내메시지를 표시하세요.
|
||||
{안내메시지}
|
||||
아래 예와 같이 CLOUD 제공자를 Azure, AWS, Google과 같이 제공하세요.
|
||||
- CLOUD: Azure
|
||||
```
|
||||
13
.claude/commands/design-prototype.md
Normal file
13
.claude/commands/design-prototype.md
Normal file
@ -0,0 +1,13 @@
|
||||
# 프로토타입 작성
|
||||
|
||||
**Command**: `/design-prototype`
|
||||
|
||||
## 설명
|
||||
프로토타입을 작성합니다. 프로토타입작성가이드를 준용하여 체계적으로 작성합니다.
|
||||
|
||||
## 프롬프트
|
||||
```
|
||||
@prototype
|
||||
프로토타입을 작성해 주세요:
|
||||
- '프로토타입작성가이드'를 준용하여 작성
|
||||
```
|
||||
14
.claude/commands/design-seq-inner.md
Normal file
14
.claude/commands/design-seq-inner.md
Normal file
@ -0,0 +1,14 @@
|
||||
# 내부 시퀀스 설계
|
||||
|
||||
**Command**: `/design-seq-inner`
|
||||
|
||||
## 설명
|
||||
내부 시퀀스 설계를 수행합니다. 공통설계원칙과 내부시퀀스설계 가이드를 준용하여 설계합니다.
|
||||
|
||||
## 프롬프트
|
||||
```
|
||||
@architecture
|
||||
내부 시퀀스 설계를 해 주세요:
|
||||
- '공통설계원칙'과 '내부시퀀스설계 가이드'를 준용하여 설계
|
||||
|
||||
```
|
||||
14
.claude/commands/design-seq-outer.md
Normal file
14
.claude/commands/design-seq-outer.md
Normal file
@ -0,0 +1,14 @@
|
||||
# 외부 시퀀스 설계
|
||||
|
||||
**Command**: `/design-seq-outer`
|
||||
|
||||
## 설명
|
||||
외부 시퀀스 설계를 수행합니다. 공통설계원칙과 외부시퀀스설계가이드를 준용하여 설계합니다.
|
||||
|
||||
## 프롬프트
|
||||
```
|
||||
@architecture
|
||||
외부 시퀀스 설계를 해 주세요:
|
||||
- '공통설계원칙'과 '외부시퀀스설계가이드'를 준용하여 설계
|
||||
|
||||
```
|
||||
12
.claude/commands/design-test-prototype.md
Normal file
12
.claude/commands/design-test-prototype.md
Normal file
@ -0,0 +1,12 @@
|
||||
# 프로토타입 검증
|
||||
|
||||
**Command**: `/design-test-prototype`
|
||||
|
||||
## 설명
|
||||
프로토타입을 테스트하여 검증합니다.
|
||||
|
||||
## 프롬프트
|
||||
```
|
||||
@test-front
|
||||
프로토타입을 테스트 해 주세요.
|
||||
```
|
||||
13
.claude/commands/design-uiux.md
Normal file
13
.claude/commands/design-uiux.md
Normal file
@ -0,0 +1,13 @@
|
||||
# UI/UX 설계
|
||||
|
||||
**Command**: `/design-uiux`
|
||||
|
||||
## 설명
|
||||
UI/UX 설계를 수행합니다. UI/UX설계가이드를 준용하여 체계적으로 설계합니다.
|
||||
|
||||
## 프롬프트
|
||||
```
|
||||
@uiux
|
||||
UI/UX 설계를 해주세요:
|
||||
- 'UI/UX설계가이드'를 준용하여 작성
|
||||
```
|
||||
12
.claude/commands/design-update-uiux.md
Normal file
12
.claude/commands/design-update-uiux.md
Normal file
@ -0,0 +1,12 @@
|
||||
# 설계서 다시 업데이트
|
||||
|
||||
**Command**: `/design-update-uiux`
|
||||
|
||||
## 설명
|
||||
현재 프로토타입과 유저스토리를 기준으로 UI/UX설계서와 스타일가이드를 수정합니다.
|
||||
|
||||
## 프롬프트
|
||||
```
|
||||
@document @front
|
||||
현재 프로토타입과 유저스토리를 기준으로 UI/UX설계서와 스타일가이드를 수정해 주세요.
|
||||
```
|
||||
12
.claude/commands/develop-db-guide.md
Normal file
12
.claude/commands/develop-db-guide.md
Normal file
@ -0,0 +1,12 @@
|
||||
# 데이터베이스 설치계획서 작성
|
||||
|
||||
**Command**: `/develop-db-guide`
|
||||
|
||||
## 설명
|
||||
데이터베이스 설치계획서를 작성합니다. 데이터베이스설치계획서가이드에 따라 작성합니다.
|
||||
|
||||
## 프롬프트
|
||||
```
|
||||
@backing-service
|
||||
"데이터베이스설치계획서가이드"에 따라 데이터베이스 설치계획서를 작성해 주십시오.
|
||||
```
|
||||
20
.claude/commands/develop-db-install.md
Normal file
20
.claude/commands/develop-db-install.md
Normal file
@ -0,0 +1,20 @@
|
||||
# 데이터베이스 설치 수행
|
||||
|
||||
**Command**: `/develop-db-install`
|
||||
|
||||
## 설명
|
||||
데이터베이스 설치를 수행합니다. 데이터베이스설치가이드에 따라 설치합니다. 설치정보가 제공되어야 합니다.
|
||||
|
||||
## 프롬프트
|
||||
```
|
||||
@backing-service
|
||||
[요구사항]
|
||||
'데이터베이스설치가이드'에 따라 설치해 주세요.
|
||||
'[설치정보]'섹션이 없으면 수행을 중단하고 안내 메시지를 표시하세요.
|
||||
{안내메시지}
|
||||
'[설치정보]'섹션 하위에 아래 예와 같이 설치에 필요한 정보를 추가해 주세요.
|
||||
- 설치대상환경: 개발환경
|
||||
- AKS Resource Group: rg-digitalgarage-01
|
||||
- AKS Name: aks-digitalgarage-01
|
||||
- Namespace: tripgen-dev
|
||||
```
|
||||
20
.claude/commands/develop-db-remove.md
Normal file
20
.claude/commands/develop-db-remove.md
Normal file
@ -0,0 +1,20 @@
|
||||
# 데이터베이스 설치 제거
|
||||
|
||||
**Command**: `/develop-db-remove`
|
||||
|
||||
## 설명
|
||||
데이터베이스 설치를 제거합니다. 데이터베이스설치결과서와 캐시설치결과서를 기반으로 모든 리소스를 삭제합니다.
|
||||
|
||||
## 프롬프트
|
||||
```
|
||||
@backing-service
|
||||
[요구사항]
|
||||
- "데이터베이스설치결과서"를 보고 관련된 모든 리소스를 삭제
|
||||
- "캐시설치결과서"를 보고 관련된 모든 리소스를 삭제
|
||||
- 현재 OS에 맞게 수행
|
||||
- 서브 에이젼트를 병렬로 수행하여 삭제
|
||||
- 결과파일은 생성할 필요 없고 화면에만 결과 표시
|
||||
[참고자료]
|
||||
- 데이터베이스설치결과서
|
||||
- 캐시설치결과서
|
||||
```
|
||||
13
.claude/commands/develop-dev-backend.md
Normal file
13
.claude/commands/develop-dev-backend.md
Normal file
@ -0,0 +1,13 @@
|
||||
# 백엔드 개발
|
||||
|
||||
**Command**: `/develop-dev-backend`
|
||||
|
||||
## 설명
|
||||
백엔드를 개발합니다. 백엔드개발가이드에 따라 개발합니다.
|
||||
|
||||
## 프롬프트
|
||||
```
|
||||
@dev-backend
|
||||
[요구사항]
|
||||
- "백엔드개발가이드"에 따라 개발해 주세요.
|
||||
```
|
||||
23
.claude/commands/develop-dev-front.md
Normal file
23
.claude/commands/develop-dev-front.md
Normal file
@ -0,0 +1,23 @@
|
||||
# 프론트엔드 개발
|
||||
|
||||
**Command**: `/develop-dev-front`
|
||||
|
||||
## 설명
|
||||
프론트엔드를 개발합니다. 프론트엔드개발가이드에 따라 개발합니다. 개발정보가 제공되어야 합니다.
|
||||
|
||||
## 프롬프트
|
||||
```
|
||||
@dev-front
|
||||
"프론트엔드개발가이드"에 따라 개발해 주세요.
|
||||
프롬프트에 '[개발정보]'항목이 없으면 수행을 중단하고 안내 메시지를 표시해 주세요.
|
||||
{안내메시지}
|
||||
'[개발정보]'섹션 하위에 아래 예와 같이 개발에 필요한 정보를 제시해 주세요.
|
||||
[개발정보]
|
||||
- 개발프레임워크: Typescript + React 18
|
||||
- UI프레임워크: MUI v5
|
||||
- 상태관리: Redux Toolkit
|
||||
- 라우팅: React Router v6
|
||||
- API통신: Axios
|
||||
- 스타일링: MUI + styled-components
|
||||
- 빌드도구: Vite
|
||||
```
|
||||
15
.claude/commands/develop-fix-backend.md
Normal file
15
.claude/commands/develop-fix-backend.md
Normal file
@ -0,0 +1,15 @@
|
||||
# 백엔드 오류 해결
|
||||
|
||||
**Command**: `/develop-fix-backend`
|
||||
|
||||
## 설명
|
||||
백엔드 개발 중 발생한 오류를 해결합니다. 개발된 각 서비스와 common 모듈을 컴파일하고 에러를 해결합니다.
|
||||
|
||||
## 프롬프트
|
||||
```
|
||||
@fix as @back
|
||||
개발된 각 서비스와 common 모듈을 컴파일하고 에러를 해결해 주세요.
|
||||
- common 모듈 우선 수행
|
||||
- 각 서비스별로 서브 에이젠트를 병렬로 수행
|
||||
- 컴파일이 모두 성공할때까지 계속 수행
|
||||
```
|
||||
43
.claude/commands/develop-help.md
Normal file
43
.claude/commands/develop-help.md
Normal file
@ -0,0 +1,43 @@
|
||||
# Develop Work Type Commands
|
||||
|
||||
**Command**: `/develop-help`
|
||||
|
||||
## 설명
|
||||
Develop 워크타입의 명령어들과 작업 순서를 표시합니다.
|
||||
|
||||
## Develop 작업 순서
|
||||
|
||||
Develop 워크타입은 백엔드 서비스 설치부터 프론트엔드 개발까지 실제 개발과 배포를 담당합니다.
|
||||
|
||||
### Phase 1: 인프라 설치 계획
|
||||
1. **데이터베이스 설치계획서 작성**: `/develop-db-guide`
|
||||
2. **Message Queue 설치 계획서 작성**: `/develop-mq-guide` (필요시)
|
||||
|
||||
### Phase 2: 인프라 설치
|
||||
3. **데이터베이스 설치**: `/develop-db-install`
|
||||
4. **Message Queue 설치**: `/develop-mq-install` (필요시)
|
||||
|
||||
### Phase 3: 백엔드 개발
|
||||
5. **백엔드 개발**: `/develop-dev-backend`
|
||||
6. **백엔드 오류 해결**: `/develop-fix-backend`
|
||||
7. **서비스 실행파일 작성**: `/develop-make-run-profile`
|
||||
8. **백엔드 테스트**: `/develop-test-backend`
|
||||
|
||||
### Phase 4: 프론트엔드 개발
|
||||
9. **프론트엔드 개발**: `/develop-dev-front`
|
||||
|
||||
### Phase 5: 인프라 정리 (필요시)
|
||||
- **데이터베이스 설치 제거**: `/develop-db-remove`
|
||||
- **Message Queue 설치 제거**: `/develop-mq-remove`
|
||||
|
||||
## 작업 흐름
|
||||
```
|
||||
인프라 계획 → 인프라 설치 → 백엔드 개발 → 프론트엔드 개발
|
||||
```
|
||||
|
||||
## 필수 사전 요구사항
|
||||
- **데이터베이스 설치**: `[설치정보]` 섹션 필요
|
||||
- **Message Queue 설치**: `[설치정보]` 섹션 필요
|
||||
- **서비스 실행파일 작성**: `[작성정보]` 섹션 (API Keys 필요)
|
||||
- **백엔드 테스트**: `[테스트정보]` 섹션 (API Keys 필요)
|
||||
- **프론트엔드 개발**: `[개발정보]` 섹션 (프레임워크 정보 필요)
|
||||
20
.claude/commands/develop-make-run-profile.md
Normal file
20
.claude/commands/develop-make-run-profile.md
Normal file
@ -0,0 +1,20 @@
|
||||
# 서비스 실행파일 작성
|
||||
|
||||
**Command**: `/develop-make-run-profile`
|
||||
|
||||
## 설명
|
||||
서비스 실행파일을 작성합니다. 서비스실행파일작성가이드에 따라 테스트합니다. 작성정보가 제공되어야 합니다.
|
||||
|
||||
## 프롬프트
|
||||
```
|
||||
@test-backend
|
||||
'서비스실행파일작성가이드'에 따라 테스트를 해 주세요.
|
||||
프롬프트에 '[작성정보]'항목이 없으면 수행을 중단하고 안내 메시지를 표시해 주세요.
|
||||
{안내메시지}
|
||||
[작성정보]
|
||||
- API Key
|
||||
- Claude: sk-ant-ap...
|
||||
- OpenAI: sk-proj-An4Q...
|
||||
- Open Weather Map: 1aa5b...
|
||||
- Kakao API Key: 5cdc24....
|
||||
```
|
||||
12
.claude/commands/develop-mq-guide.md
Normal file
12
.claude/commands/develop-mq-guide.md
Normal file
@ -0,0 +1,12 @@
|
||||
# Message Queue 설치 계획서 작성
|
||||
|
||||
**Command**: `/develop-mq-guide`
|
||||
|
||||
## 설명
|
||||
Message Queue 설치계획서를 작성합니다. MQ설치게획서가이드에 따라 작성합니다.
|
||||
|
||||
## 프롬프트
|
||||
```
|
||||
@backing-service
|
||||
"MQ설치게획서가이드"에 따라 Message Queue 설치계획서를 작성해 주세요.
|
||||
```
|
||||
19
.claude/commands/develop-mq-install.md
Normal file
19
.claude/commands/develop-mq-install.md
Normal file
@ -0,0 +1,19 @@
|
||||
# Message Queue 설치 수행
|
||||
|
||||
**Command**: `/develop-mq-install`
|
||||
|
||||
## 설명
|
||||
Message Queue 설치를 수행합니다. MQ설치가이드에 따라 설치합니다. 설치정보가 제공되어야 합니다.
|
||||
|
||||
## 프롬프트
|
||||
```
|
||||
@backing-service
|
||||
[요구사항]
|
||||
'MQ설치가이드'에 따라 설치해 주세요.
|
||||
'[설치정보]'섹션이 없으면 수행을 중단하고 안내 메시지를 표시하세요.
|
||||
{안내메시지}
|
||||
'[설치정보]'섹션 하위에 아래 예와 같이 설치에 필요한 정보를 추가해 주세요.
|
||||
- 설치대상환경: 개발환경
|
||||
- Resource Group: rg-digitalgarage-01
|
||||
- Namespace: tripgen-dev
|
||||
```
|
||||
18
.claude/commands/develop-mq-remove.md
Normal file
18
.claude/commands/develop-mq-remove.md
Normal file
@ -0,0 +1,18 @@
|
||||
# Message Queue 설치 제거
|
||||
|
||||
**Command**: `/develop-mq-remove`
|
||||
|
||||
## 설명
|
||||
Message Queue 설치를 제거합니다. MQ설치결과서를 기반으로 관련된 모든 리소스를 삭제합니다.
|
||||
|
||||
## 프롬프트
|
||||
```
|
||||
@backing-service
|
||||
[요구사항]
|
||||
- "MQ설치결과서"를 보고 관련된 모든 리소스를 삭제
|
||||
- 현재 OS에 맞게 수행
|
||||
- 서브 에이젼트를 병렬로 수행하여 삭제
|
||||
- 결과파일은 생성할 필요 없고 화면에만 결과 표시
|
||||
[참고자료]
|
||||
- MQ설치결과서
|
||||
```
|
||||
23
.claude/commands/develop-test-backend.md
Normal file
23
.claude/commands/develop-test-backend.md
Normal file
@ -0,0 +1,23 @@
|
||||
# 백엔드 테스트
|
||||
|
||||
**Command**: `/develop-test-backend`
|
||||
|
||||
## 설명
|
||||
백엔드 테스트를 수행합니다. 백엔드테스트가이드에 따라 테스트합니다. 테스트정보가 제공되어야 합니다.
|
||||
|
||||
## 프롬프트
|
||||
```
|
||||
@test-backend
|
||||
'백엔드테스트가이드'에 따라 테스트를 해 주세요.
|
||||
프롬프트에 '[테스트정보]'항목이 없으면 수행을 중단하고 안내 메시지를 표시해 주세요.
|
||||
테스트 대상 서비스를 지정안하면 모든 서비스를 테스트 합니다.
|
||||
{안내메시지}
|
||||
'[테스트정보]'섹션 하위에 아래 예와 같이 테스트에 필요한 정보를 제시해 주세요.
|
||||
테스트 대상 서비스를 콤마로 구분하여 입력할 수 있으며 전체를 테스트 할 때는 '전체'라고 입력하세요.
|
||||
- 서비스: user-service
|
||||
- API Key
|
||||
- Claude: sk-ant-ap...
|
||||
- OpenAI: sk-proj-An4Q...
|
||||
- Open Weather Map: 1aa5b...
|
||||
- Kakao API Key: 5cdc24....
|
||||
```
|
||||
30
.claude/commands/think-help.md
Normal file
30
.claude/commands/think-help.md
Normal file
@ -0,0 +1,30 @@
|
||||
# Think Work Type Commands
|
||||
|
||||
**Command**: `/think-help`
|
||||
|
||||
## 설명
|
||||
Think 워크타입의 명령어들과 작업 순서를 표시합니다.
|
||||
|
||||
## Think 작업 순서
|
||||
|
||||
Think 워크타입은 서비스 기획과 요구사항 분석을 담당합니다.
|
||||
|
||||
### 1. 서비스 기획
|
||||
**명령어**: `/think-planning`
|
||||
- AI활용 서비스 기획 가이드를 참고하여 서비스 기획 수행
|
||||
- 기본적인 서비스 기획 방향성 제시
|
||||
|
||||
### 2. 유저스토리 작성
|
||||
**명령어**: `/think-userstory`
|
||||
- 요구사항을 분석하여 유저스토리 작성
|
||||
- 피그마 채널 또는 요구사항문서를 기반으로 분석
|
||||
- 결과파일: `design/userstory.md`
|
||||
|
||||
## 작업 흐름
|
||||
```
|
||||
서비스 기획 → 유저스토리 작성
|
||||
```
|
||||
|
||||
## 필수 사전 요구사항
|
||||
- **유저스토리 작성**: `[요구사항]` 섹션이 필요합니다
|
||||
- 피그마 채널ID 또는 요구사항문서 경로 제공 필요
|
||||
15
.claude/commands/think-planning.md
Normal file
15
.claude/commands/think-planning.md
Normal file
@ -0,0 +1,15 @@
|
||||
# 서비스 기획
|
||||
|
||||
**Command**: `/think-planning`
|
||||
|
||||
## 설명
|
||||
서비스 기획을 수행합니다. AI활용 서비스 기획 가이드를 참고하여 체계적으로 기획을 진행합니다.
|
||||
|
||||
## 프롬프트
|
||||
아래 내용을 터미널에 표시만 하고 수행을 하지는 않습니다.
|
||||
```
|
||||
아래 가이드를 참고하여 서비스 기획을 수행합니다.
|
||||
|
||||
https://github.com/cna-bootcamp/aiguide/blob/main/AI%ED%99%9C%EC%9A%A9%20%EC%84%9C%EB%B9%84%EC%8A%A4%20%EA%B8%B0%ED%9A%8D%20%EA%B0%80%EC%9D%B4%EB%93%9C.md
|
||||
|
||||
```
|
||||
30
.claude/commands/think-userstory.md
Normal file
30
.claude/commands/think-userstory.md
Normal file
@ -0,0 +1,30 @@
|
||||
# 유저스토리 작성
|
||||
|
||||
**Command**: `/think-userstory`
|
||||
|
||||
## 설명
|
||||
유저스토리를 작성합니다. 요구사항을 분석하고 유저스토리 작성 방법에 따라 체계적으로 작성합니다.
|
||||
|
||||
## 프롬프트
|
||||
|
||||
```
|
||||
@document
|
||||
유저스토리를 작성하세요.
|
||||
프롬프트에 '[요구사항]'섹션이 없으면 수행을 중단하고 안내 메시지를 표시합니다.
|
||||
{안내메시지}
|
||||
'[요구사항]' 섹션에 아래 예와 같은 정보를 제공해 주십시오.
|
||||
[요구사항]
|
||||
Case 1) 이벤트스토밍을 피그마로 수행한 경우는 피그마 채널ID를 제공
|
||||
예) 피그마 채널ID 'abcde'에 접속하여 분석
|
||||
Case 2) 다른 방법으로 이벤트스토밍을 한 경우는 요구사항을 정리한 파일 경로를 제공
|
||||
예) 요구사항문서 'design/requirement.md'를 읽어 분석
|
||||
|
||||
프롬프트에 '[요구사항]'섹션이 있으면 아래와 같이 수행합니다.
|
||||
1. 요구사항 분석
|
||||
- 피그마 채널ID가 제공된 경우 figma MCP를 이용하여 해당 채널에 접속하여 분석
|
||||
- 요구사항문서 경로가 제공된 경우 해당 문서를 읽어 요구사항을 분석
|
||||
2. 유저스토리 작성
|
||||
- '유저스토리작성방법'과 '유저스토리예제'를 참고하여 유저스토리를 작성
|
||||
- 결과파일은 'design/userstory.md'에 생성
|
||||
|
||||
```
|
||||
BIN
.playwright-mcp/current-result-page.png
Normal file
BIN
.playwright-mcp/current-result-page.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 175 KiB |
494
CLAUDE.md
Normal file
494
CLAUDE.md
Normal file
@ -0,0 +1,494 @@
|
||||
# 통신요금 관리 서비스 가이드
|
||||
|
||||
[목표]
|
||||
통신요금 조회 및 상품변경 서비스 개발
|
||||
|
||||
[팀원]
|
||||
이 프로젝트는 Agentic Workflow 컨셉을 따릅니다.
|
||||
아래와 같은 각 멤버가 역할을 나누어 작업합니다.
|
||||
|
||||
```
|
||||
Product Owner
|
||||
- 책임: 프로젝트 방향성 설정, 요구사항 정의, 우선순위 결정
|
||||
- 이름/별명: 김기획/기획자
|
||||
- 성별/나이: 여자/35세
|
||||
- 주요경력: 통신업계 서비스 기획 10년, 고객 중심 서비스 설계 전문가
|
||||
|
||||
Backend Developer
|
||||
- 책임: API 개발, 데이터베이스 설계, 서버 아키텍처 구성
|
||||
- 이름/별명: 이개발/백엔더
|
||||
- 성별/나이: 남자/32세
|
||||
- 주요경력: Spring Boot 기반 마이크로서비스 개발 8년, 대용량 처리 시스템 구축 경험
|
||||
|
||||
Frontend Developer
|
||||
- 책임: 사용자 인터페이스 개발, 사용자 경험 최적화
|
||||
- 이름/별명: 박화면/프론트
|
||||
- 성별/나이: 여자/28세
|
||||
- 주요경력: React/Vue.js 개발 5년, 반응형 웹 애플리케이션 개발 전문가
|
||||
|
||||
DevOps Engineer
|
||||
- 책임: 인프라 구성, CI/CD 파이프라인 구축, 배포 자동화
|
||||
- 이름/별명: 최운영/데옵스
|
||||
- 성별/나이: 남자/38세
|
||||
- 주요경력: AWS/Docker/Kubernetes 인프라 구축 12년, 자동화 시스템 구축 전문가
|
||||
|
||||
QA Engineer
|
||||
- 책임: 테스트 계획 수립, 품질 관리, 버그 검증
|
||||
- 이름/별명: 정테스트/QA매니저
|
||||
- 성별/나이: 여자/30세
|
||||
- 주요경력: 테스트 자동화 구축 7년, 통신서비스 품질관리 경험
|
||||
```
|
||||
|
||||
[팀 행동원칙]
|
||||
- AGILE 'M'사상을 믿고 실천한다. : Value-Oriented, Interactive, Iterative
|
||||
- 'M'사상 실천을 위한 마인드셋을 가진다
|
||||
- Value Oriented: WHY First, Align WHY
|
||||
- Interactive: Believe crew, Yes And
|
||||
- Iterative: Fast fail, Learn and Pivot
|
||||
|
||||
[대화 가이드]
|
||||
- 'a:'로 시작하면 요청이나 질문입니다.
|
||||
- 프롬프트에 아무런 prefix가 없으면 요청으로 처리해 주세요.
|
||||
- 특별한 언급이 없으면 한국어로 대화해 주세요.
|
||||
- 답변할 때 답변하는 사람의 닉네임을 표시해 주세요.
|
||||
|
||||
[최적안 가이드]
|
||||
'o:'로 시작하면 최적안을 도출하라는 요청임
|
||||
1) 각자의 생각을 얘기함
|
||||
2) 의견을 종합하여 동일한 건 한 개만 남기고 비슷한 건 합침
|
||||
3) 최적안 후보 5개를 선정함
|
||||
4) 각 최적안 후보 5개에 대해 평가함
|
||||
5) 최적안 1개를 선정함
|
||||
6) 1) ~ 5)번 과정을 10번 반복함
|
||||
7) 최종으로 선정된 최적안을 제시함
|
||||
|
||||
---
|
||||
|
||||
[핵심 원칙]
|
||||
1. 병렬 처리 전략
|
||||
- **서브 에이전트 활용**: Task 도구로 서비스별 동시 작업
|
||||
- **3단계 하이브리드 접근**:
|
||||
1. 공통 컴포넌트 (순차)
|
||||
2. 서비스별 설계 (병렬)
|
||||
3. 통합 검증 (순차)
|
||||
- **의존성 기반 그룹화**: 의존 관계에 따른 순차/병렬 처리
|
||||
- **통합 검증**: 병렬 작업 완료 후 전체 검증
|
||||
|
||||
2. 마이크로서비스 설계
|
||||
- **서비스 독립성**: 캐시를 통한 직접 의존성 최소화
|
||||
- **선택적 비동기**: 장시간 작업(AI 일정 생성)만 비동기
|
||||
- **캐시 우선**: Redis를 통한 성능 최적화
|
||||
|
||||
3. 표준화
|
||||
- **PlantUML**: 모든 다이어그램 표준 (`!theme mono`)
|
||||
- **OpenAPI 3.0**: API 명세 표준
|
||||
- **PlantUML 문법 검사 필수**: 'PlantUML문법검사가이드'를 준용
|
||||
- **Mermaid 문법 검사 필수**: 'Mermaid문법검사가이드'를 준용
|
||||
- **OpenAPI 문법 검사 필수**
|
||||
|
||||
---
|
||||
|
||||
[Git 연동]
|
||||
- "pull" 명령어 입력 시 Git pull 명령을 수행하고 충돌이 있을 때 최신 파일로 병합 수행
|
||||
- "push" 또는 "푸시" 명령어 입력 시 git add, commit, push를 수행
|
||||
- Commit Message는 한글로 함
|
||||
|
||||
---
|
||||
|
||||
[URL링크 참조]
|
||||
- URL링크는 WebFetch가 아닌 'curl {URL} > claude/{filename}'명령으로 저장
|
||||
- 'claude'디렉토리가 없으면 생성하고 다운로드
|
||||
- 저장된 파일을 읽어 사용함
|
||||
- 작업을 완료한 후 다운로드한 파일은 삭제함
|
||||
|
||||
---
|
||||
|
||||
[프롬프트 로딩]
|
||||
'프롬프트 로딩'이라고 입력하면 CLAUDE.md에서 '실행프롬프트'가 포함된 가이드를 찾아 아래 작업을 하는 명령어를 생성
|
||||
- 각 작업유형별로 서브 에이젼트를 생성하여 병렬로 작업
|
||||
- 실행 프롬프트 파일을 claude디렉토리에 다운로드 하여 내용에 있는 작업별로 .claude/commands/{작업유형}-{작업}.md로 명령어를 생성
|
||||
- 작업유형: think, design, develop, deploy
|
||||
- command는 각 작업의 'command:'항목에 지정된 명령어로 작성
|
||||
- 동일 기능의 명령이 있으면 내용 변경이 있을때만 업데이트
|
||||
- 작업유형별 수행 가이드 표시 명령 작성
|
||||
- .claude/commands/{작업유형}-help.md
|
||||
- command: "/{작업유형}-help"
|
||||
- 아래 예시와 같이 작업 순서를 터미널에 표시하도록 함
|
||||
```
|
||||
기획 작업 순서
|
||||
|
||||
1단계: 서비스 기획
|
||||
/think-planning
|
||||
- AI활용 서비스 기획 가이드를 참고하여 서비스를 기획합니다
|
||||
|
||||
2단계: 유저스토리 작성
|
||||
/think-userstory
|
||||
- 유저스토리작성방법을 준용하여 작성합니다
|
||||
- 마이크로서비스로 나누어 작성합니다
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
[가이드 로딩]
|
||||
- claude 디렉토리가 없으면 생성
|
||||
- 가이드 목록을 claude/guide.md에 다운로드
|
||||
- 가이드 목록 링크: https://raw.githubusercontent.com/cna-bootcamp/clauding-guide/refs/heads/main/guides/GUIDE.md
|
||||
- 파일을 읽어 CLAUDE.md 제일 하단에 아래와 같이 가이드 섹션을 추가. 기존에 가이드 섹션이 있으면 먼저 삭제하고 다시 만듦
|
||||
[가이드]
|
||||
```
|
||||
claude/guide.md 파일 내용
|
||||
```
|
||||
- 파일을 삭제
|
||||
|
||||
---
|
||||
|
||||
[가이드]
|
||||
```
|
||||
# Clauding Guide
|
||||
최종 수정일시: 2025-08-22 11:50
|
||||
|
||||
## 서비스기획 가이드
|
||||
- 서비스기획프롬프트
|
||||
- 설명: 유저스토리 작성 등 서비스 기획을 위한 프롬프트 예시
|
||||
- URL: https://raw.githubusercontent.com/cna-bootcamp/clauding-guide/refs/heads/main/guides/prompt/02.think-prompt.md
|
||||
- 파일명: think-prompt.md
|
||||
|
||||
- 서비스기획가이드
|
||||
- 설명: 서비스 기획 방법 안내
|
||||
- URL: https://raw.githubusercontent.com/cna-bootcamp/clauding-guide/refs/heads/main/guides/think/think-guide.md
|
||||
- 파일명: think-guide.md
|
||||
|
||||
---
|
||||
|
||||
## 설계 가이드
|
||||
- 설계실행프롬프트
|
||||
- 설명: 각 설계 단계 실행을 위한 프롬프트 모음
|
||||
- URL: https://raw.githubusercontent.com/cna-bootcamp/clauding-guide/refs/heads/main/guides/prompt/03.design-prompt.md
|
||||
- 파일명: design-prompt.md
|
||||
|
||||
- 공통설계원칙
|
||||
- 설명: 모든 설계 시 적용할 공통설계원칙
|
||||
- URL: https://raw.githubusercontent.com/cna-bootcamp/clauding-guide/refs/heads/main/guides/design/common-principles.md
|
||||
- 파일명: common-principles.md
|
||||
|
||||
- UI/UX설계가이드
|
||||
- 설명: UI/UX 설계 방법 안내
|
||||
- URL: https://raw.githubusercontent.com/cna-bootcamp/clauding-guide/refs/heads/main/guides/design/uiux-design.md
|
||||
- 파일명: uiux-design.md
|
||||
|
||||
- 프로토타입작성가이드
|
||||
- 설명: 프로토타입 작성 방법 안내
|
||||
- URL: https://raw.githubusercontent.com/cna-bootcamp/clauding-guide/refs/heads/main/guides/design/uiux-prototype.md
|
||||
- 파일명: uiux-prototype.md
|
||||
|
||||
- 아키텍처패턴선정 가이드
|
||||
- 설명: 클라우드 아키텍처 패턴 선정 방법 안내
|
||||
- URL: https://raw.githubusercontent.com/cna-bootcamp/clauding-guide/refs/heads/main/guides/design/architecture-patterns.md
|
||||
- 파일명: architecture-patterns.md
|
||||
|
||||
- 논리아키텍처설계가이드
|
||||
- 설명: 논리 아키텍처 설계 방법 안내
|
||||
- URL: https://raw.githubusercontent.com/cna-bootcamp/clauding-guide/refs/heads/main/guides/design/logical-architecture-design.md
|
||||
- 파일명: logical-architecture-design.md
|
||||
|
||||
- API설계가이드
|
||||
- 설명: API 설계 방법 안내
|
||||
- URL: https://raw.githubusercontent.com/cna-bootcamp/clauding-guide/refs/heads/main/guides/design/api-design.md
|
||||
- 파일명: api-design.md
|
||||
|
||||
- 외부시퀀스설계가이드
|
||||
- 설명: 외부 시퀀스 설계 방법 안내
|
||||
- URL: https://raw.githubusercontent.com/cna-bootcamp/clauding-guide/refs/heads/main/guides/design/sequence-outer-design.md
|
||||
- 파일명: sequence-outer-design.md
|
||||
|
||||
- 내부시퀀스설계 가이드
|
||||
- 설명: 내부 시퀀스 설계 방법 안내
|
||||
- URL: https://raw.githubusercontent.com/cna-bootcamp/clauding-guide/refs/heads/main/guides/design/sequence-inner-design.md
|
||||
- 파일명: sequence-inner-design.md
|
||||
|
||||
- 클래스설계가이드
|
||||
- 설명: 클래스 설계 방법 안내
|
||||
- URL: https://raw.githubusercontent.com/cna-bootcamp/clauding-guide/refs/heads/main/guides/design/class-design.md
|
||||
- 파일명: class-design.md
|
||||
|
||||
- 데이터설계가이드
|
||||
- 설명: 데이터 설계 방법 안내
|
||||
- URL: https://raw.githubusercontent.com/cna-bootcamp/clauding-guide/refs/heads/main/guides/design/data-design.md
|
||||
- 파일명: data-design.md
|
||||
|
||||
- HighLevel아키텍처정의가이드
|
||||
- 설명: 상위수준 아키텍처 정의 방법 안내
|
||||
- URL: https://raw.githubusercontent.com/cna-bootcamp/clauding-guide/refs/heads/main/guides/design/architecture-highlevel.md
|
||||
- 파일명: architecture-highlevel.md
|
||||
|
||||
- 물리아키텍처설계가이드
|
||||
- 설명: 물리 아키텍처 설계 방법 안내
|
||||
- URL: https://raw.githubusercontent.com/cna-bootcamp/clauding-guide/refs/heads/main/guides/design/physical-architecture-design.md
|
||||
- 파일명: physical-architecture-design.md
|
||||
|
||||
- 프론트엔드설계가이드
|
||||
- 설명: 프론트엔드 설계 방법 안내
|
||||
- URL: https://raw.githubusercontent.com/cna-bootcamp/clauding-guide/refs/heads/main/guides/design/frontend-design.md
|
||||
- 파일명: frontend-design.md
|
||||
|
||||
---
|
||||
|
||||
## 개발 가이드
|
||||
- 개발실행프롬프트
|
||||
- 설명: 각 개발 단계 실행을 위한 프롬프트 모음
|
||||
- URL: https://raw.githubusercontent.com/cna-bootcamp/clauding-guide/refs/heads/main/guides/prompt/04.develop-prompt.md
|
||||
- 파일명: develop-prompt.md
|
||||
|
||||
- 데이터베이스설치계획서가이드
|
||||
- 설명: 데이터베이스 설치 방법 안내 요청 시 참조
|
||||
- URL: https://raw.githubusercontent.com/cna-bootcamp/clauding-guide/refs/heads/main/guides/develop/database-plan.md
|
||||
- 파일명: database-plan.md
|
||||
|
||||
- 데이터베이스설치가이드
|
||||
- 설명: 데이터베이스 설치 방법 안내 요청 시 참조
|
||||
- URL: https://raw.githubusercontent.com/cna-bootcamp/clauding-guide/refs/heads/main/guides/develop/database-install.md
|
||||
- 파일명: database-install.md
|
||||
|
||||
- MQ설치게획서가이드
|
||||
- 설명: Message Queue 설치 방법 안내 요청 시 참조
|
||||
- URL: https://raw.githubusercontent.com/cna-bootcamp/clauding-guide/refs/heads/main/guides/develop/mq-plan.md
|
||||
- 파일명: mq-plan.md
|
||||
|
||||
- MQ설치가이드
|
||||
- 설명: Message Queue 설치 방법 안내 요청 시 참조
|
||||
- URL: https://raw.githubusercontent.com/cna-bootcamp/clauding-guide/refs/heads/main/guides/develop/mq-install.md
|
||||
- 파일명: mq-install.md
|
||||
|
||||
- 백엔드개발가이드
|
||||
- 설명: 백엔드 개발 가이드
|
||||
- URL: https://raw.githubusercontent.com/cna-bootcamp/clauding-guide/refs/heads/main/guides/develop/dev-backend.md
|
||||
- 파일명: dev-backend.md
|
||||
|
||||
- 서비스실행프로파일작성가이드
|
||||
- 설명: 백엔드 서비스 실행 프로파일 작성 가이드
|
||||
- URL: https://raw.githubusercontent.com/cna-bootcamp/clauding-guide/refs/heads/main/guides/develop/make-run-profile.md
|
||||
- 파일명: make-run-profile.md
|
||||
|
||||
- 백엔드테스트가이드
|
||||
- 설명: 백엔드 테스트 가이드
|
||||
- URL: https://raw.githubusercontent.com/cna-bootcamp/clauding-guide/refs/heads/main/guides/develop/test-backend.md
|
||||
- 파일명: test-backend.md
|
||||
|
||||
- 프론트엔드개발가이드
|
||||
- 설명: 프론트엔드 개발 가이드
|
||||
- URL: https://raw.githubusercontent.com/cna-bootcamp/clauding-guide/refs/heads/main/guides/develop/dev-frontend.md
|
||||
- 파일명: dev-frontend.md
|
||||
|
||||
---
|
||||
|
||||
## 배포 가이드
|
||||
- 백엔드컨테이너이미지작성가이드
|
||||
- 설명: 백엔드 컨테이너 이미지 작성 가이드
|
||||
- URL: https://raw.githubusercontent.com/cna-bootcamp/clauding-guide/refs/heads/main/guides/deploy/build-image-back.md
|
||||
- 파일명: build-image-back.md
|
||||
- 프론트엔드컨테이너이미지작성가이드
|
||||
- 설명: 프론트엔드 컨테이너 이미지 작성 가이드
|
||||
- URL: https://raw.githubusercontent.com/cna-bootcamp/clauding-guide/refs/heads/main/guides/deploy/build-image-front.md
|
||||
- 파일명: build-image-front.md
|
||||
- 백엔드컨테이너실행방법가이드
|
||||
- 설명: 백엔드 컨테이너 실행방법 가이드
|
||||
- URL: https://raw.githubusercontent.com/cna-bootcamp/clauding-guide/refs/heads/main/guides/deploy/run-container-guide-back.md
|
||||
- 파일명: run-container-guide-back.md
|
||||
- 프론트엔드컨테이너실행방법가이드
|
||||
- 설명: 프론트엔드 컨테이너 실행방법 가이드
|
||||
- URL: https://raw.githubusercontent.com/cna-bootcamp/clauding-guide/refs/heads/main/guides/deploy/run-container-guide-front.md
|
||||
- 파일명: run-container-guide-front.md
|
||||
- 백엔드배포가이드
|
||||
- 설명: 백엔드 서비스를 쿠버네티스 클러스터에 배포하는 가이드
|
||||
- URL: https://raw.githubusercontent.com/cna-bootcamp/clauding-guide/refs/heads/main/guides/deploy/deploy-k8s-back.md
|
||||
- 파일명: deploy-k8s-back.md
|
||||
- 프론트엔드배포가이드
|
||||
- 설명: 프론트엔드 서비스를 쿠버네티스 클러스터에 배포하는 가이드
|
||||
- URL: https://raw.githubusercontent.com/cna-bootcamp/clauding-guide/refs/heads/main/guides/deploy/deploy-k8s-front.md
|
||||
- 파일명: deploy-k8s-front.md
|
||||
|
||||
## 참조 문서
|
||||
- 프로젝트지침템플릿
|
||||
- 설명: 프로젝트 지침인 CLAUDE.md 파일 템플릿
|
||||
- URL: https://raw.githubusercontent.com/cna-bootcamp/clauding-guide/refs/heads/main/references/instruction-template.md
|
||||
- 파일명: instruction-template.md
|
||||
|
||||
- 유저스토리작성방법
|
||||
- 설명: 유저스토리 형식과 작성법
|
||||
- URL: https://raw.githubusercontent.com/cna-bootcamp/clauding-guide/refs/heads/main/references/유저스토리작성방법.md
|
||||
- 파일명: userstory-writing.md
|
||||
|
||||
- 유저스토리예제
|
||||
- 설명: 유저스토리 예제
|
||||
- URL: https://raw.githubusercontent.com/cna-bootcamp/clauding-guide/refs/heads/main/samples/sample-%EC%9C%A0%EC%A0%80%EC%8A%A4%ED%86%A0%EB%A6%AC.md
|
||||
- 파일명: sample-userstory.md
|
||||
|
||||
- 클라우드아키텍처패턴요약표
|
||||
- 설명: 클라우드 디자인 패턴 요약표
|
||||
- URL: https://raw.githubusercontent.com/cna-bootcamp/clauding-guide/refs/heads/main/references/Cloud%20Design%20Patterns(%EA%B0%9C%EC%9A%94).md
|
||||
- 파일명: cloud-design-patterns.md
|
||||
|
||||
- HighLevel아키텍처정의서템플릿
|
||||
- 설명: MSA 7대 컴포넌트별로 상위 수준의 아키텍처를 정의한 문서
|
||||
- URL: https://raw.githubusercontent.com/cna-bootcamp/clauding-guide/refs/heads/main/references/highlevel-architecture-template.md
|
||||
- 파일명: highlevel-architecture-template.md
|
||||
|
||||
- 제품별버전가이드
|
||||
- 설명: 개발언어, 개발 프레임워크, AI제품 등의 버전 참조를 위한 페이지 링크 제공
|
||||
- URL: https://raw.githubusercontent.com/cna-bootcamp/clauding-guide/refs/heads/main/references/제품버전참조.md
|
||||
- 파일명: version-link.md
|
||||
|
||||
- 백킹서비스설치방법
|
||||
- 설명: 데이터베이스, Message Queue 등 백킹서비스설치방법 설명
|
||||
- URL: https://raw.githubusercontent.com/cna-bootcamp/clauding-guide/refs/heads/main/references/백킹서비스설치방법.md
|
||||
- 파일명: backing-service-method.md
|
||||
|
||||
---
|
||||
|
||||
## 표준
|
||||
- 개발주석표준
|
||||
- 설명: 개발 주석 표준
|
||||
- URL: https://raw.githubusercontent.com/cna-bootcamp/clauding-guide/refs/heads/main/standards/standard_comment.md
|
||||
- 파일명: standard_comment.md
|
||||
|
||||
- 패키지구조표준
|
||||
- 설명: 패키지 구조 표준과 설계 아키텍처 패턴(Layered, Clean, Hexagonal)별 예시
|
||||
- URL: https://raw.githubusercontent.com/cna-bootcamp/clauding-guide/refs/heads/main/standards/standard_package_structure.md
|
||||
- 파일명: standard_package_structure.md
|
||||
|
||||
- 테스트코드표준
|
||||
- 설명: 테스트 코드 작성 표준
|
||||
- URL: https://raw.githubusercontent.com/cna-bootcamp/clauding-guide/refs/heads/main/standards/standard_testcode.md
|
||||
- 파일명: standard_testcode.md
|
||||
|
||||
---
|
||||
|
||||
## 기술 도구
|
||||
- PlantUML문법검사가이드
|
||||
- 설명: PlantUML 문법 검사하는 방법 안내
|
||||
- URL: https://raw.githubusercontent.com/cna-bootcamp/clauding-guide/refs/heads/main/guides/tools/plantuml-guide.md
|
||||
- 파일명: plantuml-guide.md
|
||||
|
||||
- Mermaid문법검사가이드
|
||||
- 설명: Mermaid 문법 검사하는 방법 안내
|
||||
- URL: https://raw.githubusercontent.com/cna-bootcamp/clauding-guide/refs/heads/main/guides/tools/mermaid-guide.md
|
||||
- 파일명: mermaid-guide.md
|
||||
|
||||
- MCP동기화도구
|
||||
- 설명: Window에서 Cloude Desktop의 MCP설정을 읽어 Claude Code에 MCP 서버를 동기화하는 툴
|
||||
- URL: https://raw.githubusercontent.com/cna-bootcamp/clauding-guide/refs/heads/main/guides/tools/sync-mcp.md
|
||||
- 파일명: sync-mcp.md
|
||||
|
||||
- PlantUML문법검사기(Window)
|
||||
- 설명: Window용 PlantUML 스크립트 문법 검사기
|
||||
- URL: https://raw.githubusercontent.com/cna-bootcamp/clauding-guide/refs/heads/main/guides/tools/check-plantuml.ps1
|
||||
- 파일명: check-plantuml.ps1
|
||||
|
||||
- Mermaid문법검사기(Window)
|
||||
- 설명: Window용 PlantUML 스크립트 문법 검사기
|
||||
- URL: https://raw.githubusercontent.com/cna-bootcamp/clauding-guide/refs/heads/main/guides/tools/check-mermaid.ps1
|
||||
- 파일명: check-mermaid.ps1
|
||||
|
||||
- PlantUML문법검사기(Linux/Mac)
|
||||
- 설명: Linux/Mac용 PlantUML 스크립트 문법 검사기
|
||||
- URL: https://raw.githubusercontent.com/cna-bootcamp/clauding-guide/refs/heads/main/guides/tools/check-plantuml.sh
|
||||
- 파일명: check-plantuml.sh
|
||||
|
||||
- Mermaid문법검사기(Linux/Mac)
|
||||
- 설명: Linux/Mac용 PlantUML 스크립트 문법 검사기
|
||||
- URL: https://raw.githubusercontent.com/cna-bootcamp/clauding-guide/refs/heads/main/guides/tools/check-mermaid.sh
|
||||
- 파일명: check-mermaid.sh
|
||||
|
||||
- IntelliJ서비스실행기
|
||||
- 설명: IntelliJ에 등록된 실행프로파일을 이용하여 서비스 실행
|
||||
- URL: https://raw.githubusercontent.com/cna-bootcamp/clauding-guide/refs/heads/main/guides/tools/run-intellij-service-profile.py
|
||||
- 파일명: run-intellij-service-profile.py
|
||||
|
||||
---
|
||||
|
||||
## 산출물 디렉토리
|
||||
- 유저스토리: design/userstory.md
|
||||
- UI/UX설계서: design/uiux/uiux.md
|
||||
- 스타일가이드: design/uiux/style-guide.md
|
||||
- 프로토타입: design/uiux/prototype/*.html
|
||||
- 아키텍처패턴: design/pattern/architecture-pattern.md
|
||||
- 논리아키텍처: design/backend/logical/*
|
||||
- API설계서: design/backend/api/*
|
||||
- API명세서: design/backend/api/spec/*
|
||||
- 외부시퀀스설계서: design/backend/sequence/outer/{플로우명}.puml
|
||||
- 내부시퀀스설계서: design/backend/sequence/inner/{service-name}-{flow-name}.puml
|
||||
- 클래스설계서: design/backend/class/*
|
||||
- 백엔드패키지구조도: 클래스설계 결과(design/backend/class/class.md)의 '패키지 구조도' 섹션
|
||||
- 데이터설계서: design/backend/database/*
|
||||
- HighLevel아키텍처정의서: design/high-level-architecture.md
|
||||
- 물리아키텍처: design/backend/physical/*
|
||||
- 데이터베이스설치계획서
|
||||
- develop/database/plan/db-plan-{service-name}-dev.md
|
||||
- develop/database/plan/db-plan-{service-name}-prod.md
|
||||
- 캐시설치계획서:
|
||||
- develop/mq/mq-plan-dev.md
|
||||
- develop/mq/mq-plan-prod.md
|
||||
- MQ설치계획서
|
||||
- develop/database/plan/mq-plan-{service-name}-dev.md
|
||||
- develop/database/plan/mq-plan-{service-name}-prod.md
|
||||
- 데이터베이스설치결과서
|
||||
- develop/database/exec/db-exec-dev.md
|
||||
- develop/database/exec/db-exec-prod.md
|
||||
- 캐시설치결과서
|
||||
- develop/database/exec/cache-exec-{service-name}-dev.md
|
||||
- develop/database/exec/cache-exec-{service-name}-prod.md
|
||||
- MQ설치결과서
|
||||
- develop/mq/mq-exec-dev.md
|
||||
- develop/mq/mq-exec-prod.md
|
||||
- 백엔드개발결과서: develop/dev/dev-backend.md
|
||||
- 백엔드테스트결과서: develop/dev/test-backend.md
|
||||
- 프론트엔드설계서: design/frontend/frontend-design.md
|
||||
|
||||
## 프롬프트 약어
|
||||
### 역할 약어
|
||||
- "@archi": "--persona-architect"
|
||||
- "@front": "--persona-front"
|
||||
- "@back": "--persona-backend"
|
||||
- "@secu": "--persona-security"
|
||||
- "@qa": "--persona-qa"
|
||||
- "@refact": "--persona-refactor"
|
||||
- "@devops": "--persona-devops"
|
||||
- "@scribe": "--persona-scriber"
|
||||
|
||||
### 작업 약어
|
||||
- "@complex-flag": --seq --c7 --uc --wave-mode auto --wave-strategy systematic --delegate auto
|
||||
|
||||
- "@userstory": /sc:document @scribe @archi --think --wave-strategy systematic
|
||||
- "@uiux": /sc:design --think @front --uc --wave-mode auto --wave-strategy systematic
|
||||
- "@prototype": /sc:implement @front --answer-only
|
||||
- "@design-pattern": /sc:design @archi --think-hard @complex-flag
|
||||
- "@architecture": /sc:design @archi @back @refact --think-hard @complex-flag
|
||||
- "@plan": --plan --think
|
||||
- "@backing-service": /sc:implement @devops @back --think-hard @complex-flag
|
||||
- "@dev-backend": /sc:implement @back --think-hard @complex-flag
|
||||
- "@dev-front": /sc:implement @front --think-hard @complex-flag
|
||||
- "@test-backend": /sc:test @back @qa --think @complex-flag
|
||||
- "@test-api": /sc:test @back @qa --think 1) 소스 수정 후 컴파일하고 서버 시작 요청. 2) API경로와 DTO를 분석하여 정확하게 요청하여 테스트
|
||||
- "@run-back":
|
||||
- 'IntelliJ서비스실행기'를 'tools' 디렉토리에 다운로드
|
||||
- python 또는 python3 명령으로 백그라우드로 실행하고 결과 로그를 분석
|
||||
nohup python3 tools/run-intellij-service-profile.py {service-name} > logs/{service-name}.log 2>&1 & echo "Started {service-name} with PID: $!"
|
||||
- "@test-front": /sc:test @front @qa --play --think @complex-flag
|
||||
- "@cicd": /sc:implement @devops --think @complex-flag
|
||||
- "@document": /sc:document --think @scribe @complex-flag
|
||||
- "@fix": /sc:troubleshoot --think @complex-flag
|
||||
- "@estimate": /sc:estimate --think-hard @complex-flag
|
||||
- "@improve": /sc:improve --think @complex-flag
|
||||
- "@analyze": /sc:analyze --think --seq
|
||||
- "@explain": /sc:explain --think --seq --answer-only
|
||||
|
||||
### 파일 약어
|
||||
- "@error": debug/error.png파일을 의미함
|
||||
- "@info": debug/info.png파일을 의미함
|
||||
|
||||
### 작업 단계 가이드 약어
|
||||
- "@think-help": "기획실행프롬프트 내용을 터미널에 출력"
|
||||
- "@design-help": "설계실행프롬프트 내용을 터미널에 출력"
|
||||
- "@develop-help": "개발실행프롬프트 내용을 터미널에 출력"
|
||||
- "@deploy-help": "배포실행프롬프트 내용을 터미널에 출력"
|
||||
```
|
||||
485
design-backup/uiux/prototype/01-로그인.html
Normal file
485
design-backup/uiux/prototype/01-로그인.html
Normal file
@ -0,0 +1,485 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>로그인 - 통신요금 관리 서비스</title>
|
||||
<style>
|
||||
/* CSS Variables - Style Guide */
|
||||
:root {
|
||||
/* Primary Colors */
|
||||
--primary-50: #EBF8FF;
|
||||
--primary-100: #BEE3F8;
|
||||
--primary-200: #90CDF4;
|
||||
--primary-300: #63B3ED;
|
||||
--primary-400: #4299E1;
|
||||
--primary-500: #3182CE;
|
||||
--primary-600: #2B77CB;
|
||||
--primary-700: #2C5282;
|
||||
--primary-800: #2A4365;
|
||||
--primary-900: #1A365D;
|
||||
|
||||
/* Gray Colors */
|
||||
--gray-50: #F9FAFB;
|
||||
--gray-100: #F3F4F6;
|
||||
--gray-200: #E5E7EB;
|
||||
--gray-300: #D1D5DB;
|
||||
--gray-400: #9CA3AF;
|
||||
--gray-500: #6B7280;
|
||||
--gray-600: #4B5563;
|
||||
--gray-700: #374151;
|
||||
--gray-800: #1F2937;
|
||||
--gray-900: #111827;
|
||||
|
||||
/* Status Colors */
|
||||
--success-500: #38A169;
|
||||
--error-500: #E53E3E;
|
||||
--warning-500: #ED8936;
|
||||
|
||||
/* Spacing */
|
||||
--space-1: 0.25rem;
|
||||
--space-2: 0.5rem;
|
||||
--space-3: 0.75rem;
|
||||
--space-4: 1rem;
|
||||
--space-5: 1.25rem;
|
||||
--space-6: 1.5rem;
|
||||
--space-8: 2rem;
|
||||
--space-10: 2.5rem;
|
||||
--space-12: 3rem;
|
||||
|
||||
/* Typography */
|
||||
--text-xs: 0.75rem;
|
||||
--text-sm: 0.875rem;
|
||||
--text-base: 1rem;
|
||||
--text-lg: 1.125rem;
|
||||
--text-xl: 1.25rem;
|
||||
--text-2xl: 1.5rem;
|
||||
--text-3xl: 1.875rem;
|
||||
--text-4xl: 2.25rem;
|
||||
|
||||
--font-normal: 400;
|
||||
--font-medium: 500;
|
||||
--font-semibold: 600;
|
||||
--font-bold: 700;
|
||||
}
|
||||
|
||||
/* Reset & Base */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Noto Sans KR', -apple-system, BlinkMacSystemFont, 'Apple SD Gothic Neo', 'Malgun Gothic', sans-serif;
|
||||
font-size: var(--text-base);
|
||||
line-height: 1.5;
|
||||
color: var(--gray-700);
|
||||
background-color: var(--gray-50);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* Container */
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
margin: 0 auto;
|
||||
padding: var(--space-4);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
max-width: 480px;
|
||||
padding: var(--space-8);
|
||||
}
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: var(--space-8);
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
background: linear-gradient(135deg, var(--primary-500) 0%, var(--primary-600) 100%);
|
||||
border-radius: 20px;
|
||||
margin: 0 auto var(--space-4);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: var(--text-2xl);
|
||||
font-weight: var(--font-bold);
|
||||
}
|
||||
|
||||
.service-title {
|
||||
font-size: var(--text-2xl);
|
||||
font-weight: var(--font-bold);
|
||||
color: var(--gray-900);
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
|
||||
.service-subtitle {
|
||||
font-size: var(--text-base);
|
||||
color: var(--gray-500);
|
||||
}
|
||||
|
||||
/* Card */
|
||||
.card {
|
||||
background-color: white;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
||||
padding: var(--space-8);
|
||||
margin-bottom: var(--space-6);
|
||||
}
|
||||
|
||||
/* Form */
|
||||
.form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-6);
|
||||
}
|
||||
|
||||
.form-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: var(--text-sm);
|
||||
font-weight: var(--font-medium);
|
||||
color: var(--gray-700);
|
||||
}
|
||||
|
||||
.input {
|
||||
width: 100%;
|
||||
padding: var(--space-4);
|
||||
border: 2px solid var(--gray-200);
|
||||
border-radius: 12px;
|
||||
font-size: var(--text-base);
|
||||
line-height: 1.5;
|
||||
transition: all 0.2s ease-in-out;
|
||||
min-height: 52px;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.input:focus {
|
||||
outline: none;
|
||||
border-color: var(--primary-500);
|
||||
box-shadow: 0 0 0 3px rgba(49, 130, 206, 0.1);
|
||||
}
|
||||
|
||||
.input::placeholder {
|
||||
color: var(--gray-400);
|
||||
}
|
||||
|
||||
.input.error {
|
||||
border-color: var(--error-500);
|
||||
}
|
||||
|
||||
/* Checkbox */
|
||||
.checkbox-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 2px solid var(--gray-300);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.checkbox:checked {
|
||||
background-color: var(--primary-500);
|
||||
border-color: var(--primary-500);
|
||||
}
|
||||
|
||||
.checkbox-label {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--gray-600);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Button */
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--space-4) var(--space-6);
|
||||
border-radius: 12px;
|
||||
font-size: var(--text-base);
|
||||
font-weight: var(--font-semibold);
|
||||
line-height: 1.5;
|
||||
transition: all 0.2s ease-in-out;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
min-height: 52px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, var(--primary-500) 0%, var(--primary-600) 100%);
|
||||
color: white;
|
||||
box-shadow: 0 2px 8px rgba(49, 130, 206, 0.2);
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(49, 130, 206, 0.3);
|
||||
}
|
||||
|
||||
.btn-primary:disabled {
|
||||
background: var(--gray-300);
|
||||
color: var(--gray-500);
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* Alert */
|
||||
.alert {
|
||||
padding: var(--space-4);
|
||||
border-radius: 12px;
|
||||
font-size: var(--text-sm);
|
||||
margin-bottom: var(--space-4);
|
||||
display: none;
|
||||
}
|
||||
|
||||
.alert.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.alert-error {
|
||||
background-color: #FEF2F2;
|
||||
border: 1px solid #FECACA;
|
||||
color: #991B1B;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.footer {
|
||||
text-align: center;
|
||||
margin-top: var(--space-8);
|
||||
padding-top: var(--space-6);
|
||||
border-top: 1px solid var(--gray-200);
|
||||
}
|
||||
|
||||
.footer-text {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--gray-400);
|
||||
}
|
||||
|
||||
/* Loading */
|
||||
.loading {
|
||||
position: relative;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.loading::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 2px solid rgba(255, 255, 255, 0.3);
|
||||
border-top: 2px solid white;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: translate(-50%, -50%) rotate(0deg); }
|
||||
100% { transform: translate(-50%, -50%) rotate(360deg); }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<!-- Header -->
|
||||
<div class="header">
|
||||
<div class="logo">📱</div>
|
||||
<h1 class="service-title">통신요금 관리</h1>
|
||||
<p class="service-subtitle">간편하고 안전한 요금 관리 서비스</p>
|
||||
</div>
|
||||
|
||||
<!-- Login Form Card -->
|
||||
<div class="card">
|
||||
<!-- Error Alert -->
|
||||
<div id="errorAlert" class="alert alert-error">
|
||||
<span id="errorMessage"></span>
|
||||
</div>
|
||||
|
||||
<form class="form" id="loginForm">
|
||||
<!-- ID Input -->
|
||||
<div class="form-group">
|
||||
<label for="userId" class="label">아이디</label>
|
||||
<input
|
||||
type="text"
|
||||
id="userId"
|
||||
name="userId"
|
||||
class="input"
|
||||
placeholder="아이디를 입력하세요"
|
||||
required
|
||||
autocomplete="username"
|
||||
aria-describedby="userId-error"
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- Password Input -->
|
||||
<div class="form-group">
|
||||
<label for="password" class="label">비밀번호</label>
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
name="password"
|
||||
class="input"
|
||||
placeholder="비밀번호를 입력하세요"
|
||||
required
|
||||
autocomplete="current-password"
|
||||
aria-describedby="password-error"
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- Auto Login Checkbox -->
|
||||
<div class="checkbox-group">
|
||||
<input type="checkbox" id="autoLogin" name="autoLogin" class="checkbox">
|
||||
<label for="autoLogin" class="checkbox-label">자동 로그인</label>
|
||||
</div>
|
||||
|
||||
<!-- Login Button -->
|
||||
<button type="submit" class="btn btn-primary" id="loginBtn">
|
||||
로그인
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="footer">
|
||||
<p class="footer-text">© 2025 통신요금 관리 서비스. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Login form validation and submission
|
||||
const loginForm = document.getElementById('loginForm');
|
||||
const loginBtn = document.getElementById('loginBtn');
|
||||
const errorAlert = document.getElementById('errorAlert');
|
||||
const errorMessage = document.getElementById('errorMessage');
|
||||
const userIdInput = document.getElementById('userId');
|
||||
const passwordInput = document.getElementById('password');
|
||||
|
||||
let loginAttempts = 0;
|
||||
const maxAttempts = 5;
|
||||
|
||||
// Show error message
|
||||
function showError(message) {
|
||||
errorMessage.textContent = message;
|
||||
errorAlert.classList.add('show');
|
||||
}
|
||||
|
||||
// Hide error message
|
||||
function hideError() {
|
||||
errorAlert.classList.remove('show');
|
||||
}
|
||||
|
||||
// Validate form inputs
|
||||
function validateForm() {
|
||||
const userId = userIdInput.value.trim();
|
||||
const password = passwordInput.value.trim();
|
||||
|
||||
if (!userId) {
|
||||
showError('아이디를 입력해주세요.');
|
||||
userIdInput.focus();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!password) {
|
||||
showError('비밀번호를 입력해주세요.');
|
||||
passwordInput.focus();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Handle form submission
|
||||
loginForm.addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
hideError();
|
||||
|
||||
if (!validateForm()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check login attempts
|
||||
if (loginAttempts >= maxAttempts) {
|
||||
showError('로그인 시도 횟수를 초과했습니다. 30분 후 다시 시도해주세요.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Show loading state
|
||||
loginBtn.classList.add('loading');
|
||||
loginBtn.disabled = true;
|
||||
|
||||
// Simulate login API call
|
||||
setTimeout(() => {
|
||||
const userId = userIdInput.value.trim();
|
||||
const password = passwordInput.value.trim();
|
||||
|
||||
// Demo login - accept any ID/password for prototype
|
||||
if (userId && password) {
|
||||
// Success - redirect to main page
|
||||
alert('로그인 성공! 메인 화면으로 이동합니다.');
|
||||
window.location.href = '02-메인화면.html';
|
||||
} else {
|
||||
// Failure
|
||||
loginAttempts++;
|
||||
const remainingAttempts = maxAttempts - loginAttempts;
|
||||
|
||||
if (remainingAttempts > 0) {
|
||||
showError(`로그인에 실패했습니다. ${remainingAttempts}회 더 시도할 수 있습니다.`);
|
||||
} else {
|
||||
showError('로그인 시도 횟수를 초과했습니다. 30분 후 다시 시도해주세요.');
|
||||
}
|
||||
|
||||
loginBtn.classList.remove('loading');
|
||||
loginBtn.disabled = false;
|
||||
}
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
// Input validation feedback
|
||||
[userIdInput, passwordInput].forEach(input => {
|
||||
input.addEventListener('input', function() {
|
||||
this.classList.remove('error');
|
||||
hideError();
|
||||
});
|
||||
|
||||
input.addEventListener('blur', function() {
|
||||
if (!this.value.trim()) {
|
||||
this.classList.add('error');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Enter key handling for accessibility
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Enter' && e.target.tagName !== 'BUTTON') {
|
||||
loginForm.requestSubmit();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
537
design-backup/uiux/prototype/02-메인화면.html
Normal file
537
design-backup/uiux/prototype/02-메인화면.html
Normal file
@ -0,0 +1,537 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>메인 화면 - 통신요금 관리 서비스</title>
|
||||
<style>
|
||||
/* CSS Variables - Style Guide */
|
||||
:root {
|
||||
/* Primary Colors */
|
||||
--primary-50: #EBF8FF;
|
||||
--primary-100: #BEE3F8;
|
||||
--primary-200: #90CDF4;
|
||||
--primary-300: #63B3ED;
|
||||
--primary-400: #4299E1;
|
||||
--primary-500: #3182CE;
|
||||
--primary-600: #2B77CB;
|
||||
--primary-700: #2C5282;
|
||||
--primary-800: #2A4365;
|
||||
--primary-900: #1A365D;
|
||||
|
||||
/* Gray Colors */
|
||||
--gray-50: #F9FAFB;
|
||||
--gray-100: #F3F4F6;
|
||||
--gray-200: #E5E7EB;
|
||||
--gray-300: #D1D5DB;
|
||||
--gray-400: #9CA3AF;
|
||||
--gray-500: #6B7280;
|
||||
--gray-600: #4B5563;
|
||||
--gray-700: #374151;
|
||||
--gray-800: #1F2937;
|
||||
--gray-900: #111827;
|
||||
|
||||
/* Status Colors */
|
||||
--success-50: #F0FFF4;
|
||||
--success-500: #38A169;
|
||||
--error-500: #E53E3E;
|
||||
--warning-50: #FFFAF0;
|
||||
--warning-500: #ED8936;
|
||||
|
||||
/* Spacing */
|
||||
--space-1: 0.25rem;
|
||||
--space-2: 0.5rem;
|
||||
--space-3: 0.75rem;
|
||||
--space-4: 1rem;
|
||||
--space-5: 1.25rem;
|
||||
--space-6: 1.5rem;
|
||||
--space-8: 2rem;
|
||||
--space-10: 2.5rem;
|
||||
--space-12: 3rem;
|
||||
|
||||
/* Typography */
|
||||
--text-xs: 0.75rem;
|
||||
--text-sm: 0.875rem;
|
||||
--text-base: 1rem;
|
||||
--text-lg: 1.125rem;
|
||||
--text-xl: 1.25rem;
|
||||
--text-2xl: 1.5rem;
|
||||
--text-3xl: 1.875rem;
|
||||
--text-4xl: 2.25rem;
|
||||
|
||||
--font-normal: 400;
|
||||
--font-medium: 500;
|
||||
--font-semibold: 600;
|
||||
--font-bold: 700;
|
||||
}
|
||||
|
||||
/* Reset & Base */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Noto Sans KR', -apple-system, BlinkMacSystemFont, 'Apple SD Gothic Neo', 'Malgun Gothic', sans-serif;
|
||||
font-size: var(--text-base);
|
||||
line-height: 1.5;
|
||||
color: var(--gray-700);
|
||||
background-color: var(--gray-50);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* Container */
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 480px;
|
||||
margin: 0 auto;
|
||||
padding: var(--space-4);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
max-width: 600px;
|
||||
padding: var(--space-6);
|
||||
}
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: var(--space-4) 0;
|
||||
margin-bottom: var(--space-6);
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background: linear-gradient(135deg, var(--primary-500) 0%, var(--primary-600) 100%);
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: var(--text-lg);
|
||||
font-weight: var(--font-bold);
|
||||
}
|
||||
|
||||
.service-title {
|
||||
font-size: var(--text-lg);
|
||||
font-weight: var(--font-bold);
|
||||
color: var(--gray-900);
|
||||
}
|
||||
|
||||
.logout-btn {
|
||||
padding: var(--space-2) var(--space-4);
|
||||
background-color: white;
|
||||
color: var(--gray-600);
|
||||
border: 1px solid var(--gray-300);
|
||||
border-radius: 8px;
|
||||
font-size: var(--text-sm);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.logout-btn:hover {
|
||||
background-color: var(--gray-50);
|
||||
color: var(--gray-700);
|
||||
}
|
||||
|
||||
/* Welcome Section */
|
||||
.welcome-section {
|
||||
background: linear-gradient(135deg, var(--primary-500) 0%, var(--primary-600) 100%);
|
||||
color: white;
|
||||
padding: var(--space-8);
|
||||
border-radius: 20px;
|
||||
margin-bottom: var(--space-8);
|
||||
box-shadow: 0 4px 20px rgba(49, 130, 206, 0.2);
|
||||
}
|
||||
|
||||
.welcome-title {
|
||||
font-size: var(--text-2xl);
|
||||
font-weight: var(--font-bold);
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
|
||||
.user-info {
|
||||
font-size: var(--text-base);
|
||||
opacity: 0.9;
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
.user-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.user-details {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
||||
.user-phone {
|
||||
font-size: var(--text-lg);
|
||||
font-weight: var(--font-semibold);
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
padding: var(--space-2) var(--space-4);
|
||||
border-radius: 999px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.current-product {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: var(--space-1);
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.current-product {
|
||||
align-items: flex-end;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
.current-product-label {
|
||||
font-size: var(--text-sm);
|
||||
opacity: 0.8;
|
||||
font-weight: var(--font-normal);
|
||||
}
|
||||
|
||||
.current-product-name {
|
||||
font-size: var(--text-base);
|
||||
font-weight: var(--font-semibold);
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
padding: var(--space-1) var(--space-3);
|
||||
border-radius: 8px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* Service Menu */
|
||||
.service-menu {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.menu-title {
|
||||
font-size: var(--text-xl);
|
||||
font-weight: var(--font-bold);
|
||||
color: var(--gray-900);
|
||||
margin-bottom: var(--space-6);
|
||||
}
|
||||
|
||||
.menu-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.menu-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: var(--space-6);
|
||||
}
|
||||
}
|
||||
|
||||
/* Menu Card */
|
||||
.menu-card {
|
||||
background-color: white;
|
||||
border-radius: 16px;
|
||||
padding: var(--space-6);
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
||||
border: 1px solid var(--gray-100);
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.menu-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.menu-card.disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
background-color: var(--gray-50);
|
||||
}
|
||||
|
||||
.menu-card.disabled:hover {
|
||||
transform: none;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.menu-icon {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: var(--text-2xl);
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
.menu-icon.bill {
|
||||
background: linear-gradient(135deg, #10B981 0%, #059669 100%);
|
||||
}
|
||||
|
||||
.menu-icon.product {
|
||||
background: linear-gradient(135deg, #F59E0B 0%, #D97706 100%);
|
||||
}
|
||||
|
||||
.menu-title-text {
|
||||
font-size: var(--text-lg);
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--gray-900);
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
|
||||
.menu-description {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--gray-500);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* Access Denied Modal */
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.modal {
|
||||
background-color: white;
|
||||
border-radius: 16px;
|
||||
padding: var(--space-8);
|
||||
margin: var(--space-4);
|
||||
max-width: 320px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.modal-icon {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
background-color: var(--error-500);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 auto var(--space-4);
|
||||
color: white;
|
||||
font-size: var(--text-2xl);
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
font-size: var(--text-lg);
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--gray-900);
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
|
||||
.modal-message {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--gray-600);
|
||||
margin-bottom: var(--space-6);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.modal-btn {
|
||||
padding: var(--space-3) var(--space-6);
|
||||
background-color: var(--primary-500);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: var(--text-sm);
|
||||
font-weight: var(--font-medium);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.modal-btn:hover {
|
||||
background-color: var(--primary-600);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<!-- Header -->
|
||||
<div class="header">
|
||||
<div class="header-left">
|
||||
<div class="logo">📱</div>
|
||||
<h1 class="service-title">통신요금 관리</h1>
|
||||
</div>
|
||||
<button class="logout-btn" onclick="handleLogout()">로그아웃</button>
|
||||
</div>
|
||||
|
||||
<!-- Welcome Section -->
|
||||
<div class="welcome-section">
|
||||
<h2 class="welcome-title">안녕하세요!</h2>
|
||||
<p class="user-info">통신요금 관리 서비스에 오신 것을 환영합니다.</p>
|
||||
<div class="user-details">
|
||||
<span class="user-phone">010-1234-5678</span>
|
||||
<div class="current-product">
|
||||
<span class="current-product-label">현재 상품</span>
|
||||
<span class="current-product-name" id="currentProductName">5G 프리미엄 플랜</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Service Menu -->
|
||||
<div class="service-menu">
|
||||
<h3 class="menu-title">서비스 메뉴</h3>
|
||||
|
||||
<div class="menu-grid">
|
||||
<!-- 요금 조회 메뉴 -->
|
||||
<a href="03-요금조회메뉴.html" class="menu-card" id="billCard">
|
||||
<div class="menu-icon bill">📊</div>
|
||||
<h4 class="menu-title-text">요금 조회</h4>
|
||||
<p class="menu-description">
|
||||
월별 통신요금과 사용량을<br>
|
||||
상세하게 확인할 수 있습니다.
|
||||
</p>
|
||||
</a>
|
||||
|
||||
<!-- 상품 변경 메뉴 -->
|
||||
<a href="05-상품변경메뉴.html" class="menu-card" id="productCard">
|
||||
<div class="menu-icon product">🔄</div>
|
||||
<h4 class="menu-title-text">상품 변경</h4>
|
||||
<p class="menu-description">
|
||||
현재 이용 중인 요금제를<br>
|
||||
다른 상품으로 변경할 수 있습니다.
|
||||
</p>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Access Denied Modal -->
|
||||
<div class="modal-overlay" id="accessDeniedModal">
|
||||
<div class="modal">
|
||||
<div class="modal-icon">🚫</div>
|
||||
<h3 class="modal-title">접근 권한 없음</h3>
|
||||
<p class="modal-message">해당 서비스를 이용할 권한이 없습니다.<br>고객센터로 문의해주세요.</p>
|
||||
<button class="modal-btn" onclick="closeModal()">확인</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// User permissions simulation
|
||||
const userPermissions = {
|
||||
bill: true, // 요금 조회 권한
|
||||
product: true // 상품 변경 권한
|
||||
};
|
||||
|
||||
// Initialize page
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
loadCurrentProduct();
|
||||
checkPermissions();
|
||||
});
|
||||
|
||||
// Load current product from localStorage
|
||||
function loadCurrentProduct() {
|
||||
try {
|
||||
const currentProduct = localStorage.getItem('currentProduct');
|
||||
if (currentProduct) {
|
||||
const product = JSON.parse(currentProduct);
|
||||
const productNameElement = document.getElementById('currentProductName');
|
||||
if (productNameElement && product.name) {
|
||||
productNameElement.textContent = product.name;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('localStorage에서 상품 정보를 불러오는 중 오류 발생:', error);
|
||||
// 기본값 유지 (5G 프리미엄 플랜)
|
||||
}
|
||||
}
|
||||
|
||||
// Check user permissions and update UI
|
||||
function checkPermissions() {
|
||||
const billCard = document.getElementById('billCard');
|
||||
const productCard = document.getElementById('productCard');
|
||||
|
||||
// 요금 조회 권한 확인
|
||||
if (!userPermissions.bill) {
|
||||
billCard.classList.add('disabled');
|
||||
billCard.href = '#';
|
||||
billCard.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
showAccessDeniedModal('요금 조회');
|
||||
});
|
||||
}
|
||||
|
||||
// 상품 변경 권한 확인
|
||||
if (!userPermissions.product) {
|
||||
productCard.classList.add('disabled');
|
||||
productCard.href = '#';
|
||||
productCard.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
showAccessDeniedModal('상품 변경');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Show access denied modal
|
||||
function showAccessDeniedModal(serviceName) {
|
||||
const modal = document.getElementById('accessDeniedModal');
|
||||
const message = modal.querySelector('.modal-message');
|
||||
message.innerHTML = `${serviceName} 서비스를 이용할 권한이 없습니다.<br>고객센터로 문의해주세요.`;
|
||||
modal.style.display = 'flex';
|
||||
}
|
||||
|
||||
// Close modal
|
||||
function closeModal() {
|
||||
const modal = document.getElementById('accessDeniedModal');
|
||||
modal.style.display = 'none';
|
||||
}
|
||||
|
||||
// Handle logout
|
||||
function handleLogout() {
|
||||
if (confirm('로그아웃 하시겠습니까?')) {
|
||||
alert('안전하게 로그아웃되었습니다.');
|
||||
window.location.href = '01-로그인.html';
|
||||
}
|
||||
}
|
||||
|
||||
// Modal close on overlay click
|
||||
document.getElementById('accessDeniedModal').addEventListener('click', function(e) {
|
||||
if (e.target === this) {
|
||||
closeModal();
|
||||
}
|
||||
});
|
||||
|
||||
// Escape key to close modal
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape') {
|
||||
closeModal();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
690
design-backup/uiux/prototype/03-요금조회메뉴.html
Normal file
690
design-backup/uiux/prototype/03-요금조회메뉴.html
Normal file
@ -0,0 +1,690 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>요금조회 메뉴 - 통신요금 관리 서비스</title>
|
||||
<style>
|
||||
/* CSS Variables - Style Guide */
|
||||
:root {
|
||||
/* Primary Colors */
|
||||
--primary-50: #EBF8FF;
|
||||
--primary-100: #BEE3F8;
|
||||
--primary-200: #90CDF4;
|
||||
--primary-300: #63B3ED;
|
||||
--primary-400: #4299E1;
|
||||
--primary-500: #3182CE;
|
||||
--primary-600: #2B77CB;
|
||||
--primary-700: #2C5282;
|
||||
--primary-800: #2A4365;
|
||||
--primary-900: #1A365D;
|
||||
|
||||
/* Gray Colors */
|
||||
--gray-50: #F9FAFB;
|
||||
--gray-100: #F3F4F6;
|
||||
--gray-200: #E5E7EB;
|
||||
--gray-300: #D1D5DB;
|
||||
--gray-400: #9CA3AF;
|
||||
--gray-500: #6B7280;
|
||||
--gray-600: #4B5563;
|
||||
--gray-700: #374151;
|
||||
--gray-800: #1F2937;
|
||||
--gray-900: #111827;
|
||||
|
||||
/* Status Colors */
|
||||
--success-50: #F0FFF4;
|
||||
--success-500: #38A169;
|
||||
--error-500: #E53E3E;
|
||||
--warning-50: #FFFAF0;
|
||||
--warning-500: #ED8936;
|
||||
|
||||
/* Spacing */
|
||||
--space-1: 0.25rem;
|
||||
--space-2: 0.5rem;
|
||||
--space-3: 0.75rem;
|
||||
--space-4: 1rem;
|
||||
--space-5: 1.25rem;
|
||||
--space-6: 1.5rem;
|
||||
--space-8: 2rem;
|
||||
--space-10: 2.5rem;
|
||||
--space-12: 3rem;
|
||||
|
||||
/* Typography */
|
||||
--text-xs: 0.75rem;
|
||||
--text-sm: 0.875rem;
|
||||
--text-base: 1rem;
|
||||
--text-lg: 1.125rem;
|
||||
--text-xl: 1.25rem;
|
||||
--text-2xl: 1.5rem;
|
||||
--text-3xl: 1.875rem;
|
||||
--text-4xl: 2.25rem;
|
||||
|
||||
--font-normal: 400;
|
||||
--font-medium: 500;
|
||||
--font-semibold: 600;
|
||||
--font-bold: 700;
|
||||
}
|
||||
|
||||
/* Reset & Base */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Noto Sans KR', -apple-system, BlinkMacSystemFont, 'Apple SD Gothic Neo', 'Malgun Gothic', sans-serif;
|
||||
font-size: var(--text-base);
|
||||
line-height: 1.5;
|
||||
color: var(--gray-700);
|
||||
background-color: var(--gray-50);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* Container */
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 480px;
|
||||
margin: 0 auto;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
max-width: 600px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: var(--space-4);
|
||||
background-color: white;
|
||||
border-bottom: 1px solid var(--gray-200);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: none;
|
||||
background-color: var(--gray-100);
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease-in-out;
|
||||
color: var(--gray-700);
|
||||
font-size: var(--text-lg);
|
||||
}
|
||||
|
||||
.back-btn:hover {
|
||||
background-color: var(--gray-200);
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: var(--text-xl);
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--gray-900);
|
||||
}
|
||||
|
||||
.menu-btn {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: none;
|
||||
background: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
color: var(--gray-600);
|
||||
font-size: var(--text-lg);
|
||||
}
|
||||
|
||||
/* Main Content */
|
||||
.main-content {
|
||||
flex: 1;
|
||||
padding: var(--space-6);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-8);
|
||||
}
|
||||
|
||||
/* User Info Card */
|
||||
.user-info-card {
|
||||
background: linear-gradient(135deg, var(--primary-500) 0%, var(--primary-600) 100%);
|
||||
color: white;
|
||||
padding: var(--space-6);
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 4px 20px rgba(49, 130, 206, 0.2);
|
||||
}
|
||||
|
||||
.user-info-title {
|
||||
font-size: var(--text-sm);
|
||||
opacity: 0.9;
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
|
||||
.user-phone {
|
||||
font-size: var(--text-2xl);
|
||||
font-weight: var(--font-bold);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
/* Inquiry Options Card */
|
||||
.inquiry-card {
|
||||
background-color: white;
|
||||
border-radius: 16px;
|
||||
padding: var(--space-6);
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
||||
border: 1px solid var(--gray-100);
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: var(--text-xl);
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--gray-900);
|
||||
margin-bottom: var(--space-6);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.card-title-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background-color: var(--primary-100);
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--primary-600);
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: var(--space-6);
|
||||
}
|
||||
|
||||
.form-label {
|
||||
display: block;
|
||||
font-size: var(--text-sm);
|
||||
font-weight: var(--font-medium);
|
||||
color: var(--gray-700);
|
||||
margin-bottom: var(--space-3);
|
||||
}
|
||||
|
||||
.select-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.select {
|
||||
width: 100%;
|
||||
padding: var(--space-4);
|
||||
border: 2px solid var(--gray-200);
|
||||
border-radius: 12px;
|
||||
font-size: var(--text-base);
|
||||
line-height: 1.5;
|
||||
background-color: white;
|
||||
cursor: pointer;
|
||||
appearance: none;
|
||||
transition: all 0.2s ease-in-out;
|
||||
min-height: 52px;
|
||||
}
|
||||
|
||||
.select:focus {
|
||||
outline: none;
|
||||
border-color: var(--primary-500);
|
||||
box-shadow: 0 0 0 3px rgba(49, 130, 206, 0.1);
|
||||
}
|
||||
|
||||
.select-wrapper::after {
|
||||
content: "▼";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: var(--space-4);
|
||||
transform: translateY(-50%);
|
||||
color: var(--gray-400);
|
||||
font-size: var(--text-sm);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.form-help {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--gray-500);
|
||||
margin-top: var(--space-2);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.help-icon {
|
||||
color: var(--primary-500);
|
||||
font-size: var(--text-xs);
|
||||
}
|
||||
|
||||
/* Action Buttons */
|
||||
.action-buttons {
|
||||
padding: var(--space-6);
|
||||
padding-top: 0;
|
||||
display: flex;
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
.btn {
|
||||
flex: 1;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--space-4) var(--space-6);
|
||||
border-radius: 12px;
|
||||
font-size: var(--text-base);
|
||||
font-weight: var(--font-semibold);
|
||||
line-height: 1.5;
|
||||
transition: all 0.2s ease-in-out;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
min-height: 52px;
|
||||
text-decoration: none;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, var(--primary-500) 0%, var(--primary-600) 100%);
|
||||
color: white;
|
||||
box-shadow: 0 2px 8px rgba(49, 130, 206, 0.2);
|
||||
}
|
||||
|
||||
.btn-primary:hover:not(:disabled) {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(49, 130, 206, 0.3);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: white;
|
||||
color: var(--gray-700);
|
||||
border: 2px solid var(--gray-300);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background-color: var(--gray-50);
|
||||
border-color: var(--gray-400);
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* Loading State */
|
||||
.btn.loading {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.btn.loading::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 2px solid rgba(255, 255, 255, 0.3);
|
||||
border-top: 2px solid white;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: translate(-50%, -50%) rotate(0deg); }
|
||||
100% { transform: translate(-50%, -50%) rotate(360deg); }
|
||||
}
|
||||
|
||||
/* Alert */
|
||||
.alert {
|
||||
padding: var(--space-4);
|
||||
border-radius: 12px;
|
||||
font-size: var(--text-sm);
|
||||
margin-bottom: var(--space-4);
|
||||
display: none;
|
||||
align-items: flex-start;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.alert.show {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.alert-error {
|
||||
background-color: #FEF2F2;
|
||||
border: 1px solid #FECACA;
|
||||
color: #991B1B;
|
||||
}
|
||||
|
||||
.alert-warning {
|
||||
background-color: var(--warning-50);
|
||||
border: 1px solid #FED7AA;
|
||||
color: #92400E;
|
||||
}
|
||||
|
||||
.alert-icon {
|
||||
margin-top: var(--space-1);
|
||||
font-size: var(--text-base);
|
||||
}
|
||||
|
||||
.alert-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* Usage Info */
|
||||
.usage-info {
|
||||
background-color: var(--gray-50);
|
||||
border-radius: 12px;
|
||||
padding: var(--space-4);
|
||||
border: 1px solid var(--gray-200);
|
||||
}
|
||||
|
||||
.usage-title {
|
||||
font-size: var(--text-sm);
|
||||
font-weight: var(--font-medium);
|
||||
color: var(--gray-700);
|
||||
margin-bottom: var(--space-2);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.usage-list {
|
||||
list-style: none;
|
||||
font-size: var(--text-sm);
|
||||
color: var(--gray-600);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.usage-list li {
|
||||
position: relative;
|
||||
padding-left: var(--space-4);
|
||||
margin-bottom: var(--space-1);
|
||||
}
|
||||
|
||||
.usage-list li::before {
|
||||
content: "•";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
color: var(--primary-500);
|
||||
font-weight: var(--font-bold);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<!-- Header -->
|
||||
<div class="header">
|
||||
<div class="header-left">
|
||||
<button class="back-btn" onclick="goBack()" aria-label="뒤로가기">
|
||||
←
|
||||
</button>
|
||||
<h1 class="page-title">요금 조회</h1>
|
||||
</div>
|
||||
<button class="menu-btn" onclick="showMenu()" aria-label="메뉴">
|
||||
☰
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="main-content">
|
||||
<!-- User Info Card -->
|
||||
<div class="user-info-card">
|
||||
<div class="user-info-title">조회 대상 회선</div>
|
||||
<div class="user-phone">
|
||||
📱 010-1234-5678
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Inquiry Options Card -->
|
||||
<div class="inquiry-card">
|
||||
<h2 class="card-title">
|
||||
<span class="card-title-icon">📅</span>
|
||||
조회 옵션 설정
|
||||
</h2>
|
||||
|
||||
<!-- Error Alert -->
|
||||
<div id="errorAlert" class="alert alert-error">
|
||||
<span class="alert-icon">⚠️</span>
|
||||
<div class="alert-content">
|
||||
<span id="errorMessage"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Warning Alert -->
|
||||
<div id="warningAlert" class="alert alert-warning">
|
||||
<span class="alert-icon">💡</span>
|
||||
<div class="alert-content">
|
||||
<span id="warningMessage"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form id="inquiryForm">
|
||||
<!-- 조회월 선택 -->
|
||||
<div class="form-group">
|
||||
<label for="inquiryMonth" class="form-label">조회월 선택</label>
|
||||
<div class="select-wrapper">
|
||||
<select id="inquiryMonth" name="inquiryMonth" class="select" required>
|
||||
<option value="">조회할 월을 선택해주세요</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-help">
|
||||
<span class="help-icon">ℹ️</span>
|
||||
최근 6개월 요금 정보를 조회할 수 있습니다
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Usage Info -->
|
||||
<div class="usage-info">
|
||||
<div class="usage-title">
|
||||
<span>📋</span>
|
||||
조회 가능한 정보
|
||||
</div>
|
||||
<ul class="usage-list">
|
||||
<li>월 요금 상세 내역 (기본료, 통화료, 데이터료 등)</li>
|
||||
<li>사용량 정보 (통화시간, 데이터 사용량, SMS 등)</li>
|
||||
<li>할인 및 혜택 내역</li>
|
||||
<li>단말기 할부금 및 기타 부대비용</li>
|
||||
<li>약정 정보 및 예상 해지비용</li>
|
||||
</ul>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="action-buttons">
|
||||
<button type="button" class="btn btn-secondary" onclick="goBack()">
|
||||
취소
|
||||
</button>
|
||||
<button type="submit" form="inquiryForm" class="btn btn-primary" id="inquiryBtn">
|
||||
요금 조회
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 현재 날짜 기준으로 최근 6개월 옵션 생성
|
||||
function generateMonthOptions() {
|
||||
const select = document.getElementById('inquiryMonth');
|
||||
const now = new Date();
|
||||
|
||||
// 현재 월부터 6개월 전까지 옵션 생성
|
||||
for (let i = 0; i < 6; i++) {
|
||||
const date = new Date(now.getFullYear(), now.getMonth() - i, 1);
|
||||
const year = date.getFullYear();
|
||||
const month = date.getMonth() + 1;
|
||||
const monthStr = month.toString().padStart(2, '0');
|
||||
|
||||
const option = document.createElement('option');
|
||||
option.value = `${year}${monthStr}`;
|
||||
|
||||
if (i === 0) {
|
||||
option.textContent = `${year}년 ${month}월 (현재 월)`;
|
||||
option.selected = true; // 기본값으로 현재 월 선택
|
||||
} else {
|
||||
option.textContent = `${year}년 ${month}월`;
|
||||
}
|
||||
|
||||
select.appendChild(option);
|
||||
}
|
||||
}
|
||||
|
||||
// 폼 요소 참조
|
||||
const inquiryForm = document.getElementById('inquiryForm');
|
||||
const inquiryBtn = document.getElementById('inquiryBtn');
|
||||
const errorAlert = document.getElementById('errorAlert');
|
||||
const warningAlert = document.getElementById('warningAlert');
|
||||
const errorMessage = document.getElementById('errorMessage');
|
||||
const warningMessage = document.getElementById('warningMessage');
|
||||
const monthSelect = document.getElementById('inquiryMonth');
|
||||
|
||||
// 에러 메시지 표시
|
||||
function showError(message) {
|
||||
errorMessage.textContent = message;
|
||||
errorAlert.classList.add('show');
|
||||
hideWarning();
|
||||
}
|
||||
|
||||
// 경고 메시지 표시
|
||||
function showWarning(message) {
|
||||
warningMessage.textContent = message;
|
||||
warningAlert.classList.add('show');
|
||||
hideError();
|
||||
}
|
||||
|
||||
// 에러 메시지 숨기기
|
||||
function hideError() {
|
||||
errorAlert.classList.remove('show');
|
||||
}
|
||||
|
||||
// 경고 메시지 숨기기
|
||||
function hideWarning() {
|
||||
warningAlert.classList.remove('show');
|
||||
}
|
||||
|
||||
// 메시지 전체 숨기기
|
||||
function hideAllMessages() {
|
||||
hideError();
|
||||
hideWarning();
|
||||
}
|
||||
|
||||
// 폼 유효성 검사
|
||||
function validateForm() {
|
||||
const selectedMonth = monthSelect.value;
|
||||
|
||||
if (!selectedMonth) {
|
||||
showError('조회할 월을 선택해주세요.');
|
||||
monthSelect.focus();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 폼 제출 처리
|
||||
inquiryForm.addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
hideAllMessages();
|
||||
|
||||
if (!validateForm()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 로딩 상태 시작
|
||||
inquiryBtn.classList.add('loading');
|
||||
inquiryBtn.disabled = true;
|
||||
|
||||
const selectedMonth = monthSelect.value;
|
||||
const selectedText = monthSelect.options[monthSelect.selectedIndex].text;
|
||||
|
||||
// 로딩 시뮬레이션
|
||||
setTimeout(() => {
|
||||
try {
|
||||
// 현재 날짜와 비교하여 미래 월인지 확인
|
||||
const now = new Date();
|
||||
const currentYear = now.getFullYear();
|
||||
const currentMonth = now.getMonth() + 1;
|
||||
const currentYearMonth = parseInt(`${currentYear}${currentMonth.toString().padStart(2, '0')}`);
|
||||
const selectedYearMonth = parseInt(selectedMonth);
|
||||
|
||||
if (selectedYearMonth > currentYearMonth) {
|
||||
showWarning('미래 월의 요금 정보는 조회할 수 없습니다.');
|
||||
inquiryBtn.classList.remove('loading');
|
||||
inquiryBtn.disabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// 성공 - 조회 결과 페이지로 이동
|
||||
sessionStorage.setItem('selectedMonth', selectedMonth);
|
||||
sessionStorage.setItem('selectedMonthText', selectedText);
|
||||
window.location.href = '04-요금조회결과.html';
|
||||
|
||||
} catch (error) {
|
||||
// 오류 처리
|
||||
showError('요금 조회 중 오류가 발생했습니다. 잠시 후 다시 시도해주세요.');
|
||||
inquiryBtn.classList.remove('loading');
|
||||
inquiryBtn.disabled = false;
|
||||
}
|
||||
}, 1500); // 1.5초 로딩 시뮬레이션
|
||||
});
|
||||
|
||||
// 선택 변경 시 메시지 숨기기
|
||||
monthSelect.addEventListener('change', function() {
|
||||
hideAllMessages();
|
||||
});
|
||||
|
||||
// 뒤로가기
|
||||
function goBack() {
|
||||
if (confirm('요금 조회를 취소하고 메인 화면으로 돌아가시겠습니까?')) {
|
||||
window.location.href = '02-메인화면.html';
|
||||
}
|
||||
}
|
||||
|
||||
// 메뉴 표시 (추후 구현)
|
||||
function showMenu() {
|
||||
alert('메뉴 기능은 추후 구현 예정입니다.');
|
||||
}
|
||||
|
||||
// 키보드 접근성
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape') {
|
||||
hideAllMessages();
|
||||
}
|
||||
});
|
||||
|
||||
// 페이지 로드 시 초기화
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
generateMonthOptions();
|
||||
|
||||
// 페이지 진입 시 현재 월 선택에 대한 안내 표시
|
||||
setTimeout(() => {
|
||||
showWarning('기본적으로 현재 월이 선택되어 있습니다. 다른 월을 조회하려면 드롭다운에서 선택해주세요.');
|
||||
}, 500);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
1004
design-backup/uiux/prototype/04-요금조회결과.html
Normal file
1004
design-backup/uiux/prototype/04-요금조회결과.html
Normal file
File diff suppressed because it is too large
Load Diff
592
design-backup/uiux/prototype/05-상품변경메뉴.html
Normal file
592
design-backup/uiux/prototype/05-상품변경메뉴.html
Normal file
@ -0,0 +1,592 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>상품 변경 - 통신요금 관리 서비스</title>
|
||||
<style>
|
||||
/* CSS Variables - Style Guide */
|
||||
:root {
|
||||
/* Primary Colors */
|
||||
--primary-50: #EBF8FF;
|
||||
--primary-100: #BEE3F8;
|
||||
--primary-200: #90CDF4;
|
||||
--primary-300: #63B3ED;
|
||||
--primary-400: #4299E1;
|
||||
--primary-500: #3182CE;
|
||||
--primary-600: #2B77CB;
|
||||
--primary-700: #2C5282;
|
||||
--primary-800: #2A4365;
|
||||
--primary-900: #1A365D;
|
||||
|
||||
/* Gray Colors */
|
||||
--gray-50: #F9FAFB;
|
||||
--gray-100: #F3F4F6;
|
||||
--gray-200: #E5E7EB;
|
||||
--gray-300: #D1D5DB;
|
||||
--gray-400: #9CA3AF;
|
||||
--gray-500: #6B7280;
|
||||
--gray-600: #4B5563;
|
||||
--gray-700: #374151;
|
||||
--gray-800: #1F2937;
|
||||
--gray-900: #111827;
|
||||
|
||||
/* Status Colors */
|
||||
--success-500: #38A169;
|
||||
--error-500: #E53E3E;
|
||||
--warning-500: #ED8936;
|
||||
|
||||
/* Spacing */
|
||||
--space-1: 0.25rem;
|
||||
--space-2: 0.5rem;
|
||||
--space-3: 0.75rem;
|
||||
--space-4: 1rem;
|
||||
--space-5: 1.25rem;
|
||||
--space-6: 1.5rem;
|
||||
--space-8: 2rem;
|
||||
--space-10: 2.5rem;
|
||||
--space-12: 3rem;
|
||||
|
||||
/* Typography */
|
||||
--text-xs: 0.75rem;
|
||||
--text-sm: 0.875rem;
|
||||
--text-base: 1rem;
|
||||
--text-lg: 1.125rem;
|
||||
--text-xl: 1.25rem;
|
||||
--text-2xl: 1.5rem;
|
||||
--text-3xl: 1.875rem;
|
||||
--text-4xl: 2.25rem;
|
||||
|
||||
--font-normal: 400;
|
||||
--font-medium: 500;
|
||||
--font-semibold: 600;
|
||||
--font-bold: 700;
|
||||
}
|
||||
|
||||
/* Reset & Base */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Noto Sans KR', -apple-system, BlinkMacSystemFont, 'Apple SD Gothic Neo', 'Malgun Gothic', sans-serif;
|
||||
font-size: var(--text-base);
|
||||
line-height: 1.5;
|
||||
color: var(--gray-700);
|
||||
background-color: var(--gray-50);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* Container */
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
margin: 0 auto;
|
||||
padding: var(--space-4);
|
||||
min-height: 100vh;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
max-width: 480px;
|
||||
padding: var(--space-6);
|
||||
box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background: white;
|
||||
z-index: 10;
|
||||
padding: var(--space-4) 0;
|
||||
border-bottom: 1px solid var(--gray-200);
|
||||
margin-bottom: var(--space-6);
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: none;
|
||||
background: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-size: var(--text-xl);
|
||||
color: var(--gray-600);
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.back-btn:hover {
|
||||
background-color: var(--gray-100);
|
||||
color: var(--gray-800);
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: var(--text-xl);
|
||||
font-weight: var(--font-bold);
|
||||
color: var(--gray-900);
|
||||
}
|
||||
|
||||
/* Card */
|
||||
.card {
|
||||
background-color: white;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
||||
padding: var(--space-6);
|
||||
margin-bottom: var(--space-6);
|
||||
border: 1px solid var(--gray-200);
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: var(--text-lg);
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--gray-900);
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
/* Customer Info */
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: var(--space-3) 0;
|
||||
border-bottom: 1px solid var(--gray-100);
|
||||
}
|
||||
|
||||
.info-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--gray-500);
|
||||
font-weight: var(--font-medium);
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: var(--text-base);
|
||||
color: var(--gray-900);
|
||||
font-weight: var(--font-semibold);
|
||||
}
|
||||
|
||||
/* Product Info */
|
||||
.product-card {
|
||||
background: linear-gradient(135deg, var(--primary-50) 0%, var(--primary-100) 100%);
|
||||
border: 2px solid var(--primary-200);
|
||||
border-radius: 16px;
|
||||
padding: var(--space-6);
|
||||
margin-bottom: var(--space-6);
|
||||
}
|
||||
|
||||
.product-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
.product-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background: var(--primary-500);
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: var(--text-lg);
|
||||
}
|
||||
|
||||
.product-name {
|
||||
font-size: var(--text-xl);
|
||||
font-weight: var(--font-bold);
|
||||
color: var(--primary-800);
|
||||
}
|
||||
|
||||
.product-price {
|
||||
font-size: var(--text-lg);
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--primary-700);
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
.benefits-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.benefit-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
padding: var(--space-2) 0;
|
||||
font-size: var(--text-sm);
|
||||
color: var(--gray-700);
|
||||
}
|
||||
|
||||
.benefit-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: var(--success-500);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
/* Notice */
|
||||
.notice {
|
||||
background: #FFF7ED;
|
||||
border: 1px solid #FED7AA;
|
||||
border-radius: 12px;
|
||||
padding: var(--space-4);
|
||||
margin-bottom: var(--space-6);
|
||||
}
|
||||
|
||||
.notice-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
font-size: var(--text-sm);
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--warning-500);
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
|
||||
.notice-text {
|
||||
font-size: var(--text-sm);
|
||||
color: #92400E;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* Button */
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--space-4) var(--space-6);
|
||||
border-radius: 12px;
|
||||
font-size: var(--text-base);
|
||||
font-weight: var(--font-semibold);
|
||||
line-height: 1.5;
|
||||
transition: all 0.2s ease-in-out;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
min-height: 52px;
|
||||
text-decoration: none;
|
||||
width: 100%;
|
||||
margin-bottom: var(--space-3);
|
||||
}
|
||||
|
||||
.btn:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, var(--primary-500) 0%, var(--primary-600) 100%);
|
||||
color: white;
|
||||
box-shadow: 0 2px 8px rgba(49, 130, 206, 0.2);
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(49, 130, 206, 0.3);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: white;
|
||||
color: var(--gray-600);
|
||||
border: 2px solid var(--gray-200);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: var(--gray-50);
|
||||
border-color: var(--gray-300);
|
||||
color: var(--gray-700);
|
||||
}
|
||||
|
||||
/* Loading */
|
||||
.loading {
|
||||
position: relative;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.loading::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 2px solid rgba(255, 255, 255, 0.3);
|
||||
border-top: 2px solid white;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: translate(-50%, -50%) rotate(0deg); }
|
||||
100% { transform: translate(-50%, -50%) rotate(360deg); }
|
||||
}
|
||||
|
||||
/* Skeleton */
|
||||
.skeleton {
|
||||
background: linear-gradient(90deg, var(--gray-200) 25%, var(--gray-100) 50%, var(--gray-200) 75%);
|
||||
background-size: 200% 100%;
|
||||
animation: skeleton 1.5s infinite;
|
||||
}
|
||||
|
||||
@keyframes skeleton {
|
||||
0% { background-position: 200% 0; }
|
||||
100% { background-position: -200% 0; }
|
||||
}
|
||||
|
||||
/* Alert */
|
||||
.alert {
|
||||
padding: var(--space-4);
|
||||
border-radius: 12px;
|
||||
font-size: var(--text-sm);
|
||||
margin-bottom: var(--space-4);
|
||||
display: none;
|
||||
}
|
||||
|
||||
.alert.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.alert-error {
|
||||
background-color: #FEF2F2;
|
||||
border: 1px solid #FECACA;
|
||||
color: #991B1B;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<!-- Header -->
|
||||
<header class="header">
|
||||
<div class="header-content">
|
||||
<button class="back-btn" onclick="goBack()" aria-label="뒤로가기">
|
||||
←
|
||||
</button>
|
||||
<h1 class="page-title">상품 변경</h1>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Error Alert -->
|
||||
<div id="errorAlert" class="alert alert-error">
|
||||
<span id="errorMessage"></span>
|
||||
</div>
|
||||
|
||||
<!-- Customer Info Card -->
|
||||
<div class="card">
|
||||
<h2 class="card-title">고객 정보</h2>
|
||||
<div class="info-row">
|
||||
<span class="info-label">회선번호</span>
|
||||
<span class="info-value">010-1234-5678</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">고객ID</span>
|
||||
<span class="info-value">customer123</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Current Product Info -->
|
||||
<div class="product-card">
|
||||
<div class="product-header">
|
||||
<div class="product-icon">📱</div>
|
||||
<div>
|
||||
<div class="product-name">5G 프리미엄 플랜</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="product-price">월 69,000원</div>
|
||||
<ul class="benefits-list">
|
||||
<li class="benefit-item">
|
||||
<span class="benefit-icon">✓</span>
|
||||
<span>5G 데이터 무제한</span>
|
||||
</li>
|
||||
<li class="benefit-item">
|
||||
<span class="benefit-icon">✓</span>
|
||||
<span>음성통화 무제한</span>
|
||||
</li>
|
||||
<li class="benefit-item">
|
||||
<span class="benefit-icon">✓</span>
|
||||
<span>문자 무제한</span>
|
||||
</li>
|
||||
<li class="benefit-item">
|
||||
<span class="benefit-icon">✓</span>
|
||||
<span>해외 로밍 50% 할인</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Notice -->
|
||||
<div class="notice">
|
||||
<div class="notice-title">
|
||||
<span>⚠️</span>
|
||||
<span>상품 변경 시 주의사항</span>
|
||||
</div>
|
||||
<p class="notice-text">
|
||||
• 상품 변경은 다음 월 1일부터 적용됩니다<br>
|
||||
• 기존 약정 조건에 따라 위약금이 발생할 수 있습니다<br>
|
||||
• 변경 후에는 이전 상품으로 즉시 되돌릴 수 없습니다<br>
|
||||
• 부가서비스는 별도로 재신청이 필요할 수 있습니다
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="actions">
|
||||
<button class="btn btn-primary" id="changeBtn" onclick="goToProductChange()">
|
||||
상품 변경하기
|
||||
</button>
|
||||
<button class="btn btn-secondary" onclick="goBack()">
|
||||
취소
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Navigation functions
|
||||
function goBack() {
|
||||
window.history.back();
|
||||
}
|
||||
|
||||
function goToProductChange() {
|
||||
const changeBtn = document.getElementById('changeBtn');
|
||||
|
||||
// Show loading state
|
||||
changeBtn.classList.add('loading');
|
||||
changeBtn.disabled = true;
|
||||
|
||||
// Simulate loading
|
||||
setTimeout(() => {
|
||||
window.location.href = '06-상품변경화면.html';
|
||||
}, 800);
|
||||
}
|
||||
|
||||
// Show error message
|
||||
function showError(message) {
|
||||
const errorAlert = document.getElementById('errorAlert');
|
||||
const errorMessage = document.getElementById('errorMessage');
|
||||
errorMessage.textContent = message;
|
||||
errorAlert.classList.add('show');
|
||||
}
|
||||
|
||||
// Hide error message
|
||||
function hideError() {
|
||||
const errorAlert = document.getElementById('errorAlert');
|
||||
errorAlert.classList.remove('show');
|
||||
}
|
||||
|
||||
// Load customer and product info on page load
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
loadCurrentProduct();
|
||||
|
||||
// Handle keyboard navigation
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape') {
|
||||
goBack();
|
||||
}
|
||||
|
||||
if (e.key === 'Enter' && e.target.tagName === 'BUTTON') {
|
||||
e.target.click();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Load current product from localStorage
|
||||
function loadCurrentProduct() {
|
||||
try {
|
||||
const currentProduct = localStorage.getItem('currentProduct');
|
||||
if (currentProduct) {
|
||||
const product = JSON.parse(currentProduct);
|
||||
updateCurrentProductDisplay(product);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('localStorage에서 상품 정보를 불러오는 중 오류 발생:', error);
|
||||
// 기본값으로 화면이 이미 설정되어 있음
|
||||
}
|
||||
}
|
||||
|
||||
// Update current product display
|
||||
function updateCurrentProductDisplay(product) {
|
||||
// Update product name
|
||||
const productNameElement = document.querySelector('.product-name');
|
||||
if (productNameElement && product.name) {
|
||||
productNameElement.textContent = product.name;
|
||||
}
|
||||
|
||||
// Update product price
|
||||
const productPriceElement = document.querySelector('.product-price');
|
||||
if (productPriceElement && product.price) {
|
||||
productPriceElement.textContent = product.price;
|
||||
}
|
||||
|
||||
// Update benefits
|
||||
if (product.benefits && Array.isArray(product.benefits)) {
|
||||
const benefitsList = document.querySelector('.benefits-list');
|
||||
if (benefitsList) {
|
||||
benefitsList.innerHTML = '';
|
||||
product.benefits.forEach(benefit => {
|
||||
const li = document.createElement('li');
|
||||
li.className = 'benefit-item';
|
||||
li.innerHTML = `
|
||||
<span class="benefit-icon">✓</span>
|
||||
<span>${benefit}</span>
|
||||
`;
|
||||
benefitsList.appendChild(li);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle back button for accessibility
|
||||
window.addEventListener('popstate', function(e) {
|
||||
// Handle browser back button
|
||||
});
|
||||
|
||||
// Add focus management for accessibility
|
||||
const focusableElements = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
|
||||
const modal = document.querySelector('.container');
|
||||
const firstFocusableElement = modal.querySelectorAll(focusableElements)[0];
|
||||
const focusableContent = modal.querySelectorAll(focusableElements);
|
||||
const lastFocusableElement = focusableContent[focusableContent.length - 1];
|
||||
|
||||
document.addEventListener('keydown', function(e) {
|
||||
const isTabPressed = e.key === 'Tab';
|
||||
|
||||
if (!isTabPressed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.shiftKey) {
|
||||
if (document.activeElement === firstFocusableElement) {
|
||||
lastFocusableElement.focus();
|
||||
e.preventDefault();
|
||||
}
|
||||
} else {
|
||||
if (document.activeElement === lastFocusableElement) {
|
||||
firstFocusableElement.focus();
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
797
design-backup/uiux/prototype/06-상품변경화면.html
Normal file
797
design-backup/uiux/prototype/06-상품변경화면.html
Normal file
@ -0,0 +1,797 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>상품 선택 - 통신요금 관리 서비스</title>
|
||||
<style>
|
||||
/* CSS Variables - Style Guide */
|
||||
:root {
|
||||
/* Primary Colors */
|
||||
--primary-50: #EBF8FF;
|
||||
--primary-100: #BEE3F8;
|
||||
--primary-200: #90CDF4;
|
||||
--primary-300: #63B3ED;
|
||||
--primary-400: #4299E1;
|
||||
--primary-500: #3182CE;
|
||||
--primary-600: #2B77CB;
|
||||
--primary-700: #2C5282;
|
||||
--primary-800: #2A4365;
|
||||
--primary-900: #1A365D;
|
||||
|
||||
/* Gray Colors */
|
||||
--gray-50: #F9FAFB;
|
||||
--gray-100: #F3F4F6;
|
||||
--gray-200: #E5E7EB;
|
||||
--gray-300: #D1D5DB;
|
||||
--gray-400: #9CA3AF;
|
||||
--gray-500: #6B7280;
|
||||
--gray-600: #4B5563;
|
||||
--gray-700: #374151;
|
||||
--gray-800: #1F2937;
|
||||
--gray-900: #111827;
|
||||
|
||||
/* Status Colors */
|
||||
--success-500: #38A169;
|
||||
--error-500: #E53E3E;
|
||||
--warning-500: #ED8936;
|
||||
|
||||
/* Spacing */
|
||||
--space-1: 0.25rem;
|
||||
--space-2: 0.5rem;
|
||||
--space-3: 0.75rem;
|
||||
--space-4: 1rem;
|
||||
--space-5: 1.25rem;
|
||||
--space-6: 1.5rem;
|
||||
--space-8: 2rem;
|
||||
--space-10: 2.5rem;
|
||||
--space-12: 3rem;
|
||||
|
||||
/* Typography */
|
||||
--text-xs: 0.75rem;
|
||||
--text-sm: 0.875rem;
|
||||
--text-base: 1rem;
|
||||
--text-lg: 1.125rem;
|
||||
--text-xl: 1.25rem;
|
||||
--text-2xl: 1.5rem;
|
||||
--text-3xl: 1.875rem;
|
||||
--text-4xl: 2.25rem;
|
||||
|
||||
--font-normal: 400;
|
||||
--font-medium: 500;
|
||||
--font-semibold: 600;
|
||||
--font-bold: 700;
|
||||
}
|
||||
|
||||
/* Reset & Base */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Noto Sans KR', -apple-system, BlinkMacSystemFont, 'Apple SD Gothic Neo', 'Malgun Gothic', sans-serif;
|
||||
font-size: var(--text-base);
|
||||
line-height: 1.5;
|
||||
color: var(--gray-700);
|
||||
background-color: var(--gray-50);
|
||||
min-height: 100vh;
|
||||
padding-bottom: 100px; /* Space for fixed buttons */
|
||||
}
|
||||
|
||||
/* Container */
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
margin: 0 auto;
|
||||
padding: var(--space-4);
|
||||
background-color: white;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
max-width: 480px;
|
||||
padding: var(--space-6);
|
||||
box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background: white;
|
||||
z-index: 10;
|
||||
padding: var(--space-4) 0;
|
||||
border-bottom: 1px solid var(--gray-200);
|
||||
margin-bottom: var(--space-6);
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: none;
|
||||
background: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-size: var(--text-xl);
|
||||
color: var(--gray-600);
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.back-btn:hover {
|
||||
background-color: var(--gray-100);
|
||||
color: var(--gray-800);
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: var(--text-xl);
|
||||
font-weight: var(--font-bold);
|
||||
color: var(--gray-900);
|
||||
}
|
||||
|
||||
/* Current Product Summary */
|
||||
.current-product {
|
||||
background: linear-gradient(135deg, var(--gray-100) 0%, var(--gray-50) 100%);
|
||||
border: 2px solid var(--gray-200);
|
||||
border-radius: 12px;
|
||||
padding: var(--space-4);
|
||||
margin-bottom: var(--space-6);
|
||||
}
|
||||
|
||||
.current-label {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--gray-500);
|
||||
margin-bottom: var(--space-2);
|
||||
font-weight: var(--font-medium);
|
||||
}
|
||||
|
||||
.current-name {
|
||||
font-size: var(--text-lg);
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--gray-800);
|
||||
margin-bottom: var(--space-1);
|
||||
}
|
||||
|
||||
.current-price {
|
||||
font-size: var(--text-base);
|
||||
color: var(--gray-600);
|
||||
}
|
||||
|
||||
/* Product List */
|
||||
.products-section {
|
||||
margin-bottom: var(--space-8);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: var(--text-lg);
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--gray-900);
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
.product-card {
|
||||
border: 2px solid var(--gray-200);
|
||||
border-radius: 16px;
|
||||
padding: var(--space-5);
|
||||
margin-bottom: var(--space-4);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease-in-out;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.product-card:hover {
|
||||
border-color: var(--primary-300);
|
||||
box-shadow: 0 4px 12px rgba(49, 130, 206, 0.1);
|
||||
}
|
||||
|
||||
.product-card.selected {
|
||||
border-color: var(--primary-500);
|
||||
background: linear-gradient(135deg, var(--primary-50) 0%, var(--primary-100) 100%);
|
||||
box-shadow: 0 4px 12px rgba(49, 130, 206, 0.2);
|
||||
}
|
||||
|
||||
.product-radio {
|
||||
position: absolute;
|
||||
top: var(--space-4);
|
||||
right: var(--space-4);
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 2px solid var(--gray-300);
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.product-card.selected .product-radio {
|
||||
border-color: var(--primary-500);
|
||||
background: var(--primary-500);
|
||||
}
|
||||
|
||||
.product-card.selected .product-radio::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: white;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.product-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
margin-bottom: var(--space-3);
|
||||
margin-right: var(--space-8);
|
||||
}
|
||||
|
||||
.product-icon {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
background: var(--primary-500);
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: var(--text-base);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.product-card.selected .product-icon {
|
||||
background: var(--primary-600);
|
||||
}
|
||||
|
||||
.product-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.product-name {
|
||||
font-size: var(--text-lg);
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--gray-900);
|
||||
margin-bottom: var(--space-1);
|
||||
}
|
||||
|
||||
.product-price {
|
||||
font-size: var(--text-base);
|
||||
font-weight: var(--font-medium);
|
||||
color: var(--primary-600);
|
||||
}
|
||||
|
||||
.benefits-grid {
|
||||
display: grid;
|
||||
gap: var(--space-2);
|
||||
margin-bottom: var(--space-3);
|
||||
}
|
||||
|
||||
.benefit-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
font-size: var(--text-sm);
|
||||
color: var(--gray-600);
|
||||
}
|
||||
|
||||
.benefit-icon {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
background: var(--success-500);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.price-comparison {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
padding-top: var(--space-3);
|
||||
border-top: 1px solid var(--gray-200);
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
|
||||
.price-change {
|
||||
font-weight: var(--font-semibold);
|
||||
}
|
||||
|
||||
.price-up {
|
||||
color: var(--error-500);
|
||||
}
|
||||
|
||||
.price-down {
|
||||
color: var(--success-500);
|
||||
}
|
||||
|
||||
.price-same {
|
||||
color: var(--gray-500);
|
||||
}
|
||||
|
||||
/* Fixed Action Buttons */
|
||||
.fixed-actions {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
background: white;
|
||||
padding: var(--space-4);
|
||||
border-top: 1px solid var(--gray-200);
|
||||
box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.fixed-actions {
|
||||
max-width: 480px;
|
||||
padding: var(--space-6);
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--space-4) var(--space-6);
|
||||
border-radius: 12px;
|
||||
font-size: var(--text-base);
|
||||
font-weight: var(--font-semibold);
|
||||
line-height: 1.5;
|
||||
transition: all 0.2s ease-in-out;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
min-height: 52px;
|
||||
text-decoration: none;
|
||||
width: 100%;
|
||||
margin-bottom: var(--space-3);
|
||||
}
|
||||
|
||||
.btn:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, var(--primary-500) 0%, var(--primary-600) 100%);
|
||||
color: white;
|
||||
box-shadow: 0 2px 8px rgba(49, 130, 206, 0.2);
|
||||
}
|
||||
|
||||
.btn-primary:hover:not(:disabled) {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(49, 130, 206, 0.3);
|
||||
}
|
||||
|
||||
.btn-primary:disabled {
|
||||
background: var(--gray-300);
|
||||
color: var(--gray-500);
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: white;
|
||||
color: var(--gray-600);
|
||||
border: 2px solid var(--gray-200);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: var(--gray-50);
|
||||
border-color: var(--gray-300);
|
||||
color: var(--gray-700);
|
||||
}
|
||||
|
||||
/* Loading */
|
||||
.loading {
|
||||
position: relative;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.loading::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 2px solid rgba(255, 255, 255, 0.3);
|
||||
border-top: 2px solid white;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: translate(-50%, -50%) rotate(0deg); }
|
||||
100% { transform: translate(-50%, -50%) rotate(360deg); }
|
||||
}
|
||||
|
||||
/* Skeleton */
|
||||
.skeleton {
|
||||
background: linear-gradient(90deg, var(--gray-200) 25%, var(--gray-100) 50%, var(--gray-200) 75%);
|
||||
background-size: 200% 100%;
|
||||
animation: skeleton 1.5s infinite;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.skeleton-product {
|
||||
height: 140px;
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
@keyframes skeleton {
|
||||
0% { background-position: 200% 0; }
|
||||
100% { background-position: -200% 0; }
|
||||
}
|
||||
|
||||
/* Alert */
|
||||
.alert {
|
||||
padding: var(--space-4);
|
||||
border-radius: 12px;
|
||||
font-size: var(--text-sm);
|
||||
margin-bottom: var(--space-4);
|
||||
display: none;
|
||||
}
|
||||
|
||||
.alert.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.alert-error {
|
||||
background-color: #FEF2F2;
|
||||
border: 1px solid #FECACA;
|
||||
color: #991B1B;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<!-- Header -->
|
||||
<header class="header">
|
||||
<div class="header-content">
|
||||
<button class="back-btn" onclick="goBack()" aria-label="뒤로가기">
|
||||
←
|
||||
</button>
|
||||
<h1 class="page-title">상품 선택</h1>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Error Alert -->
|
||||
<div id="errorAlert" class="alert alert-error">
|
||||
<span id="errorMessage"></span>
|
||||
</div>
|
||||
|
||||
<!-- Current Product Summary -->
|
||||
<div class="current-product">
|
||||
<div class="current-label">현재 이용 중인 상품</div>
|
||||
<div class="current-name">5G 프리미엄 플랜</div>
|
||||
<div class="current-price">월 69,000원</div>
|
||||
</div>
|
||||
|
||||
<!-- Products Section -->
|
||||
<div class="products-section">
|
||||
<h2 class="section-title">변경 가능한 상품</h2>
|
||||
|
||||
<!-- Loading Skeletons (hidden by default) -->
|
||||
<div id="loadingSkeletons" style="display: none;">
|
||||
<div class="skeleton skeleton-product"></div>
|
||||
<div class="skeleton skeleton-product"></div>
|
||||
<div class="skeleton skeleton-product"></div>
|
||||
</div>
|
||||
|
||||
<!-- Product List -->
|
||||
<div id="productList">
|
||||
<!-- Product Card 1 -->
|
||||
<div class="product-card" data-product-id="basic" onclick="selectProduct(this)">
|
||||
<div class="product-radio"></div>
|
||||
<div class="product-header">
|
||||
<div class="product-icon">📱</div>
|
||||
<div class="product-info">
|
||||
<div class="product-name">5G 베이직 플랜</div>
|
||||
<div class="product-price">월 39,000원</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="benefits-grid">
|
||||
<div class="benefit-item">
|
||||
<span class="benefit-icon">✓</span>
|
||||
<span>5G 데이터 10GB</span>
|
||||
</div>
|
||||
<div class="benefit-item">
|
||||
<span class="benefit-icon">✓</span>
|
||||
<span>음성통화 300분</span>
|
||||
</div>
|
||||
<div class="benefit-item">
|
||||
<span class="benefit-icon">✓</span>
|
||||
<span>문자 무제한</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="price-comparison">
|
||||
<span>현재 상품 대비</span>
|
||||
<span class="price-change price-down">월 30,000원 절약</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Product Card 2 -->
|
||||
<div class="product-card" data-product-id="standard" onclick="selectProduct(this)">
|
||||
<div class="product-radio"></div>
|
||||
<div class="product-header">
|
||||
<div class="product-icon">📱</div>
|
||||
<div class="product-info">
|
||||
<div class="product-name">5G 스탠다드 플랜</div>
|
||||
<div class="product-price">월 59,000원</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="benefits-grid">
|
||||
<div class="benefit-item">
|
||||
<span class="benefit-icon">✓</span>
|
||||
<span>5G 데이터 50GB</span>
|
||||
</div>
|
||||
<div class="benefit-item">
|
||||
<span class="benefit-icon">✓</span>
|
||||
<span>음성통화 무제한</span>
|
||||
</div>
|
||||
<div class="benefit-item">
|
||||
<span class="benefit-icon">✓</span>
|
||||
<span>문자 무제한</span>
|
||||
</div>
|
||||
<div class="benefit-item">
|
||||
<span class="benefit-icon">✓</span>
|
||||
<span>해외 로밍 20% 할인</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="price-comparison">
|
||||
<span>현재 상품 대비</span>
|
||||
<span class="price-change price-down">월 10,000원 절약</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Product Card 3 -->
|
||||
<div class="product-card" data-product-id="unlimited" onclick="selectProduct(this)">
|
||||
<div class="product-radio"></div>
|
||||
<div class="product-header">
|
||||
<div class="product-icon">📱</div>
|
||||
<div class="product-info">
|
||||
<div class="product-name">5G 언리미티드 플랜</div>
|
||||
<div class="product-price">월 89,000원</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="benefits-grid">
|
||||
<div class="benefit-item">
|
||||
<span class="benefit-icon">✓</span>
|
||||
<span>5G 데이터 무제한</span>
|
||||
</div>
|
||||
<div class="benefit-item">
|
||||
<span class="benefit-icon">✓</span>
|
||||
<span>음성통화 무제한</span>
|
||||
</div>
|
||||
<div class="benefit-item">
|
||||
<span class="benefit-icon">✓</span>
|
||||
<span>문자 무제한</span>
|
||||
</div>
|
||||
<div class="benefit-item">
|
||||
<span class="benefit-icon">✓</span>
|
||||
<span>해외 로밍 무료</span>
|
||||
</div>
|
||||
<div class="benefit-item">
|
||||
<span class="benefit-icon">✓</span>
|
||||
<span>OTT 서비스 3개</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="price-comparison">
|
||||
<span>현재 상품 대비</span>
|
||||
<span class="price-change price-up">월 20,000원 추가</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Fixed Action Buttons -->
|
||||
<div class="fixed-actions">
|
||||
<button class="btn btn-primary" id="nextBtn" disabled onclick="goToRequest()">
|
||||
선택한 상품으로 변경
|
||||
</button>
|
||||
<button class="btn btn-secondary" onclick="goBack()">
|
||||
취소
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let selectedProductId = null;
|
||||
|
||||
// Select product function
|
||||
function selectProduct(cardElement) {
|
||||
// Remove selection from all cards
|
||||
document.querySelectorAll('.product-card').forEach(card => {
|
||||
card.classList.remove('selected');
|
||||
});
|
||||
|
||||
// Add selection to clicked card
|
||||
cardElement.classList.add('selected');
|
||||
|
||||
// Store selected product ID
|
||||
selectedProductId = cardElement.dataset.productId;
|
||||
|
||||
// Enable next button
|
||||
const nextBtn = document.getElementById('nextBtn');
|
||||
nextBtn.disabled = false;
|
||||
|
||||
// Update button text based on selection
|
||||
const productName = cardElement.querySelector('.product-name').textContent;
|
||||
nextBtn.textContent = `${productName}으로 변경`;
|
||||
}
|
||||
|
||||
// Navigation functions
|
||||
function goBack() {
|
||||
window.history.back();
|
||||
}
|
||||
|
||||
function goToRequest() {
|
||||
if (!selectedProductId) {
|
||||
showError('변경할 상품을 선택해주세요.');
|
||||
return;
|
||||
}
|
||||
|
||||
const nextBtn = document.getElementById('nextBtn');
|
||||
|
||||
// Show loading state
|
||||
nextBtn.classList.add('loading');
|
||||
nextBtn.disabled = true;
|
||||
|
||||
// Store selected product in sessionStorage
|
||||
const selectedCard = document.querySelector(`[data-product-id="${selectedProductId}"]`);
|
||||
const productData = {
|
||||
id: selectedProductId,
|
||||
name: selectedCard.querySelector('.product-name').textContent,
|
||||
price: selectedCard.querySelector('.product-price').textContent,
|
||||
benefits: Array.from(selectedCard.querySelectorAll('.benefit-item span:last-child')).map(el => el.textContent)
|
||||
};
|
||||
sessionStorage.setItem('selectedProduct', JSON.stringify(productData));
|
||||
|
||||
// Simulate loading
|
||||
setTimeout(() => {
|
||||
window.location.href = '07-상품변경요청.html';
|
||||
}, 800);
|
||||
}
|
||||
|
||||
// Show error message
|
||||
function showError(message) {
|
||||
const errorAlert = document.getElementById('errorAlert');
|
||||
const errorMessage = document.getElementById('errorMessage');
|
||||
errorMessage.textContent = message;
|
||||
errorAlert.classList.add('show');
|
||||
|
||||
// Hide after 5 seconds
|
||||
setTimeout(() => {
|
||||
hideError();
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
// Hide error message
|
||||
function hideError() {
|
||||
const errorAlert = document.getElementById('errorAlert');
|
||||
errorAlert.classList.remove('show');
|
||||
}
|
||||
|
||||
// Load products on page load
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Load current product from localStorage
|
||||
loadCurrentProduct();
|
||||
// Simulate loading products
|
||||
loadProducts();
|
||||
|
||||
// Handle keyboard navigation
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape') {
|
||||
goBack();
|
||||
}
|
||||
|
||||
if (e.key === 'Enter' && e.target.classList.contains('product-card')) {
|
||||
selectProduct(e.target);
|
||||
}
|
||||
|
||||
// Arrow key navigation for product cards
|
||||
if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
|
||||
const cards = Array.from(document.querySelectorAll('.product-card'));
|
||||
const currentIndex = cards.findIndex(card => card === document.activeElement);
|
||||
|
||||
if (e.key === 'ArrowDown' && currentIndex < cards.length - 1) {
|
||||
cards[currentIndex + 1].focus();
|
||||
e.preventDefault();
|
||||
} else if (e.key === 'ArrowUp' && currentIndex > 0) {
|
||||
cards[currentIndex - 1].focus();
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Make product cards focusable for keyboard navigation
|
||||
document.querySelectorAll('.product-card').forEach(card => {
|
||||
card.setAttribute('tabindex', '0');
|
||||
card.setAttribute('role', 'radio');
|
||||
card.setAttribute('aria-checked', 'false');
|
||||
});
|
||||
});
|
||||
|
||||
// Load current product from localStorage
|
||||
function loadCurrentProduct() {
|
||||
try {
|
||||
const currentProduct = localStorage.getItem('currentProduct');
|
||||
if (currentProduct) {
|
||||
const product = JSON.parse(currentProduct);
|
||||
const currentNameElement = document.querySelector('.current-name');
|
||||
if (currentNameElement && product.name) {
|
||||
currentNameElement.textContent = product.name;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('localStorage에서 상품 정보를 불러오는 중 오류 발생:', error);
|
||||
// 기본값 유지
|
||||
}
|
||||
}
|
||||
|
||||
function loadProducts() {
|
||||
const loadingSkeletons = document.getElementById('loadingSkeletons');
|
||||
const productList = document.getElementById('productList');
|
||||
|
||||
// Show loading
|
||||
loadingSkeletons.style.display = 'block';
|
||||
productList.style.display = 'none';
|
||||
|
||||
// Simulate API call
|
||||
setTimeout(() => {
|
||||
// Hide loading, show products
|
||||
loadingSkeletons.style.display = 'none';
|
||||
productList.style.display = 'block';
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
// Update aria-checked when product is selected
|
||||
function selectProduct(cardElement) {
|
||||
// Remove selection from all cards
|
||||
document.querySelectorAll('.product-card').forEach(card => {
|
||||
card.classList.remove('selected');
|
||||
card.setAttribute('aria-checked', 'false');
|
||||
});
|
||||
|
||||
// Add selection to clicked card
|
||||
cardElement.classList.add('selected');
|
||||
cardElement.setAttribute('aria-checked', 'true');
|
||||
|
||||
// Store selected product ID
|
||||
selectedProductId = cardElement.dataset.productId;
|
||||
|
||||
// Store selected product info in sessionStorage
|
||||
const productName = cardElement.querySelector('.product-name').textContent;
|
||||
const productPrice = cardElement.querySelector('.product-price').textContent;
|
||||
sessionStorage.setItem('selectedProductName', productName);
|
||||
sessionStorage.setItem('selectedProductPrice', productPrice);
|
||||
|
||||
// Enable next button
|
||||
const nextBtn = document.getElementById('nextBtn');
|
||||
nextBtn.disabled = false;
|
||||
|
||||
// Update button text based on selection
|
||||
nextBtn.textContent = `${productName}으로 변경`;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
774
design-backup/uiux/prototype/07-상품변경요청.html
Normal file
774
design-backup/uiux/prototype/07-상품변경요청.html
Normal file
@ -0,0 +1,774 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>상품 변경 요청 - 통신요금 관리 서비스</title>
|
||||
<style>
|
||||
/* CSS Variables - Style Guide */
|
||||
:root {
|
||||
/* Primary Colors */
|
||||
--primary-50: #EBF8FF;
|
||||
--primary-100: #BEE3F8;
|
||||
--primary-200: #90CDF4;
|
||||
--primary-300: #63B3ED;
|
||||
--primary-400: #4299E1;
|
||||
--primary-500: #3182CE;
|
||||
--primary-600: #2B77CB;
|
||||
--primary-700: #2C5282;
|
||||
--primary-800: #2A4365;
|
||||
--primary-900: #1A365D;
|
||||
|
||||
/* Gray Colors */
|
||||
--gray-50: #F9FAFB;
|
||||
--gray-100: #F3F4F6;
|
||||
--gray-200: #E5E7EB;
|
||||
--gray-300: #D1D5DB;
|
||||
--gray-400: #9CA3AF;
|
||||
--gray-500: #6B7280;
|
||||
--gray-600: #4B5563;
|
||||
--gray-700: #374151;
|
||||
--gray-800: #1F2937;
|
||||
--gray-900: #111827;
|
||||
|
||||
/* Status Colors */
|
||||
--success-500: #38A169;
|
||||
--error-500: #E53E3E;
|
||||
--warning-500: #ED8936;
|
||||
|
||||
/* Spacing */
|
||||
--space-1: 0.25rem;
|
||||
--space-2: 0.5rem;
|
||||
--space-3: 0.75rem;
|
||||
--space-4: 1rem;
|
||||
--space-5: 1.25rem;
|
||||
--space-6: 1.5rem;
|
||||
--space-8: 2rem;
|
||||
--space-10: 2.5rem;
|
||||
--space-12: 3rem;
|
||||
|
||||
/* Typography */
|
||||
--text-xs: 0.75rem;
|
||||
--text-sm: 0.875rem;
|
||||
--text-base: 1rem;
|
||||
--text-lg: 1.125rem;
|
||||
--text-xl: 1.25rem;
|
||||
--text-2xl: 1.5rem;
|
||||
--text-3xl: 1.875rem;
|
||||
--text-4xl: 2.25rem;
|
||||
|
||||
--font-normal: 400;
|
||||
--font-medium: 500;
|
||||
--font-semibold: 600;
|
||||
--font-bold: 700;
|
||||
}
|
||||
|
||||
/* Reset & Base */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Noto Sans KR', -apple-system, BlinkMacSystemFont, 'Apple SD Gothic Neo', 'Malgun Gothic', sans-serif;
|
||||
font-size: var(--text-base);
|
||||
line-height: 1.5;
|
||||
color: var(--gray-700);
|
||||
background-color: var(--gray-50);
|
||||
min-height: 100vh;
|
||||
padding-bottom: 120px; /* Space for fixed buttons */
|
||||
}
|
||||
|
||||
/* Container */
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
margin: 0 auto;
|
||||
padding: var(--space-4);
|
||||
background-color: white;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
max-width: 480px;
|
||||
padding: var(--space-6);
|
||||
box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background: white;
|
||||
z-index: 10;
|
||||
padding: var(--space-4) 0;
|
||||
border-bottom: 1px solid var(--gray-200);
|
||||
margin-bottom: var(--space-6);
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: none;
|
||||
background: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-size: var(--text-xl);
|
||||
color: var(--gray-600);
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.back-btn:hover {
|
||||
background-color: var(--gray-100);
|
||||
color: var(--gray-800);
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: var(--text-xl);
|
||||
font-weight: var(--font-bold);
|
||||
color: var(--gray-900);
|
||||
}
|
||||
|
||||
/* Card */
|
||||
.card {
|
||||
background-color: white;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
||||
padding: var(--space-6);
|
||||
margin-bottom: var(--space-6);
|
||||
border: 1px solid var(--gray-200);
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: var(--text-lg);
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--gray-900);
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
/* Change Comparison */
|
||||
.change-comparison {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-4);
|
||||
margin-bottom: var(--space-6);
|
||||
}
|
||||
|
||||
.product-summary {
|
||||
flex: 1;
|
||||
padding: var(--space-4);
|
||||
border-radius: 12px;
|
||||
border: 2px solid var(--gray-200);
|
||||
}
|
||||
|
||||
.product-summary.current {
|
||||
background: var(--gray-50);
|
||||
}
|
||||
|
||||
.product-summary.new {
|
||||
background: linear-gradient(135deg, var(--primary-50) 0%, var(--primary-100) 100%);
|
||||
border-color: var(--primary-200);
|
||||
}
|
||||
|
||||
.product-label {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--gray-500);
|
||||
margin-bottom: var(--space-1);
|
||||
font-weight: var(--font-medium);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.product-name {
|
||||
font-size: var(--text-base);
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--gray-900);
|
||||
margin-bottom: var(--space-1);
|
||||
}
|
||||
|
||||
.product-price {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--primary-600);
|
||||
font-weight: var(--font-medium);
|
||||
}
|
||||
|
||||
.change-arrow {
|
||||
font-size: var(--text-2xl);
|
||||
color: var(--primary-500);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Notice */
|
||||
.notice {
|
||||
background: #FFF7ED;
|
||||
border: 1px solid #FED7AA;
|
||||
border-radius: 12px;
|
||||
padding: var(--space-4);
|
||||
margin-bottom: var(--space-6);
|
||||
}
|
||||
|
||||
.notice-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
font-size: var(--text-sm);
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--warning-500);
|
||||
margin-bottom: var(--space-3);
|
||||
}
|
||||
|
||||
.notice-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.notice-item {
|
||||
font-size: var(--text-sm);
|
||||
color: #92400E;
|
||||
line-height: 1.4;
|
||||
margin-bottom: var(--space-2);
|
||||
padding-left: var(--space-4);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.notice-item::before {
|
||||
content: '•';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
color: var(--warning-500);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.notice-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* Progress */
|
||||
.progress-section {
|
||||
margin-bottom: var(--space-8);
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
background: var(--gray-200);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(135deg, var(--primary-500) 0%, var(--primary-600) 100%);
|
||||
border-radius: 4px;
|
||||
transition: width 0.3s ease-in-out;
|
||||
width: 0%;
|
||||
}
|
||||
|
||||
.progress-text {
|
||||
text-align: center;
|
||||
font-size: var(--text-sm);
|
||||
color: var(--gray-600);
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
|
||||
.progress-status {
|
||||
text-align: center;
|
||||
font-size: var(--text-base);
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--primary-600);
|
||||
}
|
||||
|
||||
.progress-steps {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: var(--space-4);
|
||||
}
|
||||
|
||||
.progress-step {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
font-size: var(--text-xs);
|
||||
color: var(--gray-400);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.progress-step.active {
|
||||
color: var(--primary-600);
|
||||
font-weight: var(--font-semibold);
|
||||
}
|
||||
|
||||
.progress-step.completed {
|
||||
color: var(--success-500);
|
||||
font-weight: var(--font-semibold);
|
||||
}
|
||||
|
||||
/* Fixed Action Buttons */
|
||||
.fixed-actions {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
background: white;
|
||||
padding: var(--space-4);
|
||||
border-top: 1px solid var(--gray-200);
|
||||
box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.fixed-actions {
|
||||
max-width: 480px;
|
||||
padding: var(--space-6);
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--space-4) var(--space-6);
|
||||
border-radius: 12px;
|
||||
font-size: var(--text-base);
|
||||
font-weight: var(--font-semibold);
|
||||
line-height: 1.5;
|
||||
transition: all 0.2s ease-in-out;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
min-height: 52px;
|
||||
text-decoration: none;
|
||||
width: 100%;
|
||||
margin-bottom: var(--space-3);
|
||||
}
|
||||
|
||||
.btn:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: #3182CE !important; /* Fallback color with important */
|
||||
background: linear-gradient(135deg, #3182CE 0%, #2B77CB 100%) !important;
|
||||
color: white !important;
|
||||
box-shadow: 0 2px 8px rgba(49, 130, 206, 0.2);
|
||||
}
|
||||
|
||||
.btn-primary:hover:not(:disabled) {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(49, 130, 206, 0.3);
|
||||
}
|
||||
|
||||
.btn-primary:disabled {
|
||||
background: var(--gray-300);
|
||||
color: var(--gray-500);
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: white;
|
||||
color: var(--gray-600);
|
||||
border: 2px solid var(--gray-200);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: var(--gray-50);
|
||||
border-color: var(--gray-300);
|
||||
color: var(--gray-700);
|
||||
}
|
||||
|
||||
.btn-outline {
|
||||
background: transparent;
|
||||
color: var(--primary-600);
|
||||
border: 2px solid var(--primary-200);
|
||||
}
|
||||
|
||||
.btn-outline:hover {
|
||||
background: var(--primary-50);
|
||||
border-color: var(--primary-300);
|
||||
}
|
||||
|
||||
/* Loading */
|
||||
.loading {
|
||||
position: relative;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.loading::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 2px solid rgba(255, 255, 255, 0.3);
|
||||
border-top: 2px solid white;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: translate(-50%, -50%) rotate(0deg); }
|
||||
100% { transform: translate(-50%, -50%) rotate(360deg); }
|
||||
}
|
||||
|
||||
/* Alert */
|
||||
.alert {
|
||||
padding: var(--space-4);
|
||||
border-radius: 12px;
|
||||
font-size: var(--text-sm);
|
||||
margin-bottom: var(--space-4);
|
||||
display: none;
|
||||
}
|
||||
|
||||
.alert.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.alert-error {
|
||||
background-color: #FEF2F2;
|
||||
border: 1px solid #FECACA;
|
||||
color: #991B1B;
|
||||
}
|
||||
|
||||
.alert-success {
|
||||
background-color: #F0FDF4;
|
||||
border: 1px solid #BBF7D0;
|
||||
color: #166534;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<!-- Header -->
|
||||
<header class="header">
|
||||
<div class="header-content">
|
||||
<button class="back-btn" onclick="goBack()" aria-label="뒤로가기">
|
||||
←
|
||||
</button>
|
||||
<h1 class="page-title">상품 변경 요청</h1>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Alerts -->
|
||||
<div id="errorAlert" class="alert alert-error">
|
||||
<span id="errorMessage"></span>
|
||||
</div>
|
||||
|
||||
<div id="successAlert" class="alert alert-success">
|
||||
<span id="successMessage"></span>
|
||||
</div>
|
||||
|
||||
<!-- Change Confirmation -->
|
||||
<div class="card">
|
||||
<h2 class="card-title">변경 내용 확인</h2>
|
||||
<div class="change-comparison">
|
||||
<div class="product-summary current">
|
||||
<div class="product-label">현재 상품</div>
|
||||
<div class="product-name">5G 프리미엄 플랜</div>
|
||||
<div class="product-price">월 69,000원</div>
|
||||
</div>
|
||||
<div class="change-arrow">→</div>
|
||||
<div class="product-summary new">
|
||||
<div class="product-label">변경할 상품</div>
|
||||
<div class="product-name" id="newProductName">5G 스탠다드 플랜</div>
|
||||
<div class="product-price" id="newProductPrice">월 59,000원</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Important Notice -->
|
||||
<div class="notice">
|
||||
<div class="notice-title">
|
||||
<span>⚠️</span>
|
||||
<span>중요 안내사항</span>
|
||||
</div>
|
||||
<ul class="notice-list">
|
||||
<li class="notice-item">상품 변경은 다음 월 1일부터 적용됩니다</li>
|
||||
<li class="notice-item">현재 약정 기간이 남아있는 경우 위약금이 발생할 수 있습니다</li>
|
||||
<li class="notice-item">기존 부가서비스는 자동으로 해지되며, 필요시 재신청해야 합니다</li>
|
||||
<li class="notice-item">변경 후 14일 이내에 취소 가능하나, 일부 제약이 있을 수 있습니다</li>
|
||||
<li class="notice-item">요금제 변경에 따른 데이터 이월은 불가능합니다</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Progress Section -->
|
||||
<div class="card progress-section">
|
||||
<h2 class="card-title">사전 검증 진행 상황</h2>
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" id="progressFill"></div>
|
||||
</div>
|
||||
<div class="progress-text" id="progressText">검증을 시작하세요</div>
|
||||
<div class="progress-status" id="progressStatus">검증 대기중</div>
|
||||
|
||||
<div class="progress-steps">
|
||||
<div class="progress-step" data-step="1">
|
||||
<div>약정 확인</div>
|
||||
</div>
|
||||
<div class="progress-step" data-step="2">
|
||||
<div>자격 검증</div>
|
||||
</div>
|
||||
<div class="progress-step" data-step="3">
|
||||
<div>요금 계산</div>
|
||||
</div>
|
||||
<div class="progress-step" data-step="4">
|
||||
<div>승인 완료</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Fixed Action Buttons -->
|
||||
<div class="fixed-actions">
|
||||
<button class="btn btn-primary" id="submitBtn" disabled onclick="startValidation()">
|
||||
사전 검증 시작
|
||||
</button>
|
||||
<button class="btn btn-secondary" onclick="goBack()">
|
||||
취소
|
||||
</button>
|
||||
<button class="btn btn-outline" onclick="goToPrevious()">
|
||||
이전 단계
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let validationStep = 0;
|
||||
let validationCompleted = false;
|
||||
|
||||
// Load selected product data from previous screen
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
loadSelectedProduct();
|
||||
enableSubmitButton();
|
||||
|
||||
// Handle keyboard navigation
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape') {
|
||||
goBack();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function loadSelectedProduct() {
|
||||
const selectedProduct = sessionStorage.getItem('selectedProduct');
|
||||
if (selectedProduct) {
|
||||
const product = JSON.parse(selectedProduct);
|
||||
document.getElementById('newProductName').textContent = product.name;
|
||||
document.getElementById('newProductPrice').textContent = product.price;
|
||||
}
|
||||
}
|
||||
|
||||
function enableSubmitButton() {
|
||||
const submitBtn = document.getElementById('submitBtn');
|
||||
submitBtn.disabled = false;
|
||||
}
|
||||
|
||||
function startValidation() {
|
||||
const submitBtn = document.getElementById('submitBtn');
|
||||
|
||||
if (validationCompleted) {
|
||||
// Already validated, proceed to result
|
||||
goToResult();
|
||||
return;
|
||||
}
|
||||
|
||||
// Disable button during validation
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.textContent = '검증 중...';
|
||||
|
||||
// Start validation process
|
||||
runValidationSteps();
|
||||
}
|
||||
|
||||
function runValidationSteps() {
|
||||
const steps = [
|
||||
{ name: '약정 확인', duration: 2000 },
|
||||
{ name: '자격 검증', duration: 1500 },
|
||||
{ name: '요금 계산', duration: 1800 },
|
||||
{ name: '승인 완료', duration: 1000 }
|
||||
];
|
||||
|
||||
let currentStep = 0;
|
||||
|
||||
function processNextStep() {
|
||||
if (currentStep >= steps.length) {
|
||||
onValidationComplete();
|
||||
return;
|
||||
}
|
||||
|
||||
const step = steps[currentStep];
|
||||
const stepIndex = currentStep + 1;
|
||||
|
||||
// Update progress
|
||||
updateProgress(stepIndex, steps.length, `${step.name} 진행 중...`);
|
||||
|
||||
// Mark current step as active
|
||||
document.querySelectorAll('.progress-step').forEach((el, index) => {
|
||||
if (index < stepIndex - 1) {
|
||||
el.classList.add('completed');
|
||||
el.classList.remove('active');
|
||||
} else if (index === stepIndex - 1) {
|
||||
el.classList.add('active');
|
||||
el.classList.remove('completed');
|
||||
} else {
|
||||
el.classList.remove('active', 'completed');
|
||||
}
|
||||
});
|
||||
|
||||
// Simulate step processing
|
||||
setTimeout(() => {
|
||||
currentStep++;
|
||||
processNextStep();
|
||||
}, step.duration);
|
||||
}
|
||||
|
||||
processNextStep();
|
||||
}
|
||||
|
||||
function updateProgress(current, total, statusText) {
|
||||
const progress = (current / total) * 100;
|
||||
const progressFill = document.getElementById('progressFill');
|
||||
const progressText = document.getElementById('progressText');
|
||||
const progressStatus = document.getElementById('progressStatus');
|
||||
|
||||
progressFill.style.width = `${progress}%`;
|
||||
progressText.textContent = `${current}/${total} 단계`;
|
||||
progressStatus.textContent = statusText;
|
||||
}
|
||||
|
||||
function onValidationComplete() {
|
||||
validationCompleted = true;
|
||||
|
||||
// Update UI
|
||||
const submitBtn = document.getElementById('submitBtn');
|
||||
const progressStatus = document.getElementById('progressStatus');
|
||||
|
||||
updateProgress(4, 4, '모든 검증이 완료되었습니다');
|
||||
|
||||
// Mark all steps as completed
|
||||
document.querySelectorAll('.progress-step').forEach(el => {
|
||||
el.classList.add('completed');
|
||||
el.classList.remove('active');
|
||||
});
|
||||
|
||||
// Show success message
|
||||
showSuccess('사전 검증이 성공적으로 완료되었습니다.');
|
||||
|
||||
// Enable submit button
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.textContent = '변경 신청하기';
|
||||
|
||||
// Add some celebration effect
|
||||
setTimeout(() => {
|
||||
submitBtn.style.background = 'linear-gradient(135deg, var(--success-500) 0%, var(--success-600) 100%)';
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function submitRequest() {
|
||||
const progressStatus = document.getElementById('progressStatus');
|
||||
const submitBtn = document.getElementById('submitBtn');
|
||||
|
||||
// Update status text
|
||||
progressStatus.textContent = '상품 변경 요청을 처리하고 있습니다...';
|
||||
|
||||
// Show loading on button
|
||||
submitBtn.classList.add('loading');
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.textContent = '처리 중...';
|
||||
|
||||
// Start actual submission process
|
||||
setTimeout(() => {
|
||||
goToResult();
|
||||
}, 800);
|
||||
}
|
||||
|
||||
function goToResult() {
|
||||
const submitBtn = document.getElementById('submitBtn');
|
||||
|
||||
// Show loading state
|
||||
submitBtn.classList.add('loading');
|
||||
submitBtn.disabled = true;
|
||||
|
||||
// Store request data
|
||||
let currentProductName = '5G 프리미엄 플랜';
|
||||
try {
|
||||
const currentProduct = localStorage.getItem('currentProduct');
|
||||
if (currentProduct) {
|
||||
const product = JSON.parse(currentProduct);
|
||||
currentProductName = product.name;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('localStorage에서 상품 정보를 불러오는 중 오류 발생:', error);
|
||||
}
|
||||
|
||||
const requestData = {
|
||||
currentProduct: currentProductName,
|
||||
newProduct: sessionStorage.getItem('selectedProductName') || '5G 스탠다드 플랜',
|
||||
newPrice: sessionStorage.getItem('selectedProductPrice') || '월 59,000원',
|
||||
requestTime: new Date().toISOString(),
|
||||
status: 'success'
|
||||
};
|
||||
sessionStorage.setItem('changeRequest', JSON.stringify(requestData));
|
||||
|
||||
// Simulate final processing
|
||||
setTimeout(() => {
|
||||
window.location.href = '08-처리결과화면.html';
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
// Navigation functions
|
||||
function goBack() {
|
||||
window.history.back();
|
||||
}
|
||||
|
||||
function goToPrevious() {
|
||||
window.location.href = '06-상품변경화면.html';
|
||||
}
|
||||
|
||||
// Alert functions
|
||||
function showError(message) {
|
||||
const errorAlert = document.getElementById('errorAlert');
|
||||
const errorMessage = document.getElementById('errorMessage');
|
||||
errorMessage.textContent = message;
|
||||
errorAlert.classList.add('show');
|
||||
|
||||
setTimeout(() => {
|
||||
hideError();
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
function hideError() {
|
||||
const errorAlert = document.getElementById('errorAlert');
|
||||
errorAlert.classList.remove('show');
|
||||
}
|
||||
|
||||
function showSuccess(message) {
|
||||
const successAlert = document.getElementById('successAlert');
|
||||
const successMessage = document.getElementById('successMessage');
|
||||
successMessage.textContent = message;
|
||||
successAlert.classList.add('show');
|
||||
|
||||
setTimeout(() => {
|
||||
hideSuccess();
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
function hideSuccess() {
|
||||
const successAlert = document.getElementById('successAlert');
|
||||
successAlert.classList.remove('show');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
739
design-backup/uiux/prototype/08-처리결과화면.html
Normal file
739
design-backup/uiux/prototype/08-처리결과화면.html
Normal file
@ -0,0 +1,739 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>처리 결과 - 통신요금 관리 서비스</title>
|
||||
<style>
|
||||
/* CSS Variables - Style Guide */
|
||||
:root {
|
||||
/* Primary Colors */
|
||||
--primary-50: #EBF8FF;
|
||||
--primary-100: #BEE3F8;
|
||||
--primary-200: #90CDF4;
|
||||
--primary-300: #63B3ED;
|
||||
--primary-400: #4299E1;
|
||||
--primary-500: #3182CE;
|
||||
--primary-600: #2B77CB;
|
||||
--primary-700: #2C5282;
|
||||
--primary-800: #2A4365;
|
||||
--primary-900: #1A365D;
|
||||
|
||||
/* Gray Colors */
|
||||
--gray-50: #F9FAFB;
|
||||
--gray-100: #F3F4F6;
|
||||
--gray-200: #E5E7EB;
|
||||
--gray-300: #D1D5DB;
|
||||
--gray-400: #9CA3AF;
|
||||
--gray-500: #6B7280;
|
||||
--gray-600: #4B5563;
|
||||
--gray-700: #374151;
|
||||
--gray-800: #1F2937;
|
||||
--gray-900: #111827;
|
||||
|
||||
/* Status Colors */
|
||||
--success-50: #F0FDF4;
|
||||
--success-100: #DCFCE7;
|
||||
--success-500: #38A169;
|
||||
--success-600: #2F855A;
|
||||
--error-50: #FEF2F2;
|
||||
--error-100: #FEE2E2;
|
||||
--error-500: #E53E3E;
|
||||
--error-600: #DC2626;
|
||||
--warning-500: #ED8936;
|
||||
|
||||
/* Spacing */
|
||||
--space-1: 0.25rem;
|
||||
--space-2: 0.5rem;
|
||||
--space-3: 0.75rem;
|
||||
--space-4: 1rem;
|
||||
--space-5: 1.25rem;
|
||||
--space-6: 1.5rem;
|
||||
--space-8: 2rem;
|
||||
--space-10: 2.5rem;
|
||||
--space-12: 3rem;
|
||||
|
||||
/* Typography */
|
||||
--text-xs: 0.75rem;
|
||||
--text-sm: 0.875rem;
|
||||
--text-base: 1rem;
|
||||
--text-lg: 1.125rem;
|
||||
--text-xl: 1.25rem;
|
||||
--text-2xl: 1.5rem;
|
||||
--text-3xl: 1.875rem;
|
||||
--text-4xl: 2.25rem;
|
||||
|
||||
--font-normal: 400;
|
||||
--font-medium: 500;
|
||||
--font-semibold: 600;
|
||||
--font-bold: 700;
|
||||
}
|
||||
|
||||
/* Reset & Base */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Noto Sans KR', -apple-system, BlinkMacSystemFont, 'Apple SD Gothic Neo', 'Malgun Gothic', sans-serif;
|
||||
font-size: var(--text-base);
|
||||
line-height: 1.5;
|
||||
color: var(--gray-700);
|
||||
background-color: var(--gray-50);
|
||||
min-height: 100vh;
|
||||
padding-bottom: 100px; /* Space for fixed buttons */
|
||||
}
|
||||
|
||||
/* Container */
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
margin: 0 auto;
|
||||
padding: var(--space-4);
|
||||
background-color: white;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
max-width: 480px;
|
||||
padding: var(--space-6);
|
||||
box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.header {
|
||||
text-align: center;
|
||||
padding: var(--space-6) 0;
|
||||
border-bottom: 1px solid var(--gray-200);
|
||||
margin-bottom: var(--space-8);
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: var(--text-2xl);
|
||||
font-weight: var(--font-bold);
|
||||
color: var(--gray-900);
|
||||
}
|
||||
|
||||
/* Result Status */
|
||||
.result-status {
|
||||
text-align: center;
|
||||
margin-bottom: var(--space-8);
|
||||
padding: var(--space-8);
|
||||
}
|
||||
|
||||
.result-icon {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
margin: 0 auto var(--space-6);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: var(--text-4xl);
|
||||
animation: scaleIn 0.6s ease-out;
|
||||
}
|
||||
|
||||
.result-status.success .result-icon {
|
||||
background: linear-gradient(135deg, var(--success-500) 0%, var(--success-600) 100%);
|
||||
color: white;
|
||||
box-shadow: 0 8px 25px rgba(56, 161, 105, 0.3);
|
||||
}
|
||||
|
||||
.result-status.error .result-icon {
|
||||
background: linear-gradient(135deg, var(--error-500) 0%, var(--error-600) 100%);
|
||||
color: white;
|
||||
box-shadow: 0 8px 25px rgba(229, 62, 62, 0.3);
|
||||
}
|
||||
|
||||
@keyframes scaleIn {
|
||||
0% {
|
||||
transform: scale(0);
|
||||
opacity: 0;
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.result-title {
|
||||
font-size: var(--text-2xl);
|
||||
font-weight: var(--font-bold);
|
||||
margin-bottom: var(--space-3);
|
||||
}
|
||||
|
||||
.result-status.success .result-title {
|
||||
color: var(--success-600);
|
||||
}
|
||||
|
||||
.result-status.error .result-title {
|
||||
color: var(--error-600);
|
||||
}
|
||||
|
||||
.result-description {
|
||||
font-size: var(--text-base);
|
||||
color: var(--gray-600);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* Card */
|
||||
.card {
|
||||
background-color: white;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
||||
padding: var(--space-6);
|
||||
margin-bottom: var(--space-6);
|
||||
border: 1px solid var(--gray-200);
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: var(--text-lg);
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--gray-900);
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
/* Success Details */
|
||||
.success-details {
|
||||
background: linear-gradient(135deg, var(--success-50) 0%, var(--success-100) 100%);
|
||||
border: 2px solid var(--success-100);
|
||||
}
|
||||
|
||||
.detail-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: var(--space-3) 0;
|
||||
border-bottom: 1px solid rgba(56, 161, 105, 0.1);
|
||||
}
|
||||
|
||||
.detail-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.detail-label {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--gray-600);
|
||||
font-weight: var(--font-medium);
|
||||
}
|
||||
|
||||
.detail-value {
|
||||
font-size: var(--text-base);
|
||||
font-weight: var(--font-semibold);
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.detail-value.product {
|
||||
color: var(--primary-600);
|
||||
}
|
||||
|
||||
.detail-value.date {
|
||||
color: var(--success-600);
|
||||
}
|
||||
|
||||
.detail-value.number {
|
||||
color: var(--gray-900);
|
||||
font-family: 'Courier New', monospace;
|
||||
}
|
||||
|
||||
/* Error Details */
|
||||
.error-details {
|
||||
background: linear-gradient(135deg, var(--error-50) 0%, var(--error-100) 100%);
|
||||
border: 2px solid var(--error-100);
|
||||
}
|
||||
|
||||
.error-reason {
|
||||
background: #FEF2F2;
|
||||
border: 1px solid #FECACA;
|
||||
border-radius: 12px;
|
||||
padding: var(--space-4);
|
||||
margin-bottom: var(--space-6);
|
||||
}
|
||||
|
||||
.error-title {
|
||||
font-size: var(--text-base);
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--error-600);
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
|
||||
.error-text {
|
||||
font-size: var(--text-sm);
|
||||
color: #991B1B;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.solution-steps {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.solution-step {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: var(--space-3);
|
||||
margin-bottom: var(--space-3);
|
||||
padding: var(--space-3);
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.step-number {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background: var(--primary-500);
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: var(--text-sm);
|
||||
font-weight: var(--font-semibold);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.step-text {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--gray-700);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* Contact Info */
|
||||
.contact-info {
|
||||
background: var(--gray-50);
|
||||
border: 1px solid var(--gray-200);
|
||||
border-radius: 12px;
|
||||
padding: var(--space-4);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.contact-title {
|
||||
font-size: var(--text-base);
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--gray-900);
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
|
||||
.contact-number {
|
||||
font-size: var(--text-xl);
|
||||
font-weight: var(--font-bold);
|
||||
color: var(--primary-600);
|
||||
margin-bottom: var(--space-1);
|
||||
}
|
||||
|
||||
.contact-hours {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--gray-500);
|
||||
}
|
||||
|
||||
/* Fixed Action Buttons */
|
||||
.fixed-actions {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
background: white;
|
||||
padding: var(--space-4);
|
||||
border-top: 1px solid var(--gray-200);
|
||||
box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.fixed-actions {
|
||||
max-width: 480px;
|
||||
padding: var(--space-6);
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--space-4) var(--space-6);
|
||||
border-radius: 12px;
|
||||
font-size: var(--text-base);
|
||||
font-weight: var(--font-semibold);
|
||||
line-height: 1.5;
|
||||
transition: all 0.2s ease-in-out;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
min-height: 52px;
|
||||
text-decoration: none;
|
||||
width: 100%;
|
||||
margin-bottom: var(--space-3);
|
||||
}
|
||||
|
||||
.btn:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, var(--primary-500) 0%, var(--primary-600) 100%);
|
||||
color: white;
|
||||
box-shadow: 0 2px 8px rgba(49, 130, 206, 0.2);
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(49, 130, 206, 0.3);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: white;
|
||||
color: var(--gray-600);
|
||||
border: 2px solid var(--gray-200);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: var(--gray-50);
|
||||
border-color: var(--gray-300);
|
||||
color: var(--gray-700);
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background: linear-gradient(135deg, var(--success-500) 0%, var(--success-600) 100%);
|
||||
color: white;
|
||||
box-shadow: 0 2px 8px rgba(56, 161, 105, 0.2);
|
||||
}
|
||||
|
||||
.btn-success:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(56, 161, 105, 0.3);
|
||||
}
|
||||
|
||||
.btn-error {
|
||||
background: linear-gradient(135deg, var(--error-500) 0%, var(--error-600) 100%);
|
||||
color: white;
|
||||
box-shadow: 0 2px 8px rgba(229, 62, 62, 0.2);
|
||||
}
|
||||
|
||||
.btn-error:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(229, 62, 62, 0.3);
|
||||
}
|
||||
|
||||
/* Loading */
|
||||
.loading {
|
||||
position: relative;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.loading::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 2px solid rgba(255, 255, 255, 0.3);
|
||||
border-top: 2px solid white;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: translate(-50%, -50%) rotate(0deg); }
|
||||
100% { transform: translate(-50%, -50%) rotate(360deg); }
|
||||
}
|
||||
|
||||
/* Hidden class for conditional display */
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<!-- Header -->
|
||||
<header class="header">
|
||||
<h1 class="page-title">처리 결과</h1>
|
||||
</header>
|
||||
|
||||
<!-- Success Result -->
|
||||
<div id="successResult" class="result-status success">
|
||||
<div class="result-icon">✓</div>
|
||||
<h2 class="result-title">상품 변경이 완료되었습니다</h2>
|
||||
<p class="result-description">
|
||||
선택하신 상품으로 변경 신청이 성공적으로 처리되었습니다.<br>
|
||||
변경된 상품은 다음 월 1일부터 적용됩니다.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Error Result (Hidden by default) -->
|
||||
<div id="errorResult" class="result-status error hidden">
|
||||
<div class="result-icon">✕</div>
|
||||
<h2 class="result-title">상품 변경에 실패했습니다</h2>
|
||||
<p class="result-description">
|
||||
죄송합니다. 상품 변경 처리 중 문제가 발생했습니다.<br>
|
||||
아래의 해결 방법을 확인해주세요.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Success Details -->
|
||||
<div id="successDetails" class="card success-details">
|
||||
<h3 class="card-title">변경 내용</h3>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">변경된 상품</span>
|
||||
<span class="detail-value product" id="changedProduct">5G 스탠다드 플랜</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">월 요금</span>
|
||||
<span class="detail-value product" id="changedPrice">월 59,000원</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">적용일</span>
|
||||
<span class="detail-value date">2025년 2월 1일</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">처리번호</span>
|
||||
<span class="detail-value number" id="processNumber">CHG-2025010512345</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">처리일시</span>
|
||||
<span class="detail-value" id="processTime">2025-01-05 14:23:45</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Error Details (Hidden by default) -->
|
||||
<div id="errorDetails" class="card error-details hidden">
|
||||
<h3 class="card-title">실패 사유</h3>
|
||||
<div class="error-reason">
|
||||
<div class="error-title">약정 위반으로 인한 변경 불가</div>
|
||||
<div class="error-text">
|
||||
현재 이용 중인 상품의 약정 기간이 남아있어 상품 변경이 불가능합니다.
|
||||
약정 해지 후 다시 시도하시거나 고객센터로 문의해 주세요.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4 class="card-title">해결 방법</h4>
|
||||
<ol class="solution-steps">
|
||||
<li class="solution-step">
|
||||
<span class="step-number">1</span>
|
||||
<span class="step-text">현재 약정 상태와 위약금을 확인해보세요</span>
|
||||
</li>
|
||||
<li class="solution-step">
|
||||
<span class="step-number">2</span>
|
||||
<span class="step-text">약정 기간 만료 후 다시 시도하거나 위약금을 납부하고 변경하세요</span>
|
||||
</li>
|
||||
<li class="solution-step">
|
||||
<span class="step-number">3</span>
|
||||
<span class="step-text">고객센터에 문의하여 다른 변경 방법을 안내받으세요</span>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<div class="contact-info">
|
||||
<div class="contact-title">고객센터</div>
|
||||
<div class="contact-number">1588-0000</div>
|
||||
<div class="contact-hours">평일 09:00~18:00 (토/일/공휴일 휴무)</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Fixed Action Buttons -->
|
||||
<div class="fixed-actions">
|
||||
<!-- Success Actions -->
|
||||
<div id="successActions">
|
||||
<button class="btn btn-primary" onclick="goToMain()">
|
||||
메인으로
|
||||
</button>
|
||||
<button class="btn btn-secondary" onclick="viewBill()">
|
||||
요금 조회
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Error Actions (Hidden by default) -->
|
||||
<div id="errorActions" class="hidden">
|
||||
<button class="btn btn-error" onclick="retryChange()">
|
||||
다시 시도
|
||||
</button>
|
||||
<button class="btn btn-secondary" onclick="contactSupport()">
|
||||
고객센터 연결
|
||||
</button>
|
||||
<button class="btn btn-secondary" onclick="goToMain()">
|
||||
메인으로
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Load request data and determine result
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
loadResultData();
|
||||
|
||||
// Handle keyboard navigation
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Enter' && e.target.tagName === 'BUTTON') {
|
||||
e.target.click();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function loadResultData() {
|
||||
const requestData = sessionStorage.getItem('changeRequest');
|
||||
|
||||
// Simulate random success/failure for demo
|
||||
// In real app, this would come from the API response
|
||||
const isSuccess = Math.random() > 0.2; // 80% success rate for demo
|
||||
|
||||
if (requestData) {
|
||||
const request = JSON.parse(requestData);
|
||||
|
||||
if (isSuccess) {
|
||||
showSuccessResult(request);
|
||||
} else {
|
||||
showErrorResult();
|
||||
}
|
||||
} else {
|
||||
// Default success display
|
||||
showSuccessResult({
|
||||
newProduct: '5G 스탠다드 플랜',
|
||||
newPrice: '월 59,000원'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function showSuccessResult(requestData) {
|
||||
// Show success elements
|
||||
document.getElementById('successResult').classList.remove('hidden');
|
||||
document.getElementById('successDetails').classList.remove('hidden');
|
||||
document.getElementById('successActions').classList.remove('hidden');
|
||||
|
||||
// Hide error elements
|
||||
document.getElementById('errorResult').classList.add('hidden');
|
||||
document.getElementById('errorDetails').classList.add('hidden');
|
||||
document.getElementById('errorActions').classList.add('hidden');
|
||||
|
||||
// Update success details
|
||||
if (requestData.newProduct) {
|
||||
document.getElementById('changedProduct').textContent = requestData.newProduct;
|
||||
}
|
||||
if (requestData.newPrice) {
|
||||
document.getElementById('changedPrice').textContent = requestData.newPrice;
|
||||
}
|
||||
|
||||
// Save successful product change to localStorage
|
||||
const newProduct = {
|
||||
name: requestData.newProduct || '5G 스탠다드 플랜',
|
||||
price: requestData.newPrice || '월 59,000원',
|
||||
benefits: getProductBenefits(requestData.newProduct),
|
||||
changeDate: new Date().toISOString()
|
||||
};
|
||||
|
||||
localStorage.setItem('currentProduct', JSON.stringify(newProduct));
|
||||
|
||||
// Generate process number and time
|
||||
const processNumber = `CHG-${new Date().getFullYear()}${String(new Date().getMonth() + 1).padStart(2, '0')}${String(new Date().getDate()).padStart(2, '0')}${String(Math.floor(Math.random() * 99999)).padStart(5, '0')}`;
|
||||
const processTime = new Date().toLocaleString('ko-KR', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
hour12: false
|
||||
}).replace(/\. /g, '-').replace('.', '');
|
||||
|
||||
document.getElementById('processNumber').textContent = processNumber;
|
||||
document.getElementById('processTime').textContent = processTime;
|
||||
}
|
||||
|
||||
// Get product benefits based on product name
|
||||
function getProductBenefits(productName) {
|
||||
const productBenefits = {
|
||||
'5G 스탠다드 플랜': [
|
||||
'5G 데이터 100GB',
|
||||
'음성통화 무제한',
|
||||
'문자 무제한',
|
||||
'영상통화 300분'
|
||||
],
|
||||
'5G 베이직 플랜': [
|
||||
'5G 데이터 50GB',
|
||||
'음성통화 무제한',
|
||||
'문자 무제한',
|
||||
'영상통화 300분'
|
||||
],
|
||||
'5G 프리미엄 플랜': [
|
||||
'5G 데이터 무제한',
|
||||
'음성통화 무제한',
|
||||
'문자 무제한',
|
||||
'해외 로밍 50% 할인'
|
||||
]
|
||||
};
|
||||
|
||||
return productBenefits[productName] || [
|
||||
'5G 데이터 무제한',
|
||||
'음성통화 무제한',
|
||||
'문자 무제한'
|
||||
];
|
||||
}
|
||||
|
||||
function showErrorResult() {
|
||||
// Show error elements
|
||||
document.getElementById('errorResult').classList.remove('hidden');
|
||||
document.getElementById('errorDetails').classList.remove('hidden');
|
||||
document.getElementById('errorActions').classList.remove('hidden');
|
||||
|
||||
// Hide success elements
|
||||
document.getElementById('successResult').classList.add('hidden');
|
||||
document.getElementById('successDetails').classList.add('hidden');
|
||||
document.getElementById('successActions').classList.add('hidden');
|
||||
}
|
||||
|
||||
// Navigation functions
|
||||
function goToMain() {
|
||||
const btn = event.target;
|
||||
btn.classList.add('loading');
|
||||
btn.disabled = true;
|
||||
|
||||
setTimeout(() => {
|
||||
window.location.href = '02-메인화면.html';
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function viewBill() {
|
||||
const btn = event.target;
|
||||
btn.classList.add('loading');
|
||||
btn.disabled = true;
|
||||
|
||||
setTimeout(() => {
|
||||
window.location.href = '03-요금조회메뉴.html';
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function retryChange() {
|
||||
const btn = event.target;
|
||||
btn.classList.add('loading');
|
||||
btn.disabled = true;
|
||||
|
||||
// Clear previous data
|
||||
sessionStorage.removeItem('changeRequest');
|
||||
sessionStorage.removeItem('selectedProduct');
|
||||
|
||||
setTimeout(() => {
|
||||
window.location.href = '05-상품변경메뉴.html';
|
||||
}, 800);
|
||||
}
|
||||
|
||||
function contactSupport() {
|
||||
// Simulate contacting support
|
||||
alert('고객센터 연결 기능입니다.\n\n전화번호: 1588-0000\n운영시간: 평일 09:00~18:00');
|
||||
}
|
||||
|
||||
// Auto-clear session data after 30 minutes to prevent stale data
|
||||
setTimeout(() => {
|
||||
sessionStorage.removeItem('changeRequest');
|
||||
sessionStorage.removeItem('selectedProduct');
|
||||
}, 30 * 60 * 1000); // 30 minutes
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
743
design-backup/uiux/style-guide.md
Normal file
743
design-backup/uiux/style-guide.md
Normal file
@ -0,0 +1,743 @@
|
||||
# 통신요금 관리 서비스 - 스타일 가이드
|
||||
|
||||
- [통신요금 관리 서비스 - 스타일 가이드](#통신요금-관리-서비스---스타일-가이드)
|
||||
- [브랜드 아이덴티티](#브랜드-아이덴티티)
|
||||
- [디자인 원칙](#디자인-원칙)
|
||||
- [컬러 시스템](#컬러-시스템)
|
||||
- [타이포그래피](#타이포그래피)
|
||||
- [간격 시스템](#간격-시스템)
|
||||
- [컴포넌트 스타일](#컴포넌트-스타일)
|
||||
- [반응형 브레이크포인트](#반응형-브레이크포인트)
|
||||
- [대상 서비스 특화 컴포넌트](#대상-서비스-특화-컴포넌트)
|
||||
- [인터랙션 패턴](#인터랙션-패턴)
|
||||
- [변경 이력](#변경-이력)
|
||||
|
||||
---
|
||||
|
||||
## 브랜드 아이덴티티
|
||||
|
||||
### 서비스 컨셉
|
||||
- **키워드**: 신뢰성, 편리함, 명확성
|
||||
- **브랜드 메시지**: "간편하고 안전한 통신요금 관리"
|
||||
- **타겟**: 일반 MVNO 고객 (20대~60대)
|
||||
|
||||
### 디자인 컨셉
|
||||
- **미니멀리즘**: 불필요한 요소 제거, 핵심 기능 집중
|
||||
- **명확성 우선**: 정보 전달의 명확성과 가독성
|
||||
- **안정감**: 금융 서비스의 신뢰성과 보안성 강조
|
||||
- **접근성**: 모든 사용자가 편리하게 이용할 수 있는 인터페이스
|
||||
|
||||
---
|
||||
|
||||
## 디자인 원칙
|
||||
|
||||
### 1. 명확성 (Clarity)
|
||||
- 모든 UI 요소는 그 목적이 명확해야 함
|
||||
- 전문용어 사용 최소화, 일반적인 표현 우선
|
||||
- 중요 정보는 시각적으로 강조
|
||||
|
||||
### 2. 일관성 (Consistency)
|
||||
- 동일한 요소는 동일한 스타일 적용
|
||||
- 예측 가능한 인터랙션 패턴
|
||||
- 통일된 색상과 타이포그래피 사용
|
||||
|
||||
### 3. 효율성 (Efficiency)
|
||||
- 최소한의 클릭으로 목표 달성
|
||||
- 불필요한 단계 제거
|
||||
- 빠른 로딩과 반응성 보장
|
||||
|
||||
### 4. 안전성 (Safety)
|
||||
- 중요한 액션에는 확인 단계 제공
|
||||
- 오류 방지와 명확한 피드백
|
||||
- 개인정보 보호 강조
|
||||
|
||||
### 5. 포용성 (Inclusivity)
|
||||
- 접근성 지침 준수
|
||||
- 다양한 디바이스와 환경 지원
|
||||
- 사용자 능력과 상황 고려
|
||||
|
||||
---
|
||||
|
||||
## 컬러 시스템
|
||||
|
||||
### Primary Colors
|
||||
```css
|
||||
/* 메인 브랜드 컬러 - 신뢰감을 주는 블루 */
|
||||
--primary-50: #EBF8FF;
|
||||
--primary-100: #BEE3F8;
|
||||
--primary-200: #90CDF4;
|
||||
--primary-300: #63B3ED;
|
||||
--primary-400: #4299E1;
|
||||
--primary-500: #3182CE; /* Main Brand Color */
|
||||
--primary-600: #2B77CB;
|
||||
--primary-700: #2C5282;
|
||||
--primary-800: #2A4365;
|
||||
--primary-900: #1A365D;
|
||||
```
|
||||
|
||||
### Secondary Colors
|
||||
```css
|
||||
/* 보조 컬러 - 포인트 및 상태 표시 */
|
||||
--secondary-50: #F7FAFC;
|
||||
--secondary-100: #EDF2F7;
|
||||
--secondary-200: #E2E8F0;
|
||||
--secondary-300: #CBD5E0;
|
||||
--secondary-400: #A0AEC0;
|
||||
--secondary-500: #718096;
|
||||
--secondary-600: #4A5568;
|
||||
--secondary-700: #2D3748;
|
||||
--secondary-800: #1A202C;
|
||||
--secondary-900: #171923;
|
||||
```
|
||||
|
||||
### Status Colors
|
||||
```css
|
||||
/* 성공 - 그린 */
|
||||
--success-50: #F0FFF4;
|
||||
--success-100: #C6F6D5;
|
||||
--success-500: #38A169;
|
||||
--success-600: #2F855A;
|
||||
|
||||
/* 경고 - 오렌지 */
|
||||
--warning-50: #FFFAF0;
|
||||
--warning-100: #FEEBC8;
|
||||
--warning-500: #ED8936;
|
||||
--warning-600: #DD6B20;
|
||||
|
||||
/* 오류 - 레드 */
|
||||
--error-50: #FED7D7;
|
||||
--error-100: #FED7D7;
|
||||
--error-500: #E53E3E;
|
||||
--error-600: #C53030;
|
||||
|
||||
/* 정보 - 블루 */
|
||||
--info-50: #EBF8FF;
|
||||
--info-100: #BEE3F8;
|
||||
--info-500: #3182CE;
|
||||
--info-600: #2B77CB;
|
||||
```
|
||||
|
||||
### Neutral Colors
|
||||
```css
|
||||
/* 텍스트 및 배경 */
|
||||
--gray-50: #F9FAFB; /* Background Light */
|
||||
--gray-100: #F3F4F6; /* Background */
|
||||
--gray-200: #E5E7EB; /* Border Light */
|
||||
--gray-300: #D1D5DB; /* Border */
|
||||
--gray-400: #9CA3AF; /* Text Muted */
|
||||
--gray-500: #6B7280; /* Text Secondary */
|
||||
--gray-600: #4B5563; /* Text Primary Light */
|
||||
--gray-700: #374151; /* Text Primary */
|
||||
--gray-800: #1F2937; /* Text Primary Dark */
|
||||
--gray-900: #111827; /* Text Emphasis */
|
||||
```
|
||||
|
||||
### 컬러 사용 가이드
|
||||
- **Primary**: 주요 액션 버튼, 링크, 브랜드 요소
|
||||
- **Secondary**: 보조 버튼, 아이콘, 경계선
|
||||
- **Success**: 성공 메시지, 완료 상태
|
||||
- **Warning**: 주의 메시지, 중요 알림
|
||||
- **Error**: 오류 메시지, 실패 상태
|
||||
- **Gray**: 텍스트, 배경, 구분선
|
||||
|
||||
---
|
||||
|
||||
## 타이포그래피
|
||||
|
||||
### 폰트 패밀리
|
||||
```css
|
||||
/* 기본 폰트 스택 */
|
||||
font-family:
|
||||
'Noto Sans KR', /* 한글 */
|
||||
'Roboto', /* 영문 */
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
'Apple SD Gothic Neo',
|
||||
'Malgun Gothic',
|
||||
sans-serif;
|
||||
```
|
||||
|
||||
### 폰트 크기 및 행간
|
||||
```css
|
||||
/* Heading */
|
||||
--text-4xl: 2.25rem; /* 36px - Page Title */
|
||||
--text-3xl: 1.875rem; /* 30px - Section Title */
|
||||
--text-2xl: 1.5rem; /* 24px - Card Title */
|
||||
--text-xl: 1.25rem; /* 20px - Sub Title */
|
||||
--text-lg: 1.125rem; /* 18px - Large Text */
|
||||
|
||||
/* Body */
|
||||
--text-base: 1rem; /* 16px - Body Text */
|
||||
--text-sm: 0.875rem; /* 14px - Small Text */
|
||||
--text-xs: 0.75rem; /* 12px - Caption */
|
||||
|
||||
/* Line Height */
|
||||
--leading-tight: 1.25; /* Heading */
|
||||
--leading-normal: 1.5; /* Body */
|
||||
--leading-relaxed: 1.625; /* Long Text */
|
||||
```
|
||||
|
||||
### 폰트 두께
|
||||
```css
|
||||
--font-light: 300; /* Light text */
|
||||
--font-normal: 400; /* Body text */
|
||||
--font-medium: 500; /* Emphasis */
|
||||
--font-semibold: 600; /* Sub heading */
|
||||
--font-bold: 700; /* Heading */
|
||||
```
|
||||
|
||||
### 타이포그래피 클래스
|
||||
```css
|
||||
/* Heading Styles */
|
||||
.heading-1 { font-size: 2.25rem; font-weight: 700; line-height: 1.25; }
|
||||
.heading-2 { font-size: 1.875rem; font-weight: 600; line-height: 1.25; }
|
||||
.heading-3 { font-size: 1.5rem; font-weight: 600; line-height: 1.25; }
|
||||
.heading-4 { font-size: 1.25rem; font-weight: 500; line-height: 1.25; }
|
||||
|
||||
/* Body Styles */
|
||||
.body-large { font-size: 1.125rem; font-weight: 400; line-height: 1.5; }
|
||||
.body-normal { font-size: 1rem; font-weight: 400; line-height: 1.5; }
|
||||
.body-small { font-size: 0.875rem; font-weight: 400; line-height: 1.5; }
|
||||
.caption { font-size: 0.75rem; font-weight: 400; line-height: 1.25; }
|
||||
|
||||
/* Emphasis */
|
||||
.text-emphasis { font-weight: 600; color: var(--gray-900); }
|
||||
.text-muted { color: var(--gray-500); }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 간격 시스템
|
||||
|
||||
### 기본 간격 단위 (8px 그리드 시스템)
|
||||
```css
|
||||
--space-0: 0;
|
||||
--space-1: 0.25rem; /* 4px */
|
||||
--space-2: 0.5rem; /* 8px */
|
||||
--space-3: 0.75rem; /* 12px */
|
||||
--space-4: 1rem; /* 16px */
|
||||
--space-5: 1.25rem; /* 20px */
|
||||
--space-6: 1.5rem; /* 24px */
|
||||
--space-8: 2rem; /* 32px */
|
||||
--space-10: 2.5rem; /* 40px */
|
||||
--space-12: 3rem; /* 48px */
|
||||
--space-16: 4rem; /* 64px */
|
||||
--space-20: 5rem; /* 80px */
|
||||
```
|
||||
|
||||
### 컴포넌트별 간격 가이드
|
||||
- **Component Padding**: 16px (space-4) - 24px (space-6)
|
||||
- **Content Margin**: 16px (space-4) - 32px (space-8)
|
||||
- **Section Gap**: 32px (space-8) - 48px (space-12)
|
||||
- **Page Padding**: 20px (space-5) - 40px (space-10)
|
||||
|
||||
### 레이아웃 간격
|
||||
```css
|
||||
/* Container */
|
||||
--container-padding-mobile: var(--space-4); /* 16px */
|
||||
--container-padding-tablet: var(--space-6); /* 24px */
|
||||
--container-padding-desktop: var(--space-8); /* 32px */
|
||||
|
||||
/* Grid Gap */
|
||||
--grid-gap-small: var(--space-4); /* 16px */
|
||||
--grid-gap-medium: var(--space-6); /* 24px */
|
||||
--grid-gap-large: var(--space-8); /* 32px */
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 컴포넌트 스타일
|
||||
|
||||
### 버튼 (Button)
|
||||
```css
|
||||
/* Base Button */
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--space-3) var(--space-6);
|
||||
border-radius: 8px;
|
||||
font-size: var(--text-base);
|
||||
font-weight: var(--font-medium);
|
||||
line-height: 1.5;
|
||||
transition: all 0.2s ease-in-out;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
min-height: 44px; /* 터치 접근성 */
|
||||
}
|
||||
|
||||
/* Primary Button */
|
||||
.btn-primary {
|
||||
background-color: var(--primary-500);
|
||||
color: white;
|
||||
}
|
||||
.btn-primary:hover {
|
||||
background-color: var(--primary-600);
|
||||
}
|
||||
.btn-primary:disabled {
|
||||
background-color: var(--gray-300);
|
||||
color: var(--gray-500);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Secondary Button */
|
||||
.btn-secondary {
|
||||
background-color: white;
|
||||
color: var(--gray-700);
|
||||
border: 1px solid var(--gray-300);
|
||||
}
|
||||
.btn-secondary:hover {
|
||||
background-color: var(--gray-50);
|
||||
border-color: var(--gray-400);
|
||||
}
|
||||
|
||||
/* Danger Button */
|
||||
.btn-danger {
|
||||
background-color: var(--error-500);
|
||||
color: white;
|
||||
}
|
||||
.btn-danger:hover {
|
||||
background-color: var(--error-600);
|
||||
}
|
||||
```
|
||||
|
||||
### 카드 (Card)
|
||||
```css
|
||||
.card {
|
||||
background-color: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
padding: var(--space-6);
|
||||
border: 1px solid var(--gray-200);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
margin-bottom: var(--space-4);
|
||||
padding-bottom: var(--space-4);
|
||||
border-bottom: 1px solid var(--gray-200);
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: var(--text-xl);
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--gray-900);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
color: var(--gray-700);
|
||||
}
|
||||
```
|
||||
|
||||
### 폼 요소 (Form)
|
||||
```css
|
||||
/* Input */
|
||||
.input {
|
||||
width: 100%;
|
||||
padding: var(--space-3) var(--space-4);
|
||||
border: 1px solid var(--gray-300);
|
||||
border-radius: 8px;
|
||||
font-size: var(--text-base);
|
||||
line-height: 1.5;
|
||||
transition: border-color 0.2s ease-in-out;
|
||||
min-height: 44px;
|
||||
}
|
||||
|
||||
.input:focus {
|
||||
outline: none;
|
||||
border-color: var(--primary-500);
|
||||
box-shadow: 0 0 0 3px rgba(49, 130, 206, 0.1);
|
||||
}
|
||||
|
||||
.input.error {
|
||||
border-color: var(--error-500);
|
||||
}
|
||||
|
||||
/* Label */
|
||||
.label {
|
||||
display: block;
|
||||
font-size: var(--text-sm);
|
||||
font-weight: var(--font-medium);
|
||||
color: var(--gray-700);
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
|
||||
/* Select */
|
||||
.select {
|
||||
appearance: none;
|
||||
background-image: url("data:image/svg+xml,..."); /* 드롭다운 아이콘 */
|
||||
background-repeat: no-repeat;
|
||||
background-position: right var(--space-3) center;
|
||||
background-size: 16px;
|
||||
}
|
||||
```
|
||||
|
||||
### 알림 메시지 (Alert)
|
||||
```css
|
||||
.alert {
|
||||
padding: var(--space-4);
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid;
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
.alert-success {
|
||||
background-color: var(--success-50);
|
||||
border-color: var(--success-500);
|
||||
color: var(--success-800);
|
||||
}
|
||||
|
||||
.alert-warning {
|
||||
background-color: var(--warning-50);
|
||||
border-color: var(--warning-500);
|
||||
color: var(--warning-800);
|
||||
}
|
||||
|
||||
.alert-error {
|
||||
background-color: var(--error-50);
|
||||
border-color: var(--error-500);
|
||||
color: var(--error-800);
|
||||
}
|
||||
|
||||
.alert-info {
|
||||
background-color: var(--info-50);
|
||||
border-color: var(--info-500);
|
||||
color: var(--info-800);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 반응형 브레이크포인트
|
||||
|
||||
### 브레이크포인트 정의
|
||||
```css
|
||||
/* Mobile First Approach */
|
||||
:root {
|
||||
--breakpoint-sm: 640px; /* Small devices */
|
||||
--breakpoint-md: 768px; /* Medium devices */
|
||||
--breakpoint-lg: 1024px; /* Large devices */
|
||||
--breakpoint-xl: 1280px; /* Extra large devices */
|
||||
}
|
||||
|
||||
/* Media Query Mixins */
|
||||
@media (min-width: 640px) { /* sm */ }
|
||||
@media (min-width: 768px) { /* md */ }
|
||||
@media (min-width: 1024px) { /* lg */ }
|
||||
@media (min-width: 1280px) { /* xl */ }
|
||||
```
|
||||
|
||||
### 반응형 컨테이너
|
||||
```css
|
||||
.container {
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
padding: 0 var(--space-4);
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.container {
|
||||
max-width: 640px;
|
||||
padding: 0 var(--space-6);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
max-width: 768px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.container {
|
||||
max-width: 1024px;
|
||||
padding: 0 var(--space-8);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 반응형 그리드
|
||||
```css
|
||||
.grid {
|
||||
display: grid;
|
||||
gap: var(--space-4);
|
||||
grid-template-columns: 1fr; /* Mobile: 1 column */
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.grid-md-2 {
|
||||
grid-template-columns: repeat(2, 1fr); /* Tablet: 2 columns */
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.grid-lg-3 {
|
||||
grid-template-columns: repeat(3, 1fr); /* Desktop: 3 columns */
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 대상 서비스 특화 컴포넌트
|
||||
|
||||
### 요금 정보 카드 (Bill Card)
|
||||
```css
|
||||
.bill-card {
|
||||
background: linear-gradient(135deg, var(--primary-500) 0%, var(--primary-600) 100%);
|
||||
color: white;
|
||||
padding: var(--space-6);
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 4px 20px rgba(49, 130, 206, 0.2);
|
||||
}
|
||||
|
||||
.bill-amount {
|
||||
font-size: var(--text-4xl);
|
||||
font-weight: var(--font-bold);
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
|
||||
.bill-period {
|
||||
font-size: var(--text-sm);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.bill-details {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 8px;
|
||||
padding: var(--space-4);
|
||||
margin-top: var(--space-4);
|
||||
}
|
||||
```
|
||||
|
||||
### 상품 비교 카드 (Product Card)
|
||||
```css
|
||||
.product-card {
|
||||
border: 2px solid var(--gray-200);
|
||||
border-radius: 12px;
|
||||
padding: var(--space-6);
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.product-card.selected {
|
||||
border-color: var(--primary-500);
|
||||
box-shadow: 0 0 0 3px rgba(49, 130, 206, 0.1);
|
||||
}
|
||||
|
||||
.product-card.current {
|
||||
background-color: var(--success-50);
|
||||
border-color: var(--success-500);
|
||||
}
|
||||
|
||||
.product-badge {
|
||||
position: absolute;
|
||||
top: -8px;
|
||||
right: var(--space-4);
|
||||
background-color: var(--primary-500);
|
||||
color: white;
|
||||
padding: var(--space-1) var(--space-3);
|
||||
border-radius: 999px;
|
||||
font-size: var(--text-xs);
|
||||
font-weight: var(--font-medium);
|
||||
}
|
||||
|
||||
.product-price {
|
||||
font-size: var(--text-2xl);
|
||||
font-weight: var(--font-bold);
|
||||
color: var(--primary-600);
|
||||
}
|
||||
```
|
||||
|
||||
### 진행 상태 표시 (Progress)
|
||||
```css
|
||||
.progress-container {
|
||||
background-color: var(--gray-100);
|
||||
border-radius: 999px;
|
||||
height: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, var(--primary-500) 0%, var(--primary-400) 100%);
|
||||
border-radius: 999px;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.progress-steps {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
.progress-step {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: var(--text-sm);
|
||||
color: var(--gray-500);
|
||||
}
|
||||
|
||||
.progress-step.active {
|
||||
color: var(--primary-600);
|
||||
font-weight: var(--font-medium);
|
||||
}
|
||||
|
||||
.progress-step.completed {
|
||||
color: var(--success-600);
|
||||
}
|
||||
```
|
||||
|
||||
### 상태 뱃지 (Status Badge)
|
||||
```css
|
||||
.status-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: var(--space-1) var(--space-3);
|
||||
border-radius: 999px;
|
||||
font-size: var(--text-xs);
|
||||
font-weight: var(--font-medium);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.status-badge.processing {
|
||||
background-color: var(--warning-100);
|
||||
color: var(--warning-800);
|
||||
}
|
||||
|
||||
.status-badge.completed {
|
||||
background-color: var(--success-100);
|
||||
color: var(--success-800);
|
||||
}
|
||||
|
||||
.status-badge.failed {
|
||||
background-color: var(--error-100);
|
||||
color: var(--error-800);
|
||||
}
|
||||
|
||||
.status-badge::before {
|
||||
content: "";
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
background-color: currentColor;
|
||||
margin-right: var(--space-2);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 인터랙션 패턴
|
||||
|
||||
### 애니메이션 타이밍
|
||||
```css
|
||||
:root {
|
||||
--duration-fast: 0.15s;
|
||||
--duration-normal: 0.3s;
|
||||
--duration-slow: 0.5s;
|
||||
|
||||
--ease-in: cubic-bezier(0.4, 0, 1, 1);
|
||||
--ease-out: cubic-bezier(0, 0, 0.2, 1);
|
||||
--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
```
|
||||
|
||||
### 호버 효과
|
||||
```css
|
||||
.interactive:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
transition: all var(--duration-normal) var(--ease-out);
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
```
|
||||
|
||||
### 로딩 상태
|
||||
```css
|
||||
.loading {
|
||||
position: relative;
|
||||
pointer-events: none;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.loading::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 2px solid var(--gray-300);
|
||||
border-top: 2px solid var(--primary-500);
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: translate(-50%, -50%) rotate(0deg); }
|
||||
100% { transform: translate(-50%, -50%) rotate(360deg); }
|
||||
}
|
||||
```
|
||||
|
||||
### 포커스 상태
|
||||
```css
|
||||
.focusable:focus {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 3px rgba(49, 130, 206, 0.3);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.focus-visible {
|
||||
outline: 2px solid var(--primary-500);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
```
|
||||
|
||||
### 상태 전환
|
||||
```css
|
||||
.fade-enter {
|
||||
opacity: 0;
|
||||
}
|
||||
.fade-enter-active {
|
||||
opacity: 1;
|
||||
transition: opacity var(--duration-normal) var(--ease-out);
|
||||
}
|
||||
|
||||
.slide-enter {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
.slide-enter-active {
|
||||
transform: translateX(0);
|
||||
transition: transform var(--duration-normal) var(--ease-out);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 변경 이력
|
||||
|
||||
| 버전 | 날짜 | 변경사항 | 작성자 |
|
||||
|------|------|----------|--------|
|
||||
| 1.0 | 2025-01-05 | 초기 스타일 가이드 작성 | 박화면 |
|
||||
|
||||
---
|
||||
|
||||
## 스타일 가이드 활용 방법
|
||||
|
||||
### CSS 변수 사용
|
||||
모든 스타일 정의에서 CSS 변수를 사용하여 일관성을 유지하고 쉬운 테마 변경을 지원합니다.
|
||||
|
||||
### 컴포넌트 기반 설계
|
||||
재사용 가능한 컴포넌트 스타일을 정의하여 개발 효율성과 일관성을 높입니다.
|
||||
|
||||
### 접근성 고려
|
||||
모든 컴포넌트는 WCAG 2.1 AA 기준을 준수하여 접근성을 보장합니다.
|
||||
|
||||
### 반응형 우선
|
||||
Mobile First 접근 방식으로 모든 디바이스에서 최적의 사용자 경험을 제공합니다.
|
||||
578
design-backup/uiux/uiux.md
Normal file
578
design-backup/uiux/uiux.md
Normal file
@ -0,0 +1,578 @@
|
||||
# 통신요금 관리 서비스 - UI/UX 설계서
|
||||
|
||||
- [통신요금 관리 서비스 - UI/UX 설계서](#통신요금-관리-서비스---uiux-설계서)
|
||||
- [프로젝트 개요](#프로젝트-개요)
|
||||
- [정보 아키텍처](#정보-아키텍처)
|
||||
- [프로토타입 화면 목록](#프로토타입-화면-목록)
|
||||
- [사용자 플로우](#사용자-플로우)
|
||||
- [화면별 상세 설계](#화면별-상세-설계)
|
||||
- [화면간 전환 및 네비게이션](#화면간-전환-및-네비게이션)
|
||||
- [반응형 설계 전략](#반응형-설계-전략)
|
||||
- [접근성 보장 방안](#접근성-보장-방안)
|
||||
- [성능 최적화 방안](#성능-최적화-방안)
|
||||
- [변경 이력](#변경-이력)
|
||||
|
||||
---
|
||||
|
||||
## 프로젝트 개요
|
||||
|
||||
### 서비스 목적
|
||||
MVNO 고객의 통신요금 조회 및 상품변경을 지원하는 웹 서비스
|
||||
|
||||
### 주요 기능
|
||||
1. **사용자 인증**: 안전한 로그인/로그아웃
|
||||
2. **요금 조회**: 월별 통신요금 조회
|
||||
3. **상품 변경**: 요금제 변경 요청 및 처리
|
||||
|
||||
### 설계 기준
|
||||
- **유저스토리 기반**: 총 10개 유저스토리 100% 반영
|
||||
- **B2C 웹 서비스**: 일반 고객 대상
|
||||
- **보안 우선**: 개인정보 및 금융정보 보호
|
||||
- **사용성 중심**: 직관적이고 간단한 UI/UX
|
||||
|
||||
---
|
||||
|
||||
## 정보 아키텍처
|
||||
|
||||
### 서비스 구조
|
||||
```
|
||||
통신요금 관리 서비스
|
||||
├── 인증 영역
|
||||
│ ├── 로그인
|
||||
│ └── 권한 확인
|
||||
├── 요금 조회 영역
|
||||
│ ├── 조회 메뉴
|
||||
│ ├── 조회 신청
|
||||
│ └── 조회 결과
|
||||
└── 상품 변경 영역
|
||||
├── 변경 메뉴
|
||||
├── 변경 화면
|
||||
├── 변경 요청
|
||||
└── 처리 결과
|
||||
```
|
||||
|
||||
### 네비게이션 구조
|
||||
```
|
||||
메인 화면
|
||||
├── 요금 조회 메뉴 → 요금 조회 신청 → 조회 결과
|
||||
└── 상품 변경 메뉴 → 상품 변경 화면 → 변경 요청 → 처리 결과
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 프로토타입 화면 목록
|
||||
|
||||
| 화면 ID | 화면명 | 관련 유저스토리 | 우선순위 |
|
||||
|---------|--------|-----------------|----------|
|
||||
| SCR-001 | 로그인 | UFR-AUTH-010 | M |
|
||||
| SCR-002 | 메인 화면 | UFR-AUTH-020 | M |
|
||||
| SCR-003 | 요금조회 메뉴 | UFR-BILL-010 | M |
|
||||
| SCR-004 | 요금조회 결과 | UFR-BILL-020, UFR-BILL-030, UFR-BILL-040 | M |
|
||||
| SCR-005 | 상품변경 메뉴 | UFR-PROD-010 | M |
|
||||
| SCR-006 | 상품변경 화면 | UFR-PROD-020 | M |
|
||||
| SCR-007 | 상품변경 요청 | UFR-PROD-030 | M |
|
||||
| SCR-008 | 처리결과 화면 | UFR-PROD-040 | M |
|
||||
|
||||
---
|
||||
|
||||
## 사용자 플로우
|
||||
|
||||
### 메인 플로우
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[로그인] --> B[메인 화면]
|
||||
B --> C[요금 조회]
|
||||
B --> D[상품 변경]
|
||||
|
||||
C --> C1[요금조회 메뉴]
|
||||
C1 --> C2[조회월 선택]
|
||||
C2 --> C3[요금조회 결과]
|
||||
C3 --> B
|
||||
|
||||
D --> D1[상품변경 메뉴]
|
||||
D1 --> D2[상품변경 화면]
|
||||
D2 --> D3[상품 선택]
|
||||
D3 --> D4[변경 요청]
|
||||
D4 --> D5[처리결과]
|
||||
D5 --> B
|
||||
```
|
||||
|
||||
### 오류 처리 플로우
|
||||
```mermaid
|
||||
flowchart TD
|
||||
E1[로그인 실패] --> E1_1[오류 메시지 표시] --> E1_2[로그인 재시도]
|
||||
E2[권한 없음] --> E2_1[권한 오류 메시지] --> E2_2[메인 화면]
|
||||
E3[조회 실패] --> E3_1[조회 오류 메시지] --> E3_2[조회 메뉴]
|
||||
E4[변경 실패] --> E4_1[변경 오류 메시지] --> E4_2[변경 화면]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 화면별 상세 설계
|
||||
|
||||
### SCR-001: 로그인
|
||||
**개요**
|
||||
- 목적: 사용자 인증 및 서비스 접근
|
||||
- 관련 유저스토리: UFR-AUTH-010
|
||||
- 비즈니스 중요도: M/5
|
||||
|
||||
**주요 기능**
|
||||
- ID/Password 입력
|
||||
- 자동 로그인 옵션
|
||||
- 로그인 버튼
|
||||
- 오류 메시지 표시
|
||||
|
||||
**UI 구성요소**
|
||||
```
|
||||
Header
|
||||
├── 서비스 로고
|
||||
└── 서비스 제목
|
||||
|
||||
Main Content
|
||||
├── 로그인 폼
|
||||
│ ├── ID 입력 필드 (required)
|
||||
│ ├── Password 입력 필드 (required, type=password)
|
||||
│ ├── 자동 로그인 체크박스
|
||||
│ └── 로그인 버튼 (primary)
|
||||
└── 오류 메시지 영역
|
||||
|
||||
Footer
|
||||
└── 저작권 정보
|
||||
```
|
||||
|
||||
**인터랙션**
|
||||
- ID/Password 유효성 검사 (실시간)
|
||||
- 로그인 버튼 활성화/비활성화
|
||||
- 5회 실패 시 계정 잠금 안내
|
||||
- 성공 시 메인 화면 이동
|
||||
|
||||
---
|
||||
|
||||
### SCR-002: 메인 화면
|
||||
**개요**
|
||||
- 목적: 서비스 메뉴 제공 및 권한별 접근 제어
|
||||
- 관련 유저스토리: UFR-AUTH-020
|
||||
- 비즈니스 중요도: M/3
|
||||
|
||||
**주요 기능**
|
||||
- 사용자 정보 표시 (회선번호)
|
||||
- 서비스 메뉴 제공
|
||||
- 권한별 메뉴 표시/숨김
|
||||
|
||||
**UI 구성요소**
|
||||
```
|
||||
Header
|
||||
├── 서비스 로고
|
||||
├── 사용자 정보 (회선번호)
|
||||
└── 로그아웃 버튼
|
||||
|
||||
Main Content
|
||||
├── 환영 메시지
|
||||
└── 서비스 메뉴 그리드
|
||||
├── 요금 조회 카드 (권한 확인)
|
||||
└── 상품 변경 카드 (권한 확인)
|
||||
|
||||
Footer
|
||||
└── 저작권 정보
|
||||
```
|
||||
|
||||
**인터랙션**
|
||||
- 권한 확인 후 메뉴 표시
|
||||
- 권한 없는 메뉴는 비활성화 또는 숨김
|
||||
- 카드 호버 효과
|
||||
- 카드 클릭 시 해당 서비스로 이동
|
||||
|
||||
---
|
||||
|
||||
### SCR-003: 요금조회 메뉴
|
||||
**개요**
|
||||
- 목적: 요금 조회 옵션 제공
|
||||
- 관련 유저스토리: UFR-BILL-010
|
||||
- 비즈니스 중요도: M/5
|
||||
|
||||
**주요 기능**
|
||||
- 회선번호 표시
|
||||
- 조회월 선택 옵션
|
||||
- 조회 신청 기능
|
||||
|
||||
**UI 구성요소**
|
||||
```
|
||||
Header
|
||||
├── 뒤로가기 버튼
|
||||
└── 페이지 제목 "요금 조회"
|
||||
|
||||
Main Content
|
||||
├── 고객 정보 섹션
|
||||
│ └── 회선번호 표시
|
||||
├── 조회 옵션 섹션
|
||||
│ ├── 조회월 선택 (드롭다운)
|
||||
│ │ ├── 기본값: "현재 월"
|
||||
│ │ └── 이전 6개월 옵션
|
||||
│ └── 안내 텍스트
|
||||
└── 액션 버튼 그룹
|
||||
├── 조회 버튼 (primary)
|
||||
└── 취소 버튼 (secondary)
|
||||
```
|
||||
|
||||
**인터랙션**
|
||||
- 조회월 드롭다운 선택
|
||||
- 조회 버튼 클릭 시 로딩 상태
|
||||
- 오류 시 에러 메시지 표시
|
||||
|
||||
---
|
||||
|
||||
### SCR-004: 요금조회 결과
|
||||
**개요**
|
||||
- 목적: 조회된 요금 정보 표시
|
||||
- 관련 유저스토리: UFR-BILL-020, UFR-BILL-030, UFR-BILL-040
|
||||
- 비즈니스 중요도: M/8, M/13, M/8
|
||||
|
||||
**주요 기능**
|
||||
- 요금 정보 상세 표시
|
||||
- 사용량 정보 제공
|
||||
- 새로운 조회 기능
|
||||
|
||||
**UI 구성요소**
|
||||
```
|
||||
Header
|
||||
├── 뒤로가기 버튼
|
||||
└── 페이지 제목 "요금 조회 결과"
|
||||
|
||||
Main Content
|
||||
├── 요금 정보 카드
|
||||
│ ├── 청구월
|
||||
│ ├── 상품명 (요금제)
|
||||
│ ├── 총 요금 (강조 표시)
|
||||
│ ├── 할인 정보
|
||||
│ └── 약정 정보
|
||||
├── 사용량 정보 카드
|
||||
│ ├── 통화 사용량
|
||||
│ ├── 데이터 사용량
|
||||
│ └── SMS 사용량
|
||||
├── 부가 정보 카드
|
||||
│ ├── 단말기 할부금
|
||||
│ ├── 예상 해지비용
|
||||
│ └── 청구/납부 정보
|
||||
└── 액션 버튼 그룹
|
||||
├── 다른 월 조회 버튼
|
||||
└── 메인으로 버튼
|
||||
```
|
||||
|
||||
**인터랙션**
|
||||
- 정보 카드 접기/펼치기
|
||||
- 다른 월 조회 클릭 시 조회 메뉴로 이동
|
||||
- 로딩 중 스켈레톤 UI 표시
|
||||
|
||||
---
|
||||
|
||||
### SCR-005: 상품변경 메뉴
|
||||
**개요**
|
||||
- 목적: 상품 변경 진입점 제공
|
||||
- 관련 유저스토리: UFR-PROD-010
|
||||
- 비즈니스 중요도: M/5
|
||||
|
||||
**주요 기능**
|
||||
- 고객 정보 표시
|
||||
- 현재 상품 정보 표시
|
||||
- 상품 변경 화면으로 이동
|
||||
|
||||
**UI 구성요소**
|
||||
```
|
||||
Header
|
||||
├── 뒤로가기 버튼
|
||||
└── 페이지 제목 "상품 변경"
|
||||
|
||||
Main Content
|
||||
├── 고객 정보 카드
|
||||
│ ├── 회선번호
|
||||
│ └── 고객ID
|
||||
├── 현재 상품 정보 카드
|
||||
│ ├── 상품명
|
||||
│ ├── 월 기본료
|
||||
│ └── 주요 혜택
|
||||
├── 안내 메시지
|
||||
│ └── 상품 변경 시 주의사항
|
||||
└── 액션 버튼 그룹
|
||||
├── 상품 변경하기 버튼 (primary)
|
||||
└── 취소 버튼 (secondary)
|
||||
```
|
||||
|
||||
**인터랙션**
|
||||
- 현재 상품 정보 로딩
|
||||
- 상품 변경하기 클릭 시 변경 화면으로 이동
|
||||
- 로딩 실패 시 에러 메시지
|
||||
|
||||
---
|
||||
|
||||
### SCR-006: 상품변경 화면
|
||||
**개요**
|
||||
- 목적: 변경 가능한 상품 목록 제공
|
||||
- 관련 유저스토리: UFR-PROD-020
|
||||
- 비즈니스 중요도: M/8
|
||||
|
||||
**주요 기능**
|
||||
- 변경 가능한 상품 목록 표시
|
||||
- 상품 비교 기능
|
||||
- 상품 선택 기능
|
||||
|
||||
**UI 구성요소**
|
||||
```
|
||||
Header
|
||||
├── 뒤로가기 버튼
|
||||
└── 페이지 제목 "상품 선택"
|
||||
|
||||
Main Content
|
||||
├── 현재 상품 요약 (고정)
|
||||
├── 상품 목록 섹션
|
||||
│ └── 상품 카드들
|
||||
│ ├── 상품명
|
||||
│ ├── 월 기본료
|
||||
│ ├── 주요 혜택 리스트
|
||||
│ ├── 현재 상품과 비교
|
||||
│ └── 선택 라디오 버튼
|
||||
└── 액션 버튼 그룹 (고정)
|
||||
├── 선택한 상품으로 변경 (primary, disabled)
|
||||
└── 취소 버튼 (secondary)
|
||||
```
|
||||
|
||||
**인터랙션**
|
||||
- 상품 선택 시 버튼 활성화
|
||||
- 상품 카드 선택 상태 시각화
|
||||
- 스크롤 시 헤더와 버튼 고정
|
||||
- 상품 로딩 중 스켈레톤 표시
|
||||
|
||||
---
|
||||
|
||||
### SCR-007: 상품변경 요청
|
||||
**개요**
|
||||
- 목적: 선택한 상품으로 변경 요청 확인
|
||||
- 관련 유저스토리: UFR-PROD-030
|
||||
- 비즈니스 중요도: M/13
|
||||
|
||||
**주요 기능**
|
||||
- 변경 내용 확인
|
||||
- 사전 체크 진행 상황
|
||||
- 변경 요청 최종 실행
|
||||
|
||||
**UI 구성요소**
|
||||
```
|
||||
Header
|
||||
├── 뒤로가기 버튼
|
||||
└── 페이지 제목 "상품 변경 요청"
|
||||
|
||||
Main Content
|
||||
├── 변경 내용 확인 카드
|
||||
│ ├── 현재 상품
|
||||
│ ├── 변경 화살표 아이콘
|
||||
│ └── 변경할 상품
|
||||
├── 주의사항 섹션
|
||||
│ ├── 변경 시 주의사항
|
||||
│ ├── 약정/할부 안내
|
||||
│ └── 요금 변경 안내
|
||||
├── 진행 상황 표시
|
||||
│ ├── 사전 체크 진행 바
|
||||
│ └── 상태 메시지
|
||||
└── 액션 버튼 그룹
|
||||
├── 변경 신청 버튼 (primary)
|
||||
├── 취소 버튼 (secondary)
|
||||
└── 이전 단계 버튼
|
||||
```
|
||||
|
||||
**인터랙션**
|
||||
- 사전 체크 진행 상태 실시간 업데이트
|
||||
- 체크 완료 후 변경 신청 버튼 활성화
|
||||
- 체크 실패 시 오류 메시지 및 재시도 옵션
|
||||
|
||||
---
|
||||
|
||||
### SCR-008: 처리결과 화면
|
||||
**개요**
|
||||
- 목적: 상품 변경 처리 결과 표시
|
||||
- 관련 유저스토리: UFR-PROD-040
|
||||
- 비즈니스 중요도: M/21
|
||||
|
||||
**주요 기능**
|
||||
- 처리 결과 상태 표시
|
||||
- 상세 처리 내용 제공
|
||||
- 후속 액션 안내
|
||||
|
||||
**UI 구성요소**
|
||||
```
|
||||
Header
|
||||
└── 페이지 제목 "처리 결과"
|
||||
|
||||
Main Content
|
||||
├── 결과 상태 카드
|
||||
│ ├── 성공/실패 아이콘 (대형)
|
||||
│ ├── 결과 메시지 (제목)
|
||||
│ └── 상태 설명
|
||||
├── 처리 내용 카드 (성공 시)
|
||||
│ ├── 변경된 상품 정보
|
||||
│ ├── 적용일
|
||||
│ └── 처리 번호
|
||||
├── 실패 사유 카드 (실패 시)
|
||||
│ ├── 실패 원인
|
||||
│ ├── 해결 방법
|
||||
│ └── 고객센터 안내
|
||||
└── 액션 버튼 그룹
|
||||
├── 메인으로 버튼 (primary)
|
||||
├── 다시 시도 버튼 (실패 시)
|
||||
└── 고객센터 연결 (실패 시)
|
||||
```
|
||||
|
||||
**인터랙션**
|
||||
- 결과에 따른 적절한 UI 표시
|
||||
- 성공/실패 상태별 차별화된 컬러 스킴
|
||||
- 추가 액션 버튼 제공
|
||||
|
||||
---
|
||||
|
||||
## 화면간 전환 및 네비게이션
|
||||
|
||||
### 네비게이션 패턴
|
||||
- **계층적 네비게이션**: 뒤로가기 버튼 제공
|
||||
- **브레드크럼**: 깊이 2단계 이상 시 경로 표시
|
||||
- **메인 복귀**: 모든 화면에서 홈 버튼 제공
|
||||
|
||||
### 전환 효과
|
||||
- **페이지 전환**: 부드러운 슬라이드 애니메이션 (300ms)
|
||||
- **모달/팝업**: 페이드 인/아웃 효과 (200ms)
|
||||
- **로딩 상태**: 스켈레톤 UI 또는 스피너
|
||||
|
||||
### URL 구조
|
||||
```
|
||||
/ → 로그인 페이지
|
||||
/main → 메인 화면
|
||||
/bill/menu → 요금조회 메뉴
|
||||
/bill/result → 요금조회 결과
|
||||
/product/menu → 상품변경 메뉴
|
||||
/product/change → 상품변경 화면
|
||||
/product/request → 상품변경 요청
|
||||
/product/result → 처리결과
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 반응형 설계 전략
|
||||
|
||||
### 브레이크포인트
|
||||
- **Mobile**: ~ 767px
|
||||
- **Tablet**: 768px ~ 1023px
|
||||
- **Desktop**: 1024px ~
|
||||
|
||||
### 레이아웃 전략
|
||||
**Mobile First 설계**
|
||||
- 기본: 단일 컬럼 레이아웃
|
||||
- 카드 형태의 콘텐츠 구성
|
||||
- 터치 친화적 버튼 크기 (44px 이상)
|
||||
|
||||
**Tablet**
|
||||
- 2컬럼 레이아웃 (카드 그리드)
|
||||
- 사이드바 네비게이션 고려
|
||||
- 확장된 터치 영역
|
||||
|
||||
**Desktop**
|
||||
- 3컬럼 레이아웃 가능
|
||||
- 고정 폭 컨테이너 (최대 1200px)
|
||||
- 호버 상태 적극 활용
|
||||
|
||||
### 콘텐츠 우선순위
|
||||
1. **핵심 정보**: 항상 우선 표시
|
||||
2. **액션 버튼**: 고정 위치 (하단)
|
||||
3. **부가 정보**: 접기/펼치기로 제어
|
||||
|
||||
---
|
||||
|
||||
## 접근성 보장 방안
|
||||
|
||||
### WCAG 2.1 AA 수준 준수
|
||||
**인식 가능성 (Perceivable)**
|
||||
- 명도 대비 4.5:1 이상 유지
|
||||
- 대체 텍스트 제공 (모든 이미지)
|
||||
- 텍스트 크기 조절 가능 (최대 200%)
|
||||
|
||||
**운용 가능성 (Operable)**
|
||||
- 키보드 접근성 완전 지원
|
||||
- 포커스 순서 논리적 구성
|
||||
- 자동 재생 콘텐츠 없음
|
||||
|
||||
**이해 가능성 (Understandable)**
|
||||
- 명확한 언어 사용
|
||||
- 입력 오류 방지 및 수정 지원
|
||||
- 일관된 네비게이션
|
||||
|
||||
**견고성 (Robust)**
|
||||
- 시맨틱 HTML 사용
|
||||
- ARIA 라벨 적절히 활용
|
||||
- 스크린 리더 호환성
|
||||
|
||||
### 구체적 구현 사항
|
||||
- **폼 요소**: 라벨과 입력 필드 명확한 연결
|
||||
- **버튼**: 명확한 텍스트 또는 aria-label
|
||||
- **오류 메시지**: 명확한 위치와 해결 방법 안내
|
||||
- **로딩 상태**: aria-live를 통한 상태 알림
|
||||
|
||||
---
|
||||
|
||||
## 성능 최적화 방안
|
||||
|
||||
### 로딩 성능
|
||||
**초기 로딩**
|
||||
- Critical CSS 인라인 처리
|
||||
- 이미지 지연 로딩 (Lazy Loading)
|
||||
- 폰트 최적화 (font-display: swap)
|
||||
|
||||
**코드 분할**
|
||||
- 페이지별 번들 분리
|
||||
- 동적 import 활용
|
||||
- 트리 쉐이킹 적용
|
||||
|
||||
### 런타임 성능
|
||||
**상태 관리**
|
||||
- 불필요한 리렌더링 방지
|
||||
- 메모이제이션 활용
|
||||
- 가상화 (긴 목록)
|
||||
|
||||
**네트워크 최적화**
|
||||
- API 응답 캐싱
|
||||
- 요청 중복 제거
|
||||
- 압축 및 minify
|
||||
|
||||
### 사용자 경험
|
||||
**로딩 상태**
|
||||
- 스켈레톤 UI 제공
|
||||
- 프로그레스바 표시
|
||||
- 오프라인 상태 대응
|
||||
|
||||
**오류 처리**
|
||||
- 명확한 오류 메시지
|
||||
- 재시도 메커니즘
|
||||
- 폴백 UI 제공
|
||||
|
||||
### 성능 지표 목표
|
||||
- **First Contentful Paint**: < 1.5초
|
||||
- **Largest Contentful Paint**: < 2.5초
|
||||
- **First Input Delay**: < 100ms
|
||||
- **Cumulative Layout Shift**: < 0.1
|
||||
|
||||
---
|
||||
|
||||
## 변경 이력
|
||||
|
||||
| 버전 | 날짜 | 변경사항 | 작성자 |
|
||||
|------|------|----------|--------|
|
||||
| 1.0 | 2025-01-05 | 초기 UI/UX 설계서 작성 | 박화면 |
|
||||
|
||||
---
|
||||
|
||||
## 검토 사항
|
||||
|
||||
### 유저스토리 매칭 검토 ✅
|
||||
- 총 10개 유저스토리 100% 반영
|
||||
- 화면별 관련 유저스토리 명시
|
||||
- 불필요한 추가 설계 없음
|
||||
|
||||
### 설계 원칙 준수 ✅
|
||||
- 통신요금 관리 서비스 특화 설계
|
||||
- 보안성과 사용성 균형
|
||||
- 접근성 및 성능 고려
|
||||
297
design-backup/userstory.md
Normal file
297
design-backup/userstory.md
Normal file
@ -0,0 +1,297 @@
|
||||
# 통신요금 관리 서비스 - 유저스토리
|
||||
|
||||
- [통신요금 관리 서비스 - 유저스토리](#통신요금-관리-서비스---유저스토리)
|
||||
- [마이크로서비스 구성](#마이크로서비스-구성)
|
||||
- [유저스토리](#유저스토리)
|
||||
|
||||
---
|
||||
|
||||
## 마이크로서비스 구성
|
||||
1. **Auth** - 사용자 인증 및 인가 관리
|
||||
2. **Bill-Inquiry** - 요금 조회 서비스
|
||||
3. **Product-Change** - 상품 변경 서비스
|
||||
|
||||
---
|
||||
|
||||
## 유저스토리
|
||||
|
||||
```
|
||||
1. Auth 서비스
|
||||
1) 사용자 인증 및 인가
|
||||
UFR-AUTH-010: [사용자 로그인] MVNO 고객으로서 | 나는 통신요금을 관리하기 위해 | 안전하게 로그인하고 싶다.
|
||||
- 시나리오: 고객 로그인
|
||||
MVNO 서비스에 접근한 상황에서 | ID와 Password를 입력하여 로그인 요청을 하면 | 인증이 완료되고 메인 화면이 표시된다.
|
||||
|
||||
[입력 요구사항]
|
||||
- 인증 정보 ID: 고객 식별자 입력
|
||||
- Password: 계정 비밀번호 입력
|
||||
- 자동 로그인: 선택 옵션 제공
|
||||
|
||||
[인증 처리]
|
||||
- 성공: 메인 서비스 화면으로 이동
|
||||
- 실패: "ID 또는 비밀번호를 확인해주세요" 메시지
|
||||
- 5회 연속 실패: 30분간 계정 잠금 안내
|
||||
|
||||
[검증 요구사항]
|
||||
- 계정이 있어야 함
|
||||
- 인증 정보의 정확성 검증
|
||||
- 세션 보안 처리
|
||||
|
||||
- M/5
|
||||
|
||||
---
|
||||
|
||||
UFR-AUTH-020: [사용자 인가] 인증된 고객으로서 | 나는 서비스별 접근 권한을 확인받기 위해 | 화면에 대한 접근권한이 부여되기를 원한다.
|
||||
- 시나리오: 서비스 접근 권한 확인
|
||||
로그인 완료 후 특정 서비스 화면에 접근한 상황에서 | 해당 서비스 이용 권한을 확인하면 | 권한이 있는 경우 서비스 화면이 표시된다.
|
||||
|
||||
[접근 권한 확인]
|
||||
- 요금 조회 서비스 접근권한 확인
|
||||
- 상품 변경 서비스 접근권한 확인
|
||||
- 권한별 메뉴 표시/숨김 처리
|
||||
|
||||
[권한 검증]
|
||||
- 성공: 해당 서비스 화면 제공
|
||||
- 실패: "서비스 이용 권한이 없습니다" 메시지 표시
|
||||
- 접근 권한이 부여되어 있어야 함
|
||||
|
||||
- M/3
|
||||
|
||||
---
|
||||
|
||||
2. Bill-Inquiry 서비스
|
||||
1) 요금 조회
|
||||
UFR-BILL-010: [요금조회 메뉴 접근] MVNO 고객으로서 | 나는 내 통신요금을 확인하기 위해 | 요금 조회 메뉴를 요청하고 싶다.
|
||||
- 시나리오: 요금조회 메뉴 표시
|
||||
인증된 상태에서 | 요금 조회 메뉴를 요청하면 | 요금조회 메뉴가 화면에 표시된다.
|
||||
|
||||
[메뉴 표시 요구사항]
|
||||
- 요금 조회 메뉴 화면 제공
|
||||
- 고객 회선번호 표시
|
||||
- 조회월 선택 옵션 제공
|
||||
- 요금 조회 신청 버튼 활성화
|
||||
|
||||
[접근 권한]
|
||||
- 요금 조회에 대한 접근권한이 부여되어 있어야 함
|
||||
- mvno AP server를 통한 화면 제공
|
||||
|
||||
[오류 처리]
|
||||
- 메뉴 로딩 실패 시: "요금 조회 메뉴 로딩에 실패하였습니다" 메시지
|
||||
|
||||
- M/5
|
||||
|
||||
---
|
||||
|
||||
UFR-BILL-020: [요금조회 신청] MVNO 고객으로서 | 나는 특정 월의 통신요금을 확인하기 위해 | 조회월을 선택하여 요금 조회를 신청하고 싶다.
|
||||
- 시나리오 1: 조회월 미선택 (기본 조회)
|
||||
요금조회 메뉴에서 | 조회월을 입력하지 않고 조회를 신청하면 | 조회시점 기준 당월 청구요금이 조회된다.
|
||||
|
||||
[기본 조회 처리]
|
||||
- 입력 데이터: 회선번호
|
||||
- 조회 대상: 현재 월 청구요금
|
||||
- 결과 확인: 조회시점 기준 당월 청구요금을 확인했다
|
||||
|
||||
- 시나리오 2: 조회월 선택 조회
|
||||
요금조회 메뉴에서 | 특정 조회월을 선택하고 조회를 신청하면 | 해당 조회월의 청구요금이 조회된다.
|
||||
|
||||
[선택 조회 처리]
|
||||
- 입력 데이터: 회선번호, 조회월
|
||||
- 조회 대상: 선택한 월의 청구요금
|
||||
- 결과 확인: 조회월의 청구요금을 확인했다
|
||||
|
||||
[조회 선택 옵션]
|
||||
- 조회월을 선택한다: 특정 월 선택하여 조회
|
||||
- 조회월을 선택하지 않는다: 당월 기준으로 조회
|
||||
|
||||
[처리 결과]
|
||||
- 성공: 요금 조회가 신청되었다
|
||||
- 실패: "요금 조회 신청에 실패하였습니다" 메시지
|
||||
|
||||
- M/8
|
||||
|
||||
---
|
||||
|
||||
UFR-BILL-030: [KOS 요금조회 서비스 연동] 시스템으로서 | 나는 정확한 요금 정보를 제공하기 위해 | KOS의 요금 조회 서비스를 호출하고 응답을 처리하고 싶다.
|
||||
- 시나리오: KOS 요금조회 API 호출
|
||||
요금 조회 요청을 받은 상황에서 | 요금 조회 API를 호출하면 | KOS 요금 조회 서비스의 응답을 받아 처리한다.
|
||||
|
||||
[API 호출 요구사항]
|
||||
- 입력 데이터: 회선번호, 조회월 (선택)
|
||||
- 호출 대상: KOS-Order 시스템
|
||||
- 호출 규격: KOS 요금조회 서비스가 요구하는 규격에 맞게 호출
|
||||
- 응답 처리: KOS 요금 조회 서비스의 응답을 받았다
|
||||
|
||||
[응답 데이터]
|
||||
- 요금조회 결과 정보
|
||||
- 상품명: 현재 이용 중인 요금제
|
||||
- 약정정보: 계약 약정 조건
|
||||
- 청구월: 요금 청구 월
|
||||
- 요금: 청구 요금 금액
|
||||
- 할인정보: 적용된 할인 내역
|
||||
- 사용량: 통화, 데이터 사용량
|
||||
- 예상해지비용: 중도 해지 시 비용
|
||||
- 단말기할부금: 단말기 할부 잔액
|
||||
- 청구/납부정보: 요금 청구 및 납부 상태
|
||||
|
||||
[처리 결과]
|
||||
- 성공: 요금 조회 API 호출에 성공하였다
|
||||
- 실패: 요금 조회 API 호출에 실패하였다
|
||||
|
||||
- M/13
|
||||
|
||||
---
|
||||
|
||||
UFR-BILL-040: [요금조회 결과 전송] 시스템으로서 | 나는 조회된 요금 정보를 고객에게 제공하기 위해 | MVNO AP로 조회 결과를 전송하고 연동 이력을 저장하고 싶다.
|
||||
- 시나리오: 요금조회 결과 화면 출력
|
||||
KOS에서 요금조회 결과를 받은 상황에서 | MVNO AP로 결과를 전송하면 | 요금조회 결과가 화면에 출력되고 전송 이력이 기록된다.
|
||||
|
||||
[결과 전송 처리]
|
||||
- 전송 대상: mvno AP server
|
||||
- 전송 데이터: 요금조회 결과 정보 (상품명, 청구월, 요금 등 전체)
|
||||
- 화면 출력: 요금조회 결과를 화면에 출력한다
|
||||
- 이력 기록: 요금 조회 결과를 전송하고, 전송이력을 기록했다
|
||||
|
||||
[처리 이력 관리]
|
||||
- 요금 조회 요청 이력: MVNO → MP
|
||||
- 요청일시, 회선번호, 조회월
|
||||
- 요금 조회 처리 이력: MP → KOS
|
||||
- 조회요청일시, 조회처리일시, 처리결과
|
||||
- 요청한 회선번호와 조회월 정보
|
||||
|
||||
- M/8
|
||||
|
||||
---
|
||||
|
||||
3. Product-Change 서비스
|
||||
1) 상품 변경
|
||||
UFR-PROD-010: [상품변경 메뉴 접근] MVNO 고객으로서 | 나는 내 요금제를 변경하기 위해 | 상품 변경 요청 메뉴를 요청하고 싶다.
|
||||
- 시나리오: 상품변경 메뉴 표시
|
||||
인증된 상태에서 | 상품 변경 요청 메뉴를 요청하면 | 상품변경 메뉴가 화면에 표시된다.
|
||||
|
||||
[메뉴 표시 요구사항]
|
||||
- 상품 변경 요청 메뉴 화면 제공
|
||||
- 고객 정보 표시 (회선번호, 고객ID)
|
||||
- 현재 상품 정보 표시
|
||||
- 변경 가능한 상품 목록 제공
|
||||
|
||||
[접근 권한]
|
||||
- 화면에 대한 접근권한이 부여되어 있어야 함
|
||||
- mvno AP server를 통한 화면 제공
|
||||
|
||||
[오류 처리]
|
||||
- 메뉴 로딩 실패 시: "상품 변경 요청 메뉴 로딩에 실패하였습니다" 메시지
|
||||
|
||||
- M/5
|
||||
|
||||
---
|
||||
|
||||
UFR-PROD-020: [상품변경 화면 접근] MVNO 고객으로서 | 나는 상품을 선택하고 변경하기 위해 | 상품 변경 화면을 요청하고 싶다.
|
||||
- 시나리오: 상품변경 화면 표시
|
||||
상품변경 메뉴에서 | 상품 변경 화면을 요청하면 | 상품 선택 및 변경 요청이 가능한 화면이 표시된다.
|
||||
|
||||
[화면 표시 요구사항]
|
||||
- 고객정보 및 상품정보 조회 및 표시
|
||||
- 현재 이용 상품 정보 표시
|
||||
- 변경 가능한 상품 목록 제공
|
||||
- 상품 선택 및 변경 요청 기능
|
||||
|
||||
[데이터 조회]
|
||||
- 고객정보 요청: KOS-Order 시스템에서 고객 정보 조회
|
||||
- 상품정보 요청: KOS-Order 시스템에서 상품 정보 조회
|
||||
- 조회 결과: 고객정보가 취득되었다, 상품 정보가 취득되었다
|
||||
|
||||
[처리 결과]
|
||||
- 성공: 상품 변경 화면이 보였다
|
||||
- 실패: "상품 변경 화면 접속에 실패하였습니다" 메시지
|
||||
|
||||
- M/8
|
||||
|
||||
---
|
||||
|
||||
UFR-PROD-030: [상품변경 요청] MVNO 고객으로서 | 나는 더 나은 요금제로 변경하기 위해 | 원하는 상품을 선택하여 변경을 요청하고 싶다.
|
||||
- 시나리오: 상품 선택 및 변경 요청
|
||||
상품변경 화면에서 | 상품(요금제)을 선택 후 상품 변경 요청을 하면 | 변경 요청이 접수되고 사전 체크가 진행된다.
|
||||
|
||||
[변경 요청 입력]
|
||||
- 회선번호: 고객 회선 식별자
|
||||
- 변경 전 상품코드: 현재 이용 중인 상품
|
||||
- 변경 후 상품코드: 변경하려는 상품
|
||||
- 생성일시: 요청 일시
|
||||
|
||||
[상품 변경 사전 체크]
|
||||
- 사전 체크 조건
|
||||
- 현재 판매중인 상품이어야 함
|
||||
- 변경 요청한 사업자에서 판매중인 상품이어야 함
|
||||
- 변경 요청 회선은 사용 중인 상태여야 함 (정지 상태가 아니어야 함)
|
||||
- 사전체크 결과에서 정상(변경가능)으로 응답처리 되어야 함
|
||||
|
||||
[처리 결과]
|
||||
- 성공: 상품 변경이 진행되었다, 상품 사전 체크에 성공하였다
|
||||
- 실패: 상품 사전 체크에 실패하였다
|
||||
|
||||
- M/13
|
||||
|
||||
---
|
||||
|
||||
UFR-PROD-040: [상품변경 처리] 시스템으로서 | 나는 승인된 상품 변경 요청을 완료하기 위해 | KOS 시스템과 연동하여 상품 변경을 처리하고 싶다.
|
||||
- 시나리오 1: 상품 변경 성공 처리
|
||||
사전 체크가 통과된 상황에서 | KOS에 상품 변경 처리를 요청하면 | 상품 변경이 완료되고 완료 결과가 전송된다.
|
||||
|
||||
[성공 처리]
|
||||
- 상품 변경 완료: 상품 변경이 완료되었다
|
||||
- 처리 결과 전송: 변경 후 상품 코드, 상품 변경 처리 결과(정상), 메시지
|
||||
- 화면 출력: 상품 변경 완료 문구를 화면에 출력한다
|
||||
- 이력 기록: 상품 변경 처리하고 연동 이력을 기록한다
|
||||
|
||||
- 시나리오 2: 상품 변경 실패 처리
|
||||
사전 체크는 통과했으나 실제 변경 처리 중 문제가 발생한 상황에서 | 변경 처리가 실패하면 | 실패 사유에 따른 안내 메시지가 표시된다.
|
||||
|
||||
[실패 처리]
|
||||
- 변경 실패: 상품 변경이 실패했다, 상품 변경 요청을 실패하였다
|
||||
- 처리 결과 전송: 변경 후 상품 코드, 상품 변경 처리 결과(실패), 실패 메시지
|
||||
- 화면 출력: 상품 변경에 실패하여 실패 사유에 따라 문구를 화면에 출력한다
|
||||
- 이력 기록: 상품 변경 실패 처리하고 실패 이력을 기록한다
|
||||
|
||||
[처리 이력 관리]
|
||||
- 상품 변경 요청 이력: MVNO → MP
|
||||
- 회선번호, 변경 전 상품코드, 변경 후 상품코드, 생성일시
|
||||
- 상품 변경 처리 이력: MP → KOS
|
||||
- 회선번호, 변경 전/후 상품코드, 처리 결과, 처리 메시지
|
||||
|
||||
- M/21
|
||||
|
||||
---
|
||||
|
||||
```
|
||||
|
||||
## 데이터 관계
|
||||
```
|
||||
고객 (1) : (N) 요금조회
|
||||
고객 (1) : (N) 상품변경
|
||||
고객정보 - 고객ID, 회선번호, 상품정보
|
||||
상품정보 - 상품코드, 상품명, 가격 정보
|
||||
요청이력 - 요청일시, 처리일시, 처리결과
|
||||
처리이력 - 연동 시스템, 요청/응답 데이터, 처리결과
|
||||
```
|
||||
|
||||
## 주요 기술 고려사항
|
||||
|
||||
### 외부 시스템 연동
|
||||
- **KOS-Order 시스템**: 실제 통신사 백엔드 시스템과의 안정적 연동 필요
|
||||
- **MVNO AP Server**: 프론트엔드 시스템과의 실시간 통신 처리
|
||||
- **Circuit Breaker**: 외부 시스템 장애 시 서비스 가용성 확보
|
||||
|
||||
### 보안 및 인증
|
||||
- **인증/인가**: 고객 정보 보호를 위한 강력한 인증 체계
|
||||
- **데이터 암호화**: 민감한 고객 정보 및 요금 정보 암호화
|
||||
- **세션 관리**: 안전한 세션 처리 및 타임아웃 관리
|
||||
|
||||
### 성능 및 안정성
|
||||
- **응답 시간**: KOS 연동 API의 안정적 응답 시간 확보
|
||||
- **이력 관리**: 모든 요청/처리 이력의 정확한 기록 및 추적
|
||||
- **오류 처리**: 각 단계별 명확한 오류 메시지 및 복구 방안
|
||||
|
||||
### 데이터 일관성
|
||||
- **트랜잭션 처리**: 상품 변경 시 데이터 일관성 보장
|
||||
- **이력 동기화**: 요청/처리 이력의 정확한 동기화
|
||||
- **상태 관리**: 각 요청의 상태 추적 및 관리
|
||||
358
sample-data/monthly-bills.json
Normal file
358
sample-data/monthly-bills.json
Normal file
@ -0,0 +1,358 @@
|
||||
{
|
||||
"2025-01": {
|
||||
"userId": "user123",
|
||||
"month": "2025-01",
|
||||
"basicFee": 55000,
|
||||
"callFee": 12500,
|
||||
"dataFee": 8500,
|
||||
"smsFee": 2500,
|
||||
"roamingFee": 0,
|
||||
"discountFee": -5500,
|
||||
"totalFee": 72500,
|
||||
"paymentStatus": "PAID",
|
||||
"dueDate": "2025-01-25",
|
||||
"paymentDate": "2025-01-22",
|
||||
"plan": {
|
||||
"name": "5G 프리미엄",
|
||||
"basicFee": 55000,
|
||||
"dataLimit": "무제한"
|
||||
},
|
||||
"usage": {
|
||||
"callMinutes": 850,
|
||||
"dataGB": 25.6,
|
||||
"smsCount": 125
|
||||
}
|
||||
},
|
||||
"2025-02": {
|
||||
"userId": "user123",
|
||||
"month": "2025-02",
|
||||
"basicFee": 55000,
|
||||
"callFee": 18200,
|
||||
"dataFee": 15000,
|
||||
"smsFee": 3200,
|
||||
"roamingFee": 45000,
|
||||
"discountFee": -8500,
|
||||
"totalFee": 127900,
|
||||
"paymentStatus": "PAID",
|
||||
"dueDate": "2025-02-25",
|
||||
"paymentDate": "2025-02-26",
|
||||
"plan": {
|
||||
"name": "5G 프리미엄",
|
||||
"basicFee": 55000,
|
||||
"dataLimit": "무제한"
|
||||
},
|
||||
"usage": {
|
||||
"callMinutes": 1240,
|
||||
"dataGB": 42.3,
|
||||
"smsCount": 160
|
||||
},
|
||||
"roamingUsage": {
|
||||
"country": "일본",
|
||||
"days": 5,
|
||||
"dataGB": 2.8
|
||||
}
|
||||
},
|
||||
"2025-03": {
|
||||
"userId": "user123",
|
||||
"month": "2025-03",
|
||||
"basicFee": 55000,
|
||||
"callFee": 9800,
|
||||
"dataFee": 3200,
|
||||
"smsFee": 1800,
|
||||
"roamingFee": 0,
|
||||
"discountFee": -6500,
|
||||
"totalFee": 63300,
|
||||
"paymentStatus": "PAID",
|
||||
"dueDate": "2025-03-25",
|
||||
"paymentDate": "2025-03-20",
|
||||
"plan": {
|
||||
"name": "5G 프리미엄",
|
||||
"basicFee": 55000,
|
||||
"dataLimit": "무제한"
|
||||
},
|
||||
"usage": {
|
||||
"callMinutes": 670,
|
||||
"dataGB": 18.9,
|
||||
"smsCount": 90
|
||||
}
|
||||
},
|
||||
"2025-04": {
|
||||
"userId": "user123",
|
||||
"month": "2025-04",
|
||||
"basicFee": 55000,
|
||||
"callFee": 22400,
|
||||
"dataFee": 25000,
|
||||
"smsFee": 4500,
|
||||
"roamingFee": 0,
|
||||
"discountFee": -12000,
|
||||
"totalFee": 94900,
|
||||
"paymentStatus": "PAID",
|
||||
"dueDate": "2025-04-25",
|
||||
"paymentDate": "2025-04-24",
|
||||
"plan": {
|
||||
"name": "5G 프리미엄",
|
||||
"basicFee": 55000,
|
||||
"dataLimit": "무제한"
|
||||
},
|
||||
"usage": {
|
||||
"callMinutes": 1520,
|
||||
"dataGB": 67.2,
|
||||
"smsCount": 225
|
||||
},
|
||||
"specialUsage": {
|
||||
"internationalCall": {
|
||||
"minutes": 45,
|
||||
"fee": 15000
|
||||
}
|
||||
}
|
||||
},
|
||||
"2025-05": {
|
||||
"userId": "user123",
|
||||
"month": "2025-05",
|
||||
"basicFee": 39000,
|
||||
"callFee": 8500,
|
||||
"dataFee": 18500,
|
||||
"smsFee": 1200,
|
||||
"roamingFee": 0,
|
||||
"discountFee": -3900,
|
||||
"totalFee": 63300,
|
||||
"paymentStatus": "PAID",
|
||||
"dueDate": "2025-05-25",
|
||||
"paymentDate": "2025-05-23",
|
||||
"plan": {
|
||||
"name": "5G 스탠다드",
|
||||
"basicFee": 39000,
|
||||
"dataLimit": "100GB"
|
||||
},
|
||||
"usage": {
|
||||
"callMinutes": 580,
|
||||
"dataGB": 95.6,
|
||||
"smsCount": 60
|
||||
},
|
||||
"planChangeInfo": {
|
||||
"previousPlan": "5G 프리미엄",
|
||||
"changeDate": "2025-05-01",
|
||||
"reason": "요금절약"
|
||||
}
|
||||
},
|
||||
"2025-06": {
|
||||
"userId": "user123",
|
||||
"month": "2025-06",
|
||||
"basicFee": 39000,
|
||||
"callFee": 15600,
|
||||
"dataFee": 32000,
|
||||
"smsFee": 2800,
|
||||
"roamingFee": 125000,
|
||||
"discountFee": -7800,
|
||||
"totalFee": 206600,
|
||||
"paymentStatus": "OVERDUE",
|
||||
"dueDate": "2025-06-25",
|
||||
"paymentDate": null,
|
||||
"plan": {
|
||||
"name": "5G 스탠다드",
|
||||
"basicFee": 39000,
|
||||
"dataLimit": "100GB"
|
||||
},
|
||||
"usage": {
|
||||
"callMinutes": 1060,
|
||||
"dataGB": 156.8,
|
||||
"smsCount": 140
|
||||
},
|
||||
"roamingUsage": {
|
||||
"country": "미국",
|
||||
"days": 10,
|
||||
"dataGB": 8.2,
|
||||
"callMinutes": 320
|
||||
},
|
||||
"overdueInfo": {
|
||||
"overdueAmount": 206600,
|
||||
"lateFee": 10000,
|
||||
"overdueDays": 15
|
||||
}
|
||||
},
|
||||
"2025-07": {
|
||||
"userId": "user123",
|
||||
"month": "2025-07",
|
||||
"basicFee": 39000,
|
||||
"callFee": 11200,
|
||||
"dataFee": 45000,
|
||||
"smsFee": 1500,
|
||||
"roamingFee": 0,
|
||||
"discountFee": -9500,
|
||||
"totalFee": 87200,
|
||||
"paymentStatus": "PAID",
|
||||
"dueDate": "2025-07-25",
|
||||
"paymentDate": "2025-07-10",
|
||||
"plan": {
|
||||
"name": "5G 스탠다드",
|
||||
"basicFee": 39000,
|
||||
"dataLimit": "100GB"
|
||||
},
|
||||
"usage": {
|
||||
"callMinutes": 760,
|
||||
"dataGB": 178.9,
|
||||
"smsCount": 75
|
||||
},
|
||||
"extraCharges": {
|
||||
"dataOverage": {
|
||||
"overGB": 78.9,
|
||||
"ratePerGB": 1000,
|
||||
"fee": 45000
|
||||
}
|
||||
}
|
||||
},
|
||||
"2025-08": {
|
||||
"userId": "user123",
|
||||
"month": "2025-08",
|
||||
"basicFee": 69000,
|
||||
"callFee": 9800,
|
||||
"dataFee": 0,
|
||||
"smsFee": 2200,
|
||||
"roamingFee": 0,
|
||||
"discountFee": -15000,
|
||||
"totalFee": 66000,
|
||||
"paymentStatus": "PAID",
|
||||
"dueDate": "2025-08-25",
|
||||
"paymentDate": "2025-08-20",
|
||||
"plan": {
|
||||
"name": "5G 플래티넘",
|
||||
"basicFee": 69000,
|
||||
"dataLimit": "무제한"
|
||||
},
|
||||
"usage": {
|
||||
"callMinutes": 670,
|
||||
"dataGB": 89.2,
|
||||
"smsCount": 110
|
||||
},
|
||||
"planChangeInfo": {
|
||||
"previousPlan": "5G 스탠다드",
|
||||
"changeDate": "2025-08-01",
|
||||
"reason": "데이터 초과 방지"
|
||||
},
|
||||
"promotions": [
|
||||
{
|
||||
"name": "신규가입 할인",
|
||||
"discountAmount": 10000,
|
||||
"remainingMonths": 8
|
||||
}
|
||||
]
|
||||
},
|
||||
"2025-09": {
|
||||
"userId": "user123",
|
||||
"month": "2025-09",
|
||||
"basicFee": 69000,
|
||||
"callFee": 25600,
|
||||
"dataFee": 0,
|
||||
"smsFee": 5200,
|
||||
"roamingFee": 78000,
|
||||
"discountFee": -13800,
|
||||
"totalFee": 164000,
|
||||
"paymentStatus": "PENDING",
|
||||
"dueDate": "2025-09-25",
|
||||
"paymentDate": null,
|
||||
"plan": {
|
||||
"name": "5G 플래티넘",
|
||||
"basicFee": 69000,
|
||||
"dataLimit": "무제한"
|
||||
},
|
||||
"usage": {
|
||||
"callMinutes": 1740,
|
||||
"dataGB": 124.7,
|
||||
"smsCount": 260
|
||||
},
|
||||
"roamingUsage": {
|
||||
"country": "유럽(독일, 프랑스)",
|
||||
"days": 14,
|
||||
"dataGB": 15.6,
|
||||
"callMinutes": 560
|
||||
}
|
||||
},
|
||||
"2025-10": {
|
||||
"userId": "user123",
|
||||
"month": "2025-10",
|
||||
"basicFee": 69000,
|
||||
"callFee": 13400,
|
||||
"dataFee": 0,
|
||||
"smsFee": 1800,
|
||||
"roamingFee": 0,
|
||||
"discountFee": -8500,
|
||||
"totalFee": 75700,
|
||||
"paymentStatus": "PAID",
|
||||
"dueDate": "2025-10-25",
|
||||
"paymentDate": "2025-10-22",
|
||||
"plan": {
|
||||
"name": "5G 플래티넘",
|
||||
"basicFee": 69000,
|
||||
"dataLimit": "무제한"
|
||||
},
|
||||
"usage": {
|
||||
"callMinutes": 910,
|
||||
"dataGB": 67.3,
|
||||
"smsCount": 90
|
||||
}
|
||||
},
|
||||
"2025-11": {
|
||||
"userId": "user123",
|
||||
"month": "2025-11",
|
||||
"basicFee": 69000,
|
||||
"callFee": 18900,
|
||||
"dataFee": 0,
|
||||
"smsFee": 3600,
|
||||
"roamingFee": 0,
|
||||
"discountFee": -12000,
|
||||
"totalFee": 79500,
|
||||
"paymentStatus": "PAID",
|
||||
"dueDate": "2025-11-25",
|
||||
"paymentDate": "2025-11-18",
|
||||
"plan": {
|
||||
"name": "5G 플래티넘",
|
||||
"basicFee": 69000,
|
||||
"dataLimit": "무제한"
|
||||
},
|
||||
"usage": {
|
||||
"callMinutes": 1285,
|
||||
"dataGB": 145.8,
|
||||
"smsCount": 180
|
||||
},
|
||||
"specialEvents": [
|
||||
{
|
||||
"name": "블랙프라이데이 데이터 보너스",
|
||||
"bonusDataGB": 50,
|
||||
"validUntil": "2025-11-30"
|
||||
}
|
||||
]
|
||||
},
|
||||
"2025-12": {
|
||||
"userId": "user123",
|
||||
"month": "2025-12",
|
||||
"basicFee": 69000,
|
||||
"callFee": 32500,
|
||||
"dataFee": 0,
|
||||
"smsFee": 6800,
|
||||
"roamingFee": 0,
|
||||
"discountFee": -18000,
|
||||
"totalFee": 90300,
|
||||
"paymentStatus": "CURRENT",
|
||||
"dueDate": "2025-12-25",
|
||||
"paymentDate": null,
|
||||
"plan": {
|
||||
"name": "5G 플래티넘",
|
||||
"basicFee": 69000,
|
||||
"dataLimit": "무제한"
|
||||
},
|
||||
"usage": {
|
||||
"callMinutes": 2210,
|
||||
"dataGB": 198.4,
|
||||
"smsCount": 340
|
||||
},
|
||||
"yearEndSummary": {
|
||||
"totalPaidAmount": 1021900,
|
||||
"averageMonthlyBill": 93810,
|
||||
"totalDataUsageGB": 1312.7,
|
||||
"totalCallMinutes": 13497,
|
||||
"totalSmsCount": 1825,
|
||||
"planChanges": 3,
|
||||
"roamingCountries": ["일본", "미국", "독일", "프랑스"]
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user