188 lines
6.3 KiB
Svelte
Raw Normal View History

<script>
import { Plus, Trash2, ArrowUp, ArrowDown, Box } from 'lucide-svelte';
let { formData = $bindable(), tests = [], isDirty = $bindable(false) } = $props();
let availableTests = $state([]);
let selectedTestId = $state('');
let addMemberOpen = $state(false);
const members = $derived.by(() => {
const memberIds = formData.details.members?.map(m => Number(m.TestSiteID)) || [];
return tests.filter(t => memberIds.includes(Number(t.TestSiteID)))
.map(t => {
const memberObj = formData.details.members?.find(m => Number(m.TestSiteID) === Number(t.TestSiteID));
return { ...t, seq: memberObj?.Member || 0 };
})
.sort((a, b) => a.seq - b.seq);
});
const availableOptions = $derived.by(() => {
const memberIds = formData.details.members?.map(m => Number(m.TestSiteID)) || [];
return tests.filter(t =>
Number(t.TestSiteID) !== Number(formData.TestSiteID) &&
!memberIds.includes(Number(t.TestSiteID)) &&
t.IsActive !== '0' &&
t.IsActive !== 0
).map(t => ({
value: t.TestSiteID,
label: `${t.TestSiteCode} - ${t.TestSiteName}`,
data: t
}));
});
function handleFieldChange() {
isDirty = true;
}
function addMember() {
if (!selectedTestId) return;
const currentMembers = formData.details.members || [];
const newMember = {
TestSiteID: parseInt(selectedTestId),
Member: currentMembers.length + 1
};
formData.details.members = [...currentMembers, newMember];
selectedTestId = '';
handleFieldChange();
}
function removeMember(testId) {
const remainingMembers = formData.details.members?.filter(m => Number(m.TestSiteID) !== Number(testId)) || [];
// Re-sequence the remaining members
formData.details.members = remainingMembers.map((m, idx) => ({
...m,
Member: idx + 1
}));
handleFieldChange();
}
function moveMember(index, direction) {
const members = [...formData.details.members];
const newIndex = index + direction;
if (newIndex >= 0 && newIndex < members.length) {
// Swap the members
[members[index], members[newIndex]] = [members[newIndex], members[index]];
// Update sequence numbers
formData.details.members = members.map((m, idx) => ({
...m,
Member: idx + 1
}));
handleFieldChange();
}
}
</script>
<div class="space-y-6">
<h2 class="text-lg font-semibold text-gray-800">Group Members</h2>
<div class="alert alert-info text-sm">
<Box class="w-4 h-4" />
<div>
<strong>Panel Members:</strong> Add tests, parameters, or calculated values to this panel. Order matters for display on reports.
</div>
</div>
<div class="space-y-4">
<h3 class="text-sm font-medium text-gray-600 uppercase tracking-wide">Current Members ({members.length})</h3>
{#if members.length === 0}
<div class="text-center py-8 bg-base-200 rounded-lg">
<Box class="w-12 h-12 mx-auto text-gray-400 mb-2" />
<p class="text-sm text-gray-500">No members added yet</p>
<p class="text-xs text-gray-400">Add tests to create this panel</p>
</div>
{:else}
<div class="overflow-x-auto border border-base-200 rounded-lg">
<table class="table table-sm table-compact">
<thead>
<tr class="bg-base-200">
<th class="w-12 text-center">#</th>
<th class="w-24">Code</th>
<th>Name</th>
<th class="w-20">Type</th>
<th class="w-32 text-center">Actions</th>
</tr>
</thead>
<tbody>
{#each members as member, idx (member.TestSiteID)}
<tr class="hover:bg-base-100">
<td class="text-center text-gray-500">{idx + 1}</td>
<td class="font-mono text-sm">{member.TestSiteCode}</td>
<td>{member.TestSiteName}</td>
<td>
<span class="badge badge-xs badge-ghost">{member.TestType}</span>
</td>
<td>
<div class="flex justify-center gap-1">
<button
class="btn btn-ghost btn-xs"
onclick={() => moveMember(idx, -1)}
disabled={idx === 0}
title="Move Up"
>
<ArrowUp class="w-3 h-3" />
</button>
<button
class="btn btn-ghost btn-xs"
onclick={() => moveMember(idx, 1)}
disabled={idx === members.length - 1}
title="Move Down"
>
<ArrowDown class="w-3 h-3" />
</button>
<button
class="btn btn-ghost btn-xs text-error"
onclick={() => removeMember(member.TestSiteID)}
title="Remove Member"
>
<Trash2 class="w-3 h-3" />
</button>
</div>
</td>
</tr>
{/each}
</tbody>
</table>
</div>
{/if}
</div>
<div class="space-y-4">
<h3 class="text-sm font-medium text-gray-600 uppercase tracking-wide">Add Member</h3>
{#if availableOptions.length === 0}
<div class="alert alert-warning text-sm">
<span>No available tests to add. All tests are either already members or inactive.</span>
</div>
{:else}
<div class="flex gap-2">
<select
class="select select-sm select-bordered flex-1"
bind:value={selectedTestId}
>
<option value="">Select a test to add...</option>
{#each availableOptions as opt (opt.value)}
<option value={opt.value}>{opt.label}</option>
{/each}
</select>
<button
class="btn btn-sm btn-primary"
onclick={addMember}
disabled={!selectedTestId}
>
<Plus class="w-4 h-4 mr-1" />
Add
</button>
</div>
{/if}
</div>
<div class="text-xs text-gray-500 space-y-1">
<p><strong>Tip:</strong> Members will display on reports in the order shown above.</p>
<p><strong>Note:</strong> Panels cannot contain themselves or other panels (circular references).</p>
</div>
</div>