feat(G-08): chapter field uses live datalist from resource.chapters
All chapter text inputs now show autocomplete suggestions from the database (distinct chapter values from active resources) via HTML <datalist> wired to trpc.resource.chapters: - ResourceModal: chapter input - RateCardsClient: rate card line chapter input - EffortRulesClient: effort rule chapter input - ExperienceMultipliersClient: replaces hardcoded CHAPTER_PRESETS with live data, falls back to presets when no data available Also revert blueprintRolePresetsInputSchema to z.array(z.unknown()) to restore compatibility with StaffingRequirement[] call sites. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -69,6 +69,7 @@ const emptyRuleSet: EditingRuleSet = {
|
|||||||
export function EffortRulesClient() {
|
export function EffortRulesClient() {
|
||||||
const utils = trpc.useUtils();
|
const utils = trpc.useUtils();
|
||||||
const { data: ruleSets, isLoading } = trpc.effortRule.list.useQuery();
|
const { data: ruleSets, isLoading } = trpc.effortRule.list.useQuery();
|
||||||
|
const { data: chapters } = trpc.resource.chapters.useQuery(undefined, { staleTime: 60_000 });
|
||||||
|
|
||||||
const createMutation = trpc.effortRule.create.useMutation({
|
const createMutation = trpc.effortRule.create.useMutation({
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
@@ -274,6 +275,7 @@ export function EffortRulesClient() {
|
|||||||
onChange={(e) => updateRule(i, { chapter: e.target.value })}
|
onChange={(e) => updateRule(i, { chapter: e.target.value })}
|
||||||
className="w-full rounded-lg border border-gray-200 px-2 py-1 text-sm"
|
className="w-full rounded-lg border border-gray-200 px-2 py-1 text-sm"
|
||||||
placeholder="Chapter"
|
placeholder="Chapter"
|
||||||
|
list="er-chapter-list"
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-2 py-2">
|
<td className="px-2 py-2">
|
||||||
@@ -319,6 +321,9 @@ export function EffortRulesClient() {
|
|||||||
<option key={d} value={d} />
|
<option key={d} value={d} />
|
||||||
))}
|
))}
|
||||||
</datalist>
|
</datalist>
|
||||||
|
<datalist id="er-chapter-list">
|
||||||
|
{chapters?.map((c) => <option key={c} value={c} />)}
|
||||||
|
</datalist>
|
||||||
|
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -79,6 +79,8 @@ const emptySet: EditingSet = {
|
|||||||
export function ExperienceMultipliersClient() {
|
export function ExperienceMultipliersClient() {
|
||||||
const utils = trpc.useUtils();
|
const utils = trpc.useUtils();
|
||||||
const { data: sets, isLoading } = trpc.experienceMultiplier.list.useQuery();
|
const { data: sets, isLoading } = trpc.experienceMultiplier.list.useQuery();
|
||||||
|
const { data: liveChapters } = trpc.resource.chapters.useQuery(undefined, { staleTime: 60_000 });
|
||||||
|
const chapterOptions = liveChapters?.length ? liveChapters : CHAPTER_PRESETS;
|
||||||
|
|
||||||
const createMutation = trpc.experienceMultiplier.create.useMutation({
|
const createMutation = trpc.experienceMultiplier.create.useMutation({
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
@@ -352,7 +354,7 @@ export function ExperienceMultipliersClient() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<datalist id="chapter-presets">
|
<datalist id="chapter-presets">
|
||||||
{CHAPTER_PRESETS.map((d) => (
|
{chapterOptions.map((d) => (
|
||||||
<option key={d} value={d} />
|
<option key={d} value={d} />
|
||||||
))}
|
))}
|
||||||
</datalist>
|
</datalist>
|
||||||
|
|||||||
@@ -124,6 +124,7 @@ export function RateCardsClient() {
|
|||||||
|
|
||||||
const { data: roles } = trpc.role.list.useQuery({});
|
const { data: roles } = trpc.role.list.useQuery({});
|
||||||
const { data: clientsData } = trpc.clientEntity.list.useQuery({});
|
const { data: clientsData } = trpc.clientEntity.list.useQuery({});
|
||||||
|
const { data: chapters } = trpc.resource.chapters.useQuery(undefined, { staleTime: 60_000 });
|
||||||
|
|
||||||
// ─── Mutations ──────────────────────────────────────────────────────────
|
// ─── Mutations ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -682,7 +683,11 @@ export function RateCardsClient() {
|
|||||||
onChange={(e) => setEditingLine({ ...editingLine, chapter: e.target.value })}
|
onChange={(e) => setEditingLine({ ...editingLine, chapter: e.target.value })}
|
||||||
placeholder="e.g. Animation"
|
placeholder="e.g. Animation"
|
||||||
className="border border-gray-300 dark:border-gray-600 rounded-lg px-3 py-2 text-sm w-full bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-brand-400"
|
className="border border-gray-300 dark:border-gray-600 rounded-lg px-3 py-2 text-sm w-full bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-brand-400"
|
||||||
|
list="rc-chapter-list"
|
||||||
/>
|
/>
|
||||||
|
<datalist id="rc-chapter-list">
|
||||||
|
{chapters?.map((c) => <option key={c} value={c} />)}
|
||||||
|
</datalist>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="flex items-center text-xs font-medium text-gray-600 dark:text-gray-400 mb-1">Location <InfoTooltip content="Geographic location for region-specific rate pricing." /></label>
|
<label className="flex items-center text-xs font-medium text-gray-600 dark:text-gray-400 mb-1">Location <InfoTooltip content="Geographic location for region-specific rate pricing." /></label>
|
||||||
|
|||||||
@@ -203,6 +203,7 @@ export function ResourceModal({ mode, resource, onClose, onSuccess }: ResourceMo
|
|||||||
const { canManageUsers } = usePermissions();
|
const { canManageUsers } = usePermissions();
|
||||||
const utils = trpc.useUtils();
|
const utils = trpc.useUtils();
|
||||||
|
|
||||||
|
const { data: chapters } = trpc.resource.chapters.useQuery(undefined, { staleTime: 60_000 });
|
||||||
const { data: availableRoles } = trpc.role.list.useQuery(
|
const { data: availableRoles } = trpc.role.list.useQuery(
|
||||||
{ isActive: true },
|
{ isActive: true },
|
||||||
{ staleTime: 60_000 },
|
{ staleTime: 60_000 },
|
||||||
@@ -441,7 +442,11 @@ export function ResourceModal({ mode, resource, onClose, onSuccess }: ResourceMo
|
|||||||
placeholder="Engineering"
|
placeholder="Engineering"
|
||||||
value={form.chapter}
|
value={form.chapter}
|
||||||
onChange={(e) => setField("chapter", e.target.value)}
|
onChange={(e) => setField("chapter", e.target.value)}
|
||||||
|
list="rm-chapter-list"
|
||||||
/>
|
/>
|
||||||
|
<datalist id="rm-chapter-list">
|
||||||
|
{chapters?.map((c) => <option key={c} value={c} />)}
|
||||||
|
</datalist>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export const blueprintUpdateInputSchema = z.object({
|
|||||||
|
|
||||||
export const blueprintRolePresetsInputSchema = z.object({
|
export const blueprintRolePresetsInputSchema = z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
rolePresets: z.array(z.record(z.string(), z.unknown())),
|
rolePresets: z.array(z.unknown()),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const blueprintBatchDeleteInputSchema = z.object({
|
export const blueprintBatchDeleteInputSchema = z.object({
|
||||||
|
|||||||
Reference in New Issue
Block a user