File size: 7,524 Bytes
08cac2e
8f856eb
129fac6
ed76988
8f856eb
33ebc65
 
 
 
f780380
33ebc65
 
f780380
8f856eb
 
d567faa
129fac6
 
 
 
 
8f856eb
08cac2e
ed76988
 
08cac2e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d567faa
ed76988
08cac2e
ed76988
08cac2e
 
 
 
8f856eb
08cac2e
 
 
 
 
 
 
 
 
 
129fac6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
08cac2e
 
 
 
8f856eb
 
 
 
129fac6
8f856eb
d567faa
129fac6
 
 
 
 
 
 
 
 
8f856eb
 
ed76988
 
 
 
 
 
 
33ebc65
f780380
33ebc65
129fac6
f780380
33ebc65
f780380
33ebc65
 
f780380
d567faa
8f856eb
ed76988
129fac6
079d21f
d567faa
 
 
 
 
8f856eb
33ebc65
129fac6
f780380
33ebc65
 
129fac6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33ebc65
f780380
33ebc65
8f856eb
ed76988
33ebc65
129fac6
33ebc65
 
 
 
d567faa
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165

import React, { useState, useEffect } from 'react';
import { Bell, Search, Menu, Building, Info, Check, AlertTriangle } from 'lucide-react';
import { User, School, Notification, UserRole } from '../types';
import { api } from '../services/api';

interface HeaderProps {
  user: User;
  title: string;
  onMenuClick?: () => void;
}

export const Header: React.FC<HeaderProps> = ({ user, title, onMenuClick }) => {
  const [schools, setSchools] = useState<School[]>([]);
  const [selectedSchool, setSelectedSchool] = useState(localStorage.getItem('admin_view_school_id') || '');
  const [currentSchoolName, setCurrentSchoolName] = useState('');
  
  // Notification State
  const [notifications, setNotifications] = useState<Notification[]>([]);
  const [showNotif, setShowNotif] = useState(false);
  const [hasUnread, setHasUnread] = useState(false);

  const fetchSchools = async () => {
    // Only ADMIN (Super Admin) can switch schools
    if (user.role === UserRole.ADMIN) {
        try {
            const data = await api.schools.getAll();
            setSchools(data);
            if (data.length > 0) {
               if (!selectedSchool || !data.find((s: School) => s._id === selectedSchool)) {
                  const defaultId = data[0]._id!;
                  setSelectedSchool(defaultId);
                  localStorage.setItem('admin_view_school_id', defaultId);
                  if (!localStorage.getItem('admin_view_school_id_init')) {
                     localStorage.setItem('admin_view_school_id_init', 'true');
                     window.location.reload();
                  }
               }
            }
        } catch (e) { console.error(e); }
    } else {
         // Principal, Teacher, Student see their own school
         try {
             // For display purposes, we can fetch public info or just user's school info
             const data = await api.schools.getPublic();
             const mySchool = data.find((s: School) => s._id === user.schoolId);
             if (mySchool) setCurrentSchoolName(mySchool.name);
         } catch(e) { console.error(e); }
    }
  };

  useEffect(() => {
    fetchSchools();
    
    // Listen for custom event to refresh schools
    const handleSchoolUpdate = () => {
        fetchSchools();
    };
    window.addEventListener('school-updated', handleSchoolUpdate);
    
    // Fetch notifications
    const fetchNotifs = async () => {
       try {
          const list = await api.notifications.getAll(user._id || '', user.role);
          setNotifications(list);
          const lastReadTime = localStorage.getItem('last_read_notif_time');
          if (list.length > 0) {
             if (!lastReadTime || new Date(list[0].createTime) > new Date(lastReadTime)) {
                setHasUnread(true);
             }
          }
       } catch (e) { console.error(e); }
    };
    fetchNotifs();

    return () => {
        window.removeEventListener('school-updated', handleSchoolUpdate);
    };
  }, [user]);

  const handleSchoolChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
    const newVal = e.target.value;
    if (!newVal) return;
    setSelectedSchool(newVal);
    localStorage.setItem('admin_view_school_id', newVal);
    window.location.reload();
  };

  const handleOpenNotif = () => {
     setShowNotif(!showNotif);
     if (!showNotif) {
        setHasUnread(false);
        localStorage.setItem('last_read_notif_time', new Date().toISOString());
     }
  };

  const roleLabels: Record<string, string> = {
      [UserRole.ADMIN]: '超级管理员',
      [UserRole.PRINCIPAL]: '校长',
      [UserRole.TEACHER]: '教师',
      [UserRole.STUDENT]: '学生'
  };

  return (
    <header className="h-16 md:h-20 bg-white border-b border-gray-200 flex items-center justify-between px-4 md:px-8 sticky top-0 z-10 shadow-sm w-full">
      <div className="flex items-center space-x-4">
        <button onClick={onMenuClick} className="lg:hidden p-2 hover:bg-gray-100 rounded-lg text-gray-600 focus:outline-none">
          <Menu size={24} />
        </button>
        <h1 className="text-xl md:text-2xl font-bold text-gray-800 truncate max-w-[150px] md:max-w-none">{title}</h1>
      </div>

      <div className="flex items-center space-x-2 md:space-x-6">
        <div className="hidden md:flex items-center bg-gray-100 rounded-lg px-3 py-1.5">
             <Building size={16} className="text-gray-500 mr-2"/>
             {user.role === UserRole.ADMIN ? (
               <select className="bg-transparent text-sm border-none focus:ring-0 text-gray-700 font-medium cursor-pointer min-w-[120px]" value={selectedSchool} onChange={handleSchoolChange}>
                  {schools.map((s: School) => <option key={s._id} value={s._id}>{s.name}</option>)}
               </select>
             ) : (
               <span className="text-sm font-medium text-gray-700">{currentSchoolName || '我的学校'}</span>
             )}
        </div>

        <div className="relative hidden md:block">
          <input type="text" placeholder="全局搜索..." className="w-64 pl-10 pr-4 py-2 bg-gray-50 border border-gray-200 rounded-full text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 text-gray-800"/>
          <Search className="absolute left-3 top-2.5 text-gray-500" size={16} />
        </div>

        <div className="relative">
           <button onClick={handleOpenNotif} className="relative p-2 text-gray-500 hover:text-blue-600 transition-colors">
             <Bell size={20} />
             {hasUnread && <span className="absolute top-1 right-1 w-2 h-2 bg-red-500 rounded-full border border-white"></span>}
           </button>
           
           {showNotif && (
              <div className="absolute right-0 top-12 w-80 bg-white rounded-xl shadow-2xl border border-gray-100 p-4 z-50 animate-in fade-in zoom-in-95">
                 <h3 className="font-bold text-gray-800 mb-3 text-sm">系统消息</h3>
                 <div className="max-h-64 overflow-y-auto space-y-3">
                    {notifications.length > 0 ? notifications.map(n => (
                       <div key={n._id} className="flex gap-3 items-start border-b border-gray-50 pb-2 last:border-0">
                          <div className={`mt-1 w-2 h-2 rounded-full shrink-0 ${n.type==='success'?'bg-green-500':'bg-blue-500'}`}></div>
                          <div>
                             <p className="text-sm font-bold text-gray-800">{n.title}</p>
                             <p className="text-xs text-gray-500 mt-0.5">{n.content}</p>
                             <p className="text-[10px] text-gray-300 mt-1">{new Date(n.createTime).toLocaleString()}</p>
                          </div>
                       </div>
                    )) : <p className="text-center text-xs text-gray-400 py-4">暂无新消息</p>}
                 </div>
              </div>
           )}
        </div>

        <div className="flex items-center space-x-3 border-l pl-4 md:pl-6 border-gray-200">
          <div className="text-right hidden sm:block">
            <p className="text-sm font-semibold text-gray-800">{user.trueName || user.username}</p>
            <p className="text-xs text-gray-500">{roleLabels[user.role] || '用户'}</p>
          </div>
          <img src={user.avatar || `https://api.dicebear.com/7.x/avataaars/svg?seed=${user.username}`} alt="Profile" className="h-8 w-8 md:h-10 md:w-10 rounded-full object-cover border-2 border-white shadow-sm ring-2 ring-gray-100" />
        </div>
      </div>
    </header>
  );
};