153
resources/client/admin/titles/title-editor/credits-editor/add-credit-dialog.tsx
Executable file
153
resources/client/admin/titles/title-editor/credits-editor/add-credit-dialog.tsx
Executable file
@@ -0,0 +1,153 @@
|
||||
import {Dialog} from '@common/ui/overlays/dialog/dialog';
|
||||
import {DialogHeader} from '@common/ui/overlays/dialog/dialog-header';
|
||||
import {Trans} from '@common/i18n/trans';
|
||||
import {DialogBody} from '@common/ui/overlays/dialog/dialog-body';
|
||||
import {useDialogContext} from '@common/ui/overlays/dialog/dialog-context';
|
||||
import {Form} from '@common/ui/forms/form';
|
||||
import {useForm, useFormContext} from 'react-hook-form';
|
||||
import {DialogFooter} from '@common/ui/overlays/dialog/dialog-footer';
|
||||
import {Button} from '@common/ui/buttons/button';
|
||||
import {
|
||||
CreateTitleCreditPayload,
|
||||
useCreateTitleCredit,
|
||||
} from '@app/admin/titles/requests/use-create-title-credit';
|
||||
import {FormNormalizedModelField} from '@common/ui/forms/normalized-model-field';
|
||||
import {useValueLists} from '@common/http/value-lists';
|
||||
import {UpdateTitleCreditPayload} from '@app/admin/titles/requests/use-update-title-credit';
|
||||
import {Fragment, useMemo} from 'react';
|
||||
import {FormSelect} from '@common/ui/forms/select/select';
|
||||
import {Item} from '@common/ui/forms/listbox/item';
|
||||
import {FormTextField} from '@common/ui/forms/input-field/text-field/text-field';
|
||||
import clsx from 'clsx';
|
||||
|
||||
interface Props {
|
||||
isCrew: boolean;
|
||||
}
|
||||
export function AddCreditDialog({isCrew}: Props) {
|
||||
const {formId, close} = useDialogContext();
|
||||
const form = useForm<CreateTitleCreditPayload>({
|
||||
defaultValues: {
|
||||
department: !isCrew ? 'actors' : undefined,
|
||||
job: !isCrew ? 'actor' : undefined,
|
||||
},
|
||||
});
|
||||
const createCredit = useCreateTitleCredit(form);
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogHeader>
|
||||
<Trans message="Create credit" />
|
||||
</DialogHeader>
|
||||
<DialogBody>
|
||||
<Form
|
||||
id={formId}
|
||||
form={form}
|
||||
onSubmit={values => {
|
||||
createCredit.mutate(values, {onSuccess: () => close()});
|
||||
}}
|
||||
>
|
||||
<FormNormalizedModelField
|
||||
endpoint="normalized-models/person"
|
||||
name="person_id"
|
||||
label={<Trans message="Person" />}
|
||||
className="mb-24"
|
||||
autoFocus
|
||||
/>
|
||||
<SharedCreditDialogFields isCrew={isCrew} />
|
||||
</Form>
|
||||
</DialogBody>
|
||||
<DialogFooter>
|
||||
<Button onClick={() => close()}>
|
||||
<Trans message="Cancel" />
|
||||
</Button>
|
||||
<Button
|
||||
form={formId}
|
||||
type="submit"
|
||||
variant="flat"
|
||||
color="primary"
|
||||
disabled={createCredit.isPending}
|
||||
>
|
||||
<Trans message="Create" />
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
interface SharedCreditDialogFieldsProps {
|
||||
isCrew: boolean;
|
||||
}
|
||||
export function SharedCreditDialogFields({
|
||||
isCrew,
|
||||
}: SharedCreditDialogFieldsProps) {
|
||||
return (
|
||||
<Fragment>
|
||||
<FormTextField
|
||||
name="character"
|
||||
label={<Trans message="Character" />}
|
||||
required={!isCrew}
|
||||
className={clsx('mb-24', isCrew && 'hidden')}
|
||||
/>
|
||||
<CrewFields isCrew={isCrew} />
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
interface CrewFieldsProps {
|
||||
isCrew: boolean;
|
||||
}
|
||||
function CrewFields({isCrew}: CrewFieldsProps) {
|
||||
const {data} = useValueLists(['tmdbDepartments']);
|
||||
const {watch} = useFormContext<UpdateTitleCreditPayload>();
|
||||
const selectedDepartment = watch('department');
|
||||
const {jobs, departments} = useMemo(() => {
|
||||
const departments =
|
||||
data?.tmdbDepartments.map(d => ({
|
||||
department: d.department.toLowerCase(),
|
||||
jobs: d.jobs,
|
||||
})) || [];
|
||||
const department = departments.find(
|
||||
d => d.department === selectedDepartment,
|
||||
);
|
||||
const jobs = department?.jobs.map(job => ({job: job.toLowerCase()})) || [];
|
||||
return {
|
||||
jobs,
|
||||
departments,
|
||||
};
|
||||
}, [data, selectedDepartment]);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<FormSelect
|
||||
name="department"
|
||||
label={<Trans message="Department" />}
|
||||
required
|
||||
disabled={!isCrew}
|
||||
items={departments}
|
||||
className="mb-24"
|
||||
selectionMode="single"
|
||||
showSearchField
|
||||
>
|
||||
{item => (
|
||||
<Item value={item.department}>
|
||||
<Trans message={item.department} />
|
||||
</Item>
|
||||
)}
|
||||
</FormSelect>
|
||||
<FormSelect
|
||||
name="job"
|
||||
label={<Trans message="Job" />}
|
||||
required
|
||||
disabled={!isCrew}
|
||||
items={jobs}
|
||||
selectionMode="single"
|
||||
showSearchField
|
||||
>
|
||||
{item => (
|
||||
<Item value={item.job} key={item.job}>
|
||||
<Trans message={item.job} />
|
||||
</Item>
|
||||
)}
|
||||
</FormSelect>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
124
resources/client/admin/titles/title-editor/credits-editor/cast-editor-table.tsx
Executable file
124
resources/client/admin/titles/title-editor/credits-editor/cast-editor-table.tsx
Executable file
@@ -0,0 +1,124 @@
|
||||
import {ColumnConfig} from '@common/datatable/column-config';
|
||||
import {TitleCredit} from '@app/titles/models/title';
|
||||
import {Trans} from '@common/i18n/trans';
|
||||
import {DragHandleIcon} from '@common/icons/material/DragHandle';
|
||||
import {PersonPoster} from '@app/people/person-poster/person-poster';
|
||||
import React, {Fragment, useContext, useRef} from 'react';
|
||||
import {Table} from '@common/ui/tables/table';
|
||||
import {RowElementProps} from '@common/ui/tables/table-row';
|
||||
import {NormalizedModel} from '@common/datatable/filters/normalized-model';
|
||||
import {useIsTouchDevice} from '@common/utils/hooks/is-touch-device';
|
||||
import {TableContext} from '@common/ui/tables/table-context';
|
||||
import {DragPreviewRenderer} from '@common/ui/interactions/dnd/use-draggable';
|
||||
import {mergeProps} from '@react-aria/utils';
|
||||
import {DragPreview} from '@common/ui/interactions/dnd/drag-preview';
|
||||
import {useSortTitleCredits} from '@app/admin/titles/requests/use-sort-title-credits';
|
||||
import {moveItemInNewArray} from '@common/utils/array/move-item-in-new-array';
|
||||
import {getCreditsEditorActionColumn} from '@app/admin/titles/title-editor/credits-editor/get-credits-editor-action-column';
|
||||
import {UseInfiniteDataResult} from '@common/ui/infinite-scroll/use-infinite-data';
|
||||
import {CreditsTableQueryIndicator} from '@app/admin/titles/title-editor/credits-editor/credits-table-query-indicator';
|
||||
import {useSortable} from '@common/ui/interactions/dnd/sortable/use-sortable';
|
||||
|
||||
const columnConfig: ColumnConfig<TitleCredit>[] = [
|
||||
{
|
||||
key: 'dragHandle',
|
||||
width: 'w-42 flex-shrink-0',
|
||||
header: () => <Trans message="Drag handle" />,
|
||||
hideHeader: true,
|
||||
body: () => (
|
||||
<DragHandleIcon className="cursor-pointer text-muted hover:text" />
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'name',
|
||||
header: () => <Trans message="Person" />,
|
||||
visibleInMode: 'all',
|
||||
body: credit => (
|
||||
<div className="flex items-center gap-12">
|
||||
<PersonPoster rounded person={credit} size="w-44" />
|
||||
<div className="min-w-0 overflow-hidden">{credit.name}</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'character',
|
||||
header: () => <Trans message="Character" />,
|
||||
body: credit => credit.pivot.character,
|
||||
},
|
||||
getCreditsEditorActionColumn(),
|
||||
];
|
||||
|
||||
interface Props {
|
||||
query: UseInfiniteDataResult<TitleCredit>;
|
||||
}
|
||||
export function CastEditorTable({query}: Props) {
|
||||
return (
|
||||
<Fragment>
|
||||
<Table
|
||||
enableSelection={false}
|
||||
columns={columnConfig}
|
||||
data={query.items}
|
||||
renderRowAs={CreditsTableRow}
|
||||
cellHeight="h-54"
|
||||
/>
|
||||
<CreditsTableQueryIndicator query={query} />
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
function CreditsTableRow({
|
||||
item,
|
||||
children,
|
||||
className,
|
||||
...domProps
|
||||
}: RowElementProps<TitleCredit>) {
|
||||
const isTouchDevice = useIsTouchDevice();
|
||||
const context = useContext(TableContext);
|
||||
const domRef = useRef<HTMLTableRowElement>(null);
|
||||
const previewRef = useRef<DragPreviewRenderer>(null);
|
||||
const credits = context.data as TitleCredit[];
|
||||
|
||||
const sortCredits = useSortTitleCredits();
|
||||
|
||||
const {sortableProps} = useSortable({
|
||||
ref: domRef,
|
||||
disabled: isTouchDevice ?? false,
|
||||
item,
|
||||
items: credits,
|
||||
type: 'cast-editor-item',
|
||||
preview: previewRef,
|
||||
strategy: 'line',
|
||||
onSortEnd: (oldIndex, newIndex) => {
|
||||
const ids = credits.map(item => item.pivot.id);
|
||||
const sortedIds = moveItemInNewArray(ids, oldIndex, newIndex);
|
||||
sortCredits.mutate({ids: sortedIds});
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
className={className}
|
||||
ref={domRef}
|
||||
{...mergeProps(sortableProps, domProps)}
|
||||
>
|
||||
{children}
|
||||
{!item.isPlaceholder && <RowDragPreview item={item} ref={previewRef} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface RowDragPreviewProps {
|
||||
item: NormalizedModel;
|
||||
}
|
||||
const RowDragPreview = React.forwardRef<
|
||||
DragPreviewRenderer,
|
||||
RowDragPreviewProps
|
||||
>(({item}, ref) => {
|
||||
return (
|
||||
<DragPreview ref={ref}>
|
||||
{() => (
|
||||
<div className="rounded bg-chip p-8 text-sm shadow">{item.name}</div>
|
||||
)}
|
||||
</DragPreview>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
import {UseInfiniteDataResult} from '@common/ui/infinite-scroll/use-infinite-data';
|
||||
import {TitleCredit} from '@app/titles/models/title';
|
||||
import React from 'react';
|
||||
import {IllustratedMessage} from '@common/ui/images/illustrated-message';
|
||||
import {RecentActorsIcon} from '@common/icons/material/RecentActors';
|
||||
import {Trans} from '@common/i18n/trans';
|
||||
import {TitleEditorPageStatus} from '@app/admin/titles/title-editor/title-editor-page-status';
|
||||
import {InfiniteScrollSentinel} from '@common/ui/infinite-scroll/infinite-scroll-sentinel';
|
||||
|
||||
interface Props {
|
||||
query: UseInfiniteDataResult<TitleCredit>;
|
||||
}
|
||||
export function CreditsTableQueryIndicator({query}: Props) {
|
||||
if (query.data && !query.items.length) {
|
||||
return <NoCreditsMessage />;
|
||||
}
|
||||
|
||||
if (!query.data) {
|
||||
return <TitleEditorPageStatus query={query} />;
|
||||
}
|
||||
|
||||
return <InfiniteScrollSentinel query={query} />;
|
||||
}
|
||||
|
||||
function NoCreditsMessage() {
|
||||
return (
|
||||
<IllustratedMessage
|
||||
className="mt-40"
|
||||
imageMargin="mb-8"
|
||||
image={
|
||||
<div className="text-muted">
|
||||
<RecentActorsIcon size="xl" />
|
||||
</div>
|
||||
}
|
||||
imageHeight="h-auto"
|
||||
title={<Trans message="No credits have been added yet" />}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import {ColumnConfig} from '@common/datatable/column-config';
|
||||
import {TitleCredit} from '@app/titles/models/title';
|
||||
import {Trans} from '@common/i18n/trans';
|
||||
import {PersonPoster} from '@app/people/person-poster/person-poster';
|
||||
import React, {Fragment, useContext} from 'react';
|
||||
import {Table, TableBodyProps} from '@common/ui/tables/table';
|
||||
import {TableRow} from '@common/ui/tables/table-row';
|
||||
import {TableContext} from '@common/ui/tables/table-context';
|
||||
import {getCreditsEditorActionColumn} from '@app/admin/titles/title-editor/credits-editor/get-credits-editor-action-column';
|
||||
import {UseInfiniteDataResult} from '@common/ui/infinite-scroll/use-infinite-data';
|
||||
import {CreditsTableQueryIndicator} from '@app/admin/titles/title-editor/credits-editor/credits-table-query-indicator';
|
||||
|
||||
const columnConfig: ColumnConfig<TitleCredit>[] = [
|
||||
{
|
||||
key: 'name',
|
||||
header: () => <Trans message="Person" />,
|
||||
visibleInMode: 'all',
|
||||
body: credit => (
|
||||
<div className="flex items-center gap-12">
|
||||
<PersonPoster rounded person={credit} size="w-44" />
|
||||
<div className="overflow-hidden min-w-0">{credit.name}</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'department',
|
||||
header: () => <Trans message="Department" />,
|
||||
body: credit => credit.pivot.department,
|
||||
},
|
||||
{
|
||||
key: 'job',
|
||||
header: () => <Trans message="Job" />,
|
||||
body: credit => credit.pivot.job,
|
||||
},
|
||||
getCreditsEditorActionColumn(),
|
||||
];
|
||||
|
||||
interface Props {
|
||||
query: UseInfiniteDataResult<TitleCredit>;
|
||||
}
|
||||
export function CrewEditorTable({query}: Props) {
|
||||
return (
|
||||
<Fragment>
|
||||
<Table
|
||||
enableSelection={false}
|
||||
columns={columnConfig}
|
||||
data={query.items}
|
||||
cellHeight="h-54"
|
||||
tableBody={<CreditsTableBody />}
|
||||
/>
|
||||
<CreditsTableQueryIndicator query={query} />
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
function CreditsTableBody({renderRowAs}: TableBodyProps) {
|
||||
const {data} = useContext(TableContext);
|
||||
return (
|
||||
<Fragment>
|
||||
{data.map((item, rowIndex) => (
|
||||
<TableRow
|
||||
item={item}
|
||||
index={rowIndex}
|
||||
// use pivot id for key because some person might
|
||||
// appear multiple times with different department
|
||||
key={(item as TitleCredit).pivot.id}
|
||||
renderAs={renderRowAs}
|
||||
/>
|
||||
))}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import {Dialog} from '@common/ui/overlays/dialog/dialog';
|
||||
import {DialogHeader} from '@common/ui/overlays/dialog/dialog-header';
|
||||
import {Trans} from '@common/i18n/trans';
|
||||
import {DialogBody} from '@common/ui/overlays/dialog/dialog-body';
|
||||
import {useDialogContext} from '@common/ui/overlays/dialog/dialog-context';
|
||||
import {Form} from '@common/ui/forms/form';
|
||||
import {useForm} from 'react-hook-form';
|
||||
import {TextField} from '@common/ui/forms/input-field/text-field/text-field';
|
||||
import {DialogFooter} from '@common/ui/overlays/dialog/dialog-footer';
|
||||
import {Button} from '@common/ui/buttons/button';
|
||||
import {TitleCredit} from '@app/titles/models/title';
|
||||
import {
|
||||
UpdateTitleCreditPayload,
|
||||
useUpdateTitleCredit,
|
||||
} from '@app/admin/titles/requests/use-update-title-credit';
|
||||
import {SharedCreditDialogFields} from '@app/admin/titles/title-editor/credits-editor/add-credit-dialog';
|
||||
|
||||
interface Props {
|
||||
credit: TitleCredit;
|
||||
}
|
||||
export function EditCreditDialog({credit}: Props) {
|
||||
const {formId, close} = useDialogContext();
|
||||
const isCrew = credit.pivot.department !== 'actors';
|
||||
const form = useForm<UpdateTitleCreditPayload>({
|
||||
defaultValues: {
|
||||
character: credit.pivot.character,
|
||||
department: credit.pivot.department,
|
||||
job: credit.pivot.job,
|
||||
},
|
||||
});
|
||||
const updateCredit = useUpdateTitleCredit(form, credit.pivot.id);
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogHeader>
|
||||
<Trans message="Edit credit" />
|
||||
</DialogHeader>
|
||||
<DialogBody>
|
||||
<Form
|
||||
id={formId}
|
||||
form={form}
|
||||
onSubmit={values => {
|
||||
updateCredit.mutate(values, {onSuccess: () => close()});
|
||||
}}
|
||||
>
|
||||
<TextField
|
||||
value={credit.name}
|
||||
label={<Trans message="Person" />}
|
||||
required
|
||||
readOnly
|
||||
disabled
|
||||
className="mb-24"
|
||||
/>
|
||||
<SharedCreditDialogFields isCrew={isCrew} />
|
||||
</Form>
|
||||
</DialogBody>
|
||||
<DialogFooter>
|
||||
<Button onClick={() => close()}>
|
||||
<Trans message="Cancel" />
|
||||
</Button>
|
||||
<Button
|
||||
form={formId}
|
||||
type="submit"
|
||||
variant="flat"
|
||||
color="primary"
|
||||
disabled={updateCredit.isPending}
|
||||
>
|
||||
<Trans message="Save" />
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import {Trans} from '@common/i18n/trans';
|
||||
import {DialogTrigger} from '@common/ui/overlays/dialog/dialog-trigger';
|
||||
import {IconButton} from '@common/ui/buttons/icon-button';
|
||||
import {EditIcon} from '@common/icons/material/Edit';
|
||||
import {EditCreditDialog} from '@app/admin/titles/title-editor/credits-editor/edit-credit-dialog';
|
||||
import React from 'react';
|
||||
import {ColumnConfig} from '@common/datatable/column-config';
|
||||
import {TitleCredit} from '@app/titles/models/title';
|
||||
import {useDeleteTitleCredit} from '@app/admin/titles/requests/use-delete-title-credit';
|
||||
import {DeleteIcon} from '@common/icons/material/Delete';
|
||||
import {ConfirmationDialog} from '@common/ui/overlays/dialog/confirmation-dialog';
|
||||
|
||||
export const getCreditsEditorActionColumn = (): ColumnConfig<TitleCredit> => {
|
||||
return {
|
||||
key: 'actions',
|
||||
header: () => <Trans message="Actions" />,
|
||||
hideHeader: true,
|
||||
align: 'end',
|
||||
width: 'w-84 flex-shrink-0',
|
||||
visibleInMode: 'all',
|
||||
body: item => (
|
||||
<div className="text-muted">
|
||||
<DialogTrigger type="modal">
|
||||
<IconButton>
|
||||
<EditIcon />
|
||||
</IconButton>
|
||||
<EditCreditDialog credit={item} />
|
||||
</DialogTrigger>
|
||||
<DeleteButton creditId={item.pivot.id} />
|
||||
</div>
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
interface DeleteButtonProps {
|
||||
creditId: number;
|
||||
}
|
||||
function DeleteButton({creditId}: DeleteButtonProps) {
|
||||
const deleteCredit = useDeleteTitleCredit(creditId);
|
||||
return (
|
||||
<DialogTrigger
|
||||
type="modal"
|
||||
onClose={confirmed => {
|
||||
if (confirmed) {
|
||||
deleteCredit.mutate();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<IconButton>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
<ConfirmationDialog
|
||||
isDanger
|
||||
title={<Trans message="Delete credit" />}
|
||||
body={<Trans message="Are you sure you want to delete this credit?" />}
|
||||
confirm={<Trans message="Delete" />}
|
||||
/>
|
||||
</DialogTrigger>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import {CastEditorTable} from '@app/admin/titles/title-editor/credits-editor/cast-editor-table';
|
||||
import {TitleEditorLayout} from '@app/admin/titles/title-editor/title-editor-layout';
|
||||
import {useTitleCredits} from '@app/admin/titles/requests/use-title-credits';
|
||||
import {TitleCreditsTableHeader} from '@app/admin/titles/title-editor/credits-editor/title-credits-table-header';
|
||||
|
||||
export function TitleCastEditor() {
|
||||
const query = useTitleCredits({
|
||||
department: 'actors',
|
||||
});
|
||||
|
||||
return (
|
||||
<TitleEditorLayout>
|
||||
<TitleCreditsTableHeader query={query} isCrew={false} />
|
||||
<CastEditorTable query={query} />
|
||||
</TitleEditorLayout>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import {DialogTrigger} from '@common/ui/overlays/dialog/dialog-trigger';
|
||||
import {Button} from '@common/ui/buttons/button';
|
||||
import {AddIcon} from '@common/icons/material/Add';
|
||||
import {Trans} from '@common/i18n/trans';
|
||||
import {AddCreditDialog} from '@app/admin/titles/title-editor/credits-editor/add-credit-dialog';
|
||||
import {TextField} from '@common/ui/forms/input-field/text-field/text-field';
|
||||
import {UseInfiniteDataResult} from '@common/ui/infinite-scroll/use-infinite-data';
|
||||
import {TitleCredit} from '@app/titles/models/title';
|
||||
import {SearchIcon} from '@common/icons/material/Search';
|
||||
import {useTrans} from '@common/i18n/use-trans';
|
||||
import {message} from '@common/i18n/message';
|
||||
|
||||
interface Props {
|
||||
query: UseInfiniteDataResult<TitleCredit>;
|
||||
isCrew: boolean;
|
||||
}
|
||||
export function TitleCreditsTableHeader({query, isCrew}: Props) {
|
||||
const {trans} = useTrans();
|
||||
return (
|
||||
<div className="flex items-center gap-24 justify-between mb-14">
|
||||
<DialogTrigger type="modal">
|
||||
<Button variant="outline" color="primary" startIcon={<AddIcon />}>
|
||||
<Trans message="Add credit" />
|
||||
</Button>
|
||||
<AddCreditDialog isCrew={isCrew} />
|
||||
</DialogTrigger>
|
||||
<TextField
|
||||
size="sm"
|
||||
value={query.searchQuery}
|
||||
onChange={e => query.setSearchQuery(e.target.value)}
|
||||
placeholder={trans(message('Search'))}
|
||||
startAdornment={<SearchIcon />}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import {TitleEditorLayout} from '@app/admin/titles/title-editor/title-editor-layout';
|
||||
import {useTitleCredits} from '@app/admin/titles/requests/use-title-credits';
|
||||
import {CrewEditorTable} from '@app/admin/titles/title-editor/credits-editor/crew-editor-table';
|
||||
import {TitleCreditsTableHeader} from '@app/admin/titles/title-editor/credits-editor/title-credits-table-header';
|
||||
|
||||
export function TitleCrewEditor() {
|
||||
const query = useTitleCredits({
|
||||
crewOnly: 'true',
|
||||
});
|
||||
return (
|
||||
<TitleEditorLayout>
|
||||
<TitleCreditsTableHeader query={query} isCrew />
|
||||
<CrewEditorTable query={query} />
|
||||
</TitleEditorLayout>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user