feat(frontend): 승인 템플릿 API 통합 및 디버그 로그인 확장

- docs 폴더 문서를 최신 API 계약으로 갱신하고 가이드를 다듬었다\n- approvals data/presentation 레이어를 API v4 스펙에 맞춰 리팩터링했다\n- approver 자동완성 위젯을 신규 공유 레포지토리에 맞춰 교체하고 UX를 보강했다\n- inventory/rental 페이지 테이블 초기화 시 승인 기준 연동을 정비했다\n- 로그인 페이지 디버그 버튼을 tera/exa 계정으로 분리해 QA 로그인을 단순화했다\n- get_it 등록과 테스트 케이스를 신규 공유 리포지토리에 맞춰 업데이트했다
This commit is contained in:
JiWoong Sul
2025-11-05 17:05:38 +09:00
parent 3e83408aa7
commit fa0bda5ea4
28 changed files with 1102 additions and 545 deletions

View File

@@ -0,0 +1,84 @@
import 'package:dio/dio.dart';
import '../../../../../core/network/api_client.dart';
import '../../../../../core/network/api_routes.dart';
import '../../domain/entities/approval_approver_candidate.dart';
import '../../domain/repositories/approval_approver_repository.dart';
import '../dtos/approval_approver_candidate_dto.dart';
/// 승인자 자동완성용 원격 저장소 구현체.
class ApprovalApproverRepositoryRemote implements ApprovalApproverRepository {
ApprovalApproverRepositoryRemote({required ApiClient apiClient})
: _api = apiClient;
final ApiClient _api;
static const _basePath = '${ApiRoutes.apiV1}/users';
@override
Future<List<ApprovalApproverCandidate>> search({
required String keyword,
int limit = 20,
}) async {
final trimmed = keyword.trim();
if (trimmed.isEmpty) {
return const [];
}
final response = await _api.get<Map<String, dynamic>>(
_basePath,
query: _buildQuery(limit: limit, keyword: trimmed),
options: Options(responseType: ResponseType.json),
);
return _mapCandidates(response.data);
}
@override
Future<ApprovalApproverCandidate?> fetchById(int id) async {
final response = await _api.get<Map<String, dynamic>>(
'$_basePath/$id',
query: ApiClient.buildQuery(include: const ['group']),
options: Options(responseType: ResponseType.json),
);
final payload = _api.unwrapAsMap(response);
if (payload.isEmpty) {
return null;
}
return ApprovalApproverCandidateDto.fromJson(payload).toEntity();
}
@override
Future<List<ApprovalApproverCandidate>> listInitial({int limit = 20}) async {
final response = await _api.get<Map<String, dynamic>>(
_basePath,
query: _buildQuery(limit: limit),
options: Options(responseType: ResponseType.json),
);
return _mapCandidates(response.data);
}
Map<String, dynamic> _buildQuery({required int limit, String? keyword}) {
return ApiClient.buildQuery(
page: 1,
pageSize: limit,
q: keyword,
sort: 'name',
order: 'asc',
include: const ['group'],
filters: const {'is_active': true},
);
}
List<ApprovalApproverCandidate> _mapCandidates(
Map<String, dynamic>? payload,
) {
return (payload?['items'] as List<dynamic>? ?? const [])
.whereType<Map<String, dynamic>>()
.map(ApprovalApproverCandidateDto.fromJson)
.map((dto) => dto.toEntity())
.toList(growable: false);
}
}