Merge pull request #2 from zdam-egzamin-zawodowy/QualificationsPage

add QualificationsPage
This commit is contained in:
Dawid Wysokiński 2021-03-13 13:11:18 +01:00 committed by GitHub
commit dbd8e41c90
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 1024 additions and 222 deletions

View File

@ -3,8 +3,8 @@ import { useLocation } from 'react-router-dom';
import clsx from 'clsx';
import { Route } from 'config/routing';
import { Route as NavRoute } from './components/Nav/types';
import { useTheme } from '@material-ui/core/styles';
import { useTheme } from '@material-ui/core/styles';
import {
SwipeableDrawer,
DrawerProps,
@ -12,7 +12,11 @@ import {
Divider,
} from '@material-ui/core';
import useStyles from './useStyles';
import { Group as GroupIcon, Work as WorkIcon } from '@material-ui/icons';
import {
Group as GroupIcon,
Work as WorkIcon,
Description as DescriptionIcon,
} from '@material-ui/icons';
import Nav from './components/Nav/Nav';
import CurrentUser from './components/CurrentUser/CurrentUser';
@ -41,6 +45,12 @@ const Sidebar = ({ className, open, variant, onClose, onOpen }: Props) => {
exact: true,
Icon: <WorkIcon color="inherit" />,
},
{
name: 'Kwalifikacje',
to: Route.QualificationsPage,
exact: true,
Icon: <DescriptionIcon color="inherit" />,
},
];
useEffect(() => {

View File

@ -2,6 +2,7 @@ export enum Route {
SignInPage = '/',
UsersPage = '/users',
ProfessionsPage = '/professions',
QualificationsPage = '/qualifications',
}
export const PUBLIC_ROUTES = [Route.SignInPage];

View File

@ -3,6 +3,7 @@ import { Route } from '../config/routing';
import AppLayout from 'common/AppLayout/AppLayout';
import UsersPage from './UsersPage/UsersPage';
import ProfessionsPage from './ProfessionsPage/ProfessionsPage';
import QualificationsPage from './QualificationsPage/QualificationsPage';
function AdminRoutes() {
return (
@ -14,6 +15,9 @@ function AdminRoutes() {
<RRDRoute exact path={Route.ProfessionsPage}>
<ProfessionsPage />
</RRDRoute>
<RRDRoute exact path={Route.QualificationsPage}>
<QualificationsPage />
</RRDRoute>
</Switch>
</AppLayout>
);

View File

@ -0,0 +1,229 @@
import { useState } from 'react';
import { ApolloError, useMutation } from '@apollo/client';
import { useUpdateEffect } from 'react-use';
import {
NumberParam,
StringParam,
useQueryParams,
withDefault,
} from 'use-query-params';
import { useSnackbar } from 'notistack';
import SortParam, { decodeSort } from 'libs/serialize-query-params/SortParam';
import useQualifications from './QualificationsPage.useQualifications';
import { validateRowsPerPage } from 'common/Table/helpers';
import {
MUTATION_CREATE_QUALIFICATION,
MUTATION_UPDATE_QUALIFICATION,
MUTATION_DELETE_QUALIFICATIONS,
} from './mutations';
import { COLUMNS, DEFAULT_SORT, DialogType } from './constants';
import {
Maybe,
MutationCreateQualificationArgs,
MutationDeleteQualificationsArgs,
MutationUpdateQualificationArgs,
Qualification,
QualificationInput,
} from 'libs/graphql/types';
import {
Button,
Container,
IconButton,
Paper,
Snackbar,
} from '@material-ui/core';
import { Edit as EditIcon } from '@material-ui/icons';
import Table from 'common/Table/Table';
import TableToolbar from './components/TableToolbar/TableToolbar';
import FormDialog from './components/FormDialog/FormDialog';
const QualificationsPage = () => {
const [createQualificationMutation] = useMutation<
any,
MutationCreateQualificationArgs
>(MUTATION_CREATE_QUALIFICATION, { ignoreResults: true });
const [updateQualificationMutation] = useMutation<
any,
MutationUpdateQualificationArgs
>(MUTATION_UPDATE_QUALIFICATION, { ignoreResults: true });
const [deleteQualificationsMutation] = useMutation<
any,
MutationDeleteQualificationsArgs
>(MUTATION_DELETE_QUALIFICATIONS, { ignoreResults: true });
const [dialogType, setDialogType] = useState<DialogType>(DialogType.None);
const [qualificationBeingEdited, setQualificationBeingEdited] = useState<
Maybe<Qualification>
>(null);
const [selectedQualifications, setSelectedQualifications] = useState<
Qualification[]
>([]);
const snackbar = useSnackbar();
const [{ page, sort, search, ...rest }, setQueryParams] = useQueryParams({
limit: NumberParam,
page: withDefault(NumberParam, 0),
sort: withDefault(SortParam, DEFAULT_SORT),
search: withDefault(StringParam, ''),
});
const limit = validateRowsPerPage(rest.limit);
const { qualifications, total, loading, refetch } = useQualifications(
page,
limit,
sort.toString(),
search
);
useUpdateEffect(() => {
if (selectedQualifications.length > 0) {
setSelectedQualifications([]);
}
}, [qualifications]);
const handleFormDialogSubmit = async (input: QualificationInput) => {
try {
if (dialogType === DialogType.Create) {
await createQualificationMutation({ variables: { input } });
} else {
await updateQualificationMutation({
variables: { input, id: qualificationBeingEdited?.id ?? -1 },
});
}
await refetch();
snackbar.enqueueSnackbar(
dialogType === DialogType.Create
? 'Pomyślnie utworzono kwalifikację.'
: 'Zapisano zmiany.',
{ variant: 'success' }
);
return true;
} catch (e) {
snackbar.enqueueSnackbar(
e instanceof ApolloError && e.graphQLErrors.length > 0
? e.graphQLErrors[0].message
: e.message,
{ variant: 'error' }
);
}
return false;
};
const handleDeleteQualifications = async () => {
if (!window.confirm('Czy na pewno chcesz usunąć wybrane kwalifikacje?')) {
return;
}
try {
const ids = selectedQualifications.map(qualification => qualification.id);
await deleteQualificationsMutation({ variables: { ids } });
await refetch();
snackbar.enqueueSnackbar(`Usuwanie przebiegło pomyślnie.`, {
variant: 'success',
});
} catch (e) {
snackbar.enqueueSnackbar(
e instanceof ApolloError && e.graphQLErrors.length > 0
? e.graphQLErrors[0].message
: e.message,
{ variant: 'error' }
);
}
};
const handleSelect = (checked: boolean, items: Qualification[]) => {
setSelectedQualifications(prevState =>
checked
? [...prevState, ...items]
: prevState.filter(
item => !items.some(otherItem => otherItem.id === item.id)
)
);
};
return (
<Container>
<Paper>
<TableToolbar
search={search}
onClickCreateQualification={() => {
setQualificationBeingEdited(null);
setDialogType(DialogType.Create);
}}
onChangeSearchValue={val => {
setQueryParams({ page: 0, search: val });
}}
/>
<Table
selection
columns={COLUMNS}
data={qualifications}
selected={selectedQualifications}
onSelect={handleSelect}
actions={[
{
icon: row => {
return (
<IconButton
onClick={() => {
setQualificationBeingEdited(row);
setDialogType(DialogType.Edit);
}}
>
<EditIcon />
</IconButton>
);
},
tooltip: 'Edytuj',
},
]}
loading={loading}
orderBy={sort.orderBy}
orderDirection={sort.orderDirection}
onRequestSort={(orderBy, orderDirection) => {
setQueryParams({
page: 0,
sort: decodeSort(orderBy + ' ' + orderDirection),
});
}}
footerProps={{
count: total,
page,
onChangePage: page => {
setQueryParams({ page });
},
onChangeRowsPerPage: limit => {
setQueryParams({ page: 0, limit });
},
rowsPerPage: limit,
}}
/>
</Paper>
<FormDialog
open={
dialogType === DialogType.Create || dialogType === DialogType.Edit
}
qualification={qualificationBeingEdited}
onSubmit={handleFormDialogSubmit}
onClose={() => setDialogType(DialogType.None)}
/>
<Snackbar
open={selectedQualifications.length > 0}
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
message={`Wybrane kwalifikacje: ${selectedQualifications.length}`}
action={
<>
<Button onClick={handleDeleteQualifications} color="secondary">
Usuń
</Button>
<Button
color="secondary"
onClick={() => setSelectedQualifications([])}
>
Anuluj
</Button>
</>
}
/>
</Container>
);
};
export default QualificationsPage;

View File

@ -0,0 +1,39 @@
import { useQuery } from '@apollo/client';
import { QUERY_QUALIFICATIONS } from './queries';
import { Query, QueryQualificationsArgs } from 'libs/graphql/types';
const useQualifications = (
page: number,
limit: number,
sort: string,
search: string
) => {
const { data, loading, refetch } = useQuery<
Pick<Query, 'qualifications'>,
QueryQualificationsArgs
>(QUERY_QUALIFICATIONS, {
fetchPolicy: 'cache-and-network',
variables: {
offset: page * limit,
sort: [sort],
limit,
filter: {
or: {
nameIEQ: '%' + search + '%',
codeIEQ: '%' + search + '%',
},
},
},
});
return {
qualifications: data?.qualifications.items ?? [],
get loading() {
return this.qualifications.length === 0 && loading;
},
total: data?.qualifications.total ?? 0,
refetch,
};
};
export default useQualifications;

View File

@ -0,0 +1,261 @@
import { useEffect, useMemo } from 'react';
import { Controller, useFieldArray, useForm } from 'react-hook-form';
import { omit, pick } from 'lodash';
import useProfessionAutocomplete from './FormDialog.useProfessionAutocomplete.js';
import { FORMULAS, MAX_NAME_LENGTH } from './constants';
import { Maybe, Qualification, QualificationInput } from 'libs/graphql/types';
import { ExtendedProfession, Input } from './types';
import { makeStyles } from '@material-ui/core/styles';
import {
Button,
CircularProgress,
Dialog,
DialogActions,
DialogContent,
DialogProps,
DialogTitle,
MenuItem,
TextField,
} from '@material-ui/core';
import { Autocomplete } from '@material-ui/lab';
export interface FormDialogProps extends Pick<DialogProps, 'open'> {
qualification?: Maybe<Qualification>;
onClose: () => void;
onSubmit: (input: QualificationInput) => Promise<boolean> | boolean;
}
const FormDialog = ({
open,
onClose,
qualification,
onSubmit,
}: FormDialogProps) => {
const editMode = Boolean(qualification);
const {
register,
handleSubmit,
errors,
control,
reset,
setValue,
formState: { isSubmitting },
} = useForm<Input>({});
const { fields: selectedProfessions } = useFieldArray<
ExtendedProfession,
'key'
>({
control,
name: 'professions',
keyName: 'key',
});
const {
professions,
loading,
isLoadingSuggestions,
suggestions,
setSearch,
} = useProfessionAutocomplete({
qualificationID: qualification?.id,
omit: selectedProfessions.map(profession => profession?.id ?? 0),
});
const classes = useStyles();
const autocompleteOptions:
| typeof selectedProfessions
| typeof suggestions = useMemo(() => {
return [
...suggestions
.filter(
profession =>
!selectedProfessions.some(
otherProfession => otherProfession.id === profession.id
)
)
.map(p => ({ ...p, disabled: false })),
...selectedProfessions.map(p => ({
...p,
disabled: true,
})),
];
}, [suggestions, selectedProfessions]);
useEffect(() => {
reset({
professions,
});
}, [professions, reset]);
const prepateDataBeforeSave = (data: Input): QualificationInput => {
return {
...pick(data, ['name', 'description', 'formula', 'code']),
associateProfession: data.professions
.filter(
profession =>
!professions.some(
otherProfession => otherProfession.id === profession.id
)
)
.map(profession => profession.id),
dissociateProfession: professions
.filter(
profession =>
!data.professions.some(
otherProfession => otherProfession.id === profession.id
)
)
.map(profession => profession.id),
};
};
const _onSubmit = async (data: Input) => {
const success = await onSubmit(prepateDataBeforeSave(data));
if (success) {
onClose();
}
};
return (
<Dialog
open={open}
onClose={isSubmitting ? undefined : onClose}
fullWidth
maxWidth="xs"
>
<form onSubmit={handleSubmit(_onSubmit)}>
<DialogTitle>
{editMode ? 'Edycja kwalifikacji' : 'Tworzenie kwalifikacji'}
</DialogTitle>
<DialogContent className={classes.dialogContent}>
<TextField
fullWidth
label="Nazwa kwalifikacji"
name="name"
defaultValue={qualification?.name}
inputRef={register({
required: 'Te pole jest wymagane.',
maxLength: {
value: MAX_NAME_LENGTH,
message: `Maksymalna długość nazwy kwalifikacji to ${MAX_NAME_LENGTH} znaki.`,
},
})}
error={!!errors.name?.message}
helperText={errors.name?.message ?? ''}
/>
<TextField
fullWidth
label="Oznaczenie kwalifikacji"
name="code"
defaultValue={qualification?.code}
inputRef={register({
required: 'Te pole jest wymagane.',
})}
error={!!errors.code?.message}
helperText={errors.code?.message ?? ''}
/>
<Controller
name="formula"
defaultValue={qualification?.formula ?? FORMULAS[0]}
control={control}
as={
<TextField select fullWidth label="Formuła">
{FORMULAS.map(formula => (
<MenuItem key={formula} value={formula}>
{formula}
</MenuItem>
))}
</TextField>
}
/>
<TextField
fullWidth
label="Opis"
name="description"
defaultValue={qualification?.description}
inputRef={register}
multiline
/>
{!loading && (
<Autocomplete
multiple
options={autocompleteOptions}
getOptionLabel={option => option?.name ?? ''}
loading={isLoadingSuggestions}
value={selectedProfessions}
getOptionDisabled={option => !!option.disabled}
onChange={(_, opts, reason) => {
setValue(
'professions',
opts.map(profession => omit(profession, 'key'))
);
}}
getOptionSelected={(option, value) =>
option && value && option.id === value.id
}
renderInput={params => (
<TextField
{...params}
variant="standard"
label="Zawody"
onChange={e => {
setSearch(e.target.value);
}}
InputProps={{
...params.InputProps,
endAdornment: (
<>
{isLoadingSuggestions ? (
<CircularProgress color="inherit" size={20} />
) : null}
</>
),
}}
/>
)}
/>
)}
{selectedProfessions.map((profession, index) => {
return (
<input
type="hidden"
key={profession.id}
name={`professions[${index}].id`}
ref={register({ valueAsNumber: true })}
defaultValue={profession.id}
/>
);
})}
</DialogContent>
<DialogActions>
<Button
color="secondary"
type="button"
variant="contained"
onClick={onClose}
disabled={isSubmitting}
>
Anuluj
</Button>
<Button
type="submit"
color="primary"
variant="contained"
disabled={isSubmitting}
>
{editMode ? 'Zapisz' : 'Utwórz'}
</Button>
</DialogActions>
</form>
</Dialog>
);
};
const useStyles = makeStyles(theme => ({
dialogContent: {
'& > *:not(:last-child)': {
marginBottom: theme.spacing(1),
},
},
}));
export default FormDialog;

View File

@ -0,0 +1,80 @@
import { useMemo, useState } from 'react';
import { useApolloClient, useQuery } from '@apollo/client';
import { useDebounce } from 'react-use';
import { QUERY_PROFESSIONS } from './queries';
import {
Maybe,
Query,
QueryProfessionsArgs,
Scalars,
} from 'libs/graphql/types';
import { ExtendedProfession } from './types';
export interface Options {
qualificationID?: Maybe<Scalars['ID']>;
omit?: Scalars['ID'][];
}
const useProfessionAutocomplete = ({ qualificationID, omit = [] }: Options) => {
const [suggestions, setSuggestions] = useState<ExtendedProfession[]>([]);
const [isLoadingSuggestions, setIsLoadingSuggestions] = useState<boolean>(
false
);
const [search, setSearch] = useState<string>('');
const client = useApolloClient();
const { data, loading } = useQuery<
Pick<Query, 'professions'>,
QueryProfessionsArgs
>(QUERY_PROFESSIONS, {
fetchPolicy: 'cache-and-network',
skip: !qualificationID,
variables: {
filter: {
qualificationID: [qualificationID ?? 0],
},
},
});
const professions = useMemo(() => data?.professions.items ?? [], [data]);
const loadSuggestions = async (search: string) => {
setIsLoadingSuggestions(true);
try {
const { data } = await client.query<
Pick<Query, 'professions'>,
QueryProfessionsArgs
>({
query: QUERY_PROFESSIONS,
fetchPolicy: 'no-cache',
variables: {
filter: { nameIEQ: '%' + search + '%', idNEQ: omit },
limit: 10,
},
});
if (data.professions?.items) {
setSuggestions(data.professions.items);
}
} catch (e) {}
setIsLoadingSuggestions(false);
};
useDebounce(
() => {
loadSuggestions(search);
},
500,
[search]
);
return {
professions,
get loading() {
return this.professions.length === 0 && loading;
},
isLoadingSuggestions,
suggestions,
setSearch,
search,
};
};
export default useProfessionAutocomplete;

View File

@ -0,0 +1,2 @@
export const MAX_NAME_LENGTH = 100;
export const FORMULAS = ['Formuła 2012', 'Formuła 2017', 'Formuła 2019'];

View File

@ -0,0 +1,17 @@
import { gql } from '@apollo/client';
export const QUERY_PROFESSIONS = gql`
query professions(
$offset: Int
$limit: Int
$filter: ProfessionFilter
$sort: [String!]
) {
professions(offset: $offset, limit: $limit, filter: $filter, sort: $sort) {
items {
id
name
}
}
}
`;

View File

@ -0,0 +1,12 @@
import { Profession, QualificationInput } from 'libs/graphql/types';
export type ExtendedProfession = Profession & {
disabled?: boolean;
};
export type Input = Omit<
QualificationInput,
'associateProfession' | 'dissociateProfession'
> & {
professions: ExtendedProfession[];
};

View File

@ -0,0 +1,60 @@
import { useState } from 'react';
import { useDebounce } from 'react-use';
import { makeStyles } from '@material-ui/core/styles';
import { IconButton, Tooltip } from '@material-ui/core';
import { Add as AddIcon } from '@material-ui/icons';
import BaseTableToolbar from 'common/Table/TableToolbar';
import SearchInput from 'common/Form/SearchInput';
export interface TableToolbarProps {
search: string;
onChangeSearchValue: (search: string) => void;
onClickCreateQualification: () => void;
}
const TableToolbar = ({
search,
onChangeSearchValue,
onClickCreateQualification,
}: TableToolbarProps) => {
const classes = useStyles();
const [_search, setSearch] = useState<string>(search);
useDebounce(
() => {
onChangeSearchValue(_search);
},
500,
[_search]
);
return (
<BaseTableToolbar className={classes.toolbar}>
<SearchInput
value={_search}
onChange={e => {
setSearch(e.target.value);
}}
onResetValue={() => {
setSearch('');
}}
/>
<Tooltip title="Utwórz kwalifikację">
<IconButton onClick={onClickCreateQualification}>
<AddIcon />
</IconButton>
</Tooltip>
</BaseTableToolbar>
);
};
const useStyles = makeStyles(theme => ({
toolbar: {
justifyContent: 'flex-end',
'& > *:not(:last-child)': {
marginRight: theme.spacing(0.5),
},
},
}));
export default TableToolbar;

View File

@ -0,0 +1,34 @@
import { decodeSort } from 'libs/serialize-query-params/SortParam';
import { Column } from 'common/Table/types';
import { Qualification } from 'libs/graphql/types';
export const DEFAULT_SORT = decodeSort('id DESC');
export const COLUMNS: Column<Qualification>[] = [
{
field: 'id',
sortable: true,
label: 'ID',
},
{
field: 'formula',
sortable: true,
label: 'Formuła',
},
{
field: 'name',
sortable: true,
label: 'Nazwa',
valueFormatter: v => `${v.name} (${v.code})`,
},
{
field: 'createdAt',
sortable: true,
label: 'Data utworzenia',
type: 'datetime',
},
];
export enum DialogType {
Create = 'create',
Edit = 'edit',
None = '',
}

View File

@ -0,0 +1,25 @@
import { gql } from '@apollo/client';
export const MUTATION_CREATE_QUALIFICATION = gql`
mutation createProfession($input: QualificationInput!) {
createQualification(input: $input) {
id
}
}
`;
export const MUTATION_UPDATE_QUALIFICATION = gql`
mutation updateQualification($id: ID!, $input: QualificationInput!) {
updateQualification(id: $id, input: $input) {
id
}
}
`;
export const MUTATION_DELETE_QUALIFICATIONS = gql`
mutation deleteQualifications($ids: [ID!]!) {
deleteQualifications(ids: $ids) {
id
}
}
`;

View File

@ -0,0 +1,27 @@
import { gql } from '@apollo/client';
export const QUERY_QUALIFICATIONS = gql`
query qualifications(
$offset: Int
$limit: Int
$filter: QualificationFilter
$sort: [String!]
) {
qualifications(
offset: $offset
limit: $limit
filter: $filter
sort: $sort
) {
total
items {
id
name
code
formula
description
createdAt
}
}
}
`;

View File

@ -9,35 +9,67 @@ export type Scalars = {
Boolean: boolean;
Int: number;
Float: number;
Upload: any;
Time: any;
Upload: any;
};
export type UpdateManyUsersInput = {
role?: Maybe<Role>;
activated?: Maybe<Scalars['Boolean']>;
export type UserWithToken = {
token: Scalars['String'];
user: User;
};
export type QualificationFilterOr = {
nameMatch?: Maybe<Scalars['String']>;
nameIEQ?: Maybe<Scalars['String']>;
codeMatch?: Maybe<Scalars['String']>;
codeIEQ?: Maybe<Scalars['String']>;
export type ProfessionInput = {
name?: Maybe<Scalars['String']>;
description?: Maybe<Scalars['String']>;
};
export type QuestionList = {
export type QualificationList = {
total: Scalars['Int'];
items?: Maybe<Array<Question>>;
items?: Maybe<Array<Qualification>>;
};
export type ProfessionList = {
total: Scalars['Int'];
items?: Maybe<Array<Profession>>;
export enum Role {
Admin = 'admin',
User = 'user'
}
export type User = {
id: Scalars['ID'];
displayName: Scalars['String'];
role: Role;
email: Scalars['String'];
activated: Scalars['Boolean'];
createdAt: Scalars['Time'];
};
export type UserFilterOr = {
displayNameIEQ?: Maybe<Scalars['String']>;
displayNameMATCH?: Maybe<Scalars['String']>;
emailIEQ?: Maybe<Scalars['String']>;
emailMATCH?: Maybe<Scalars['String']>;
};
export type Qualification = {
id: Scalars['ID'];
slug: Scalars['String'];
name: Scalars['String'];
code: Scalars['String'];
formula?: Maybe<Scalars['String']>;
description?: Maybe<Scalars['String']>;
createdAt: Scalars['Time'];
};
export enum Answer {
A = 'a',
B = 'b',
C = 'c',
D = 'd'
}
export type QuestionInput = {
content?: Maybe<Scalars['String']>;
from?: Maybe<Scalars['String']>;
@ -60,89 +92,6 @@ export type QuestionInput = {
deleteAnswerDImage?: Maybe<Scalars['Boolean']>;
};
export type UserFilter = {
id?: Maybe<Array<Scalars['ID']>>;
idNEQ?: Maybe<Array<Scalars['ID']>>;
activated?: Maybe<Scalars['Boolean']>;
displayName?: Maybe<Array<Scalars['String']>>;
displayNameNEQ?: Maybe<Array<Scalars['String']>>;
displayNameIEQ?: Maybe<Scalars['String']>;
displayNameMATCH?: Maybe<Scalars['String']>;
email?: Maybe<Array<Scalars['String']>>;
emailNEQ?: Maybe<Array<Scalars['String']>>;
emailIEQ?: Maybe<Scalars['String']>;
emailMATCH?: Maybe<Scalars['String']>;
role?: Maybe<Array<Role>>;
roleNEQ?: Maybe<Array<Role>>;
createdAt?: Maybe<Scalars['Time']>;
createdAtGT?: Maybe<Scalars['Time']>;
createdAtGTE?: Maybe<Scalars['Time']>;
createdAtLT?: Maybe<Scalars['Time']>;
createdAtLTE?: Maybe<Scalars['Time']>;
or?: Maybe<UserFilterOr>;
};
export type Profession = {
id: Scalars['ID'];
slug: Scalars['String'];
name: Scalars['String'];
description?: Maybe<Scalars['String']>;
createdAt: Scalars['Time'];
};
export enum Answer {
A = 'a',
B = 'b',
C = 'c',
D = 'd'
}
export type QuestionFilter = {
id?: Maybe<Array<Scalars['ID']>>;
idNEQ?: Maybe<Array<Scalars['ID']>>;
from?: Maybe<Array<Scalars['String']>>;
contentIEQ?: Maybe<Scalars['String']>;
contentMATCH?: Maybe<Scalars['String']>;
qualificationID?: Maybe<Array<Scalars['Int']>>;
qualificationIDNEQ?: Maybe<Array<Scalars['Int']>>;
qualificationFilter?: Maybe<QualificationFilter>;
createdAt?: Maybe<Scalars['Time']>;
createdAtGT?: Maybe<Scalars['Time']>;
createdAtGTE?: Maybe<Scalars['Time']>;
createdAtLT?: Maybe<Scalars['Time']>;
createdAtLTE?: Maybe<Scalars['Time']>;
};
export type UserFilterOr = {
displayNameIEQ?: Maybe<Scalars['String']>;
displayNameMATCH?: Maybe<Scalars['String']>;
emailIEQ?: Maybe<Scalars['String']>;
emailMATCH?: Maybe<Scalars['String']>;
};
export type ProfessionFilter = {
id?: Maybe<Array<Scalars['ID']>>;
idNEQ?: Maybe<Array<Scalars['ID']>>;
slug?: Maybe<Array<Scalars['String']>>;
slugNEQ?: Maybe<Array<Scalars['String']>>;
name?: Maybe<Array<Scalars['String']>>;
nameNEQ?: Maybe<Array<Scalars['String']>>;
nameIEQ?: Maybe<Scalars['String']>;
nameMATCH?: Maybe<Scalars['String']>;
descriptionIEQ?: Maybe<Scalars['String']>;
descriptionMATCH?: Maybe<Scalars['String']>;
createdAt?: Maybe<Scalars['Time']>;
createdAtGT?: Maybe<Scalars['Time']>;
createdAtGTE?: Maybe<Scalars['Time']>;
createdAtLT?: Maybe<Scalars['Time']>;
createdAtLTE?: Maybe<Scalars['Time']>;
};
export type QualificationList = {
total: Scalars['Int'];
items?: Maybe<Array<Qualification>>;
};
export type Question = {
id: Scalars['ID'];
from?: Maybe<Scalars['String']>;
@ -163,81 +112,44 @@ export type Question = {
updatedAt: Scalars['Time'];
};
export type UserList = {
export type ProfessionList = {
total: Scalars['Int'];
items?: Maybe<Array<User>>;
items?: Maybe<Array<Profession>>;
};
export type UserWithToken = {
token: Scalars['String'];
user: User;
export type QualificationInput = {
name?: Maybe<Scalars['String']>;
description?: Maybe<Scalars['String']>;
code?: Maybe<Scalars['String']>;
formula?: Maybe<Scalars['String']>;
associateProfession?: Maybe<Array<Scalars['Int']>>;
dissociateProfession?: Maybe<Array<Scalars['Int']>>;
};
export type Query = {
professions: ProfessionList;
profession?: Maybe<Profession>;
qualifications: QualificationList;
qualification?: Maybe<Qualification>;
questions: QuestionList;
generateTest?: Maybe<Array<Question>>;
users: UserList;
user?: Maybe<User>;
me?: Maybe<User>;
};
export type QueryProfessionsArgs = {
filter?: Maybe<ProfessionFilter>;
limit?: Maybe<Scalars['Int']>;
offset?: Maybe<Scalars['Int']>;
sort?: Maybe<Array<Scalars['String']>>;
};
export type QueryProfessionArgs = {
id?: Maybe<Scalars['Int']>;
slug?: Maybe<Scalars['String']>;
};
export type QueryQualificationsArgs = {
filter?: Maybe<QualificationFilter>;
limit?: Maybe<Scalars['Int']>;
offset?: Maybe<Scalars['Int']>;
sort?: Maybe<Array<Scalars['String']>>;
};
export type QueryQualificationArgs = {
id?: Maybe<Scalars['Int']>;
slug?: Maybe<Scalars['String']>;
};
export type QueryQuestionsArgs = {
filter?: Maybe<QuestionFilter>;
limit?: Maybe<Scalars['Int']>;
offset?: Maybe<Scalars['Int']>;
sort?: Maybe<Array<Scalars['String']>>;
};
export type QueryGenerateTestArgs = {
qualificationIDs: Array<Scalars['ID']>;
limit?: Maybe<Scalars['Int']>;
};
export type QueryUsersArgs = {
filter?: Maybe<UserFilter>;
limit?: Maybe<Scalars['Int']>;
offset?: Maybe<Scalars['Int']>;
sort?: Maybe<Array<Scalars['String']>>;
};
export type QueryUserArgs = {
id: Scalars['Int'];
export type QualificationFilter = {
id?: Maybe<Array<Scalars['ID']>>;
idNEQ?: Maybe<Array<Scalars['ID']>>;
slug?: Maybe<Array<Scalars['String']>>;
slugNEQ?: Maybe<Array<Scalars['String']>>;
formula?: Maybe<Array<Scalars['String']>>;
formulaNEQ?: Maybe<Array<Scalars['String']>>;
name?: Maybe<Array<Scalars['String']>>;
nameNEQ?: Maybe<Array<Scalars['String']>>;
nameIEQ?: Maybe<Scalars['String']>;
nameMATCH?: Maybe<Scalars['String']>;
code?: Maybe<Array<Scalars['String']>>;
codeNEQ?: Maybe<Array<Scalars['String']>>;
codeIEQ?: Maybe<Scalars['String']>;
codeMATCH?: Maybe<Scalars['String']>;
descriptionIEQ?: Maybe<Scalars['String']>;
descriptionMATCH?: Maybe<Scalars['String']>;
professionID?: Maybe<Array<Scalars['Int']>>;
createdAt?: Maybe<Scalars['Time']>;
createdAtGT?: Maybe<Scalars['Time']>;
createdAtGTE?: Maybe<Scalars['Time']>;
createdAtLT?: Maybe<Scalars['Time']>;
createdAtLTE?: Maybe<Scalars['Time']>;
or?: Maybe<QualificationFilterOr>;
};
export type Mutation = {
@ -334,25 +246,145 @@ export type MutationSignInArgs = {
staySignedIn?: Maybe<Scalars['Boolean']>;
};
export type ProfessionInput = {
name?: Maybe<Scalars['String']>;
description?: Maybe<Scalars['String']>;
export type UserFilter = {
id?: Maybe<Array<Scalars['ID']>>;
idNEQ?: Maybe<Array<Scalars['ID']>>;
activated?: Maybe<Scalars['Boolean']>;
displayName?: Maybe<Array<Scalars['String']>>;
displayNameNEQ?: Maybe<Array<Scalars['String']>>;
displayNameIEQ?: Maybe<Scalars['String']>;
displayNameMATCH?: Maybe<Scalars['String']>;
email?: Maybe<Array<Scalars['String']>>;
emailNEQ?: Maybe<Array<Scalars['String']>>;
emailIEQ?: Maybe<Scalars['String']>;
emailMATCH?: Maybe<Scalars['String']>;
role?: Maybe<Array<Role>>;
roleNEQ?: Maybe<Array<Role>>;
createdAt?: Maybe<Scalars['Time']>;
createdAtGT?: Maybe<Scalars['Time']>;
createdAtGTE?: Maybe<Scalars['Time']>;
createdAtLT?: Maybe<Scalars['Time']>;
createdAtLTE?: Maybe<Scalars['Time']>;
or?: Maybe<UserFilterOr>;
};
export type QualificationInput = {
name?: Maybe<Scalars['String']>;
export type Profession = {
id: Scalars['ID'];
slug: Scalars['String'];
name: Scalars['String'];
description?: Maybe<Scalars['String']>;
code?: Maybe<Scalars['String']>;
formula?: Maybe<Scalars['String']>;
associateProfession?: Maybe<Array<Scalars['Int']>>;
dissociateProfession?: Maybe<Array<Scalars['Int']>>;
createdAt: Scalars['Time'];
};
export enum Role {
Admin = 'admin',
User = 'user'
}
export type QuestionFilter = {
id?: Maybe<Array<Scalars['ID']>>;
idNEQ?: Maybe<Array<Scalars['ID']>>;
from?: Maybe<Array<Scalars['String']>>;
contentIEQ?: Maybe<Scalars['String']>;
contentMATCH?: Maybe<Scalars['String']>;
qualificationID?: Maybe<Array<Scalars['Int']>>;
qualificationIDNEQ?: Maybe<Array<Scalars['Int']>>;
qualificationFilter?: Maybe<QualificationFilter>;
createdAt?: Maybe<Scalars['Time']>;
createdAtGT?: Maybe<Scalars['Time']>;
createdAtGTE?: Maybe<Scalars['Time']>;
createdAtLT?: Maybe<Scalars['Time']>;
createdAtLTE?: Maybe<Scalars['Time']>;
};
export type Query = {
professions: ProfessionList;
profession?: Maybe<Profession>;
qualifications: QualificationList;
qualification?: Maybe<Qualification>;
questions: QuestionList;
generateTest?: Maybe<Array<Question>>;
users: UserList;
user?: Maybe<User>;
me?: Maybe<User>;
};
export type QueryProfessionsArgs = {
filter?: Maybe<ProfessionFilter>;
limit?: Maybe<Scalars['Int']>;
offset?: Maybe<Scalars['Int']>;
sort?: Maybe<Array<Scalars['String']>>;
};
export type QueryProfessionArgs = {
id?: Maybe<Scalars['Int']>;
slug?: Maybe<Scalars['String']>;
};
export type QueryQualificationsArgs = {
filter?: Maybe<QualificationFilter>;
limit?: Maybe<Scalars['Int']>;
offset?: Maybe<Scalars['Int']>;
sort?: Maybe<Array<Scalars['String']>>;
};
export type QueryQualificationArgs = {
id?: Maybe<Scalars['Int']>;
slug?: Maybe<Scalars['String']>;
};
export type QueryQuestionsArgs = {
filter?: Maybe<QuestionFilter>;
limit?: Maybe<Scalars['Int']>;
offset?: Maybe<Scalars['Int']>;
sort?: Maybe<Array<Scalars['String']>>;
};
export type QueryGenerateTestArgs = {
qualificationIDs: Array<Scalars['ID']>;
limit?: Maybe<Scalars['Int']>;
};
export type QueryUsersArgs = {
filter?: Maybe<UserFilter>;
limit?: Maybe<Scalars['Int']>;
offset?: Maybe<Scalars['Int']>;
sort?: Maybe<Array<Scalars['String']>>;
};
export type QueryUserArgs = {
id: Scalars['Int'];
};
export type ProfessionFilter = {
id?: Maybe<Array<Scalars['ID']>>;
idNEQ?: Maybe<Array<Scalars['ID']>>;
slug?: Maybe<Array<Scalars['String']>>;
slugNEQ?: Maybe<Array<Scalars['String']>>;
name?: Maybe<Array<Scalars['String']>>;
nameNEQ?: Maybe<Array<Scalars['String']>>;
nameIEQ?: Maybe<Scalars['String']>;
nameMATCH?: Maybe<Scalars['String']>;
descriptionIEQ?: Maybe<Scalars['String']>;
descriptionMATCH?: Maybe<Scalars['String']>;
qualificationID?: Maybe<Array<Scalars['ID']>>;
createdAt?: Maybe<Scalars['Time']>;
createdAtGT?: Maybe<Scalars['Time']>;
createdAtGTE?: Maybe<Scalars['Time']>;
createdAtLT?: Maybe<Scalars['Time']>;
createdAtLTE?: Maybe<Scalars['Time']>;
};
export type QualificationFilterOr = {
nameMatch?: Maybe<Scalars['String']>;
nameIEQ?: Maybe<Scalars['String']>;
codeMatch?: Maybe<Scalars['String']>;
codeIEQ?: Maybe<Scalars['String']>;
};
export type UserInput = {
displayName?: Maybe<Scalars['String']>;
@ -362,48 +394,17 @@ export type UserInput = {
activated?: Maybe<Scalars['Boolean']>;
};
export type Qualification = {
id: Scalars['ID'];
slug: Scalars['String'];
name: Scalars['String'];
code: Scalars['String'];
formula?: Maybe<Scalars['String']>;
description?: Maybe<Scalars['String']>;
createdAt: Scalars['Time'];
export type QuestionList = {
total: Scalars['Int'];
items?: Maybe<Array<Question>>;
};
export type QualificationFilter = {
id?: Maybe<Array<Scalars['ID']>>;
idNEQ?: Maybe<Array<Scalars['ID']>>;
slug?: Maybe<Array<Scalars['String']>>;
slugNEQ?: Maybe<Array<Scalars['String']>>;
formula?: Maybe<Array<Scalars['String']>>;
formulaNEQ?: Maybe<Array<Scalars['String']>>;
name?: Maybe<Array<Scalars['String']>>;
nameNEQ?: Maybe<Array<Scalars['String']>>;
nameIEQ?: Maybe<Scalars['String']>;
nameMATCH?: Maybe<Scalars['String']>;
code?: Maybe<Array<Scalars['String']>>;
codeNEQ?: Maybe<Array<Scalars['String']>>;
codeIEQ?: Maybe<Scalars['String']>;
codeMATCH?: Maybe<Scalars['String']>;
descriptionIEQ?: Maybe<Scalars['String']>;
descriptionMATCH?: Maybe<Scalars['String']>;
professionID?: Maybe<Array<Scalars['Int']>>;
createdAt?: Maybe<Scalars['Time']>;
createdAtGT?: Maybe<Scalars['Time']>;
createdAtGTE?: Maybe<Scalars['Time']>;
createdAtLT?: Maybe<Scalars['Time']>;
createdAtLTE?: Maybe<Scalars['Time']>;
or?: Maybe<QualificationFilterOr>;
export type UserList = {
total: Scalars['Int'];
items?: Maybe<Array<User>>;
};
export type User = {
id: Scalars['ID'];
displayName: Scalars['String'];
role: Role;
email: Scalars['String'];
activated: Scalars['Boolean'];
createdAt: Scalars['Time'];
export type UpdateManyUsersInput = {
role?: Maybe<Role>;
activated?: Maybe<Scalars['Boolean']>;
};