web: migrate health notifications to js_interop; add browser hook
- Replace dart:js with package:js in health_check_service_web.dart\n- Implement showHealthCheckNotification in web/index.html\n- Pin js dependency to ^0.6.7 for flutter_secure_storage_web compatibility auth: harden AuthInterceptor + tests - Allow overrideAuthRepository injection for testing\n- Normalize imports to package: paths\n- Add unit test covering token attach, 401→refresh→retry, and failure path\n- Add integration test skeleton gated by env vars ui/data: map User.companyName to list column - Add companyName to domain User\n- Map UserDto.company?.name\n- Render companyName in user_list cleanup: remove legacy equipment table + unused code; minor warnings - Remove _buildFlexibleTable and unused helpers\n- Remove unused zipcode details and cache retry constant\n- Fix null-aware and non-null assertions\n- Address child-last warnings in administrator dialog docs: update AGENTS.md session context
This commit is contained in:
@@ -1137,6 +1137,14 @@ class SidebarMenu extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
_buildMenuItem(
|
||||
icon: Icons.calendar_month_outlined,
|
||||
title: '임대 관리',
|
||||
route: Routes.rent,
|
||||
isActive: currentRoute == Routes.rent,
|
||||
badge: null,
|
||||
),
|
||||
|
||||
|
||||
if (!collapsed) ...[
|
||||
@@ -1376,6 +1384,8 @@ class SidebarMenu extends StatelessWidget {
|
||||
return Icons.factory;
|
||||
case Icons.category_outlined:
|
||||
return Icons.category;
|
||||
case Icons.calendar_month_outlined:
|
||||
return Icons.calendar_month;
|
||||
default:
|
||||
return outlinedIcon;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:superport/screens/common/components/shadcn_components.dart';
|
||||
import 'package:superport/screens/common/theme_shadcn.dart';
|
||||
|
||||
/// 폼 화면의 일관된 레이아웃을 제공하는 템플릿 위젯
|
||||
class FormLayoutTemplate extends StatelessWidget {
|
||||
@@ -27,27 +28,30 @@ class FormLayoutTemplate extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Color(0xFFF5F7FA),
|
||||
backgroundColor: ShadcnTheme.background, // Phase 10: 통일된 배경색
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
style: ShadcnTheme.headingH3.copyWith( // Phase 10: 표준 헤딩 스타일
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF1A1F36),
|
||||
color: ShadcnTheme.foreground,
|
||||
),
|
||||
),
|
||||
backgroundColor: Colors.white,
|
||||
backgroundColor: ShadcnTheme.card, // Phase 10: 카드 배경색
|
||||
elevation: 0,
|
||||
leading: IconButton(
|
||||
icon: Icon(Icons.arrow_back_ios, color: Color(0xFF6B7280), size: 20),
|
||||
icon: Icon(
|
||||
Icons.arrow_back_ios,
|
||||
color: ShadcnTheme.mutedForeground, // Phase 10: 뮤트된 전경색
|
||||
size: 20,
|
||||
),
|
||||
onPressed: onCancel ?? () => Navigator.of(context).pop(),
|
||||
),
|
||||
actions: customActions != null ? [customActions!] : null,
|
||||
bottom: PreferredSize(
|
||||
preferredSize: Size.fromHeight(1),
|
||||
child: Container(
|
||||
color: Color(0xFFE5E7EB),
|
||||
color: ShadcnTheme.border, // Phase 10: 통일된 테두리 색상
|
||||
height: 1,
|
||||
),
|
||||
),
|
||||
@@ -60,13 +64,13 @@ class FormLayoutTemplate extends StatelessWidget {
|
||||
Widget _buildBottomBar(BuildContext context) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
color: ShadcnTheme.card, // Phase 10: 카드 배경색
|
||||
border: Border(
|
||||
top: BorderSide(color: Color(0xFFE5E7EB), width: 1),
|
||||
top: BorderSide(color: ShadcnTheme.border, width: 1), // Phase 10: 통일된 테두리
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.05),
|
||||
color: ShadcnTheme.foreground.withValues(alpha: 0.05), // Phase 10: 그림자 색상
|
||||
offset: Offset(0, -2),
|
||||
blurRadius: 4,
|
||||
),
|
||||
@@ -125,24 +129,22 @@ class FormSection extends StatelessWidget {
|
||||
if (title != null) ...[
|
||||
Text(
|
||||
title!,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
style: ShadcnTheme.bodyLarge.copyWith( // Phase 10: 표준 바디 라지
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF1A1F36),
|
||||
color: ShadcnTheme.foreground, // Phase 10: 전경색
|
||||
),
|
||||
),
|
||||
if (subtitle != null) ...[
|
||||
SizedBox(height: 4),
|
||||
Text(
|
||||
subtitle!,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Color(0xFF6B7280),
|
||||
style: ShadcnTheme.bodyMedium.copyWith( // Phase 10: 표준 바디 미디엄
|
||||
color: ShadcnTheme.mutedForeground, // Phase 10: 뮤트된 전경색
|
||||
),
|
||||
),
|
||||
],
|
||||
SizedBox(height: 20),
|
||||
Divider(color: Color(0xFFE5E7EB), height: 1),
|
||||
Divider(color: ShadcnTheme.border, height: 1), // Phase 10: 테두리 색상
|
||||
SizedBox(height: 20),
|
||||
],
|
||||
if (children.isNotEmpty)
|
||||
|
||||
@@ -45,19 +45,19 @@ class ShadcnTheme {
|
||||
static const Color infoLight = Color(0xFFCFFAFE); // cyan-100
|
||||
static const Color infoForeground = Color(0xFFFFFFFF);
|
||||
|
||||
// ============= 비즈니스 상태 색상 =============
|
||||
// 회사 구분 색상
|
||||
static const Color companyHeadquarters = Color(0xFF2563EB); // 본사 - Primary Blue (권위)
|
||||
static const Color companyBranch = Color(0xFF7C3AED); // 지점 - Purple (연결성)
|
||||
static const Color companyPartner = Color(0xFF059669); // 파트너사 - Green (협력)
|
||||
static const Color companyCustomer = Color(0xFFEA580C); // 고객사 - Orange (활력)
|
||||
// ============= 비즈니스 상태 색상 (색체심리학 기반) =============
|
||||
// 회사 구분 색상 - Phase 10 업데이트
|
||||
static const Color companyHeadquarters = Color(0xFF2563EB); // 본사 - 진한 파랑 (권위, 안정성)
|
||||
static const Color companyBranch = Color(0xFF3B82F6); // 지점 - 밝은 파랑 (연결성, 확장)
|
||||
static const Color companyPartner = Color(0xFF10B981); // 파트너사 - 에메랄드 (협력, 신뢰)
|
||||
static const Color companyCustomer = Color(0xFF059669); // 고객사 - 진한 그린 (성장, 번영)
|
||||
|
||||
// 장비 상태 색상
|
||||
static const Color equipmentIn = Color(0xFF059669); // 입고 - Green (진입/추가)
|
||||
static const Color equipmentOut = Color(0xFF0891B2); // 출고 - Cyan (이동/프로세스)
|
||||
static const Color equipmentRent = Color(0xFF7C3AED); // 대여 - Purple (임시 상태)
|
||||
static const Color equipmentDisposal = Color(0xFF6B7280); // 폐기 - Gray (비활성)
|
||||
static const Color equipmentRepair = Color(0xFFD97706); // 수리중 - Amber (주의 필요)
|
||||
// 트랜잭션 상태 색상 - Phase 10 업데이트
|
||||
static const Color equipmentIn = Color(0xFF10B981); // 입고 - 에메랄드 (추가/성장)
|
||||
static const Color equipmentOut = Color(0xFF3B82F6); // 출고 - 블루 (이동/처리)
|
||||
static const Color equipmentRent = Color(0xFF8B5CF6); // 대여 - Purple (임시 상태)
|
||||
static const Color equipmentDisposal = Color(0xFF6B7280); // 폐기 - Gray (종료/비활성)
|
||||
static const Color equipmentRepair = Color(0xFFF59E0B); // 수리중 - Amber (주의/진행)
|
||||
static const Color equipmentUnknown = Color(0xFF9CA3AF); // 알수없음 - Light Gray
|
||||
|
||||
// ============= UI 요소 색상 =============
|
||||
@@ -93,8 +93,15 @@ class ShadcnTheme {
|
||||
|
||||
// 추가 색상 (기존 호환)
|
||||
static const Color blue = primary;
|
||||
static const Color purple = companyBranch;
|
||||
static const Color green = companyPartner;
|
||||
static const Color purple = equipmentRent;
|
||||
static const Color green = equipmentIn;
|
||||
|
||||
// Phase 10 - 알림/경고 색상 체계 (긴급도 기반)
|
||||
static const Color alertNormal = Color(0xFF10B981); // 60일 이상 - 안전 (그린)
|
||||
static const Color alertWarning60 = Color(0xFFF59E0B); // 60일 이내 - 주의 (앰버)
|
||||
static const Color alertWarning30 = Color(0xFFF97316); // 30일 이내 - 경고 (오렌지)
|
||||
static const Color alertCritical7 = Color(0xFFEF4444); // 7일 이내 - 위험 (레드)
|
||||
static const Color alertExpired = Color(0xFFDC2626); // 만료됨 - 심각 (진한 레드)
|
||||
|
||||
static const Color radius = Color(0xFF000000); // 사용하지 않음
|
||||
|
||||
@@ -526,51 +533,60 @@ class ShadcnTheme {
|
||||
}
|
||||
|
||||
// ============= 유틸리티 메서드 =============
|
||||
/// 회사 타입에 따른 색상 반환
|
||||
/// 회사 타입에 따른 색상 반환 (Phase 10 업데이트)
|
||||
static Color getCompanyColor(String type) {
|
||||
switch (type.toLowerCase()) {
|
||||
case '본사':
|
||||
case 'headquarters':
|
||||
return companyHeadquarters;
|
||||
return companyHeadquarters; // #2563eb - 진한 파랑
|
||||
case '지점':
|
||||
case 'branch':
|
||||
return companyBranch;
|
||||
return companyBranch; // #3b82f6 - 밝은 파랑
|
||||
case '파트너사':
|
||||
case 'partner':
|
||||
return companyPartner;
|
||||
return companyPartner; // #10b981 - 에메랄드
|
||||
case '고객사':
|
||||
case 'customer':
|
||||
return companyCustomer;
|
||||
return companyCustomer; // #059669 - 진한 그린
|
||||
default:
|
||||
return secondary;
|
||||
}
|
||||
}
|
||||
|
||||
/// 장비 상태에 따른 색상 반환
|
||||
/// 트랜잭션 상태에 따른 색상 반환 (Phase 10 업데이트)
|
||||
static Color getEquipmentStatusColor(String status) {
|
||||
switch (status.toLowerCase()) {
|
||||
case '입고':
|
||||
case 'in':
|
||||
return equipmentIn;
|
||||
return equipmentIn; // #10b981 - 에메랄드
|
||||
case '출고':
|
||||
case 'out':
|
||||
return equipmentOut;
|
||||
return equipmentOut; // #3b82f6 - 블루
|
||||
case '대여':
|
||||
case 'rent':
|
||||
return equipmentRent;
|
||||
return equipmentRent; // #8b5cf6 - 퍼플
|
||||
case '폐기':
|
||||
case 'disposal':
|
||||
return equipmentDisposal;
|
||||
return equipmentDisposal; // #6b7280 - 그레이
|
||||
case '수리중':
|
||||
case 'repair':
|
||||
return equipmentRepair;
|
||||
return equipmentRepair; // #f59e0b - 앰버
|
||||
case '알수없음':
|
||||
case 'unknown':
|
||||
return equipmentUnknown;
|
||||
return equipmentUnknown; // #9ca3af - 라이트 그레이
|
||||
default:
|
||||
return secondary;
|
||||
}
|
||||
}
|
||||
|
||||
/// 알림/경고 긴급도에 따른 색상 반환 (Phase 10 신규 추가)
|
||||
static Color getAlertColor(int daysUntilExpiry) {
|
||||
if (daysUntilExpiry < 0) return alertExpired; // 만료됨
|
||||
if (daysUntilExpiry <= 7) return alertCritical7; // 7일 이내
|
||||
if (daysUntilExpiry <= 30) return alertWarning30; // 30일 이내
|
||||
if (daysUntilExpiry <= 60) return alertWarning60; // 60일 이내
|
||||
return alertNormal; // 60일 이상
|
||||
}
|
||||
|
||||
/// 상태별 배경색 반환 (연한 버전)
|
||||
static Color getStatusBackgroundColor(String status) {
|
||||
|
||||
Reference in New Issue
Block a user