first commit
Some checks failed
Build / run (push) Has been cancelled

This commit is contained in:
maher
2025-10-29 11:42:25 +01:00
commit 703f50a09d
4595 changed files with 385164 additions and 0 deletions

View File

@@ -0,0 +1,313 @@
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 {useForm} from 'react-hook-form';
import {Form} from '@common/ui/forms/form';
import {useDialogContext} from '@common/ui/overlays/dialog/dialog-context';
import {DialogFooter} from '@common/ui/overlays/dialog/dialog-footer';
import {Button} from '@common/ui/buttons/button';
import {FormChipField} from '@common/ui/forms/input-field/chip-field/form-chip-field';
import {useValueLists} from '@common/http/value-lists';
import {Item} from '@common/ui/forms/listbox/item';
import {FormDateRangePicker} from '@common/ui/forms/input-field/date/date-range-picker/form-date-range-picker';
import {FormSelect, Option} from '@common/ui/forms/select/select';
import {InfoDialogTriggerIcon} from '@common/ui/overlays/dialog/info-dialog-trigger/info-dialog-trigger-icon';
import React, {Fragment, useState} from 'react';
import {FormTextField} from '@common/ui/forms/input-field/text-field/text-field';
import {
ImportMultipleFromTmdbFormValue,
ImportMultipleProgressData,
useImportMultipleFromTmdb,
} from '@app/admin/titles/requests/use-import-multiple-from-tmdb';
import {ProgressBar} from '@common/ui/progress/progress-bar';
import {Skeleton} from '@common/ui/skeleton/skeleton';
export function ImportMultipleFromTmdbDialog() {
const form = useForm<ImportMultipleFromTmdbFormValue>({
defaultValues: {
type: 'movie',
start_from_page: 1,
pages_to_import: 10,
},
});
const {formId, close} = useDialogContext();
const importTitles = useImportMultipleFromTmdb();
const [activePanel, setActivePanel] = useState<'form' | 'progress'>('form');
const [progressData, setProgressData] =
useState<ImportMultipleProgressData>();
return (
<Dialog size="lg">
<DialogHeader>
<Trans message="Import from TheMovieDB" />
</DialogHeader>
<DialogBody>
<Form
id={formId}
form={form}
onSubmit={values => {
importTitles.mutate(values, {
onProgress: data => {
setActivePanel('progress');
setProgressData(data);
},
});
}}
>
{progressData ? <ProgressPanel data={progressData} /> : <FormPanel />}
</Form>
</DialogBody>
<DialogFooter>
<Button
onClick={() => {
if (progressData) {
importTitles.cancel();
setProgressData(undefined);
setActivePanel('form');
} else {
close();
}
}}
>
{importTitles.isLoading || activePanel === 'form' ? (
<Trans message="Cancel" />
) : (
<Trans message="Back" />
)}
</Button>
<Button
form={formId}
variant="flat"
color="primary"
type="submit"
disabled={importTitles.isLoading || activePanel === 'progress'}
>
<Trans message="Import" />
</Button>
</DialogFooter>
</Dialog>
);
}
function FormPanel() {
return (
<Fragment>
<p className="mb-24 flex items-center gap-8 text-muted">
<InfoDialogTriggerIcon size="xs" viewBox="0 0 16 16" />
<Trans message="All filters below are optional and can be left empty." />
</p>
<FormSelect
name="type"
label={<Trans message="Type" />}
className="mb-24"
selectionMode="single"
>
<Option value="movie">
<Trans message="Movie" />
</Option>
<Option value="series">
<Trans message="Series" />
</Option>
</FormSelect>
<PaginationFields />
<GenreChipField />
<KeywordChipField />
<LanguageSelect />
<CountrySelect />
<RatingFields />
<FormDateRangePicker
name="release_date"
granularity="day"
label={<Trans message="Release date" />}
description={
<Trans message="Only import titles released between specified dates." />
}
/>
</Fragment>
);
}
interface ProgressPanelProps {
data: ImportMultipleProgressData;
}
function ProgressPanel({data}: ProgressPanelProps) {
return (
<div>
<ProgressBar
value={data.progress}
label={
<Trans
message="Imported :number titles of :total"
values={{number: data.currentItem, total: data.totalItems}}
/>
}
/>
<div className="compact-scrollbar mt-24 h-400 overflow-auto text-xs">
{data.titleList.map((title, index) => (
<div key={index}>{title}</div>
))}
{!data.titleList.length ? (
<Fragment>
{[...new Array(20).keys()].map(index => (
<Skeleton className="mb-2 max-w-200" key={index} />
))}
</Fragment>
) : null}
</div>
</div>
);
}
function PaginationFields() {
return (
<div className="mb-24">
<div className="flex items-center gap-24">
<FormTextField
className="flex-1"
name="start_from_page"
label={<Trans message="Starting page" />}
type="number"
min={1}
max={500}
/>
<FormTextField
className="flex-1"
name="pages_to_import"
label={<Trans message="How many pages to import" />}
type="number"
min={1}
max={500}
/>
</div>
<p className="pt-10 text-xs text-muted">
<Trans message="20 titles per page are imported. " />
</p>
</div>
);
}
function GenreChipField() {
const {data} = useFilterValueLists();
const genres = data?.genres.map(genre => ({
id: genre.value,
name: genre.name,
}));
return (
<FormChipField
className="mb-24"
name="genres"
label={<Trans message="Genres" />}
suggestions={genres}
allowCustomValue={false}
description={
<Trans message="Only import titles belonging to specified genres." />
}
>
{genre => (
<Item value={genre.id}>
<Trans message={genre.name} />
</Item>
)}
</FormChipField>
);
}
function KeywordChipField() {
const {data} = useFilterValueLists();
const keywords = data?.keywords.map(keyword => ({
id: keyword.value,
name: keyword.name,
}));
return (
<FormChipField
name="keywords"
className="mb-24"
label={<Trans message="Keywords" />}
suggestions={keywords}
allowCustomValue={false}
description={
<Trans message="Only import titles that have specied keywords attached." />
}
>
{keyword => (
<Item value={keyword.id}>
<Trans message={keyword.name} />
</Item>
)}
</FormChipField>
);
}
function LanguageSelect() {
const {data} = useFilterValueLists();
return (
<FormSelect
name="language"
className="mb-24"
label={<Trans message="Language" />}
items={data?.languages}
selectionMode="single"
description={
<Trans message="Only import titles with specied primary spoken language." />
}
>
{language => (
<Item value={language.code}>
<Trans message={language.name} />
</Item>
)}
</FormSelect>
);
}
function CountrySelect() {
const {data} = useFilterValueLists();
return (
<FormSelect
name="country"
className="mb-24"
label={<Trans message="Country" />}
items={data?.countries}
selectionMode="single"
description={
<Trans message="Only import titles with specied origin country." />
}
>
{country => (
<Item value={country.code}>
<Trans message={country.name} />
</Item>
)}
</FormSelect>
);
}
function RatingFields() {
return (
<div className="mb-24 flex items-center gap-24">
<FormTextField
className="flex-1"
name="min_rating"
label={<Trans message="Minimum rating" />}
type="number"
min={1}
max={10}
/>
<FormTextField
className="flex-1"
name="max_rating"
label={<Trans message="Maximum rating" />}
type="number"
min={1}
max={10}
/>
</div>
);
}
function useFilterValueLists() {
return useValueLists(['genres', 'keywords', 'languages', 'countries'], {
type: 'tmdb',
});
}

View File

@@ -0,0 +1,90 @@
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 {useForm} from 'react-hook-form';
import {Form} from '@common/ui/forms/form';
import {useDialogContext} from '@common/ui/overlays/dialog/dialog-context';
import {FormTextField} 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 {
ImportMediaItemPayload,
useImportSingleFromTmdb,
} from '@app/admin/titles/requests/use-import-single-from-tmdb';
import {FormSelect, Option} from '@common/ui/forms/select/select';
import {TITLE_MODEL} from '@app/titles/models/title';
import {PERSON_MODEL} from '@app/titles/models/person';
interface ImportFromTmdbDialogProps {
modelType: typeof TITLE_MODEL | typeof PERSON_MODEL;
}
export function ImportSingleFromTmdbDialog({
modelType,
}: ImportFromTmdbDialogProps) {
const form = useForm<ImportMediaItemPayload>({
defaultValues: {
media_type: modelType === TITLE_MODEL ? 'movie' : 'person',
},
});
const {formId, close} = useDialogContext();
const importItem = useImportSingleFromTmdb();
return (
<Dialog>
<DialogHeader>
<Trans message="Import from TheMovieDB" />
</DialogHeader>
<DialogBody>
<Form
id={formId}
form={form}
onSubmit={values => {
importItem.mutate(values, {
onSuccess: response => {
close(response.mediaItem);
},
});
}}
>
{modelType === TITLE_MODEL && (
<FormSelect
name="media_type"
label={<Trans message="Type" />}
className="mb-24"
selectionMode="single"
>
<Option value="movie">
<Trans message="Movie" />
</Option>
<Option value="series">
<Trans message="Series" />
</Option>
</FormSelect>
)}
<FormTextField
autoFocus
required
name="tmdb_id"
min={1}
type="number"
label={<Trans message="TheMovieDB ID" />}
/>
</Form>
</DialogBody>
<DialogFooter>
<Button onClick={() => close()}>
<Trans message="Cancel" />
</Button>
<Button
form={formId}
variant="flat"
color="primary"
type="submit"
disabled={importItem.isPending}
>
<Trans message="Import" />
</Button>
</DialogFooter>
</Dialog>
);
}