Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
337fccf
docs: 기능 구현 목록 작성
khcho96 Jan 5, 2026
6a03b20
feat: 파일 입력 및 크루별 출석 기록 저장 기능 구현
khcho96 Jan 5, 2026
07b4491
feat: 파일 입력 및 크루별 출석 기록 저장 기능 수정
khcho96 Jan 5, 2026
bcef7c0
feat: 오늘 날짜 출력 및 기능 선택 입력 및 검증 기능 구현
khcho96 Jan 5, 2026
c73fcd9
feat: 출석 확인 기능 구현
khcho96 Jan 5, 2026
063ec20
feat: 출석 수정 기능 구현
khcho96 Jan 5, 2026
532d195
feat: 제적 위험자 확인 기능 구현
khcho96 Jan 5, 2026
0991e35
feat: 제적 위험자 확인 기능 구현
khcho96 Jan 5, 2026
87ed601
test: 테스트 코드 추가
khcho96 Jan 5, 2026
03d688a
docs: 기능 구현 목록 수정
khcho96 Jan 5, 2026
0a493fd
feat: 리팩토링
khcho96 Jan 6, 2026
3f71603
first commit
khcho96 Jan 8, 2026
d608838
docs: 기능 구현 목록 작성
khcho96 Jan 8, 2026
9df6dbc
docs: 기능 구현 목록 수정
khcho96 Jan 8, 2026
9a3971c
feat: 파일 입력 및 출석 정보 저장
khcho96 Jan 8, 2026
d661da7
feat: 기능 선택 및 검증 기능 구현
khcho96 Jan 8, 2026
0d92968
feat: 출석 확인 - 오늘이 등교일이 아니면 예외 발생 기능 구현
khcho96 Jan 8, 2026
cd528b4
feat: 출석 확인 - 닉네임 입력 및 검증 기능 구현
khcho96 Jan 8, 2026
696b5a7
feat: 출석 확인 - 닉네임 입력 및 검증 기능 수정
khcho96 Jan 8, 2026
e22986c
feat: 출석 확인 - 등교 시간 입력 및 검증 기능 구현
khcho96 Jan 8, 2026
c5082a0
feat: 출석 확인 - 출석 정보 저장 기능 구현
khcho96 Jan 8, 2026
02f6f66
feat: 출석 확인 - 출력 기능 구현
khcho96 Jan 8, 2026
0ab0641
feat: 출석 수정 - 닉네임 입력 및 검증 기능 구현
khcho96 Jan 8, 2026
81b4572
feat: 출석 수정 - 날짜 입력 및 검증 기능 구현
khcho96 Jan 8, 2026
55f9649
feat: 출석 수정 - 시각 입력 및 검증 기능 구현
khcho96 Jan 8, 2026
65ff73b
feat: 출석 수정 - 출석 정보 수정 기능 구현
khcho96 Jan 8, 2026
bfc3cea
feat: 출석 수정 - 수정 결과 출력 기능 구현
khcho96 Jan 8, 2026
68b86ea
feat: 크루별 출석 기록 확인 모든 기능 구현
khcho96 Jan 8, 2026
294f833
feat: 제적 위험자 확인 모든 기능 구현
khcho96 Jan 8, 2026
47657a3
refactor: 리팩토링
khcho96 Jan 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 72 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,74 @@
# java-attendance-precourse

<img width="800" height="1100" alt="스크린샷 2025-11-27 20 49 53" src="https://github.com/user-attachments/assets/2d22a1eb-c88c-4874-be38-522ca8f39ef0" />
<img width="800" height="1100" alt="스크린샷 2025-11-27 20 49 55" src="https://github.com/user-attachments/assets/3e800b13-b514-46ca-ab04-f7b96a91e3d3" />
<img width="800" height="1100" alt="스크린샷 2025-11-27 20 49 56" src="https://github.com/user-attachments/assets/2612dc78-c368-43d4-a889-c96635bb81e2" />
<img width="800" height="1100" alt="스크린샷 2025-11-27 20 49 57" src="https://github.com/user-attachments/assets/91b8f32d-d8eb-4afb-8618-2db857046be5" />
<img width="800" height="1100" alt="스크린샷 2025-11-27 20 49 58" src="https://github.com/user-attachments/assets/a60dff46-b1ba-4a9c-b553-b2d33b6f83c3" />
<img width="800" height="1100" alt="스크린샷 2025-11-27 20 50 05" src="https://github.com/user-attachments/assets/ca7622c4-a817-42e6-bd51-b82ea4e64687" />
<img width="800" height="1100" alt="스크린샷 2025-11-27 20 50 06" src="https://github.com/user-attachments/assets/41ba608f-32d7-4cd5-81b8-4ce6815a8df0" />
<img width="800" height="1100" alt="스크린샷 2025-11-27 20 50 08" src="https://github.com/user-attachments/assets/01a2d89f-8974-472b-bc3c-66aa38860e20" />
<img width="800" height="1100" alt="스크린샷 2025-11-27 20 50 09" src="https://github.com/user-attachments/assets/6af5f4c8-d713-484d-925b-612c4c1dd28e" />
<img width="800" height="1100" alt="스크린샷 2025-11-27 20 50 10" src="https://github.com/user-attachments/assets/902e6200-fa14-43aa-b0f1-be86d9740973" />
<img width="800" height="1100" alt="스크린샷 2025-11-27 20 50 11" src="https://github.com/user-attachments/assets/d123d70f-ade3-4f0a-afd8-3b92c95f6d44" />
<img width="800" height="1100" alt="스크린샷 2025-11-27 20 51 49" src="https://github.com/user-attachments/assets/3889a8a8-86e2-4bc8-aca0-8577a5b8253e" />
## 기능 구현 목록
> 기능 작동 순서대로 작성

1. 파일 입력 및 출석 정보 저장
2. 기능 선택 및 검증
- 출력문
- 날짜 요일
- 예외 사항
- 1,2,3,4,Q가 아닌 경우 -> Validator
- 잘못된 형식을 입력하였습니다.
3. 출석확인
1. 오늘이 등교일이 아니면 예외 발생 -> Service
- %s은 등교일이 아닙니다.
2. 닉네임 입력 및 검증
- 예외 사항
- 등록되지 않은 닉네임인 경우 -> Crews
- 등록되지 않은 닉네임입니다.
- 이미 출석을 하였는데 다시 출석 확인을 하는 경우 -> Attendance
- 이미 출석을 확인하였습니다. 필요한 경우 수정 기능을 이용해주세요.
3. 등교 시간 입력 및 검증
- 예외 사항
- 시간을 잘못된 형식으로 입력한 경우 -> Validator
- 잘못된 형식을 입력하였습니다.
- 등교 시간이 캠퍼스 운영 시간이 아닌 경우 -> Service
- 캠퍼스 운영 시간에만 출석이 가능합니다.
4. 출석 정보 저장
5. 확인 결과 출력
- 월, 일, 요일, 시간, 출석 상태
4. 출석 수정
1. 닉네임 입력 및 검증
- 예외 사항
- 등록되지 않은 닉네임인 경우 -> Crews
- 등록되지 않은 닉네임입니다.
2. 날짜 입력 및 검증
- 예외 사항
- 잘못된 형식을 입력한 경우 -> Validator
- 잘못된 형식을 입력하였습니다.
- 등교일이 아니면 예외 발생 -> Service
- %s은 등교일이 아닙니다.
- 미래 날짜로 출석을 수정하는 경우 -> Service
- 아직 수정할 수 없습니다.
3. 시각 입력 및 검증
- 예외 사항
- 시간을 잘못된 형식으로 입력한 경우 -> Validator
- 잘못된 형식을 입력하였습니다.
- 등교 시간이 캠퍼스 운영 시간이 아닌 경우 -> Service
- 캠퍼스 운영 시간에만 출석이 가능합니다.
4. 출석 정보 수정
5. 수정 결과 출력
- 월, 일, 요일
- 이전 시간, 이전 출석 상태
- 변경 시간, 변경 출석 상태
5. 크루별 출석 기록 확인
1. 닉네임 입력 및 검증
- 예외 사항
- 등록되지 않은 닉네임인 경우 -> Crews
- 등록되지 않은 닉네임입니다.
2. 출석 기록 출력
- 전날까지의 출석 기록 출력
- 월, 일, 요일, 시간, 출석 상태
- 아예 안온날은 시간을 --:-- 으로 출력
- 출석, 지각, 결석 횟수
- 위험 대상자라면 해당 상태 출력
6. 제적 위험자 확인
- 전날까지의 기록으로 제적 위험자 파악
- 이름, 결석 횟수, 지각 횟수, 위험 상태
- 정렬 기준
1. 결석 + 지각/3 내림
2. 결석 내림
3. 지각 내림
4. 이름 오름
7. 종료
14 changes: 13 additions & 1 deletion src/main/java/attendance/Application.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
package attendance;

import attendance.command.MenuCommandRegistry;
import attendance.controller.AttendanceController;
import attendance.service.AttendanceService;
import java.io.IOException;

public class Application {
public static void main(String[] args) {
// TODO: 프로그램 구현
AttendanceService service = new AttendanceService();
MenuCommandRegistry registry = MenuCommandRegistry.from(service);
AttendanceController controller = new AttendanceController(registry, service);
try {
controller.run();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
5 changes: 5 additions & 0 deletions src/main/java/attendance/command/Command.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package attendance.command;

public interface Command {
void execute();
}
30 changes: 30 additions & 0 deletions src/main/java/attendance/command/MenuCommandRegistry.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package attendance.command;

import attendance.command.impl.CheckCommand;
import attendance.command.impl.ModificationCommand;
import attendance.command.impl.RecordQueryCommand;
import attendance.command.impl.DangerQueryCommand;
import attendance.service.AttendanceService;
import java.util.EnumMap;

public class MenuCommandRegistry {

private final EnumMap<MenuOption, Command> commands;

private MenuCommandRegistry(EnumMap<MenuOption, Command> commands) {
this.commands = commands;
}

public static MenuCommandRegistry from(AttendanceService service) {
EnumMap<MenuOption, Command> map = new EnumMap<>(MenuOption.class);
map.put(MenuOption.A, new CheckCommand(service));
map.put(MenuOption.B, new ModificationCommand(service));
map.put(MenuOption.C, new RecordQueryCommand(service));
map.put(MenuOption.D, new DangerQueryCommand(service));
return new MenuCommandRegistry(map);
}

public void execute(MenuOption option) {
commands.get(option).execute();
}
}
25 changes: 25 additions & 0 deletions src/main/java/attendance/command/MenuOption.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package attendance.command;

import attendance.constant.ErrorMessage;
import java.util.Arrays;

public enum MenuOption {
A("1"),
B("2"),
C("3"),
D("4"),
QUIT("Q");

private final String command;

MenuOption(String command) {
this.command = command;
}

public static MenuOption from(String command) {
return Arrays.stream(values())
.filter(opt -> opt.command.equals(command))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException(ErrorMessage.FORMAT_ERROR.getErrorMessage()));
}
}
35 changes: 35 additions & 0 deletions src/main/java/attendance/command/impl/CheckCommand.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package attendance.command.impl;

import attendance.command.Command;
import attendance.service.AttendanceService;
import attendance.util.InputParser;
import attendance.view.InputView;
import attendance.view.OutputView;
import camp.nextstep.edu.missionutils.DateTimes;
import java.time.LocalDate;
import java.time.LocalTime;

public class CheckCommand implements Command {

private final AttendanceService service;

public CheckCommand(AttendanceService service) {
this.service = service;
}

@Override
public void execute() {
LocalDate now = DateTimes.now().toLocalDate();
service.validateHoliday(now);

String name = InputParser.parseName(InputView.readName());
service.validateCheckPossible(name, now);

LocalTime time = InputParser.parseTime(InputView.readTime());
service.validateOperationTime(time);

service.check(name, now, time);

OutputView.printCheckResult(now, time);
}
}
23 changes: 23 additions & 0 deletions src/main/java/attendance/command/impl/DangerQueryCommand.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package attendance.command.impl;

import attendance.command.Command;
import attendance.domain.Crew;
import attendance.service.AttendanceService;
import attendance.view.OutputView;
import java.util.List;

public class DangerQueryCommand implements Command {

private final AttendanceService service;

public DangerQueryCommand(AttendanceService service) {
this.service = service;
}

@Override
public void execute() {
List<Crew> dangers = service.getDangers();

OutputView.printDangers(dangers);
}
}
34 changes: 34 additions & 0 deletions src/main/java/attendance/command/impl/ModificationCommand.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package attendance.command.impl;

import attendance.command.Command;
import attendance.service.AttendanceService;
import attendance.util.InputParser;
import attendance.view.InputView;
import attendance.view.OutputView;
import java.time.LocalDate;
import java.time.LocalTime;

public class ModificationCommand implements Command {

private final AttendanceService service;

public ModificationCommand(AttendanceService service) {
this.service = service;
}

@Override
public void execute() {
String name = InputParser.parseName(InputView.readModifiedName());
service.validateModificationPossible(name);

LocalDate date = InputParser.parseDate(InputView.readModifiedDate());
service.validateModificationPossible(date);

LocalTime time = InputParser.parseTime(InputView.readModifiedTime());
service.validateModificationPossible(time);

LocalTime oldTime = service.modify(name, date, time);

OutputView.printModificationResult(date, time, oldTime);
}
}
25 changes: 25 additions & 0 deletions src/main/java/attendance/command/impl/RecordQueryCommand.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package attendance.command.impl;

import attendance.command.Command;
import attendance.domain.Crew;
import attendance.service.AttendanceService;
import attendance.util.InputParser;
import attendance.view.InputView;
import attendance.view.OutputView;

public class RecordQueryCommand implements Command {

private final AttendanceService service;

public RecordQueryCommand(AttendanceService service) {
this.service = service;
}

@Override
public void execute() {
String name = InputParser.parseName(InputView.readName());
Crew crew = service.getAttendanceRecords(name);

OutputView.printRecords(crew);
}
}
34 changes: 34 additions & 0 deletions src/main/java/attendance/constant/AttendanceState.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package attendance.constant;

import java.time.LocalDate;
import java.time.LocalTime;
import java.util.Arrays;

public enum AttendanceState {

ABSENCE(30, "결석"),
LATE(5, "지각"),
ATTENDANCE(0, "출석"),
;

private final int lateMinutes;
private final String name;

AttendanceState(int lateMinutes, String name) {
this.lateMinutes = lateMinutes;
this.name = name;
}

public static AttendanceState of(LocalDate date, LocalTime time) {
Standard standard = Standard.from(date);

return Arrays.stream(values())
.filter(danger -> standard.getStartTime().plusMinutes(danger.lateMinutes).isBefore(time))
.findFirst()
.orElse(ATTENDANCE);
}

public String getName() {
return name;
}
}
31 changes: 31 additions & 0 deletions src/main/java/attendance/constant/Danger.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package attendance.constant;

import java.util.Arrays;

public enum Danger {

OUT(6, "제적"),
INTERVIEW(3, "면담"),
WARNING(2, "경고"),
NONE(0, ""),
;

private final int absenceCount;
private final String name;

Danger(int absenceCount, String name) {
this.absenceCount = absenceCount;
this.name = name;
}

public static Danger from(int absenceCount) {
return Arrays.stream(values())
.filter(danger -> danger.absenceCount <= absenceCount)
.findFirst()
.orElse(NONE);
}

public String getName() {
return name;
}
}
23 changes: 23 additions & 0 deletions src/main/java/attendance/constant/ErrorMessage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package attendance.constant;

public enum ErrorMessage {

FORMAT_ERROR("잘못된 형식을 입력하였습니다."),
NO_ATTENDANCE_DAY_ERROR("%s은 등교일이 아닙니다."),
NO_EXIST_NAME_ERROR("등록되지 않은 닉네임입니다."),
ALREADY_ATTENDANCE_ERROR("이미 출석을 확인하였습니다. 필요한 경우 수정 기능을 이용해주세요."),
NO_OPERATION_TIME_ERROR("캠퍼스 운영 시간에만 출석이 가능합니다."),
FUTURE_DATE_ERROR("아직 수정할 수 없습니다."),
;

private static final String ERROR_MESSAGE_PREFIX = "[ERROR] ";
private final String errorMessage;

ErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}

public String getErrorMessage(Object... args) {
return ERROR_MESSAGE_PREFIX + String.format(errorMessage, args);
}
}
Loading