- 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>
181 lines
5.8 KiB
Dart
181 lines
5.8 KiB
Dart
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;
|
|
}
|
|
} |