dvc890 commited on
Commit
d7063e5
·
verified ·
1 Parent(s): ea97403

Upload 53 files

Browse files
components/ConfirmModal.tsx CHANGED
@@ -22,7 +22,7 @@ export const ConfirmModal: React.FC<ConfirmModalProps> = ({
22
 
23
  const handleConfirm = () => {
24
  onConfirm();
25
- onClose(); // Automatically close modal after confirmation
26
  };
27
 
28
  return (
 
22
 
23
  const handleConfirm = () => {
24
  onConfirm();
25
+ onClose();
26
  };
27
 
28
  return (
pages/AchievementTeacher.tsx CHANGED
@@ -5,6 +5,7 @@ import { AchievementConfig, AchievementItem, ExchangeRule, Student, TeacherExcha
5
  import { Plus, Trash2, Edit, Save, Gift, Award, Coins, Users, Search, Loader2, CheckCircle, Filter } from 'lucide-react';
6
  import { Emoji } from '../components/Emoji';
7
  import { ConfirmModal } from '../components/ConfirmModal';
 
8
 
9
  const PRESET_ICONS = [
10
  { icon: '🏆', label: '冠军杯' }, { icon: '🥇', label: '金牌' }, { icon: '🥈', label: '银牌' }, { icon: '🥉', label: '铜牌' },
@@ -161,7 +162,7 @@ export const AchievementTeacher: React.FC<{className?: string}> = ({ className }
161
  <div className="flex justify-end items-center gap-2"><span className="text-xs font-bold text-gray-500"><Filter size={14} className="inline mr-1"/>筛选添加者:</span><select className="border rounded text-xs p-1" value={filterCreator} onChange={e=>setFilterCreator(e.target.value)}><option value="ALL">全部老师</option>{uniqueCreators.map(c => <option key={c} value={c}>{c}</option>)}</select></div>
162
  <div className="bg-blue-50 p-4 rounded-xl border border-blue-100 flex flex-col md:flex-row gap-4 items-end">
163
  <div className="flex-1 w-full"><label className="text-xs font-bold text-gray-500 uppercase mb-1 block">成就名称</label><input className="w-full border rounded p-2 text-sm" placeholder="如: 劳动小能手" value={newAchieve.name} onChange={e => setNewAchieve({...newAchieve, name: e.target.value})} /></div>
164
- <div className="w-full md:w-32"><label className="text-xs font-bold text-gray-500 uppercase mb-1 block">分值 (小红花)</label><input type="number" min={1} className="w-full border rounded p-2 text-sm" value={newAchieve.points} onChange={e => setNewAchieve({...newAchieve, points: Number(e.target.value)})} /></div>
165
  <div className="w-full md:w-auto"><label className="text-xs font-bold text-gray-500 uppercase mb-1 block">图标预设</label><select className="w-full border rounded p-2 min-w-[100px] text-sm bg-white" value={newAchieve.icon} onChange={e => setNewAchieve({...newAchieve, icon: e.target.value})}>{PRESET_ICONS.map(p => <option key={p.label} value={p.icon}>{p.icon} {p.label}</option>)}</select></div>
166
  <button onClick={addAchievement} className="bg-blue-600 text-white px-4 py-2 rounded-lg font-bold hover:bg-blue-700 whitespace-nowrap text-sm">添加成就</button>
167
  </div>
@@ -223,10 +224,10 @@ export const AchievementTeacher: React.FC<{className?: string}> = ({ className }
223
  <div className="space-y-6 max-w-4xl mx-auto">
224
  <div className="bg-purple-50 p-4 rounded-lg border border-purple-100 text-sm text-purple-800 mb-4"><span className="font-bold">提示:</span> 这里的兑换规则是您个人专属的,对您教的所有班级学生可见。学生兑换后,只有您能看到待处理的申请。</div>
225
  <div className="bg-green-50 p-4 rounded-xl border border-green-100 flex flex-col md:flex-row gap-4 items-end">
226
- <div className="w-full md:w-32"><label className="text-xs font-bold text-gray-500 uppercase mb-1 block">消耗小红花</label><input type="number" min={1} className="w-full border rounded p-2 text-sm" value={newRule.cost} onChange={e => setNewRule({...newRule,cost: Number(e.target.value)})}/></div>
227
  <div className="w-full md:w-32"><label className="text-xs font-bold text-gray-500 uppercase mb-1 block">奖励类型</label><select className="w-full border rounded p-2 text-sm bg-white" value={newRule.rewardType} onChange={e => setNewRule({...newRule, rewardType: e.target.value as any})}><option value="DRAW_COUNT">🎲 抽奖券</option><option value="ITEM">🎁 实物/特权</option></select></div>
228
  <div className="flex-1 w-full"><label className="text-xs font-bold text-gray-500 uppercase mb-1 block">奖励名称</label><input className="w-full border rounded p-2 text-sm" placeholder="如: 免作业券" value={newRule.rewardName} onChange={e => setNewRule({...newRule, rewardName: e.target.value})}/></div>
229
- <div className="w-24"><label className="text-xs font-bold text-gray-500 uppercase mb-1 block">数量</label><input type="number" min={1} className="w-full border rounded p-2 text-sm" value={newRule.rewardValue} onChange={e => setNewRule({...newRule, rewardValue: Number(e.target.value)})}/></div>
230
  <button onClick={addRule} className="bg-green-600 text-white px-4 py-2 rounded-lg font-bold hover:bg-green-700 whitespace-nowrap text-sm">添加规则</button>
231
  </div>
232
  <div className="space-y-3">{myRules.length === 0 ? <div className="text-center text-gray-400 py-4">暂无个人兑换规则</div> : myRules.map(rule => (
 
5
  import { Plus, Trash2, Edit, Save, Gift, Award, Coins, Users, Search, Loader2, CheckCircle, Filter } from 'lucide-react';
6
  import { Emoji } from '../components/Emoji';
7
  import { ConfirmModal } from '../components/ConfirmModal';
8
+ import { NumberInput } from '../components/NumberInput';
9
 
10
  const PRESET_ICONS = [
11
  { icon: '🏆', label: '冠军杯' }, { icon: '🥇', label: '金牌' }, { icon: '🥈', label: '银牌' }, { icon: '🥉', label: '铜牌' },
 
162
  <div className="flex justify-end items-center gap-2"><span className="text-xs font-bold text-gray-500"><Filter size={14} className="inline mr-1"/>筛选添加者:</span><select className="border rounded text-xs p-1" value={filterCreator} onChange={e=>setFilterCreator(e.target.value)}><option value="ALL">全部老师</option>{uniqueCreators.map(c => <option key={c} value={c}>{c}</option>)}</select></div>
163
  <div className="bg-blue-50 p-4 rounded-xl border border-blue-100 flex flex-col md:flex-row gap-4 items-end">
164
  <div className="flex-1 w-full"><label className="text-xs font-bold text-gray-500 uppercase mb-1 block">成就名称</label><input className="w-full border rounded p-2 text-sm" placeholder="如: 劳动小能手" value={newAchieve.name} onChange={e => setNewAchieve({...newAchieve, name: e.target.value})} /></div>
165
+ <div className="w-full md:w-32"><label className="text-xs font-bold text-gray-500 uppercase mb-1 block">分值 (小红花)</label><NumberInput min={1} className="w-full" value={newAchieve.points} onChange={v => setNewAchieve({...newAchieve, points: v})} /></div>
166
  <div className="w-full md:w-auto"><label className="text-xs font-bold text-gray-500 uppercase mb-1 block">图标预设</label><select className="w-full border rounded p-2 min-w-[100px] text-sm bg-white" value={newAchieve.icon} onChange={e => setNewAchieve({...newAchieve, icon: e.target.value})}>{PRESET_ICONS.map(p => <option key={p.label} value={p.icon}>{p.icon} {p.label}</option>)}</select></div>
167
  <button onClick={addAchievement} className="bg-blue-600 text-white px-4 py-2 rounded-lg font-bold hover:bg-blue-700 whitespace-nowrap text-sm">添加成就</button>
168
  </div>
 
224
  <div className="space-y-6 max-w-4xl mx-auto">
225
  <div className="bg-purple-50 p-4 rounded-lg border border-purple-100 text-sm text-purple-800 mb-4"><span className="font-bold">提示:</span> 这里的兑换规则是您个人专属的,对您教的所有班级学生可见。学生兑换后,只有您能看到待处理的申请。</div>
226
  <div className="bg-green-50 p-4 rounded-xl border border-green-100 flex flex-col md:flex-row gap-4 items-end">
227
+ <div className="w-full md:w-32"><label className="text-xs font-bold text-gray-500 uppercase mb-1 block">消耗小红花</label><NumberInput min={1} className="w-full" value={newRule.cost} onChange={v => setNewRule({...newRule,cost: v})}/></div>
228
  <div className="w-full md:w-32"><label className="text-xs font-bold text-gray-500 uppercase mb-1 block">奖励类型</label><select className="w-full border rounded p-2 text-sm bg-white" value={newRule.rewardType} onChange={e => setNewRule({...newRule, rewardType: e.target.value as any})}><option value="DRAW_COUNT">🎲 抽奖券</option><option value="ITEM">🎁 实物/特权</option></select></div>
229
  <div className="flex-1 w-full"><label className="text-xs font-bold text-gray-500 uppercase mb-1 block">奖励名称</label><input className="w-full border rounded p-2 text-sm" placeholder="如: 免作业券" value={newRule.rewardName} onChange={e => setNewRule({...newRule, rewardName: e.target.value})}/></div>
230
+ <div className="w-24"><label className="text-xs font-bold text-gray-500 uppercase mb-1 block">数量</label><NumberInput min={1} className="w-full" value={newRule.rewardValue} onChange={v => setNewRule({...newRule, rewardValue: v})}/></div>
231
  <button onClick={addRule} className="bg-green-600 text-white px-4 py-2 rounded-lg font-bold hover:bg-green-700 whitespace-nowrap text-sm">添加规则</button>
232
  </div>
233
  <div className="space-y-3">{myRules.length === 0 ? <div className="text-center text-gray-400 py-4">暂无个人兑换规则</div> : myRules.map(rule => (
pages/Attendance.tsx CHANGED
@@ -149,45 +149,62 @@ export const AttendancePage: React.FC = () => {
149
  });
150
  };
151
 
152
- // Toggle Day Type (Workday <-> Holiday/Off)
153
  const toggleDayType = async () => {
154
- if (isHoliday) {
155
- // Current is Holiday -> Set to Workday
156
- setConfirmModal({
157
- isOpen: true,
158
- title: '开启考勤',
159
- message: `确定将 ${date} 设为“工作日”并开启考勤吗?\n(此操作将覆盖周末或假期的免打卡状态)`,
160
- onConfirm: async () => {
161
- await api.calendar.add({
162
- schoolId: currentUser?.schoolId!,
163
- className: targetClass,
164
- type: 'WORKDAY',
165
- startDate: date,
166
- endDate: date,
167
- name: '补班/工作日'
168
- });
169
- loadData();
170
- }
171
- });
172
- } else {
173
- // Current is Workday -> Set to Off
174
- setConfirmModal({
175
- isOpen: true,
176
- title: '关闭考勤',
177
- message: `确定将 ${date} 标记为“休息日”并免除考勤吗?`,
178
- onConfirm: async () => {
179
- await api.calendar.add({
180
- schoolId: currentUser?.schoolId!,
181
- className: targetClass,
182
- type: 'OFF',
183
- startDate: date,
184
- endDate: date,
185
- name: '休息/停课'
186
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
187
  loadData();
 
 
188
  }
189
- });
190
- }
191
  };
192
 
193
  if (!targetClass) return <div className="p-10 text-center text-gray-500">您不是班主任,无法管理考勤。</div>;
 
149
  });
150
  };
151
 
152
+ // Improved Logic: Smart Toggle Day Type
153
  const toggleDayType = async () => {
154
+ const isCurrentlyHoliday = isHoliday; // Based on calculation
155
+ const targetIsWorkday = isCurrentlyHoliday; // If currently holiday, we want to make it workday
156
+
157
+ const confirmMsg = targetIsWorkday
158
+ ? `确定将 ${date} 设为“工作日”并开启考勤吗?`
159
+ : `确定将 ${date} 标记为“休息日”并免除考勤吗?`;
160
+
161
+ setConfirmModal({
162
+ isOpen: true,
163
+ title: targetIsWorkday ? '开启考勤' : '关闭考勤',
164
+ message: confirmMsg,
165
+ onConfirm: async () => {
166
+ try {
167
+ // 1. Find overlapping single-day entries and delete them
168
+ const overlapping = calendarEntries.filter(e => e.startDate === date && e.endDate === date);
169
+ for (const entry of overlapping) {
170
+ if (entry._id) await api.calendar.delete(entry._id);
171
+ }
172
+
173
+ // 2. Check default state (Weekend vs Weekday)
174
+ const dayOfWeek = new Date(date).getDay();
175
+ const isDefaultHoliday = excludeWeekends && (dayOfWeek === 0 || dayOfWeek === 6);
176
+
177
+ // 3. Add new entry ONLY if target state differs from default state
178
+ if (targetIsWorkday && isDefaultHoliday) {
179
+ // It's a weekend, but we want Workday -> Add WORKDAY exception
180
+ await api.calendar.add({
181
+ schoolId: currentUser?.schoolId!,
182
+ className: targetClass,
183
+ type: 'WORKDAY',
184
+ startDate: date,
185
+ endDate: date,
186
+ name: '补班/工作日'
187
+ });
188
+ } else if (!targetIsWorkday && !isDefaultHoliday) {
189
+ // It's a weekday, but we want Holiday -> Add OFF exception
190
+ await api.calendar.add({
191
+ schoolId: currentUser?.schoolId!,
192
+ className: targetClass,
193
+ type: 'OFF',
194
+ startDate: date,
195
+ endDate: date,
196
+ name: '休息/停课'
197
+ });
198
+ }
199
+ // If target state matches default (e.g. making Monday a Workday), we just deleted the 'OFF' exception above, so it reverts to default Workday.
200
+
201
+ setToast({ show: true, message: '日程已更新', type: 'success' });
202
  loadData();
203
+ } catch (e) {
204
+ setToast({ show: true, message: '操作失败', type: 'error' });
205
  }
206
+ }
207
+ });
208
  };
209
 
210
  if (!targetClass) return <div className="p-10 text-center text-gray-500">您不是班主任,无法管理考勤。</div>;