Select checklist reviewer from users
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 40s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 40s
This commit is contained in:
@@ -31,6 +31,7 @@ const ProductionPlans = () => {
|
||||
const [operationalUsers, setOperationalUsers] = useState<OperationalUser[]>([]);
|
||||
const [responsiblePickerPlanId, setResponsiblePickerPlanId] = useState<number | null>(null);
|
||||
const [reviewingPlanId, setReviewingPlanId] = useState<number | null>(null);
|
||||
const [isReviewUserPickerOpen, setIsReviewUserPickerOpen] = useState(false);
|
||||
const [reviewForm, setReviewForm] = useState({
|
||||
decision: 'pending' as ProductionPlanReviewDecision,
|
||||
materialsReserved: false,
|
||||
@@ -91,6 +92,11 @@ const ProductionPlans = () => {
|
||||
if (!operationalUsers.length) void loadOperationalUsers();
|
||||
};
|
||||
|
||||
const toggleReviewUserPicker = () => {
|
||||
setIsReviewUserPickerOpen(current => !current);
|
||||
if (!operationalUsers.length) void loadOperationalUsers();
|
||||
};
|
||||
|
||||
const movePlan = async (plan: ProductionPlan, status: ProductionOrderStatus) => {
|
||||
if (status === plan.status) return;
|
||||
setBusyPlanId(plan.id);
|
||||
@@ -165,6 +171,7 @@ const ProductionPlans = () => {
|
||||
reviewedBy: plan.review?.reviewedBy || plan.responsibleName || '',
|
||||
});
|
||||
setResponsiblePickerPlanId(null);
|
||||
setIsReviewUserPickerOpen(false);
|
||||
setReviewingPlanId(plan.id);
|
||||
};
|
||||
|
||||
@@ -255,7 +262,7 @@ const ProductionPlans = () => {
|
||||
<div className="overflow-y-auto p-5">
|
||||
<p className="text-sm font-medium leading-6 text-dark-muted">Revise os pontos de liberação antes de avançar a operação. Este registro não movimenta estoque nem substitui o recebimento físico.</p>
|
||||
<section className="mt-5"><h3 className="text-sm font-bold text-dark-text">Conferências</h3><div className="mt-3 grid gap-2 sm:grid-cols-2">{[{ key: 'materialsReserved', label: 'Materiais reservados' }, { key: 'cutSpecificationReady', label: 'Ficha e corte definidos' }, { key: 'sizeAndColorReviewed', label: 'Tamanhos e cores revisados' }, { key: 'responsibleAndDateDefined', label: 'Responsável e prazo definidos' }].map(item => <label key={item.key} className="flex min-h-12 items-center gap-3 rounded-xl border border-dark-border bg-dark-input/45 px-3 py-2.5 text-sm font-bold text-dark-text transition-colors hover:border-brand-primary/50 cursor-pointer"><input type="checkbox" checked={reviewForm[item.key as keyof Pick<typeof reviewForm, 'materialsReserved' | 'cutSpecificationReady' | 'sizeAndColorReviewed' | 'responsibleAndDateDefined'>]} onChange={(event) => setReviewForm(current => ({ ...current, [item.key]: event.target.checked }))} className="h-4 w-4 shrink-0 accent-brand-primary cursor-pointer" />{item.label}</label>)}</div></section>
|
||||
<section className="mt-5 grid gap-4 sm:grid-cols-2"><label className="text-xs font-bold text-dark-muted">Decisão<select value={reviewForm.decision} onChange={(event) => setReviewForm(current => ({ ...current, decision: event.target.value as ProductionPlanReviewDecision }))} className="mt-1.5 h-10 w-full rounded-lg border border-dark-border bg-dark-input px-3 text-sm font-bold text-dark-text outline-none transition-colors focus:border-brand-primary cursor-pointer"><option value="pending">Em revisão</option><option value="approved">Liberar para operação</option><option value="adjustments_required">Solicitar ajustes</option></select></label><label className="text-xs font-bold text-dark-muted">Revisado por<input value={reviewForm.reviewedBy} onChange={(event) => setReviewForm(current => ({ ...current, reviewedBy: event.target.value }))} className="mt-1.5 h-10 w-full rounded-lg border border-dark-border bg-dark-input px-3 text-sm font-semibold text-dark-text outline-none transition-colors focus:border-brand-primary" placeholder="Nome do usuário" /></label><label className="sm:col-span-2 text-xs font-bold text-dark-muted">Observação<input value={reviewForm.notes} onChange={(event) => setReviewForm(current => ({ ...current, notes: event.target.value }))} className="mt-1.5 h-10 w-full rounded-lg border border-dark-border bg-dark-input px-3 text-sm font-semibold text-dark-text outline-none transition-colors focus:border-brand-primary" placeholder="Ajuste, orientação ou motivo" /></label></section>
|
||||
<section className="mt-5 grid gap-4 sm:grid-cols-2"><label className="text-xs font-bold text-dark-muted">Decisão<select value={reviewForm.decision} onChange={(event) => setReviewForm(current => ({ ...current, decision: event.target.value as ProductionPlanReviewDecision }))} className="mt-1.5 h-10 w-full rounded-lg border border-dark-border bg-dark-input px-3 text-sm font-bold text-dark-text outline-none transition-colors focus:border-brand-primary cursor-pointer"><option value="pending">Em revisão</option><option value="approved">Liberar para operação</option><option value="adjustments_required">Solicitar ajustes</option></select></label><div className="relative text-xs font-bold text-dark-muted"><p>Revisado por</p><button type="button" onClick={toggleReviewUserPicker} aria-expanded={isReviewUserPickerOpen} className="mt-1.5 flex h-10 w-full items-center justify-between gap-2 rounded-lg border border-dark-border bg-dark-input px-3 text-left text-sm font-semibold text-dark-text outline-none transition-colors hover:border-brand-primary cursor-pointer"><span className="flex min-w-0 items-center gap-2"><UserRound className="h-4 w-4 shrink-0 text-brand-primary" /><span className="truncate">{reviewForm.reviewedBy || 'Selecionar usuário'}</span></span><ChevronDown className={`h-4 w-4 shrink-0 text-dark-muted transition-transform ${isReviewUserPickerOpen ? 'rotate-180' : ''}`} /></button>{isReviewUserPickerOpen && <div className="absolute inset-x-0 top-full z-20 mt-2 overflow-hidden rounded-xl border border-dark-border bg-dark-card shadow-2xl"><div className="border-b border-dark-border px-3 py-2 text-[10px] font-bold uppercase tracking-wider text-dark-muted">Usuários ativos do Graphs</div><div className="max-h-52 overflow-y-auto p-1.5">{isLoadingOperationalUsers ? <p className="flex items-center gap-2 px-2 py-3 text-xs font-semibold text-dark-muted"><Loader2 className="h-4 w-4 animate-spin text-brand-primary" />Carregando usuários...</p> : operationalUsers.length ? operationalUsers.map(user => <button key={user.id ?? 'super-admin'} type="button" onClick={() => { setReviewForm(current => ({ ...current, reviewedBy: user.name })); setIsReviewUserPickerOpen(false); }} className="flex w-full items-center justify-between gap-3 rounded-lg px-2.5 py-2 text-left text-sm font-semibold text-dark-text transition-colors hover:bg-dark-input cursor-pointer"><span className="truncate">{user.name}</span>{reviewForm.reviewedBy === user.name && <Check className="h-4 w-4 shrink-0 text-brand-primary" />}</button>) : <p className="px-2 py-3 text-xs font-semibold text-dark-muted">Nenhum usuário ativo encontrado.</p>}</div>{reviewForm.reviewedBy && <button type="button" onClick={() => { setReviewForm(current => ({ ...current, reviewedBy: '' })); setIsReviewUserPickerOpen(false); }} className="w-full border-t border-dark-border px-3 py-2 text-left text-xs font-bold text-dark-muted transition-colors hover:bg-dark-input hover:text-red-300 cursor-pointer">Remover revisor</button>}</div>}</div><label className="sm:col-span-2 text-xs font-bold text-dark-muted">Observação<input value={reviewForm.notes} onChange={(event) => setReviewForm(current => ({ ...current, notes: event.target.value }))} className="mt-1.5 h-10 w-full rounded-lg border border-dark-border bg-dark-input px-3 text-sm font-semibold text-dark-text outline-none transition-colors focus:border-brand-primary" placeholder="Ajuste, orientação ou motivo" /></label></section>
|
||||
</div>
|
||||
<footer className="flex flex-col-reverse gap-2 border-t border-dark-border px-5 py-4 sm:flex-row sm:items-center sm:justify-end"><button type="button" onClick={() => setReviewingPlanId(null)} disabled={busyPlanId === reviewingPlan.id} className="h-10 rounded-xl border border-dark-border bg-dark-input px-4 text-sm font-bold text-dark-text transition-colors hover:border-brand-primary disabled:cursor-not-allowed disabled:opacity-60 cursor-pointer">Cancelar</button><button type="button" disabled={busyPlanId === reviewingPlan.id} onClick={() => void submitReview(reviewingPlan)} className="inline-flex h-10 items-center justify-center gap-2 rounded-xl bg-brand-primary px-4 text-sm font-bold text-brand-contrast transition-colors hover:bg-brand-primary/90 disabled:cursor-not-allowed disabled:opacity-60 cursor-pointer"><Check className="h-4 w-4" />{busyPlanId === reviewingPlan.id ? 'Salvando...' : 'Salvar checklist'}</button></footer>
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user