iloom-flatten/src/components/plans/PlanEditModal.tsx

283 lines
10 KiB
TypeScript
Raw Normal View History

import React, { useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { X, AlertTriangle, Edit2, Trash2 } from 'lucide-react';
import { useResponsive } from '../../hooks/useResponsive';
import { supabase } from '../../supabase/client';
import type { PlanWithSteps } from '../../types';
interface PlanEditModalProps {
plan: PlanWithSteps;
isOpen: boolean;
onClose: () => void;
onPlanUpdated?: () => void;
}
export function PlanEditModal({ plan, isOpen, onClose, onPlanUpdated }: PlanEditModalProps) {
const { isMobile } = useResponsive();
const [activeTab, setActiveTab] = useState<'edit' | 'delete'>('edit');
const [isSubmitting, setIsSubmitting] = useState(false);
const [error, setError] = useState('');
const [editForm, setEditForm] = useState({
product_name: plan.product_name,
color: plan.color,
target_quantity: plan.target_quantity,
remark: plan.remark || ''
});
// 判断是否可以编辑/删除:只有当"确认计划"步骤状态为 pending 时才允许
const confirmStep = plan.processSteps.find(s => s.step_type === 'confirm');
const canModify = confirmStep?.status === 'pending';
const handleUpdate = async (e: React.FormEvent) => {
e.preventDefault();
if (!canModify) {
setError('计划已被工厂确认,无法修改');
return;
}
setIsSubmitting(true);
setError('');
const { error: updateError } = await supabase
.from('production_plans')
.update({
product_name: editForm.product_name,
color: editForm.color,
target_quantity: editForm.target_quantity,
remark: editForm.remark || null,
updated_at: new Date().toISOString()
})
.eq('id', plan.id);
setIsSubmitting(false);
if (updateError) {
setError('更新失败: ' + updateError.message);
} else {
onPlanUpdated?.();
onClose();
}
};
const handleDelete = async () => {
if (!canModify) {
setError('计划已被工厂确认,无法删除');
return;
}
setIsSubmitting(true);
setError('');
// 先删除关联的 process_steps
await supabase.from('plan_process_steps').delete().eq('plan_id', plan.id);
// 再删除 plan_factories 关联
await supabase.from('plan_factories').delete().eq('plan_id', plan.id);
// 最后删除计划
const { error: deleteError } = await supabase
.from('production_plans')
.delete()
.eq('id', plan.id);
setIsSubmitting(false);
if (deleteError) {
setError('删除失败: ' + deleteError.message);
} else {
onPlanUpdated?.();
onClose();
}
};
if (!isOpen) return null;
return (
<div className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center p-4 z-50">
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.95 }}
className="bg-white rounded-2xl w-full max-w-md shadow-2xl overflow-hidden"
>
{/* 头部 */}
<div className="flex items-center justify-between p-4 border-b border-gray-100">
<div className="flex items-center gap-2">
{activeTab === 'edit' ? (
<Edit2 className="w-5 h-5 text-blue-600" />
) : (
<Trash2 className="w-5 h-5 text-red-600" />
)}
<h3 className="text-lg font-semibold text-gray-900">
{activeTab === 'edit' ? '编辑计划' : '删除计划'}
</h3>
</div>
<button
onClick={onClose}
className="p-2 hover:bg-gray-100 rounded-lg transition-colors"
>
<X className="w-5 h-5 text-gray-500" />
</button>
</div>
{/* Tab 切换 */}
{canModify && (
<div className="flex border-b border-gray-100">
<button
onClick={() => setActiveTab('edit')}
className={`flex-1 py-3 text-sm font-medium transition-colors ${
activeTab === 'edit'
? 'text-blue-600 border-b-2 border-blue-600'
: 'text-gray-500 hover:text-gray-700'
}`}
>
</button>
<button
onClick={() => setActiveTab('delete')}
className={`flex-1 py-3 text-sm font-medium transition-colors ${
activeTab === 'delete'
? 'text-red-600 border-b-2 border-red-600'
: 'text-gray-500 hover:text-gray-700'
}`}
>
</button>
</div>
)}
{/* 内容区域 */}
<div className="p-4">
{!canModify ? (
<div className="text-center py-8">
<AlertTriangle className="w-12 h-12 text-amber-500 mx-auto mb-3" />
<p className="text-gray-600 mb-2"></p>
<p className="text-sm text-gray-400"></p>
</div>
) : activeTab === 'edit' ? (
<form onSubmit={handleUpdate} className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
</label>
<input
type="text"
value={editForm.product_name}
onChange={(e) => setEditForm({ ...editForm, product_name: e.target.value })}
className="w-full px-3 py-2 border border-gray-200 rounded-lg focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-all"
required
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
</label>
<input
type="text"
value={editForm.color}
onChange={(e) => setEditForm({ ...editForm, color: e.target.value })}
className="w-full px-3 py-2 border border-gray-200 rounded-lg focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-all"
required
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
</label>
<input
type="number"
value={editForm.target_quantity}
onChange={(e) => setEditForm({ ...editForm, target_quantity: parseInt(e.target.value) || 0 })}
className="w-full px-3 py-2 border border-gray-200 rounded-lg focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-all"
min="1"
required
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
</label>
<textarea
value={editForm.remark}
onChange={(e) => setEditForm({ ...editForm, remark: e.target.value })}
className="w-full px-3 py-2 border border-gray-200 rounded-lg focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-all resize-none"
rows={3}
/>
</div>
{error && (
<div className="p-3 bg-red-50 border border-red-200 rounded-lg text-sm text-red-600">
{error}
</div>
)}
<div className="flex gap-3 pt-2">
<button
type="button"
onClick={onClose}
className="flex-1 py-2.5 border border-gray-200 text-gray-700 rounded-lg hover:bg-gray-50 transition-colors font-medium"
>
</button>
<button
type="submit"
disabled={isSubmitting}
className="flex-1 py-2.5 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors font-medium disabled:opacity-50"
>
{isSubmitting ? '保存中...' : '保存修改'}
</button>
</div>
</form>
) : (
<div className="space-y-4">
<div className="p-4 bg-red-50 border border-red-200 rounded-xl">
<div className="flex items-start gap-3">
<AlertTriangle className="w-5 h-5 text-red-600 flex-shrink-0 mt-0.5" />
<div>
<p className="font-medium text-red-900"></p>
<p className="text-sm text-red-700 mt-1">
: {plan.plan_code}
</p>
<p className="text-sm text-red-700">
: {plan.product_name}-{plan.color}
</p>
<p className="text-xs text-red-600 mt-2">
</p>
</div>
</div>
</div>
{error && (
<div className="p-3 bg-red-50 border border-red-200 rounded-lg text-sm text-red-600">
{error}
</div>
)}
<div className="flex gap-3">
<button
onClick={onClose}
className="flex-1 py-2.5 border border-gray-200 text-gray-700 rounded-lg hover:bg-gray-50 transition-colors font-medium"
>
</button>
<button
onClick={handleDelete}
disabled={isSubmitting}
className="flex-1 py-2.5 bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors font-medium disabled:opacity-50"
>
{isSubmitting ? '删除中...' : '确认删除'}
</button>
</div>
</div>
)}
</div>
</motion.div>
</div>
);
}