Skip to content

Commit

Permalink
feat(chat): groups management works
Browse files Browse the repository at this point in the history
  • Loading branch information
Mati365 committed Dec 29, 2024
1 parent 8add01a commit 8f3c0dd
Show file tree
Hide file tree
Showing 30 changed files with 402 additions and 91 deletions.
5 changes: 3 additions & 2 deletions apps/chat/src/i18n/packs/i18n-lang-en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,8 +291,6 @@ export const I18N_PACK_EN = deepmerge(I18N_FORWARDED_EN_PACK, {
},
chooseAppModal: {
title: 'Choose App',
select: 'Select',
selected: 'Selected',
},
},
appsCreator: {
Expand Down Expand Up @@ -444,6 +442,9 @@ export const I18N_PACK_EN = deepmerge(I18N_FORWARDED_EN_PACK, {
email: 'Email',
},
},
chooseUsersModal: {
title: 'Choose users',
},
},
usersGroups: {
form: {
Expand Down
5 changes: 3 additions & 2 deletions apps/chat/src/i18n/packs/i18n-lang-pl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,8 +293,6 @@ export const I18N_PACK_PL: I18nLangPack = deepmerge(I18N_FORWARDED_PL_PACK, {
},
chooseAppModal: {
title: 'Wybierz aplikację',
select: 'Wybierz',
selected: 'Wybrano',
},
},
appsCreator: {
Expand Down Expand Up @@ -446,6 +444,9 @@ export const I18N_PACK_PL: I18nLangPack = deepmerge(I18N_FORWARDED_PL_PACK, {
password: 'Hasło',
},
},
chooseUsersModal: {
title: 'Wybierz użytkowników',
},
},
usersGroups: {
form: {
Expand Down
31 changes: 10 additions & 21 deletions apps/chat/src/modules/apps/choose-app/choose-app-modal.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { suppressEvent } from '@under-control/forms';

import type { SdkAppT, SdkTableRowWithIdT } from '@llm/sdk';

import { findItemById } from '@llm/commons';
import { Modal, type ModalProps, ModalTitle } from '@llm/ui';
import { Modal, type ModalProps, ModalTitle, SelectRecordButton } from '@llm/ui';
import { useI18n } from '~/i18n';

import { AppsContainer } from '../grid';
Expand All @@ -22,31 +24,18 @@ export function ChooseAppModal({
const t = useI18n().pack.apps.chooseAppModal;

const renderAppCTA = (app: SdkAppT) => {
if (findItemById(app.id)(selectedApps || [])) {
return (
<a
href=""
className="opacity-50 pointer-events-none uk-button uk-button-secondary uk-button-small uk-disabled"
onClick={(e) => {
e.preventDefault();
}}
>
{t.selected}
</a>
);
}
const installed = !!findItemById(app.id)(selectedApps || []);

return (
<a
href=""
className="uk-button uk-button-secondary uk-button-small"
<SelectRecordButton
className="uk-button-small"
disabled={installed}
selected={installed}
onClick={(e) => {
e.preventDefault();
suppressEvent(e);
onSelect?.(app);
}}
>
{t.select}
</a>
/>
);
};

Expand Down
16 changes: 14 additions & 2 deletions apps/chat/src/modules/shared/ghost-placeholder.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
import type { PropsWithChildren } from 'react';

import clsx from 'clsx';
import { GhostIcon } from 'lucide-react';

export function GhostPlaceholder({ children }: PropsWithChildren) {
type Props = PropsWithChildren & {
spaced?: boolean;
className?: string;
};

export function GhostPlaceholder({ children, className, spaced = true }: Props) {
return (
<div className="flex flex-col justify-center items-center p-12 text-gray-300">
<div
className={clsx(
'flex flex-col justify-center items-center text-gray-300',
spaced && 'p-12',
className,
)}
>
<div className="mb-4">
<GhostIcon size={48} />
</div>
Expand Down
5 changes: 5 additions & 0 deletions apps/chat/src/modules/users-groups/form/create/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import type { SdkCreateUsersGroupInputT, SdkUserListItemT } from '@llm/sdk';

export type CreateUsersGroupValue = Omit<SdkCreateUsersGroupInputT, 'users'> & {
users: SdkUserListItemT[];
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import { type FormHookAttrs, useForm } from '@under-control/forms';
import { flow } from 'fp-ts/lib/function';

import { runTask, tapTaskEither } from '@llm/commons';
import { type SdkCreateUsersGroupInputT, useSdkForLoggedIn } from '@llm/sdk';
import { useSdkForLoggedIn } from '@llm/sdk';
import { usePredefinedFormValidators, useSaveTaskEitherNotification } from '@llm/ui';

import type { CreateUsersGroupValue } from './types';

type CreateUsersGroupFormHookAttrs =
& Omit<
FormHookAttrs<SdkCreateUsersGroupInputT>,
FormHookAttrs<CreateUsersGroupValue>,
'validation' | 'onSubmit'
>
& {
Expand All @@ -21,7 +23,7 @@ export function useUsersGroupCreateForm(
}: CreateUsersGroupFormHookAttrs,
) {
const { sdks } = useSdkForLoggedIn();
const { required, requiredListItem } = usePredefinedFormValidators<SdkCreateUsersGroupInputT>();
const { required, requiredListItem } = usePredefinedFormValidators<CreateUsersGroupValue>();
const saveNotifications = useSaveTaskEitherNotification();

return useForm({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { SdkCreateUsersGroupInputT } from '@llm/sdk';

import { useAnimatedModal } from '@llm/commons-front';
import { useWorkspaceOrganizationOrThrow } from '~/modules/workspace';

import type { CreateUsersGroupValue } from './types';

import {
UsersGroupCreateFormModal,
type UsersGroupCreateFormModalProps,
Expand All @@ -11,7 +11,7 @@ import {
type UsersGroupShowModalProps =
& Pick<UsersGroupCreateFormModalProps, 'onAfterSubmit'>
& {
defaultValue: Omit<SdkCreateUsersGroupInputT, 'organization'>;
defaultValue: Omit<CreateUsersGroupValue, 'organization'>;
};

export function useUsersGroupCreateModal() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import type { SdkCreateUsersGroupInputT } from '@llm/sdk';

import {
CancelButton,
CreateButton,
Expand All @@ -10,13 +8,15 @@ import {
} from '@llm/ui';
import { useI18n } from '~/i18n';

import type { CreateUsersGroupValue } from './types';

import { UsersGroupSharedFormFields } from '../shared';
import { useUsersGroupCreateForm } from './use-users-group-create-form';

export type UsersGroupCreateFormModalProps =
& Omit<ModalProps, 'children' | 'header' | 'formProps'>
& {
defaultValue: SdkCreateUsersGroupInputT;
defaultValue: CreateUsersGroupValue;
onAfterSubmit?: VoidFunction;
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { controlled, useControlStrict } from '@under-control/forms';
import { pipe } from 'fp-ts/lib/function';

import type { SdkOffsetPaginationInputT, SdkUserListItemT } from '@llm/sdk';

import { tapTaskOption } from '@llm/commons';
import { useAsyncCallback } from '@llm/commons-front';
import { AddButton, EllipsisCrudDropdownButton, FormField, PaginatedTable } from '@llm/ui';
import { useI18n } from '~/i18n';
import { GhostPlaceholder } from '~/modules/shared';
import { useChooseUsersModal } from '~/modules/users/choose-users';

export const GroupUsersSelectTable = controlled<SdkUserListItemT[]>(({ control: { value, setValue } }) => {
const { pack } = useI18n();
const t = pack.table.columns;

const pagination = useControlStrict<SdkOffsetPaginationInputT>({
defaultValue: {
offset: 0,
limit: 10,
},
});

const chooseUsersModal = useChooseUsersModal();

const [onShowChooseUsersModal, choosingUsersState] = useAsyncCallback(
pipe(
chooseUsersModal.showAsOptional({
selectedUsers: value,
}),
tapTaskOption((users) => {
setValue({
value: users,
});
}),
),
);

const paginationOutput = {
total: Math.min(value.length, pagination.value.limit),
items: value.slice(
pagination.value.offset,
pagination.value.offset + pagination.value.limit,
),
};

return (
<FormField
className="uk-margin"
label={pack.usersGroups.form.fields.users.label}
>
<AddButton
className="mt-1 uk-button-secondary"
loading={choosingUsersState.isLoading}
onClick={onShowChooseUsersModal}
/>

{value.length > 0 && (
<PaginatedTable
className="mt-3"
spaced={false}
result={paginationOutput}
pagination={pagination.bind.entire()}
columns={[
{ id: 'id', name: t.id, className: 'uk-table-shrink' },
{ id: 'email', name: t.email, className: 'uk-table-expand' },
{ id: 'actions', name: t.actions, className: 'uk-table-shrink' },
]}
footerProps={{
centered: true,
withNthToNthOf: false,
withPageNumber: false,
withPageSizeSelector: false,
}}
>
{({ item }) => (
<tr key={item.id}>
<td>{item.id}</td>
<td>{item.email}</td>
<td>
<EllipsisCrudDropdownButton
onDelete={() => {
setValue({
value: value.filter(user => user.id !== item.id),
});
}}
/>
</td>
</tr>
)}
</PaginatedTable>
)}

{!value.length && (
<GhostPlaceholder spaced={false} className="pt-6" />
)}
</FormField>
);
});
1 change: 1 addition & 0 deletions apps/chat/src/modules/users-groups/form/shared/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './group-users-select-table';
export * from './users-group-shared-form-fields';
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import type { SdkUsersGroupT } from '@llm/sdk';
import { FormField, Input } from '@llm/ui';
import { useI18n } from '~/i18n';

type Value = Pick<SdkUsersGroupT, 'name'>;
import { GroupUsersSelectTable } from './group-users-select-table';

type Value = Pick<SdkUsersGroupT, 'name' | 'users'>;

type Props = ValidationErrorsListProps<Value>;

Expand All @@ -14,17 +16,21 @@ export const UsersGroupSharedFormFields = controlled<Value, Props>(({ errors, co
const validation = useFormValidatorMessages({ errors });

return (
<FormField
className="uk-margin"
label={t.fields.name.label}
{...validation.extract('name')}
>
<Input
name="name"
placeholder={t.fields.name.placeholder}
required
{...bind.path('name')}
/>
</FormField>
<>
<FormField
className="uk-margin"
label={t.fields.name.label}
{...validation.extract('name')}
>
<Input
name="name"
placeholder={t.fields.name.placeholder}
required
{...bind.path('name')}
/>
</FormField>

<GroupUsersSelectTable {...bind.path('users')} />
</>
);
});
Loading

0 comments on commit 8f3c0dd

Please sign in to comment.