web: migrate health notifications to js_interop; add browser hook
Some checks failed
Flutter Test & Quality Check / Test on macos-latest (push) Has been cancelled
Flutter Test & Quality Check / Test on ubuntu-latest (push) Has been cancelled
Flutter Test & Quality Check / Build APK (push) Has been cancelled

- 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:
JiWoong Sul
2025-09-08 17:39:00 +09:00
parent 519e1883a3
commit 655d473413
55 changed files with 2729 additions and 4968 deletions

View File

@@ -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;
}

View File

@@ -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)

View File

@@ -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) {