feat: V/R 유지보수 시스템 전환 및 대시보드 테이블 형태 완성
- V/R 시스템 완전 전환: WARRANTY/CONTRACT/INSPECTION → V(방문)/R(원격) - 유지보수 대시보드 카드 → StandardDataTable 테이블 형태 전환 - "조회중..." 문제 해결: 백엔드 직접 필드 사용 (equipment_model, company_name) - MaintenanceDto 신규 필드 추가: company_id, company_name, equipment_serial, equipment_model - preloadEquipmentData 비활성화로 불필요한 equipment-history API 호출 제거 - CO-STAR 프레임워크 적용 및 CLAUDE.md v3.0 업데이트 - Flutter Analyze ERROR: 0 유지, 100% shadcn_ui 컴플라이언스 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
181
lib/data/models/maintenance_stats_dto.dart
Normal file
181
lib/data/models/maintenance_stats_dto.dart
Normal file
@@ -0,0 +1,181 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'maintenance_stats_dto.freezed.dart';
|
||||
part 'maintenance_stats_dto.g.dart';
|
||||
|
||||
@freezed
|
||||
class MaintenanceStatsDto with _$MaintenanceStatsDto {
|
||||
const factory MaintenanceStatsDto({
|
||||
// 기본 계약 통계
|
||||
@JsonKey(name: 'active_contracts') @Default(0) int activeContracts,
|
||||
@JsonKey(name: 'total_contracts') @Default(0) int totalContracts,
|
||||
|
||||
// 만료 기간별 통계 (사용자 요구사항)
|
||||
@JsonKey(name: 'expiring_60_days') @Default(0) int expiring60Days,
|
||||
@JsonKey(name: 'expiring_30_days') @Default(0) int expiring30Days,
|
||||
@JsonKey(name: 'expiring_7_days') @Default(0) int expiring7Days,
|
||||
@JsonKey(name: 'expired_contracts') @Default(0) int expiredContracts,
|
||||
|
||||
// 유지보수 타입별 통계 (V/R 시스템)
|
||||
@JsonKey(name: 'visit_contracts') @Default(0) int visitContracts,
|
||||
@JsonKey(name: 'remote_contracts') @Default(0) int remoteContracts,
|
||||
|
||||
// 예정된 작업 통계
|
||||
@JsonKey(name: 'upcoming_visits') @Default(0) int upcomingVisits,
|
||||
@JsonKey(name: 'overdue_maintenances') @Default(0) int overdueMaintenances,
|
||||
|
||||
// 추가 메트릭
|
||||
@JsonKey(name: 'total_revenue_at_risk') @Default(0.0) double totalRevenueAtRisk,
|
||||
@JsonKey(name: 'completion_rate') @Default(0.0) double completionRate,
|
||||
|
||||
// 마지막 업데이트 시간
|
||||
@JsonKey(name: 'updated_at') DateTime? updatedAt,
|
||||
}) = _MaintenanceStatsDto;
|
||||
|
||||
factory MaintenanceStatsDto.fromJson(Map<String, dynamic> json) =>
|
||||
_$MaintenanceStatsDtoFromJson(json);
|
||||
}
|
||||
|
||||
// 대시보드 상태별 카드 데이터
|
||||
@freezed
|
||||
class MaintenanceStatusCardData with _$MaintenanceStatusCardData {
|
||||
const factory MaintenanceStatusCardData({
|
||||
required String title,
|
||||
required int count,
|
||||
required String subtitle,
|
||||
required MaintenanceCardStatus status,
|
||||
String? actionLabel,
|
||||
}) = _MaintenanceStatusCardData;
|
||||
|
||||
factory MaintenanceStatusCardData.fromJson(Map<String, dynamic> json) =>
|
||||
_$MaintenanceStatusCardDataFromJson(json);
|
||||
}
|
||||
|
||||
// 카드 상태 열거형
|
||||
enum MaintenanceCardStatus {
|
||||
@JsonValue('active')
|
||||
active,
|
||||
@JsonValue('warning')
|
||||
warning,
|
||||
@JsonValue('urgent')
|
||||
urgent,
|
||||
@JsonValue('critical')
|
||||
critical,
|
||||
@JsonValue('expired')
|
||||
expired,
|
||||
}
|
||||
|
||||
// 카드 상태별 정보 헬퍼
|
||||
class MaintenanceCardHelper {
|
||||
static Map<MaintenanceCardStatus, Map<String, dynamic>> get statusConfig => {
|
||||
MaintenanceCardStatus.active: {
|
||||
'color': '#059669', // Green
|
||||
'icon': 'assignment',
|
||||
'description': '정상 활성'
|
||||
},
|
||||
MaintenanceCardStatus.warning: {
|
||||
'color': '#F59E0B', // Amber
|
||||
'icon': 'warning',
|
||||
'description': '주의 필요'
|
||||
},
|
||||
MaintenanceCardStatus.urgent: {
|
||||
'color': '#EA580C', // Orange
|
||||
'icon': 'schedule',
|
||||
'description': '긴급 처리'
|
||||
},
|
||||
MaintenanceCardStatus.critical: {
|
||||
'color': '#DC2626', // Red
|
||||
'icon': 'alert_circle',
|
||||
'description': '즉시 조치'
|
||||
},
|
||||
MaintenanceCardStatus.expired: {
|
||||
'color': '#991B1B', // Dark Red
|
||||
'icon': 'error',
|
||||
'description': '만료됨'
|
||||
},
|
||||
};
|
||||
|
||||
static String getColorForStatus(MaintenanceCardStatus status) {
|
||||
return statusConfig[status]?['color'] ?? '#6B7280';
|
||||
}
|
||||
|
||||
static String getIconForStatus(MaintenanceCardStatus status) {
|
||||
return statusConfig[status]?['icon'] ?? 'info';
|
||||
}
|
||||
|
||||
static String getDescriptionForStatus(MaintenanceCardStatus status) {
|
||||
return statusConfig[status]?['description'] ?? '상태 정보 없음';
|
||||
}
|
||||
}
|
||||
|
||||
// 대시보드 카드 생성 헬퍼
|
||||
extension MaintenanceStatsDashboard on MaintenanceStatsDto {
|
||||
List<MaintenanceStatusCardData> get dashboardCards => [
|
||||
// 60일 내 만료 예정
|
||||
MaintenanceStatusCardData(
|
||||
title: '60일 내',
|
||||
count: expiring60Days,
|
||||
subtitle: '만료 예정',
|
||||
status: expiring60Days > 0 ? MaintenanceCardStatus.warning : MaintenanceCardStatus.active,
|
||||
actionLabel: '계획하기',
|
||||
),
|
||||
|
||||
// 30일 내 만료 예정
|
||||
MaintenanceStatusCardData(
|
||||
title: '30일 내',
|
||||
count: expiring30Days,
|
||||
subtitle: '만료 예정',
|
||||
status: expiring30Days > 0 ? MaintenanceCardStatus.urgent : MaintenanceCardStatus.active,
|
||||
actionLabel: '예약하기',
|
||||
),
|
||||
|
||||
// 7일 내 만료 예정
|
||||
MaintenanceStatusCardData(
|
||||
title: '7일 내',
|
||||
count: expiring7Days,
|
||||
subtitle: '만료 임박',
|
||||
status: expiring7Days > 0 ? MaintenanceCardStatus.critical : MaintenanceCardStatus.active,
|
||||
actionLabel: '즉시 처리',
|
||||
),
|
||||
|
||||
// 만료된 계약
|
||||
MaintenanceStatusCardData(
|
||||
title: '만료됨',
|
||||
count: expiredContracts,
|
||||
subtitle: '조치 필요',
|
||||
status: expiredContracts > 0 ? MaintenanceCardStatus.expired : MaintenanceCardStatus.active,
|
||||
actionLabel: '갱신하기',
|
||||
),
|
||||
];
|
||||
|
||||
// 추가 요약 정보
|
||||
Map<String, int> get summaryStats => {
|
||||
'총 활성 계약': activeContracts,
|
||||
'방문 계약': visitContracts,
|
||||
'원격 계약': remoteContracts,
|
||||
'예정된 방문': upcomingVisits,
|
||||
'연체 유지보수': overdueMaintenances,
|
||||
};
|
||||
|
||||
// 위험도 계산
|
||||
double get riskScore {
|
||||
double score = 0.0;
|
||||
if (totalContracts == 0) return 0.0;
|
||||
|
||||
score += (expiredContracts / totalContracts) * 0.4; // 40% 가중치
|
||||
score += (expiring7Days / totalContracts) * 0.3; // 30% 가중치
|
||||
score += (expiring30Days / totalContracts) * 0.2; // 20% 가중치
|
||||
score += (expiring60Days / totalContracts) * 0.1; // 10% 가중치
|
||||
|
||||
return score.clamp(0.0, 1.0);
|
||||
}
|
||||
|
||||
// 위험도 상태
|
||||
MaintenanceCardStatus get riskStatus {
|
||||
final risk = riskScore;
|
||||
if (risk >= 0.7) return MaintenanceCardStatus.critical;
|
||||
if (risk >= 0.5) return MaintenanceCardStatus.urgent;
|
||||
if (risk >= 0.3) return MaintenanceCardStatus.warning;
|
||||
return MaintenanceCardStatus.active;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user