feat: 发薪日期多选+提前N天提醒+电子签设置区域修复

- Schema: 去掉 payrollFrequency,新增 payrollDays (JSON数组) + payrollReminderDays (Int)
- 设置页: 发薪日期改为1-28号多选按钮,新增提前提醒天数设置
- 设置页: 恢复电子签署设置区域(3个开关:规章制度/工资条/入职文件)
- 工作日历: 发薪日期作为 PAYROLL_DAY 事件显示
- 工作台: 提前N天提醒发薪日期,N可配置
- TaskCenter: 新增发薪提醒分类图标
- seed文件: 更新为 payrollDays 格式
This commit is contained in:
freedakgmail
2026-08-05 08:04:39 +08:00
parent 41b3030442
commit 6ec939dc7f
11 changed files with 180 additions and 25 deletions
+2
View File
@@ -15,6 +15,7 @@ const EVENT_TYPE_COLORS: Record<string, string> = {
ANNIVERSARY: 'bg-green-100 text-green-700 border-green-200',
RISK_DEADLINE: 'bg-orange-100 text-orange-700 border-orange-200',
RETIREMENT: 'bg-purple-100 text-purple-700 border-purple-200',
PAYROLL_DAY: 'bg-emerald-100 text-emerald-700 border-emerald-200',
CUSTOM: 'bg-blue-100 text-blue-700 border-blue-200',
MEETING: 'bg-cyan-100 text-cyan-700 border-cyan-200',
TEAM_BUILDING: 'bg-pink-100 text-pink-700 border-pink-200',
@@ -29,6 +30,7 @@ const EVENT_TYPE_LABELS: Record<string, string> = {
ANNIVERSARY: '入职周年',
RISK_DEADLINE: '风险截止',
RETIREMENT: '退休',
PAYROLL_DAY: '发薪日',
CUSTOM: '自定义',
MEETING: '会议',
TEAM_BUILDING: '团建',
+98 -10
View File
@@ -94,7 +94,8 @@ function OrgSettings({ orgData, onSave, saving }: { orgData: any; onSave: (data:
name: '',
contactName: '',
contactPhone: '',
payrollFrequency: 1,
payrollDays: [] as number[],
payrollReminderDays: 3,
retirementReminderEnabled: false,
esignPolicyEnabled: false,
esignPayslipEnabled: false,
@@ -107,7 +108,8 @@ function OrgSettings({ orgData, onSave, saving }: { orgData: any; onSave: (data:
name: orgData.name || '',
contactName: orgData.contactName || '',
contactPhone: orgData.contactPhone || '',
payrollFrequency: orgData.payrollFrequency || 1,
payrollDays: Array.isArray(orgData.payrollDays) ? orgData.payrollDays : [],
payrollReminderDays: orgData.payrollReminderDays ?? 3,
retirementReminderEnabled: orgData.retirementReminderEnabled || false,
esignPolicyEnabled: orgData.esignPolicyEnabled || false,
esignPayslipEnabled: orgData.esignPayslipEnabled || false,
@@ -133,20 +135,106 @@ function OrgSettings({ orgData, onSave, saving }: { orgData: any; onSave: (data:
<Input value={form.contactPhone} onChange={(e) => setForm({ ...form, contactPhone: e.target.value })} placeholder="联系电话" />
</div>
<div>
<Label></Label>
<Select value={String(form.payrollFrequency)} onChange={(e) => setForm({ ...form, payrollFrequency: Number(e.target.value) })}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
</Select>
<p className="text-xs text-gray-500 mt-1"></p>
<Label></Label>
<p className="text-xs text-gray-500 mt-1 mb-2"></p>
<div className="flex flex-wrap gap-1.5">
{Array.from({ length: 28 }, (_, i) => i + 1).map(day => (
<button
key={day}
type="button"
onClick={() => {
const days = form.payrollDays.includes(day)
? form.payrollDays.filter(d => d !== day)
: [...form.payrollDays, day].sort((a, b) => a - b)
setForm({ ...form, payrollDays: days })
}}
className={`w-9 h-9 rounded-md text-xs font-medium border transition-colors ${
form.payrollDays.includes(day)
? 'bg-primary text-white border-primary'
: 'bg-white text-gray-600 border-gray-200 hover:bg-gray-50'
}`}
>
{day}
</button>
))}
</div>
{form.payrollDays.length > 0 && (
<p className="text-xs text-gray-500 mt-2"> {form.payrollDays.map(d => `${d}`).join('、')} </p>
)}
</div>
<div>
<Label></Label>
<div className="flex items-center gap-3">
<Input type="number" min={0} max={30} value={form.payrollReminderDays} onChange={(e) => setForm({ ...form, payrollReminderDays: Number(e.target.value) })} className="!w-24" />
<span className="text-xs text-gray-500"></span>
</div>
</div>
<Button onClick={() => onSave(form)} disabled={saving}>
{saving ? '保存中...' : '保存'}
</Button>
</div>
{/* 电子签署设置 */}
<div className="mt-6 pt-6 border-t">
<div className="flex items-center gap-2 mb-4">
<PenTool className="w-5 h-5 text-primary" />
<h3 className="font-medium"></h3>
</div>
<div className="space-y-4">
<div className="flex items-center justify-between">
<div>
<div className="text-sm font-medium"></div>
<p className="text-xs text-gray-500 mt-0.5"></p>
</div>
<button
type="button"
onClick={() => {
const next = !form.esignPolicyEnabled
setForm({ ...form, esignPolicyEnabled: next })
onSave({ esignPolicyEnabled: next })
}}
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${form.esignPolicyEnabled ? 'bg-primary' : 'bg-gray-200'}`}
>
<span className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${form.esignPolicyEnabled ? 'translate-x-6' : 'translate-x-1'}`} />
</button>
</div>
<div className="flex items-center justify-between">
<div>
<div className="text-sm font-medium"></div>
<p className="text-xs text-gray-500 mt-0.5"></p>
</div>
<button
type="button"
onClick={() => {
const next = !form.esignPayslipEnabled
setForm({ ...form, esignPayslipEnabled: next })
onSave({ esignPayslipEnabled: next })
}}
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${form.esignPayslipEnabled ? 'bg-primary' : 'bg-gray-200'}`}
>
<span className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${form.esignPayslipEnabled ? 'translate-x-6' : 'translate-x-1'}`} />
</button>
</div>
<div className="flex items-center justify-between">
<div>
<div className="text-sm font-medium"></div>
<p className="text-xs text-gray-500 mt-0.5">HR审批通过入职流程时自动创建入职文件签署</p>
</div>
<button
type="button"
onClick={() => {
const next = !form.esignOnboardingEnabled
setForm({ ...form, esignOnboardingEnabled: next })
onSave({ esignOnboardingEnabled: next })
}}
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${form.esignOnboardingEnabled ? 'bg-primary' : 'bg-gray-200'}`}
>
<span className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${form.esignOnboardingEnabled ? 'translate-x-6' : 'translate-x-1'}`} />
</button>
</div>
</div>
</div>
{/* 显示设置 */}
<div className="mt-6 pt-6 border-t">
<div className="flex items-center gap-2 mb-4">
+2 -1
View File
@@ -4,7 +4,7 @@
*/
import { useQuery } from '@tanstack/react-query'
import { Link } from 'react-router-dom'
import { AlertCircle, Layers, FileText, Heart, ArrowRight, ListTodo } from 'lucide-react'
import { AlertCircle, Layers, FileText, Heart, ArrowRight, ListTodo, Wallet } from 'lucide-react'
import { dashboardApi } from '../../lib/api-services'
import { InlineAlert } from '../../components/ui/InlineAlert'
@@ -27,6 +27,7 @@ interface ActionGroup {
const categoryIcons: Record<string, typeof AlertCircle> = {
'待办事项': AlertCircle,
'发薪批次': Layers,
'发薪提醒': Wallet,
'合同到期': FileText,
'特殊状态': Heart,
}
+1 -1
View File
@@ -12,7 +12,7 @@ import Button from '../../components/ui/Button'
interface Org {
id: string; name: string; plan: string; maxEmployees: number
city: string | null; contactName: string | null; contactPhone: string | null
payrollFrequency: number; retirementReminderEnabled: boolean
payrollDays: number[]; retirementReminderEnabled: boolean
createdAt: string; updatedAt: string
employeeCount: number; userCount: number; contractCount: number; payslipCount: number
}