Replace native selects with NovaSelect

This commit is contained in:
2026-05-01 07:45:37 +02:00
parent 67be537c86
commit 35011001ba
55 changed files with 3136 additions and 1662 deletions

View File

@@ -1,6 +1,7 @@
import React from 'react'
import { router, useForm, usePage } from '@inertiajs/react'
import StudioLayout from '../../Layouts/StudioLayout'
import NovaSelect from '../../components/ui/NovaSelect'
function normalizeIds(values) {
return Array.from(values || []).map((option) => Number(option.value)).filter((value) => Number.isFinite(value) && value > 0)
@@ -48,35 +49,21 @@ export default function StudioGroupProjectEditor() {
<textarea value={form.data.summary} onChange={(event) => form.setData('summary', event.target.value)} placeholder="Short summary" rows={3} className="rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" />
<textarea value={form.data.description} onChange={(event) => form.setData('description', event.target.value)} placeholder="Longer project description" rows={8} className="rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" />
<div className="grid gap-4 md:grid-cols-2">
<select value={form.data.visibility} onChange={(event) => form.setData('visibility', event.target.value)} className="rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none">{(props.visibilityOptions || []).map((option) => <option key={option.value} value={option.value}>{option.label}</option>)}</select>
<select value={form.data.status} onChange={(event) => form.setData('status', event.target.value)} className="rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none">{(props.statusOptions || []).map((option) => <option key={option.value} value={option.value}>{option.label}</option>)}</select>
<NovaSelect value={form.data.visibility} onChange={(val) => form.setData('visibility', val)} options={props.visibilityOptions || []} searchable={false} />
<NovaSelect value={form.data.status} onChange={(val) => form.setData('status', val)} options={props.statusOptions || []} searchable={false} />
</div>
<div className="grid gap-4 md:grid-cols-2">
<input type="date" value={form.data.start_date} onChange={(event) => form.setData('start_date', event.target.value)} className="rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" />
<input type="date" value={form.data.target_date} onChange={(event) => form.setData('target_date', event.target.value)} className="rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" />
</div>
<div className="grid gap-4 md:grid-cols-2">
<select value={form.data.lead_user_id} onChange={(event) => form.setData('lead_user_id', event.target.value)} className="rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none">
<option value="">No lead</option>
{(props.memberOptions || []).map((option) => <option key={option.id} value={option.id}>{option.name || option.username}</option>)}
</select>
<select value={form.data.linked_collection_id} onChange={(event) => form.setData('linked_collection_id', event.target.value)} className="rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none">
<option value="">No linked collection</option>
{(props.collectionOptions || []).map((option) => <option key={option.id} value={option.id}>{option.title}</option>)}
</select>
<NovaSelect value={String(form.data.lead_user_id || '')} onChange={(val) => form.setData('lead_user_id', val)} placeholder="No lead" options={(props.memberOptions || []).map((o) => ({ value: String(o.id), label: o.name || o.username }))} />
<NovaSelect value={String(form.data.linked_collection_id || '')} onChange={(val) => form.setData('linked_collection_id', val)} placeholder="No linked collection" options={(props.collectionOptions || []).map((o) => ({ value: String(o.id), label: o.title }))} />
</div>
<select multiple value={form.data.member_user_ids.map(String)} onChange={(event) => form.setData('member_user_ids', normalizeIds(event.target.selectedOptions))} className="min-h-40 rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none">
{(props.memberOptions || []).map((option) => <option key={option.id} value={option.id}>{option.name || option.username}</option>)}
</select>
<NovaSelect multi value={form.data.member_user_ids} onChange={(vals) => form.setData('member_user_ids', (vals || []).map(Number).filter(Boolean))} placeholder="Select team members" options={(props.memberOptions || []).map((o) => ({ value: o.id, label: o.name || o.username }))} />
<div className="grid gap-4 md:grid-cols-2">
<select value={form.data.linked_featured_artwork_id} onChange={(event) => form.setData('linked_featured_artwork_id', event.target.value)} className="rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none">
<option value="">No featured artwork</option>
{(props.artworkOptions || []).map((option) => <option key={option.id} value={option.id}>{option.title}</option>)}
</select>
<select value={form.data.pinned_post_id} onChange={(event) => form.setData('pinned_post_id', event.target.value)} className="rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none">
<option value="">No pinned post</option>
{(props.postOptions || []).map((option) => <option key={option.id} value={option.id}>{option.title}</option>)}
</select>
<NovaSelect value={String(form.data.linked_featured_artwork_id || '')} onChange={(val) => form.setData('linked_featured_artwork_id', val)} placeholder="No featured artwork" options={(props.artworkOptions || []).map((o) => ({ value: String(o.id), label: o.title }))} />
<NovaSelect value={String(form.data.pinned_post_id || '')} onChange={(val) => form.setData('pinned_post_id', val)} placeholder="No pinned post" options={(props.postOptions || []).map((o) => ({ value: String(o.id), label: o.title }))} />
</div>
<input type="file" accept="image/*" onChange={(event) => form.setData('cover_file', event.target.files?.[0] || null)} className="rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" />
</div>
@@ -87,7 +74,7 @@ export default function StudioGroupProjectEditor() {
{props.statusUrl ? (
<form onSubmit={(event) => { event.preventDefault(); statusForm.post(props.statusUrl, { preserveScroll: true }) }} className="rounded-[28px] border border-white/10 bg-white/[0.03] p-6">
<h2 className="text-lg font-semibold text-white">Status</h2>
<select value={statusForm.data.status} onChange={(event) => statusForm.setData('status', event.target.value)} className="mt-4 w-full rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none">{(props.statusOptions || []).map((option) => <option key={option.value} value={option.value}>{option.label}</option>)}</select>
<NovaSelect value={statusForm.data.status} onChange={(val) => statusForm.setData('status', val)} options={props.statusOptions || []} searchable={false} />
<button type="submit" className="mt-4 rounded-full border border-white/10 bg-white/[0.05] px-4 py-2 text-sm font-semibold text-white">Update status</button>
</form>
) : null}
@@ -95,10 +82,7 @@ export default function StudioGroupProjectEditor() {
{props.attachArtworkUrl ? (
<form onSubmit={(event) => { event.preventDefault(); artworkAttach.post(props.attachArtworkUrl, { preserveScroll: true }) }} className="rounded-[28px] border border-white/10 bg-white/[0.03] p-6">
<h2 className="text-lg font-semibold text-white">Attach artwork</h2>
<select value={artworkAttach.data.artwork_id} onChange={(event) => artworkAttach.setData('artwork_id', event.target.value)} className="mt-4 w-full rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none">
<option value="">Choose artwork</option>
{(props.artworkOptions || []).map((option) => <option key={option.id} value={option.id}>{option.title}</option>)}
</select>
<NovaSelect value={String(artworkAttach.data.artwork_id || '')} onChange={(val) => artworkAttach.setData('artwork_id', val)} placeholder="Choose artwork" options={(props.artworkOptions || []).map((o) => ({ value: String(o.id), label: o.title }))} />
<button type="submit" className="mt-4 rounded-full border border-white/10 bg-white/[0.05] px-4 py-2 text-sm font-semibold text-white">Attach artwork</button>
</form>
) : null}
@@ -106,10 +90,7 @@ export default function StudioGroupProjectEditor() {
{props.attachAssetUrl ? (
<form onSubmit={(event) => { event.preventDefault(); assetAttach.post(props.attachAssetUrl, { preserveScroll: true }) }} className="rounded-[28px] border border-white/10 bg-white/[0.03] p-6">
<h2 className="text-lg font-semibold text-white">Attach asset</h2>
<select value={assetAttach.data.asset_id} onChange={(event) => assetAttach.setData('asset_id', event.target.value)} className="mt-4 w-full rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none">
<option value="">Choose asset</option>
{(props.assetOptions || []).map((option) => <option key={option.id} value={option.id}>{option.title}</option>)}
</select>
<NovaSelect value={String(assetAttach.data.asset_id || '')} onChange={(val) => assetAttach.setData('asset_id', val)} placeholder="Choose asset" options={(props.assetOptions || []).map((o) => ({ value: String(o.id), label: o.title }))} />
<button type="submit" className="mt-4 rounded-full border border-white/10 bg-white/[0.05] px-4 py-2 text-sm font-semibold text-white">Attach asset</button>
</form>
) : null}
@@ -121,15 +102,10 @@ export default function StudioGroupProjectEditor() {
<input value={milestoneForm.data.title} onChange={(event) => milestoneForm.setData('title', event.target.value)} placeholder="Milestone title" className="w-full rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" />
<textarea value={milestoneForm.data.summary} onChange={(event) => milestoneForm.setData('summary', event.target.value)} placeholder="Summary" rows={3} className="w-full rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" />
<div className="grid gap-3 md:grid-cols-2">
<select value={milestoneForm.data.status} onChange={(event) => milestoneForm.setData('status', event.target.value)} className="rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none">
{['pending', 'active', 'blocked', 'completed', 'cancelled'].map((status) => <option key={status} value={status}>{status}</option>)}
</select>
<NovaSelect value={milestoneForm.data.status} onChange={(val) => milestoneForm.setData('status', val)} searchable={false} options={['pending', 'active', 'blocked', 'completed', 'cancelled'].map((s) => ({ value: s, label: s }))} />
<input type="date" value={milestoneForm.data.due_date} onChange={(event) => milestoneForm.setData('due_date', event.target.value)} className="rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" />
</div>
<select value={milestoneForm.data.owner_user_id} onChange={(event) => milestoneForm.setData('owner_user_id', event.target.value)} className="w-full rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none">
<option value="">No owner</option>
{(props.memberOptions || []).map((option) => <option key={option.id} value={option.id}>{option.name || option.username}</option>)}
</select>
<NovaSelect value={String(milestoneForm.data.owner_user_id || '')} onChange={(val) => milestoneForm.setData('owner_user_id', val)} placeholder="No owner" options={(props.memberOptions || []).map((o) => ({ value: String(o.id), label: o.name || o.username }))} />
<textarea value={milestoneForm.data.notes} onChange={(event) => milestoneForm.setData('notes', event.target.value)} placeholder="Notes" rows={3} className="w-full rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none" />
<button type="submit" className="rounded-full border border-white/10 bg-white/[0.05] px-4 py-2 text-sm font-semibold text-white">Add milestone</button>
</div>