The PM panel (PmPanelPage -> iframe /pm/) needs the project-management platform served at /pm/ on the ERP domain. Source: org repo Aramesh/PM, branch feature/project-management-platform. Update via: git subtree pull --prefix=pm aramesh-pm feature/project-management-platform --squash
195 lines
7.4 KiB
TypeScript
195 lines
7.4 KiB
TypeScript
import { useState } from "react";
|
|
import { Plus, Trash2, Layers, X } from "lucide-react";
|
|
import type { HumanResourceItem, MaterialItem } from "../lib/api";
|
|
|
|
interface ResourceSectionInputsProps {
|
|
humanResources?: HumanResourceItem[];
|
|
setHumanResources?: (hrs: HumanResourceItem[]) => void;
|
|
materials: MaterialItem[];
|
|
setMaterials: (mats: MaterialItem[]) => void;
|
|
}
|
|
|
|
function TagInput({
|
|
tags,
|
|
onChange,
|
|
placeholder = "افزودن مشخصه...",
|
|
}: {
|
|
tags: string[];
|
|
onChange: (tags: string[]) => void;
|
|
placeholder?: string;
|
|
}) {
|
|
const [val, setVal] = useState("");
|
|
|
|
const addTag = () => {
|
|
const trimmed = val.trim();
|
|
if (trimmed && !tags.includes(trimmed)) {
|
|
onChange([...tags, trimmed]);
|
|
setVal("");
|
|
}
|
|
};
|
|
|
|
const removeTag = (index: number) => {
|
|
onChange(tags.filter((_, i) => i !== index));
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-1.5">
|
|
<div className="flex gap-1">
|
|
<input
|
|
value={val}
|
|
onChange={(e) => setVal(e.target.value)}
|
|
onKeyDown={(e) => {
|
|
if (e.key === "Enter") {
|
|
e.preventDefault();
|
|
addTag();
|
|
}
|
|
}}
|
|
placeholder={placeholder}
|
|
className="h-8 flex-1 rounded-lg border border-slate-300 bg-white px-2.5 text-xs text-slate-800 placeholder:text-slate-400 focus:border-sky-500 focus:outline-none focus:ring-1 focus:ring-sky-200"
|
|
/>
|
|
<button
|
|
type="button"
|
|
onClick={addTag}
|
|
className="inline-flex h-8 items-center justify-center rounded-lg bg-slate-100 px-2.5 text-xs font-semibold text-slate-700 hover:bg-slate-200 transition-colors"
|
|
>
|
|
<Plus size={14} />
|
|
</button>
|
|
</div>
|
|
|
|
{tags.length > 0 && (
|
|
<div className="flex flex-wrap gap-1">
|
|
{tags.map((tag, i) => (
|
|
<span
|
|
key={i}
|
|
className="inline-flex items-center gap-1 rounded-md bg-slate-100 px-2 py-0.5 text-[11px] font-medium text-slate-700 border border-slate-200"
|
|
>
|
|
{tag}
|
|
<button
|
|
type="button"
|
|
onClick={() => removeTag(i)}
|
|
className="text-slate-400 hover:text-rose-600 transition-colors"
|
|
aria-label="حذف مشخصه"
|
|
>
|
|
<X size={12} />
|
|
</button>
|
|
</span>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function ResourceSectionInputs({
|
|
materials,
|
|
setMaterials,
|
|
}: ResourceSectionInputsProps) {
|
|
// --- Materials handlers ---
|
|
const addMaterial = () => {
|
|
setMaterials([...materials, { name: "", unit: "", specifications: [], count: 1 }]);
|
|
};
|
|
|
|
const updateMaterial = (index: number, field: keyof MaterialItem, value: any) => {
|
|
const next = [...materials];
|
|
next[index] = { ...next[index], [field]: value };
|
|
setMaterials(next);
|
|
};
|
|
|
|
const removeMaterial = (index: number) => {
|
|
setMaterials(materials.filter((_, i) => i !== index));
|
|
};
|
|
|
|
return (
|
|
<div className="rounded-xl border border-slate-200 bg-slate-50/60 p-3 space-y-3">
|
|
<div className="flex items-center justify-between border-b border-slate-200 pb-2">
|
|
<span className="text-xs font-bold text-slate-700 flex items-center gap-1.5">
|
|
<Layers size={14} className="text-amber-600" />
|
|
<span>مصالح و تجهیزات وظیفه</span>
|
|
</span>
|
|
{materials.length > 0 && (
|
|
<span className="rounded-full bg-amber-100 text-amber-700 px-2 py-0.5 text-[10px] font-bold">
|
|
{materials.length} قلم
|
|
</span>
|
|
)}
|
|
</div>
|
|
|
|
{/* --- Materials & Equipment Content --- */}
|
|
<div className="space-y-2.5">
|
|
{materials.length === 0 ? (
|
|
<p className="py-2 text-center text-xs text-slate-400">
|
|
هیچ مصالح یا تجهیزاتی تعریف نشده است.
|
|
</p>
|
|
) : (
|
|
materials.map((m, i) => (
|
|
<div
|
|
key={i}
|
|
className="rounded-lg border border-slate-200 bg-white p-2.5 shadow-sm space-y-2"
|
|
>
|
|
<div className="flex items-start gap-2">
|
|
<div className="flex-1">
|
|
<label className="mb-0.5 block text-[10px] font-medium text-slate-500">نام مصالح، کالا یا تجهیزات</label>
|
|
<input
|
|
value={m.name}
|
|
onChange={(e) => updateMaterial(i, "name", e.target.value)}
|
|
placeholder="مثلاً: سیمان تیپ ۲، میلگرد ۱۶، پمپ بتن، تاور کرین"
|
|
className="h-8 w-full rounded-md border border-slate-300 px-2 text-xs text-slate-800 focus:border-amber-500 focus:outline-none"
|
|
/>
|
|
</div>
|
|
|
|
<div className="w-24">
|
|
<label className="mb-0.5 block text-[10px] font-medium text-slate-500">واحد سنجش</label>
|
|
<input
|
|
value={m.unit || ""}
|
|
onChange={(e) => updateMaterial(i, "unit", e.target.value)}
|
|
placeholder="تن، متر، دستگاه، شاخه"
|
|
className="h-8 w-full rounded-md border border-slate-300 px-2 text-xs text-slate-800 focus:border-amber-500 focus:outline-none"
|
|
/>
|
|
</div>
|
|
|
|
<div className="w-20">
|
|
<label className="mb-0.5 block text-[10px] font-medium text-slate-500">مقدار</label>
|
|
<input
|
|
type="number"
|
|
step="any"
|
|
min={0}
|
|
value={m.count}
|
|
onChange={(e) => updateMaterial(i, "count", Math.max(0, parseFloat(e.target.value) || 0))}
|
|
className="h-8 w-full rounded-md border border-slate-300 px-2 text-xs text-slate-800 focus:border-amber-500 focus:outline-none"
|
|
/>
|
|
</div>
|
|
|
|
<button
|
|
type="button"
|
|
onClick={() => removeMaterial(i)}
|
|
className="mt-3.5 flex h-8 w-8 items-center justify-center rounded-md text-slate-400 hover:bg-rose-50 hover:text-rose-600 transition-colors"
|
|
title="حذف این ردیف"
|
|
>
|
|
<Trash2 size={15} />
|
|
</button>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="mb-0.5 block text-[10px] font-medium text-slate-500">مشخصهها، استانداردها و مدل</label>
|
|
<TagInput
|
|
tags={m.specifications || []}
|
|
onChange={(tags) => updateMaterial(i, "specifications", tags)}
|
|
placeholder="مثال: استاندارد A3، عیار ۳۵۰، بوم ۳۶ متری (Enter بزنید)"
|
|
/>
|
|
</div>
|
|
</div>
|
|
))
|
|
)}
|
|
|
|
<button
|
|
type="button"
|
|
onClick={addMaterial}
|
|
className="inline-flex h-8 w-full items-center justify-center gap-1.5 rounded-lg border border-dashed border-amber-300 bg-amber-50/60 text-xs font-semibold text-amber-800 hover:bg-amber-100/70 transition-colors"
|
|
>
|
|
<Plus size={14} />
|
|
<span>افزودن مصالح و تجهیزات</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|