학생/교사/관리자 대시보드를 하나의 공통 화면으로 통합
StudentDashboard, TeacherDashboard, AdminDashboard(진입 라우팅)를 MainDashboard 하나로 합치고, 역할(role)에 따라 배너 색상/문구와 보이는 타일만 달라지게 함. 그리드 레이아웃/카드 디자인은 이제 한 곳에서만 관리되어, 앞으로 UI를 고치면 모든 역할에 자동 적용됨. - 2061(개발자) 계정에 기존에 빠져있던 "스마트기기 반출 대장" 타일 추가 - role=='admin' 실 계정도 이제 학생 계정 관리/출석 확인 등 교사 기능에 접근 가능 (관리자는 교사 권한을 포함하는 것으로 정리) - 작은 DB 초기화 전용 화면(admin_dashboard.dart)은 "서버 DB 제어" 타일의 목적지로만 유지, 로그인 직후 랜딩 화면 역할은 제거 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+17
-32
@@ -3,9 +3,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import '../config.dart' show schoolName;
|
import '../config.dart' show schoolName;
|
||||||
import '../function/login_controller.dart';
|
import '../function/login_controller.dart';
|
||||||
import 'student_dashboard.dart';
|
import 'main_dashboard.dart';
|
||||||
import 'teacher_dashboard.dart';
|
|
||||||
import 'admin_dashboard.dart';
|
|
||||||
import 'teacher_register_screen.dart';
|
import 'teacher_register_screen.dart';
|
||||||
|
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
@@ -50,9 +48,10 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||||||
Navigator.pushReplacement(
|
Navigator.pushReplacement(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => StudentDashboard(
|
builder: (context) => MainDashboard(
|
||||||
studentId: result.studentId!,
|
userId: result.studentId!,
|
||||||
studentName: result.name!,
|
userName: result.name!,
|
||||||
|
role: 'student',
|
||||||
isDeviceMatched: true,
|
isDeviceMatched: true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -154,9 +153,9 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
Navigator.pop(dialogContext);
|
Navigator.pop(dialogContext);
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(
|
||||||
SnackBar(content: Text(message)),
|
context,
|
||||||
);
|
).showSnackBar(SnackBar(content: Text(message)));
|
||||||
_navigateBasedOnRole(
|
_navigateBasedOnRole(
|
||||||
role,
|
role,
|
||||||
studentId,
|
studentId,
|
||||||
@@ -164,9 +163,9 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||||||
isDeviceMatched,
|
isDeviceMatched,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(
|
||||||
SnackBar(content: Text(message)),
|
context,
|
||||||
);
|
).showSnackBar(SnackBar(content: Text(message)));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: const Text('변경하고 시작하기'),
|
child: const Text('변경하고 시작하기'),
|
||||||
@@ -186,32 +185,18 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||||||
String name,
|
String name,
|
||||||
bool isDeviceMatched,
|
bool isDeviceMatched,
|
||||||
) {
|
) {
|
||||||
if (role == 'teacher') {
|
|
||||||
Navigator.pushReplacement(
|
Navigator.pushReplacement(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) =>
|
builder: (context) => MainDashboard(
|
||||||
TeacherDashboard(teacherId: studentId, teacherName: name),
|
userId: studentId,
|
||||||
),
|
userName: name,
|
||||||
);
|
role: role,
|
||||||
} else if (role == 'admin') {
|
|
||||||
Navigator.pushReplacement(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(builder: (context) => const AdminDashboard()),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
Navigator.pushReplacement(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => StudentDashboard(
|
|
||||||
studentId: studentId,
|
|
||||||
studentName: name,
|
|
||||||
isDeviceMatched: isDeviceMatched,
|
isDeviceMatched: isDeviceMatched,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void _showErrorDialog(String message) {
|
void _showErrorDialog(String message) {
|
||||||
showDialog(
|
showDialog(
|
||||||
@@ -403,7 +388,7 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||||||
onPressed: () => _showLegacyPasswordDialog(
|
onPressed: () => _showLegacyPasswordDialog(
|
||||||
'선생님',
|
'선생님',
|
||||||
'1234',
|
'1234',
|
||||||
const TeacherDashboard(),
|
const MainDashboard(role: 'teacher'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
TextButton.icon(
|
TextButton.icon(
|
||||||
@@ -419,7 +404,7 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||||||
onPressed: () => _showLegacyPasswordDialog(
|
onPressed: () => _showLegacyPasswordDialog(
|
||||||
'시스템 관리자',
|
'시스템 관리자',
|
||||||
'4936',
|
'4936',
|
||||||
const AdminDashboard(),
|
const MainDashboard(role: 'admin'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,59 +1,68 @@
|
|||||||
// 🎓 학생 대시보드 (UI 전용). NFC 주머니 체크인 진입, 실시간 학교 상황 안내,
|
// 🏠 통합 대시보드 (UI 전용). 학생/교사/관리자가 전부 이 화면 하나를 공유하고,
|
||||||
// 개발자 마스터 계정 전용 관리 메뉴(현황/DB제어/계정관리/삭제) 카드를 그린다.
|
// 권한(role)에 따라 배너 색상/문구와 보이는 타일만 달라진다.
|
||||||
// 계정 삭제 서버 통신은 lib/function/student_dashboard_controller.dart가 담당한다.
|
// 레이아웃(그리드, 카드 디자인)을 한 곳에서 고치면 모든 역할에 동시에 적용된다.
|
||||||
|
// 계정 강제 삭제 서버 통신은 lib/function/student_dashboard_controller.dart가 담당한다.
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import '../function/student_dashboard_controller.dart';
|
import '../function/student_dashboard_controller.dart';
|
||||||
import 'device_checkout_request_screen.dart';
|
|
||||||
import 'nfc_poccket_checkin_screen.dart';
|
|
||||||
import 'login_screen.dart';
|
|
||||||
import 'teacher_attendance_page.dart';
|
|
||||||
import 'admin_dashboard.dart';
|
import 'admin_dashboard.dart';
|
||||||
import 'teacher_student_management_page.dart';
|
import 'device_checkout_ledger_page.dart';
|
||||||
|
import 'device_checkout_request_screen.dart';
|
||||||
|
import 'login_screen.dart';
|
||||||
|
import 'nfc_poccket_checkin_screen.dart';
|
||||||
import 'nfc_tag_writer_screen.dart';
|
import 'nfc_tag_writer_screen.dart';
|
||||||
|
import 'teacher_attendance_page.dart';
|
||||||
|
import 'teacher_student_management_page.dart';
|
||||||
|
|
||||||
// -------------------------------------------------------------
|
class MainDashboard extends StatefulWidget {
|
||||||
// 2. 학생 대시보드 (StudentDashboard)
|
final String? userId;
|
||||||
// -------------------------------------------------------------
|
final String? userName;
|
||||||
|
final String role; // 'student' | 'teacher' | 'admin'
|
||||||
|
final bool isDeviceMatched;
|
||||||
|
|
||||||
class StudentDashboard extends StatefulWidget {
|
const MainDashboard({
|
||||||
final String studentId;
|
|
||||||
final String studentName;
|
|
||||||
final bool isDeviceMatched; // 🆕 [변경] 기기 UUID가 매칭되었는지 확인하는 변수 추가
|
|
||||||
|
|
||||||
const StudentDashboard({
|
|
||||||
super.key,
|
super.key,
|
||||||
required this.studentId,
|
this.userId,
|
||||||
required this.studentName,
|
this.userName,
|
||||||
required this.isDeviceMatched, // 🆕 [변경] 필수 매개변수로 등록
|
required this.role,
|
||||||
|
this.isDeviceMatched = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<StudentDashboard> createState() => _StudentDashboardState();
|
State<MainDashboard> createState() => _MainDashboardState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _StudentDashboardState extends State<StudentDashboard> {
|
class _MainDashboardState extends State<MainDashboard> {
|
||||||
final StudentDashboardController _controller = StudentDashboardController();
|
final StudentDashboardController _controller = StudentDashboardController();
|
||||||
|
|
||||||
|
// 🔑 [권한 판정] 2061은 role이 'student'로 저장돼 있지만 실질적으로 최고 권한을 가진다.
|
||||||
|
bool get _isDeveloper => widget.userId == '2061';
|
||||||
|
bool get _isAdmin => widget.role == 'admin' || _isDeveloper;
|
||||||
|
bool get _isTeacherOrAbove => widget.role == 'teacher' || _isAdmin;
|
||||||
|
bool get _isStudent => widget.role == 'student';
|
||||||
|
|
||||||
|
String get _displayId =>
|
||||||
|
widget.userId ?? (widget.role == 'admin' ? '관리자' : '간편인증');
|
||||||
|
String get _displayName =>
|
||||||
|
widget.userName ?? (widget.role == 'admin' ? '시스템 관리자' : '간편인증 선생');
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_controller.dispose();
|
_controller.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1️⃣ NFC 태그 카드 → 실제 NFC 주머니 체크인 화면으로 이동
|
|
||||||
void _openPocketCheckIn(BuildContext context) {
|
void _openPocketCheckIn(BuildContext context) {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => NfcPocketCheckInScreen(
|
builder: (context) => NfcPocketCheckInScreen(
|
||||||
studentId: widget.studentId,
|
studentId: _displayId,
|
||||||
studentName: widget.studentName,
|
studentName: _displayName,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2️⃣ [기존 동일] 마스터 계정 전용 회원 삭제 버튼 동작
|
|
||||||
Future<void> _deleteUser(String userId) async {
|
Future<void> _deleteUser(String userId) async {
|
||||||
final (_, message) = await _controller.deleteUser(userId);
|
final (_, message) = await _controller.deleteUser(userId);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
@@ -62,7 +71,6 @@ class _StudentDashboardState extends State<StudentDashboard> {
|
|||||||
).showSnackBar(SnackBar(content: Text(message)));
|
).showSnackBar(SnackBar(content: Text(message)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3️⃣ [기존 동일] 학번/교직원 번호를 입력받는 모던 팝업창(Dialog)
|
|
||||||
void _showDeleteUserDialog(BuildContext context) {
|
void _showDeleteUserDialog(BuildContext context) {
|
||||||
final TextEditingController idController = TextEditingController();
|
final TextEditingController idController = TextEditingController();
|
||||||
|
|
||||||
@@ -152,27 +160,57 @@ class _StudentDashboardState extends State<StudentDashboard> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 🎨 [역할별 테마] 배너 색상/제목/문구만 역할에 따라 다르게. 레이아웃 자체는 공통.
|
||||||
|
({String title, String subtitle, Color color, IconData icon}) _theme() {
|
||||||
|
if (_isDeveloper) {
|
||||||
|
return (
|
||||||
|
title: '👑 MASTER CONTROL',
|
||||||
|
subtitle: '최고 관리 권한 활성화됨',
|
||||||
|
color: Colors.deepPurple[700]!,
|
||||||
|
icon: Icons.admin_panel_settings_rounded,
|
||||||
|
);
|
||||||
|
} else if (widget.role == 'admin') {
|
||||||
|
return (
|
||||||
|
title: '🛠️ ADMIN CONTROL',
|
||||||
|
subtitle: '시스템 관리자 권한 활성화됨',
|
||||||
|
color: Colors.deepPurple[700]!,
|
||||||
|
icon: Icons.admin_panel_settings_rounded,
|
||||||
|
);
|
||||||
|
} else if (widget.role == 'teacher') {
|
||||||
|
return (
|
||||||
|
title: '👨🏫 TEACHER PORTAL',
|
||||||
|
subtitle: '교직원 번호: $_displayId | 교사 권한 활성화됨',
|
||||||
|
color: Colors.green[700]!,
|
||||||
|
icon: Icons.admin_panel_settings_rounded,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
title: '🎓 STUDENT PORTAL',
|
||||||
|
subtitle: '학번: $_displayId | 인증 완료',
|
||||||
|
color: Colors.indigo[700]!,
|
||||||
|
icon: Icons.school_rounded,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ListenableBuilder(
|
return ListenableBuilder(
|
||||||
listenable: _controller,
|
listenable: _controller,
|
||||||
builder: (context, _) {
|
builder: (context, _) {
|
||||||
final bool isDeveloper = widget.studentId == "2061";
|
final theme = _theme();
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: Colors.grey[100],
|
backgroundColor: Colors.grey[100],
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(
|
title: Text(
|
||||||
isDeveloper ? '👑 MASTER CONTROL' : '🎓 STUDENT PORTAL',
|
theme.title,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
letterSpacing: 1.2,
|
letterSpacing: 1.2,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
backgroundColor: isDeveloper
|
backgroundColor: theme.color,
|
||||||
? Colors.deepPurple[700]
|
|
||||||
: Colors.indigo[700],
|
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: Colors.white,
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
actions: [
|
actions: [
|
||||||
@@ -196,9 +234,7 @@ class _StudentDashboardState extends State<StudentDashboard> {
|
|||||||
vertical: 28,
|
vertical: 28,
|
||||||
),
|
),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: isDeveloper
|
color: theme.color,
|
||||||
? Colors.deepPurple[700]
|
|
||||||
: Colors.indigo[700],
|
|
||||||
borderRadius: const BorderRadius.only(
|
borderRadius: const BorderRadius.only(
|
||||||
bottomLeft: Radius.circular(32),
|
bottomLeft: Radius.circular(32),
|
||||||
bottomRight: Radius.circular(32),
|
bottomRight: Radius.circular(32),
|
||||||
@@ -214,18 +250,8 @@ class _StudentDashboardState extends State<StudentDashboard> {
|
|||||||
),
|
),
|
||||||
child: CircleAvatar(
|
child: CircleAvatar(
|
||||||
radius: 30,
|
radius: 30,
|
||||||
backgroundColor: isDeveloper
|
backgroundColor: theme.color.withValues(alpha: 0.08),
|
||||||
? Colors.deepPurple[50]
|
child: Icon(theme.icon, size: 32, color: theme.color),
|
||||||
: Colors.indigo[50],
|
|
||||||
child: Icon(
|
|
||||||
isDeveloper
|
|
||||||
? Icons.admin_panel_settings_rounded
|
|
||||||
: Icons.school_rounded,
|
|
||||||
size: 32,
|
|
||||||
color: isDeveloper
|
|
||||||
? Colors.deepPurple
|
|
||||||
: Colors.indigo,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 18),
|
const SizedBox(width: 18),
|
||||||
@@ -233,7 +259,7 @@ class _StudentDashboardState extends State<StudentDashboard> {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'${widget.studentName} 님',
|
'$_displayName 님',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 22,
|
fontSize: 22,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
@@ -242,9 +268,7 @@ class _StudentDashboardState extends State<StudentDashboard> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(
|
Text(
|
||||||
isDeveloper
|
theme.subtitle,
|
||||||
? '최고 관리 권한 활성화됨'
|
|
||||||
: '학번: ${widget.studentId} | 인증 완료',
|
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.white.withValues(alpha: 0.8),
|
color: Colors.white.withValues(alpha: 0.8),
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
@@ -264,9 +288,7 @@ class _StudentDashboardState extends State<StudentDashboard> {
|
|||||||
width: 4,
|
width: 4,
|
||||||
height: 18,
|
height: 18,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: isDeveloper
|
color: theme.color,
|
||||||
? Colors.deepPurple
|
|
||||||
: Colors.indigo,
|
|
||||||
borderRadius: BorderRadius.circular(2),
|
borderRadius: BorderRadius.circular(2),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -302,8 +324,8 @@ class _StudentDashboardState extends State<StudentDashboard> {
|
|||||||
mainAxisSpacing: 16,
|
mainAxisSpacing: 16,
|
||||||
childAspectRatio: 0.95,
|
childAspectRatio: 0.95,
|
||||||
children: [
|
children: [
|
||||||
// 🆕 [변경 지점] 기기가 일치하면 정상 활성화, 일치하지 않으면 자물쇠Lock 처리
|
// 👨🎓 학생 전용 -----------------------------------
|
||||||
if (isDeveloper || widget.studentId != "2061")
|
if (_isStudent)
|
||||||
_buildModernCard(
|
_buildModernCard(
|
||||||
icon: widget.isDeviceMatched
|
icon: widget.isDeviceMatched
|
||||||
? Icons.contactless_rounded
|
? Icons.contactless_rounded
|
||||||
@@ -331,8 +353,7 @@ class _StudentDashboardState extends State<StudentDashboard> {
|
|||||||
isActionButton: widget.isDeviceMatched,
|
isActionButton: widget.isDeviceMatched,
|
||||||
isLoading: _controller.isLoading,
|
isLoading: _controller.isLoading,
|
||||||
),
|
),
|
||||||
|
if (_isStudent)
|
||||||
// 🆕 [추가 지점] 기기 일치 여부 상관없이 태블릿에서도 누구나 확인 가능한 학교 상황판 카드
|
|
||||||
_buildModernCard(
|
_buildModernCard(
|
||||||
icon: Icons.fastfood_rounded,
|
icon: Icons.fastfood_rounded,
|
||||||
title: '실시간 학교 상황',
|
title: '실시간 학교 상황',
|
||||||
@@ -341,13 +362,14 @@ class _StudentDashboardState extends State<StudentDashboard> {
|
|||||||
onTap: () {
|
onTap: () {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(
|
const SnackBar(
|
||||||
content: Text('🍔 실시간 학교 상황 페이지로 이동합니다.'),
|
content: Text(
|
||||||
|
'🍔 실시간 학교 상황 페이지로 이동합니다.',
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
if (_isStudent)
|
||||||
// 📱 패드는 NFC가 안 되니, 시간+목적 적어서 신청 → 선생님 승인받는 대체 절차
|
|
||||||
_buildModernCard(
|
_buildModernCard(
|
||||||
icon: Icons.tablet_mac_rounded,
|
icon: Icons.tablet_mac_rounded,
|
||||||
title: '스마트기기 반출',
|
title: '스마트기기 반출',
|
||||||
@@ -358,19 +380,20 @@ class _StudentDashboardState extends State<StudentDashboard> {
|
|||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) =>
|
builder: (context) =>
|
||||||
DeviceCheckoutRequestScreen(
|
DeviceCheckoutRequestScreen(
|
||||||
studentId: widget.studentId,
|
studentId: _displayId,
|
||||||
studentName: widget.studentName,
|
studentName: _displayName,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
if (isDeveloper)
|
// 👨🏫 교사 이상 -----------------------------------
|
||||||
|
if (_isTeacherOrAbove)
|
||||||
_buildModernCard(
|
_buildModernCard(
|
||||||
icon: Icons.monitor_heart_rounded,
|
icon: Icons.assignment_turned_in_rounded,
|
||||||
title: '실시간 현황',
|
title: '실시간 출석 확인',
|
||||||
subtitle: '교사용 수거 모니터링',
|
subtitle: '학생 제출 로그 모니터링',
|
||||||
color: Colors.teal,
|
color: Colors.blue,
|
||||||
onTap: () => Navigator.push(
|
onTap: () => Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
@@ -379,7 +402,40 @@ class _StudentDashboardState extends State<StudentDashboard> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (isDeveloper)
|
if (_isTeacherOrAbove)
|
||||||
|
_buildModernCard(
|
||||||
|
icon: Icons.manage_accounts_rounded,
|
||||||
|
title: '학생 계정 관리',
|
||||||
|
subtitle: '계정 추가 및 강제 리셋',
|
||||||
|
color: Colors.orange,
|
||||||
|
onTap: () => Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) =>
|
||||||
|
const TeacherStudentManagementPage(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (_isTeacherOrAbove)
|
||||||
|
_buildModernCard(
|
||||||
|
icon: Icons.tablet_mac_rounded,
|
||||||
|
title: '스마트기기 반출 대장',
|
||||||
|
subtitle: '패드 반출 신청 승인/거절',
|
||||||
|
color: Colors.teal,
|
||||||
|
onTap: () => Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) =>
|
||||||
|
DeviceCheckoutLedgerPage(
|
||||||
|
teacherId: widget.userId,
|
||||||
|
teacherName: widget.userName,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// 🛠️ 관리자 전용 -----------------------------------
|
||||||
|
if (_isAdmin)
|
||||||
_buildModernCard(
|
_buildModernCard(
|
||||||
icon: Icons.terminal_rounded,
|
icon: Icons.terminal_rounded,
|
||||||
title: '서버 DB 제어',
|
title: '서버 DB 제어',
|
||||||
@@ -393,21 +449,7 @@ class _StudentDashboardState extends State<StudentDashboard> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (isDeveloper)
|
if (_isAdmin)
|
||||||
_buildModernCard(
|
|
||||||
icon: Icons.add_moderator_rounded,
|
|
||||||
title: '학생 계정 관리',
|
|
||||||
subtitle: 'UUID 리셋 및 승인',
|
|
||||||
color: Colors.purple,
|
|
||||||
onTap: () => Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) =>
|
|
||||||
const TeacherStudentManagementPage(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (isDeveloper)
|
|
||||||
_buildModernCard(
|
_buildModernCard(
|
||||||
icon: Icons.delete_sweep_rounded,
|
icon: Icons.delete_sweep_rounded,
|
||||||
title: '계정 강제 삭제',
|
title: '계정 강제 삭제',
|
||||||
@@ -415,7 +457,7 @@ class _StudentDashboardState extends State<StudentDashboard> {
|
|||||||
color: Colors.red[600]!,
|
color: Colors.red[600]!,
|
||||||
onTap: () => _showDeleteUserDialog(context),
|
onTap: () => _showDeleteUserDialog(context),
|
||||||
),
|
),
|
||||||
if (isDeveloper)
|
if (_isAdmin)
|
||||||
_buildModernCard(
|
_buildModernCard(
|
||||||
icon: Icons.edit_note_rounded,
|
icon: Icons.edit_note_rounded,
|
||||||
title: 'NFC 태그 쓰기',
|
title: 'NFC 태그 쓰기',
|
||||||
@@ -445,7 +487,7 @@ class _StudentDashboardState extends State<StudentDashboard> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5️⃣ [카드 디자인 위젯] - 기존 형태 완전 보존
|
// 💎 [카드 디자인 위젯] 학생/교사 대시보드에서 쓰던 것과 동일 — 이제 한 곳에만 존재.
|
||||||
Widget _buildModernCard({
|
Widget _buildModernCard({
|
||||||
required IconData icon,
|
required IconData icon,
|
||||||
required String title,
|
required String title,
|
||||||
@@ -489,14 +531,16 @@ class _StudentDashboardState extends State<StudentDashboard> {
|
|||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Expanded(
|
||||||
|
child: Text(
|
||||||
title,
|
title,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 15,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: Colors.black87,
|
color: Colors.black87,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
if (isLoading)
|
if (isLoading)
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
width: 16,
|
width: 16,
|
||||||
@@ -1,295 +0,0 @@
|
|||||||
// 👨🏫 교사 대시보드. 실시간 출석 확인, 학생 계정 관리 화면으로 가는 메뉴만 담당한다.
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'device_checkout_ledger_page.dart';
|
|
||||||
import 'login_screen.dart';
|
|
||||||
import 'teacher_attendance_page.dart';
|
|
||||||
import 'teacher_student_management_page.dart';
|
|
||||||
|
|
||||||
// ==========================================
|
|
||||||
// 👨🏫 4. 선생님 대시보드 (기존 3초 타이머 완벽 유지)
|
|
||||||
// ==========================================
|
|
||||||
class TeacherDashboard extends StatefulWidget {
|
|
||||||
final String? teacherId;
|
|
||||||
final String? teacherName;
|
|
||||||
|
|
||||||
const TeacherDashboard({super.key, this.teacherId, this.teacherName});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<TeacherDashboard> createState() => _TeacherDashboardState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _TeacherDashboardState extends State<TeacherDashboard> {
|
|
||||||
// 🧼 [수정] 사용하지 않던 _isLoading 변수를 삭제하여 경고를 완벽히 해결했습니다!
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
// 다른 화면에서 null이 넘어왔을 때를 대비한 안전망 방탄 코드
|
|
||||||
final String displayName = widget.teacherName ?? "간편인증 선생";
|
|
||||||
final String displayId = widget.teacherId ?? "간편인증";
|
|
||||||
|
|
||||||
return Scaffold(
|
|
||||||
backgroundColor: Colors.grey[100],
|
|
||||||
appBar: AppBar(
|
|
||||||
title: const Text(
|
|
||||||
'👨🏫 TEACHER PORTAL',
|
|
||||||
style: TextStyle(fontWeight: FontWeight.bold, letterSpacing: 1.2),
|
|
||||||
),
|
|
||||||
centerTitle: true,
|
|
||||||
backgroundColor: Colors.green[700], // 교사 전용 그린 테마 컬러
|
|
||||||
foregroundColor: Colors.white,
|
|
||||||
elevation: 0,
|
|
||||||
actions: [
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(Icons.logout_rounded),
|
|
||||||
onPressed: () => Navigator.pushReplacement(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(builder: (context) => const LoginScreen()),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
body: SingleChildScrollView(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
// 💳 [상단 배너 가이드] StudentDashboard와 100% 일치하는 프로필 카드 레이아웃
|
|
||||||
Container(
|
|
||||||
width: double.infinity,
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 28),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.green[700],
|
|
||||||
borderRadius: const BorderRadius.only(
|
|
||||||
bottomLeft: Radius.circular(32),
|
|
||||||
bottomRight: Radius.circular(32),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.all(4),
|
|
||||||
decoration: const BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
),
|
|
||||||
child: CircleAvatar(
|
|
||||||
radius: 30,
|
|
||||||
backgroundColor: Colors.green[50],
|
|
||||||
child: Icon(
|
|
||||||
Icons.admin_panel_settings_rounded,
|
|
||||||
size: 32,
|
|
||||||
color: Colors.green[700],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 18),
|
|
||||||
Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'$displayName 님',
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 22,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text(
|
|
||||||
'교직원 번호: $displayId | 교사 권한 활성화됨',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.white.withValues(alpha: 0.8),
|
|
||||||
fontSize: 14,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 32),
|
|
||||||
|
|
||||||
// 🏷️ [세로 바 타이틀 인디케이터] 구조 일치화
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
width: 4,
|
|
||||||
height: 18,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.green[700],
|
|
||||||
borderRadius: BorderRadius.circular(2),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
const Text(
|
|
||||||
'스마트 교사용 관리 메뉴',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Colors.black87,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
|
|
||||||
// 📊 [그리드 레이아웃 메뉴] 시후의 카드 컴포넌트 스타일 적용
|
|
||||||
LayoutBuilder(
|
|
||||||
builder: (context, constraints) {
|
|
||||||
// 🖥️ 웹(넓은 화면)은 한 줄에 5개씩, 폰(좁은 화면)은 기존 2개 그대로.
|
|
||||||
final bool isWide = constraints.maxWidth >= 800;
|
|
||||||
return Center(
|
|
||||||
child: ConstrainedBox(
|
|
||||||
constraints: BoxConstraints(maxWidth: isWide ? 1300 : 700),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
|
||||||
child: GridView.count(
|
|
||||||
shrinkWrap: true,
|
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
|
||||||
crossAxisCount: isWide ? 5 : 2,
|
|
||||||
crossAxisSpacing: 16,
|
|
||||||
mainAxisSpacing: 16,
|
|
||||||
childAspectRatio: 0.95,
|
|
||||||
children: [
|
|
||||||
_buildModernCard(
|
|
||||||
icon: Icons.assignment_turned_in_rounded,
|
|
||||||
title: '실시간 출석 확인',
|
|
||||||
subtitle: '학생 제출 로그 모니터링',
|
|
||||||
color: Colors.blue,
|
|
||||||
onTap: () => Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) =>
|
|
||||||
const TeacherAttendancePage(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
_buildModernCard(
|
|
||||||
icon: Icons.manage_accounts_rounded,
|
|
||||||
title: '학생 계정 관리',
|
|
||||||
subtitle: '계정 추가 및 강제 리셋',
|
|
||||||
color: Colors.orange,
|
|
||||||
onTap: () => Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) =>
|
|
||||||
const TeacherStudentManagementPage(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
_buildModernCard(
|
|
||||||
icon: Icons.tablet_mac_rounded,
|
|
||||||
title: '스마트기기 반출 대장',
|
|
||||||
subtitle: '패드 반출 신청 승인/거절',
|
|
||||||
color: Colors.teal,
|
|
||||||
onTap: () => Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => DeviceCheckoutLedgerPage(
|
|
||||||
teacherId: widget.teacherId,
|
|
||||||
teacherName: widget.teacherName,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
const SizedBox(height: 32),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 💎 [시후 대시보드 전용 카드 위젯 이식 완료]
|
|
||||||
Widget _buildModernCard({
|
|
||||||
required IconData icon,
|
|
||||||
required String title,
|
|
||||||
required String subtitle,
|
|
||||||
required Color color,
|
|
||||||
required VoidCallback onTap,
|
|
||||||
bool isActionButton = false,
|
|
||||||
bool isLoading = false,
|
|
||||||
}) {
|
|
||||||
return InkWell(
|
|
||||||
onTap: isLoading ? null : onTap,
|
|
||||||
borderRadius: BorderRadius.circular(24),
|
|
||||||
child: Ink(
|
|
||||||
padding: const EdgeInsets.all(20),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
borderRadius: BorderRadius.circular(24),
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: Colors.black.withValues(alpha: 0.04),
|
|
||||||
blurRadius: 16,
|
|
||||||
offset: const Offset(0, 4),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.all(12),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: color.withValues(alpha: 0.1),
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
),
|
|
||||||
child: Icon(icon, color: color, size: 28),
|
|
||||||
),
|
|
||||||
Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
title,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 15,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Colors.black87,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (isLoading)
|
|
||||||
const SizedBox(
|
|
||||||
width: 16,
|
|
||||||
height: 16,
|
|
||||||
child: CircularProgressIndicator(strokeWidth: 2),
|
|
||||||
)
|
|
||||||
else if (isActionButton)
|
|
||||||
Icon(
|
|
||||||
Icons.touch_app_rounded,
|
|
||||||
size: 16,
|
|
||||||
color: color.withValues(alpha: 0.5),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text(
|
|
||||||
subtitle,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
color: Colors.grey[500],
|
|
||||||
height: 1.2,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user