diff --git a/.eslintrc.js b/.eslintrc.js
index a9454410..7a00feda 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -5,7 +5,6 @@ module.exports = init({
modules: {
auto: true,
esm: true,
- disableExpensiveRules: !process.env.CI && !process.env.HUSKY,
typescript: {
parserProject: ['./tsconfig.eslint.json'],
resolverProject: ['./tsconfig.json'],
diff --git a/app/Dashboard/Households/HouseholdDetail/HouseholdDetail.ids.ts b/app/Dashboard/Households/HouseholdDetail/HouseholdDetail.ids.ts
index 58c00689..e3076831 100644
--- a/app/Dashboard/Households/HouseholdDetail/HouseholdDetail.ids.ts
+++ b/app/Dashboard/Households/HouseholdDetail/HouseholdDetail.ids.ts
@@ -1,6 +1,7 @@
export const householdDetailIds = {
memberFormTab: 'member-form-tab',
householderTab: 'householder-tab',
+ visitsTab: 'visits-tab',
nameField: 'household-name-input',
severityField: 'household-severity-input',
statusField: 'household-status-field',
diff --git a/app/Dashboard/Households/HouseholdDetail/HouseholdDetail.tsx b/app/Dashboard/Households/HouseholdDetail/HouseholdDetail.tsx
index 19660995..c911b524 100644
--- a/app/Dashboard/Households/HouseholdDetail/HouseholdDetail.tsx
+++ b/app/Dashboard/Households/HouseholdDetail/HouseholdDetail.tsx
@@ -35,6 +35,7 @@ import { InformationBadge } from '../../_components/InformationBadge';
import { SeverityBadge } from '../../_components/SeverityBadge';
import { openDeleteHouseholdModal } from '../_components/DeleteHouseholdModal';
import { HouseholderDetail } from './_components/HouseholderDetail';
+import { HouseholderVisits } from './_components/HouseholderVisits';
import { MemberList } from './_components/MemberList';
import { householdDetailIds as ids } from './HouseholdDetail.ids';
import { householdNotifications } from './householdNotifications';
@@ -296,6 +297,11 @@ export const HouseholdDetail = () => {
panel: ,
id: ids.memberFormTab,
},
+ {
+ tab:
{t.tabs.visitsTitle},
+ panel: ,
+ id: ids.visitsTab,
+ },
]}
/>
>
diff --git a/app/Dashboard/Households/HouseholdDetail/_components/AddHouseholderVisit/AddHouseholderVisitButton/AddHouseholderVisitButton.cy.tsx b/app/Dashboard/Households/HouseholdDetail/_components/AddHouseholderVisit/AddHouseholderVisitButton/AddHouseholderVisitButton.cy.tsx
new file mode 100644
index 00000000..12e48333
--- /dev/null
+++ b/app/Dashboard/Households/HouseholdDetail/_components/AddHouseholderVisit/AddHouseholderVisitButton/AddHouseholderVisitButton.cy.tsx
@@ -0,0 +1,12 @@
+import { AddHouseholderVisitButton } from './AddHouseholderVisitButton';
+import { addHouseholderVisitButtonId } from './AddHouseholderVisitButton.ids';
+
+describe('Create Project Button', () => {
+ beforeEach(() => {
+ cy.mount();
+ });
+
+ it('should be visible to users', () => {
+ cy.findByTestId(addHouseholderVisitButtonId).should('exist');
+ });
+});
diff --git a/app/Dashboard/Households/HouseholdDetail/_components/AddHouseholderVisit/AddHouseholderVisitButton/AddHouseholderVisitButton.ids.ts b/app/Dashboard/Households/HouseholdDetail/_components/AddHouseholderVisit/AddHouseholderVisitButton/AddHouseholderVisitButton.ids.ts
new file mode 100644
index 00000000..72c562b8
--- /dev/null
+++ b/app/Dashboard/Households/HouseholdDetail/_components/AddHouseholderVisit/AddHouseholderVisitButton/AddHouseholderVisitButton.ids.ts
@@ -0,0 +1 @@
+export const addHouseholderVisitButtonId = 'add-householder-visit-button';
diff --git a/app/Dashboard/Households/HouseholdDetail/_components/AddHouseholderVisit/AddHouseholderVisitButton/AddHouseholderVisitButton.stories.tsx b/app/Dashboard/Households/HouseholdDetail/_components/AddHouseholderVisit/AddHouseholderVisitButton/AddHouseholderVisitButton.stories.tsx
new file mode 100644
index 00000000..0c4f2b3f
--- /dev/null
+++ b/app/Dashboard/Households/HouseholdDetail/_components/AddHouseholderVisit/AddHouseholderVisitButton/AddHouseholderVisitButton.stories.tsx
@@ -0,0 +1,11 @@
+import type { Meta, StoryObj } from '@storybook/react';
+
+import { AddHouseholderVisitButton } from './AddHouseholderVisitButton';
+
+export default {
+ component: AddHouseholderVisitButton,
+} as Meta;
+
+type Story = StoryObj;
+
+export const Default: Story = {};
diff --git a/app/Dashboard/Households/HouseholdDetail/_components/AddHouseholderVisit/AddHouseholderVisitButton/AddHouseholderVisitButton.tsx b/app/Dashboard/Households/HouseholdDetail/_components/AddHouseholderVisit/AddHouseholderVisitButton/AddHouseholderVisitButton.tsx
new file mode 100644
index 00000000..d811ac4d
--- /dev/null
+++ b/app/Dashboard/Households/HouseholdDetail/_components/AddHouseholderVisit/AddHouseholderVisitButton/AddHouseholderVisitButton.tsx
@@ -0,0 +1,27 @@
+import { PlusIcon } from '@camp/icons';
+import { messages } from '@camp/messages';
+import { tid } from '@camp/test';
+import { Button } from '@mantine/core';
+
+import { openAddHouseholderVisitModal } from '../AddHouseholderVisitModal';
+import { addHouseholderVisitButtonId as id } from './AddHouseholderVisitButton.ids';
+
+interface Props {
+ householdId: string;
+}
+
+export const AddHouseholderVisitButton = ({ householdId }: Props) => {
+ const t = messages.householder.visits;
+
+ return (
+
+ );
+};
diff --git a/app/Dashboard/Households/HouseholdDetail/_components/AddHouseholderVisit/AddHouseholderVisitButton/index.ts b/app/Dashboard/Households/HouseholdDetail/_components/AddHouseholderVisit/AddHouseholderVisitButton/index.ts
new file mode 100644
index 00000000..c37d2ee3
--- /dev/null
+++ b/app/Dashboard/Households/HouseholdDetail/_components/AddHouseholderVisit/AddHouseholderVisitButton/index.ts
@@ -0,0 +1 @@
+export * from './AddHouseholderVisitButton';
diff --git a/app/Dashboard/Households/HouseholdDetail/_components/AddHouseholderVisit/AddHouseholderVisitForm/AddHouseholderVisitForm.ids.ts b/app/Dashboard/Households/HouseholdDetail/_components/AddHouseholderVisit/AddHouseholderVisitForm/AddHouseholderVisitForm.ids.ts
new file mode 100644
index 00000000..946de23a
--- /dev/null
+++ b/app/Dashboard/Households/HouseholdDetail/_components/AddHouseholderVisit/AddHouseholderVisitForm/AddHouseholderVisitForm.ids.ts
@@ -0,0 +1,11 @@
+export const addHouseholderVisitFormIds = {
+ nameInput: 'visit-name',
+ form: 'add-householder-visit-form',
+ dateInput: 'project-document-date',
+ descriptionInput: 'project-description-input',
+ submitBtn: 'submit-button',
+ notification: {
+ success: 'add-householder-visit-success-notification',
+ failure: 'add-householder-visit-failure-notification',
+ },
+} as const;
diff --git a/app/Dashboard/Households/HouseholdDetail/_components/AddHouseholderVisit/AddHouseholderVisitForm/AddHouseholderVisitForm.tsx b/app/Dashboard/Households/HouseholdDetail/_components/AddHouseholderVisit/AddHouseholderVisitForm/AddHouseholderVisitForm.tsx
new file mode 100644
index 00000000..bed00952
--- /dev/null
+++ b/app/Dashboard/Households/HouseholdDetail/_components/AddHouseholderVisit/AddHouseholderVisitForm/AddHouseholderVisitForm.tsx
@@ -0,0 +1,171 @@
+import { useCreateVisitMutation } from '@camp/data-layer';
+import { debug } from '@camp/debug';
+import {
+ ControlledDateInput,
+ ControlledFileUpload,
+ showNotification,
+} from '@camp/design';
+import type { StorageFile } from '@camp/domain';
+import {
+ createResolver,
+ documentFileValidator,
+ documentSchema,
+} from '@camp/domain';
+import { fileStorageClient } from '@camp/file-storage-client';
+import { messages } from '@camp/messages';
+import { tid } from '@camp/test';
+import { isNull } from '@fullstacksjs/toolbox';
+import { Button, createStyles, Group, Stack, TextInput } from '@mantine/core';
+import { useForm } from 'react-hook-form';
+
+import { addHouseholderVisitFormIds as ids } from './AddHouseholderVisitForm.ids';
+
+interface FormSchema {
+ name: string;
+ date: Date;
+ description?: string;
+ documents: StorageFile[];
+}
+
+export interface AddHouseholderVisitFormProps {
+ dismiss: () => void;
+ householdId: string;
+}
+
+const resolver = createResolver({
+ name: documentSchema.name(),
+ date: documentSchema.date(),
+ description: documentSchema.description(),
+ documents: documentSchema.documents(),
+});
+
+const useStyle = createStyles(theme => ({
+ label: {
+ label: {
+ color: theme.colors.fg[6],
+ },
+ },
+}));
+
+export const AddHouseholderVisitForm = ({
+ dismiss,
+ householdId,
+}: AddHouseholderVisitFormProps) => {
+ const t = messages.householder.visits;
+ const tt = t.form;
+ const { handleSubmit, register, formState, control } = useForm({
+ resolver,
+ mode: 'onChange',
+ });
+ const [createVisit, { loading }] = useCreateVisitMutation();
+
+ const onSubmit = handleSubmit(async inputs => {
+ try {
+ const { data } = await createVisit({
+ variables: { ...inputs, householdId },
+ });
+ const visit = data.visit!;
+ if (isNull(visit)) throw Error('Assert: Visit is null');
+
+ showNotification({
+ title: t.addVisit,
+ message: messages.projects.notification.successfulCreate(inputs.name),
+ type: 'success',
+ ...tid(ids.notification.success),
+ });
+ dismiss();
+ } catch (err) {
+ debug.error(err);
+ showNotification({
+ title: t.addVisit,
+ message: messages.projects.notification.failedCreate(inputs.name),
+ type: 'failure',
+ ...tid(ids.notification.failure),
+ });
+ }
+ });
+
+ const { classes } = useStyle();
+ return (
+
+ );
+};
diff --git a/app/Dashboard/Households/HouseholdDetail/_components/AddHouseholderVisit/AddHouseholderVisitForm/index.ts b/app/Dashboard/Households/HouseholdDetail/_components/AddHouseholderVisit/AddHouseholderVisitForm/index.ts
new file mode 100644
index 00000000..d4c5e51b
--- /dev/null
+++ b/app/Dashboard/Households/HouseholdDetail/_components/AddHouseholderVisit/AddHouseholderVisitForm/index.ts
@@ -0,0 +1 @@
+export * from './AddHouseholderVisitForm';
diff --git a/app/Dashboard/Households/HouseholdDetail/_components/AddHouseholderVisit/AddHouseholderVisitModal/AddHouseholderVisitModal.ids.ts b/app/Dashboard/Households/HouseholdDetail/_components/AddHouseholderVisit/AddHouseholderVisitModal/AddHouseholderVisitModal.ids.ts
new file mode 100644
index 00000000..fb8fc611
--- /dev/null
+++ b/app/Dashboard/Households/HouseholdDetail/_components/AddHouseholderVisit/AddHouseholderVisitModal/AddHouseholderVisitModal.ids.ts
@@ -0,0 +1 @@
+export const addHouseholderVisitModalId = 'add-householder-visit-modal';
diff --git a/app/Dashboard/Households/HouseholdDetail/_components/AddHouseholderVisit/AddHouseholderVisitModal/AddHouseholderVisitModal.stories.tsx b/app/Dashboard/Households/HouseholdDetail/_components/AddHouseholderVisit/AddHouseholderVisitModal/AddHouseholderVisitModal.stories.tsx
new file mode 100644
index 00000000..630ea013
--- /dev/null
+++ b/app/Dashboard/Households/HouseholdDetail/_components/AddHouseholderVisit/AddHouseholderVisitModal/AddHouseholderVisitModal.stories.tsx
@@ -0,0 +1,39 @@
+import { ModalsProvider } from '@mantine/modals';
+import type { Meta, StoryObj } from '@storybook/react';
+import { useEffect } from 'react';
+
+import {
+ AddHouseholderVisitModal,
+ openAddHouseholderVisitModal,
+} from './AddHouseholderVisitModal';
+
+export default {
+ argTypes: {
+ opened: {
+ defaultValue: true,
+ type: 'boolean',
+ description: 'Mounts modal if true',
+ },
+ },
+ component: AddHouseholderVisitModal,
+ decorators: [
+ Story => (
+
+
+
+ ),
+ ],
+ chromatic: { delay: 500 },
+} as Meta;
+
+type Story = StoryObj;
+
+export const Default: Story = {
+ render: () => {
+ useEffect(() => {
+ openAddHouseholderVisitModal({ householdId: 'null' });
+ }, []);
+
+ return <>>;
+ },
+};
diff --git a/app/Dashboard/Households/HouseholdDetail/_components/AddHouseholderVisit/AddHouseholderVisitModal/AddHouseholderVisitModal.tsx b/app/Dashboard/Households/HouseholdDetail/_components/AddHouseholderVisit/AddHouseholderVisitModal/AddHouseholderVisitModal.tsx
new file mode 100644
index 00000000..ae3c9d2c
--- /dev/null
+++ b/app/Dashboard/Households/HouseholdDetail/_components/AddHouseholderVisit/AddHouseholderVisitModal/AddHouseholderVisitModal.tsx
@@ -0,0 +1,24 @@
+import { messages } from '@camp/messages';
+import { tid } from '@camp/test';
+import { closeModal, openModal } from '@mantine/modals';
+
+import type { AddHouseholderVisitFormProps } from '../AddHouseholderVisitForm';
+import { AddHouseholderVisitForm } from '../AddHouseholderVisitForm';
+import { addHouseholderVisitModalId as id } from './AddHouseholderVisitModal.ids';
+
+type Props = Omit;
+
+export const AddHouseholderVisitModal = (props: Props) => (
+ closeModal(id)} />
+);
+
+export const openAddHouseholderVisitModal = (props: Props) =>
+ openModal({
+ modalId: id,
+ children: ,
+ title: messages.householder.visits.title,
+ size: '490',
+ padding: '30px',
+ centered: true,
+ ...tid(id),
+ });
diff --git a/app/Dashboard/Households/HouseholdDetail/_components/AddHouseholderVisit/AddHouseholderVisitModal/index.ts b/app/Dashboard/Households/HouseholdDetail/_components/AddHouseholderVisit/AddHouseholderVisitModal/index.ts
new file mode 100644
index 00000000..6e38b27f
--- /dev/null
+++ b/app/Dashboard/Households/HouseholdDetail/_components/AddHouseholderVisit/AddHouseholderVisitModal/index.ts
@@ -0,0 +1 @@
+export * from './AddHouseholderVisitModal';
diff --git a/app/Dashboard/Households/HouseholdDetail/_components/AddHouseholderVisit/index.ts b/app/Dashboard/Households/HouseholdDetail/_components/AddHouseholderVisit/index.ts
new file mode 100644
index 00000000..c37d2ee3
--- /dev/null
+++ b/app/Dashboard/Households/HouseholdDetail/_components/AddHouseholderVisit/index.ts
@@ -0,0 +1 @@
+export * from './AddHouseholderVisitButton';
diff --git a/app/Dashboard/Households/HouseholdDetail/_components/DeleteVisitModal/DeleteVisitModal.ids.ts b/app/Dashboard/Households/HouseholdDetail/_components/DeleteVisitModal/DeleteVisitModal.ids.ts
new file mode 100644
index 00000000..cb11931b
--- /dev/null
+++ b/app/Dashboard/Households/HouseholdDetail/_components/DeleteVisitModal/DeleteVisitModal.ids.ts
@@ -0,0 +1,3 @@
+export const deleteVisitModalIds = {
+ modal: 'delete-visit-modal',
+};
diff --git a/app/Dashboard/Households/HouseholdDetail/_components/DeleteVisitModal/DeleteVisitModal.tsx b/app/Dashboard/Households/HouseholdDetail/_components/DeleteVisitModal/DeleteVisitModal.tsx
new file mode 100644
index 00000000..7a806131
--- /dev/null
+++ b/app/Dashboard/Households/HouseholdDetail/_components/DeleteVisitModal/DeleteVisitModal.tsx
@@ -0,0 +1,23 @@
+import { showModal } from '@camp/design';
+import { messages } from '@camp/messages';
+
+import { deleteVisitModalIds as Ids } from './DeleteVisitModal.ids';
+
+const t = messages.householder.visits.delete.modal;
+
+interface Props {
+ name: string;
+ onDeleteVisit: () => Promise;
+}
+
+export const openDeleteVisitModal = ({ name, onDeleteVisit }: Props) =>
+ showModal({
+ id: Ids.modal,
+ title: t.title,
+ children: t.children(name),
+ cancelLable: t.cancel,
+ confirmLabel: t.confirm,
+ size: 'sm',
+ onConfirm: () => void onDeleteVisit(),
+ destructive: true,
+ });
diff --git a/app/Dashboard/Households/HouseholdDetail/_components/DeleteVisitModal/index.ts b/app/Dashboard/Households/HouseholdDetail/_components/DeleteVisitModal/index.ts
new file mode 100644
index 00000000..ff8209e6
--- /dev/null
+++ b/app/Dashboard/Households/HouseholdDetail/_components/DeleteVisitModal/index.ts
@@ -0,0 +1 @@
+export * from './DeleteVisitModal';
diff --git a/app/Dashboard/Households/HouseholdDetail/_components/HouseholderVisits/HouseholderVisits.ids.ts b/app/Dashboard/Households/HouseholdDetail/_components/HouseholderVisits/HouseholderVisits.ids.ts
new file mode 100644
index 00000000..1d6544ce
--- /dev/null
+++ b/app/Dashboard/Households/HouseholdDetail/_components/HouseholderVisits/HouseholderVisits.ids.ts
@@ -0,0 +1,3 @@
+export const HouseholderVisitsFailureNotification =
+ 'householder-visits-list-failure-notification';
+export const HouseholderVisitsTableId = 'householder-visits-list-table';
diff --git a/app/Dashboard/Households/HouseholdDetail/_components/HouseholderVisits/HouseholderVisits.tsx b/app/Dashboard/Households/HouseholdDetail/_components/HouseholderVisits/HouseholderVisits.tsx
new file mode 100644
index 00000000..55c1e7a2
--- /dev/null
+++ b/app/Dashboard/Households/HouseholdDetail/_components/HouseholderVisits/HouseholderVisits.tsx
@@ -0,0 +1,114 @@
+import type { VisitsDto } from '@camp/data-layer';
+import { useHouseholderVisitsQuery } from '@camp/data-layer';
+import {
+ DashboardTitle,
+ formatToPersian,
+ ImagePreview,
+ showNotification,
+} from '@camp/design';
+import { visitColumnHelper } from '@camp/domain';
+import { errorMessages, messages } from '@camp/messages';
+import { tid } from '@camp/test';
+import { isEmpty } from '@fullstacksjs/toolbox';
+import { Group, Stack, Text } from '@mantine/core';
+import type { PaginationState, SortingState } from '@tanstack/react-table';
+import { getCoreRowModel, useReactTable } from '@tanstack/react-table';
+import { useState } from 'react';
+
+import { AddHouseholderVisitButton } from '../AddHouseholderVisit';
+import * as ids from './HouseholderVisits.ids';
+import { VisitActionButton } from './VisitActionButton';
+import { openVisitDetailModal } from './VisitDetail';
+import { VisitsTable } from './VisitsTable';
+import { visitsActionIds as actionIds } from './VisitsTableRow.ids';
+
+const t = messages.householder.visits;
+
+interface Props {
+ householdId: string;
+}
+
+const columns = [
+ visitColumnHelper.accessor('documents', {
+ header: t.table.columns.documents,
+ cell: doc => ,
+ }),
+ visitColumnHelper.accessor('date', {
+ id: 'date',
+ header: t.table.columns.date,
+ cell: date => {formatToPersian(date.getValue())},
+ }),
+ visitColumnHelper.accessor('description', {
+ header: t.table.columns.description,
+ size: '100%' as any,
+ cell: props => {props.getValue()},
+ }),
+ visitColumnHelper.display({
+ id: 'action',
+ cell: props => (
+ {
+ openVisitDetailModal({ id: props.row.original.id });
+ }}
+ visitDate={props.row.original.date}
+ menuButtonId={actionIds.actionButton}
+ menuId={actionIds.actionMenu}
+ />
+ ),
+ }),
+];
+
+const empty: VisitsDto['visits'] = [];
+
+export const HouseholderVisits = ({ householdId }: Props) => {
+ const [sorting, setSorting] = useState([]);
+ const [pagination, setPagination] = useState({
+ pageIndex: 0,
+ pageSize: 10,
+ });
+
+ const { data, loading, error, previousData } = useHouseholderVisitsQuery({
+ variables: {
+ orderBy: sorting,
+ range: pagination,
+ },
+ });
+
+ const visitsCount = data?.totalCount ?? previousData?.totalCount ?? 0;
+ const visits = data?.visits ?? empty;
+
+ const table = useReactTable({
+ data: visits,
+ columns,
+ state: { sorting, pagination },
+ onPaginationChange: setPagination,
+ manualPagination: true,
+ pageCount: Math.ceil(visitsCount / pagination.pageSize),
+ onSortingChange: setSorting,
+ getCoreRowModel: getCoreRowModel(),
+ });
+
+ if (error) {
+ showNotification({
+ type: 'failure',
+ title: t.title,
+ message: errorMessages.UNKNOWN_ERROR,
+ ...tid(ids.HouseholderVisitsFailureNotification),
+ });
+ return null;
+ }
+
+ return (
+
+
+ {t.title}
+
+
+ {/* FIXME: we need empty state here to */}
+ {isEmpty(visits) && !loading ? null : (
+
+ )}
+
+ );
+};
diff --git a/app/Dashboard/Households/HouseholdDetail/_components/HouseholderVisits/VisitActionButton/VisitActionButton.ids.ts b/app/Dashboard/Households/HouseholdDetail/_components/HouseholderVisits/VisitActionButton/VisitActionButton.ids.ts
new file mode 100644
index 00000000..a8a1bd90
--- /dev/null
+++ b/app/Dashboard/Households/HouseholdDetail/_components/HouseholderVisits/VisitActionButton/VisitActionButton.ids.ts
@@ -0,0 +1,10 @@
+export const visitActionIds = {
+ actionButton: 'visit-table-menu-button',
+ actionMenu: 'visit-table-menu',
+ notifications: {
+ delete: {
+ success: 'delete-visit-success-notification',
+ failure: 'delete-visit-failure-notification',
+ },
+ },
+};
diff --git a/app/Dashboard/Households/HouseholdDetail/_components/HouseholderVisits/VisitActionButton/VisitActionButton.tsx b/app/Dashboard/Households/HouseholdDetail/_components/HouseholderVisits/VisitActionButton/VisitActionButton.tsx
new file mode 100644
index 00000000..33bc09b9
--- /dev/null
+++ b/app/Dashboard/Households/HouseholdDetail/_components/HouseholderVisits/VisitActionButton/VisitActionButton.tsx
@@ -0,0 +1,81 @@
+import { useDeleteVisitMutation } from '@camp/data-layer';
+import { debug } from '@camp/debug';
+import { formatToPersian, showNotification } from '@camp/design';
+import { MenuIcon } from '@camp/icons';
+import { messages } from '@camp/messages';
+import { tid } from '@camp/test';
+import { isNull } from '@fullstacksjs/toolbox';
+import { ActionIcon, Menu } from '@mantine/core';
+
+import { openDeleteVisitModal } from '../../DeleteVisitModal';
+import { visitActionIds as ids } from './VisitActionButton.ids';
+
+interface Props {
+ open: VoidFunction;
+ menuId?: string;
+ menuButtonId?: string;
+ visitDate: Date;
+ visitId: string;
+}
+
+export const VisitActionButton = ({
+ menuId,
+ open,
+ menuButtonId,
+ visitDate,
+ visitId: id,
+}: Props) => {
+ const [deleteVisit] = useDeleteVisitMutation();
+ const t = messages.notification.visits.delete;
+ const date = formatToPersian(visitDate);
+
+ const onDeleteVisit = async () => {
+ try {
+ const { data } = await deleteVisit({
+ variables: { id },
+ });
+
+ if (isNull(data)) throw Error('Assert: data is null');
+ showNotification({
+ title: t.title,
+ message: t.success(data.visit!.name),
+ type: 'success',
+ ...tid(ids.notifications.delete.success),
+ });
+ } catch (err) {
+ debug.error(err);
+ showNotification({
+ title: t.title,
+ message: t.failed(date),
+ type: 'failure',
+ ...tid(ids.notifications.delete.failure),
+ });
+ }
+ };
+
+ const handleDeleteVisit = () => {
+ openDeleteVisitModal({ name: date, onDeleteVisit });
+ };
+
+ return (
+
+ );
+};
diff --git a/app/Dashboard/Households/HouseholdDetail/_components/HouseholderVisits/VisitActionButton/index.ts b/app/Dashboard/Households/HouseholdDetail/_components/HouseholderVisits/VisitActionButton/index.ts
new file mode 100644
index 00000000..0c37eca3
--- /dev/null
+++ b/app/Dashboard/Households/HouseholdDetail/_components/HouseholderVisits/VisitActionButton/index.ts
@@ -0,0 +1 @@
+export * from './VisitActionButton';
diff --git a/app/Dashboard/Households/HouseholdDetail/_components/HouseholderVisits/VisitDetail/DocumentGrid.tsx b/app/Dashboard/Households/HouseholdDetail/_components/HouseholderVisits/VisitDetail/DocumentGrid.tsx
new file mode 100644
index 00000000..21aec9f2
--- /dev/null
+++ b/app/Dashboard/Households/HouseholdDetail/_components/HouseholderVisits/VisitDetail/DocumentGrid.tsx
@@ -0,0 +1,28 @@
+import type { Document } from '@camp/domain';
+import { SimpleGrid } from '@mantine/core';
+
+import { VisitDetailDocumentItem } from './VisitDetailDocumentItem';
+
+export const DocumentGrid = ({
+ documents,
+ selectedDocument,
+ onSelect,
+ onDelete,
+}: {
+ documents: Document[];
+ selectedDocument?: Document;
+ onSelect: (d: Document) => void;
+ onDelete: (d: Document) => Promise;
+}) => (
+
+ {documents.map(doc => (
+ onSelect(d)}
+ onDelete={onDelete}
+ />
+ ))}
+
+);
diff --git a/app/Dashboard/Households/HouseholdDetail/_components/HouseholderVisits/VisitDetail/ImagePreview.tsx b/app/Dashboard/Households/HouseholdDetail/_components/HouseholderVisits/VisitDetail/ImagePreview.tsx
new file mode 100644
index 00000000..bbac1c6a
--- /dev/null
+++ b/app/Dashboard/Households/HouseholdDetail/_components/HouseholderVisits/VisitDetail/ImagePreview.tsx
@@ -0,0 +1,24 @@
+import type { Document } from '@camp/domain';
+import { Image } from '@mantine/core';
+
+export const ImagePreview = ({ document }: { document: Document }) => {
+ return (
+
+ );
+};
diff --git a/app/Dashboard/Households/HouseholdDetail/_components/HouseholderVisits/VisitDetail/VisitDetail.tsx b/app/Dashboard/Households/HouseholdDetail/_components/HouseholderVisits/VisitDetail/VisitDetail.tsx
new file mode 100644
index 00000000..0c9f866f
--- /dev/null
+++ b/app/Dashboard/Households/HouseholdDetail/_components/HouseholderVisits/VisitDetail/VisitDetail.tsx
@@ -0,0 +1,139 @@
+import {
+ useDeleteDocumentMutation,
+ useVisitDetailQuery,
+} from '@camp/data-layer';
+import { FullPageLoader, showNotification } from '@camp/design';
+import type { Document } from '@camp/domain';
+import { fileStorageClient } from '@camp/file-storage-client';
+import { DownloadIcon, PdfFileIcon, TrashIcon, VideoIcon } from '@camp/icons';
+import { errorMessages, messages } from '@camp/messages';
+import { getFileName, getFileType } from '@camp/router';
+import { isEmpty } from '@fullstacksjs/toolbox';
+import { Box, Button, Center, Group, Stack } from '@mantine/core';
+import download from 'downloadjs';
+import { useMemo, useState } from 'react';
+
+import { DocumentGrid } from './DocumentGrid';
+import { ImagePreview } from './ImagePreview';
+
+export interface VisitDetailProps {
+ id: string;
+}
+
+export const VisitDetail = ({ id }: VisitDetailProps) => {
+ const t = messages.householder.visits.detail;
+
+ const [selectedDocument, setSelectedDocument] = useState();
+ const [deleteOneDocument, { loading: isDeleting }] =
+ useDeleteDocumentMutation();
+
+ const { data, loading, error } = useVisitDetailQuery({
+ variables: { id },
+ onCompleted: () => {
+ setSelectedDocument(data!.visit!.documents[0]);
+ },
+ });
+
+ const documents = useMemo(
+ () => data?.visit?.documents ?? [],
+ [data?.visit?.documents],
+ );
+
+ if (error) {
+ showNotification({
+ type: 'failure',
+ title: t.title,
+ message: errorMessages.UNKNOWN_ERROR,
+ });
+ return null;
+ }
+
+ if (loading)
+ return (
+
+ ;
+
+ );
+
+ const deleteDocument = async (d: Document): Promise => {
+ const deleted = await deleteOneDocument({
+ variables: { id: d.id },
+ });
+
+ setSelectedDocument(
+ documents.filter(
+ document => document.id !== deleted.data.document?.id,
+ )[0],
+ );
+ };
+
+ const downloadSelectedFile = async () => {
+ const url = selectedDocument!.url;
+ const file = await fileStorageClient.get(url);
+ download(file, getFileName(url));
+ };
+
+ const fileType = selectedDocument?.url
+ ? getFileType(selectedDocument.url)
+ : null;
+
+ return (
+
+ {isEmpty(documents) ? null : (
+ <>
+
+ ({
+ flexGrow: 1,
+ borderLeft: `1px solid ${theme.colors.bg[5]}`,
+ })}
+ >
+ {fileType === 'pdf' ? (
+
+
+
+ ) : fileType === 'video' ? (
+
+
+
+ ) : selectedDocument != null ? (
+
+ ) : null}
+
+ }
+ >
+ {messages.actions.download}
+
+ }
+ onClick={() => deleteDocument(selectedDocument!)}
+ >
+ {messages.actions.delete}
+
+
+
+ >
+ )}
+
+ );
+};
diff --git a/app/Dashboard/Households/HouseholdDetail/_components/HouseholderVisits/VisitDetail/VisitDetailDocumentItem.tsx b/app/Dashboard/Households/HouseholdDetail/_components/HouseholderVisits/VisitDetail/VisitDetailDocumentItem.tsx
new file mode 100644
index 00000000..b6045fd2
--- /dev/null
+++ b/app/Dashboard/Households/HouseholdDetail/_components/HouseholderVisits/VisitDetail/VisitDetailDocumentItem.tsx
@@ -0,0 +1,138 @@
+import { debug, DebugScopes } from '@camp/debug';
+import type { Document } from '@camp/domain';
+import { PdfFileIcon, VerticalMenuIcon, VideoIcon } from '@camp/icons';
+import { messages } from '@camp/messages';
+import { getFileName, getFileType } from '@camp/router';
+import {
+ ActionIcon,
+ Card,
+ Center,
+ clsx,
+ createStyles,
+ Group,
+ Image,
+ Loader,
+ Menu,
+ Text,
+} from '@mantine/core';
+import { useState } from 'react';
+
+const useStyles = createStyles(theme => ({
+ selected: {
+ backgroundColor: theme.colors.primary[0],
+ border: `1px solid ${theme.colors.primary[5]}`,
+ boxShadow: '0px 0px 0px 5px #4C6EF533',
+ },
+ default: {
+ cursor: 'pointer',
+ userSelect: 'none',
+ transition: 'all 300ms ease-in-out',
+ },
+ pdf: {
+ backgroundColor: theme.colors.fg[2],
+ },
+}));
+
+interface Props {
+ isSelected: boolean;
+ document: Document;
+ onSelect: (doc: Document) => void;
+ onDelete: (doc: Document) => Promise;
+}
+
+export const VisitDetailDocumentItem = ({
+ isSelected,
+ document,
+ onDelete,
+ onSelect,
+}: Props) => {
+ const fileType = getFileType(document.url);
+
+ const { classes } = useStyles();
+ const [loading, setLoading] = useState(false);
+ return (
+ {
+ onSelect(document);
+ }}
+ padding={15}
+ withBorder={!isSelected || loading}
+ radius="md"
+ key={document.id}
+ >
+
+ {loading ? (
+
+ ) : (
+
+
+
+ {getFileName(document.url)}
+
+
+ )}
+
+
+ {loading ? (
+
+
+
+ ) : fileType === 'image' ? (
+
+ ) : fileType === 'pdf' ? (
+
+
+
+ ) : (
+
+
+
+ )}
+
+
+ );
+};
diff --git a/app/Dashboard/Households/HouseholdDetail/_components/HouseholderVisits/VisitDetail/VisitDetailHeader.tsx b/app/Dashboard/Households/HouseholdDetail/_components/HouseholderVisits/VisitDetail/VisitDetailHeader.tsx
new file mode 100644
index 00000000..9a95f36a
--- /dev/null
+++ b/app/Dashboard/Households/HouseholdDetail/_components/HouseholderVisits/VisitDetail/VisitDetailHeader.tsx
@@ -0,0 +1,59 @@
+import { useVisitDetailQuery } from '@camp/data-layer';
+import {
+ formatToPersian,
+ FullPageLoader,
+ showNotification,
+} from '@camp/design';
+import { CalendarIcon, FilePlusIcon } from '@camp/icons';
+import { errorMessages, messages } from '@camp/messages';
+import { Group, Stack, Text } from '@mantine/core';
+
+interface HeaderFieldProps {
+ label: string;
+ children: React.ReactNode;
+ icon: React.ReactNode;
+}
+
+export const HeaderField = ({ label, children, icon }: HeaderFieldProps) => {
+ return (
+
+ {icon}
+ {label}:
+ {children}
+
+ );
+};
+
+interface Props {
+ id: string;
+}
+
+export const VisitDetailHeader = ({ id }: Props) => {
+ const { data, loading, error } = useVisitDetailQuery({ variables: { id } });
+ const t = messages.householder.visits.detail;
+
+ if (error) {
+ showNotification({
+ type: 'failure',
+ title: t.title,
+ message: errorMessages.UNKNOWN_ERROR,
+ });
+ return null;
+ }
+
+ if (loading) return ;
+ const visit = data!.visit;
+ return (
+
+ {visit!.name}
+
+ } label="تاریخ">
+ {formatToPersian(visit!.date)}
+
+ } label="توضیحات بازدید">
+ {visit!.description}
+
+
+
+ );
+};
diff --git a/app/Dashboard/Households/HouseholdDetail/_components/HouseholderVisits/VisitDetail/VisitDetailModal.ids.ts b/app/Dashboard/Households/HouseholdDetail/_components/HouseholderVisits/VisitDetail/VisitDetailModal.ids.ts
new file mode 100644
index 00000000..ce999d99
--- /dev/null
+++ b/app/Dashboard/Households/HouseholdDetail/_components/HouseholderVisits/VisitDetail/VisitDetailModal.ids.ts
@@ -0,0 +1 @@
+export const visitDetailModalId = 'visit-detail-modal';
diff --git a/app/Dashboard/Households/HouseholdDetail/_components/HouseholderVisits/VisitDetail/VisitDetailModal.tsx b/app/Dashboard/Households/HouseholdDetail/_components/HouseholderVisits/VisitDetail/VisitDetailModal.tsx
new file mode 100644
index 00000000..e010e86d
--- /dev/null
+++ b/app/Dashboard/Households/HouseholdDetail/_components/HouseholderVisits/VisitDetail/VisitDetailModal.tsx
@@ -0,0 +1,40 @@
+import { tid } from '@camp/test';
+import { openModal } from '@mantine/modals';
+
+import type { VisitDetailProps } from './VisitDetail';
+import { VisitDetail } from './VisitDetail';
+import { VisitDetailHeader } from './VisitDetailHeader';
+import { visitDetailModalId as modalId } from './VisitDetailModal.ids';
+
+export const VisitDetailModal = (props: VisitDetailProps) => (
+
+);
+
+type Props = VisitDetailProps;
+
+export const openVisitDetailModal = (props: Props) =>
+ openModal({
+ modalId,
+ children: ,
+ styles: t => ({
+ content: {
+ display: 'flex',
+ flexDirection: 'column',
+ },
+ header: {
+ borderBottom: `1px solid ${t.colors.bg[5]}`,
+ alignItems: 'flex-start',
+ },
+ body: {
+ flexGrow: 1,
+ padding: 0,
+ display: 'flex',
+ },
+ }),
+ title: ,
+
+ padding: '40px',
+ fullScreen: true,
+ centered: true,
+ ...tid(modalId),
+ });
diff --git a/app/Dashboard/Households/HouseholdDetail/_components/HouseholderVisits/VisitDetail/index.ts b/app/Dashboard/Households/HouseholdDetail/_components/HouseholderVisits/VisitDetail/index.ts
new file mode 100644
index 00000000..89a2948d
--- /dev/null
+++ b/app/Dashboard/Households/HouseholdDetail/_components/HouseholderVisits/VisitDetail/index.ts
@@ -0,0 +1,2 @@
+export * from './VisitDetail';
+export * from './VisitDetailModal';
diff --git a/app/Dashboard/Households/HouseholdDetail/_components/HouseholderVisits/VisitsTable.tsx b/app/Dashboard/Households/HouseholdDetail/_components/HouseholderVisits/VisitsTable.tsx
new file mode 100644
index 00000000..9b5f23ba
--- /dev/null
+++ b/app/Dashboard/Households/HouseholdDetail/_components/HouseholderVisits/VisitsTable.tsx
@@ -0,0 +1,27 @@
+import { DataTable, Table } from '@camp/design';
+import type { VisitKeys, VisitListItem } from '@camp/domain';
+import type { Table as TableType } from '@tanstack/react-table';
+
+import * as ids from './HouseholderVisits.ids';
+import { openVisitDetailModal } from './VisitDetail';
+
+interface Props {
+ loading: boolean;
+ table: TableType;
+}
+
+export const VisitsTable = ({ loading, table }: Props) => {
+ // FIXME: should open full page modal with the specific id
+
+ return (
+
+ id={ids.HouseholderVisitsTableId}
+ table={table}
+ loading={loading}
+ fallback={}
+ onRowClick={({ id }: VisitKeys & VisitListItem) => {
+ openVisitDetailModal({ id });
+ }}
+ />
+ );
+};
diff --git a/app/Dashboard/Households/HouseholdDetail/_components/HouseholderVisits/VisitsTableRow.ids.ts b/app/Dashboard/Households/HouseholdDetail/_components/HouseholderVisits/VisitsTableRow.ids.ts
new file mode 100644
index 00000000..b90dddae
--- /dev/null
+++ b/app/Dashboard/Households/HouseholdDetail/_components/HouseholderVisits/VisitsTableRow.ids.ts
@@ -0,0 +1,10 @@
+export const visitsActionIds = {
+ actionButton: 'visit-table-menu-button',
+ actionMenu: 'visit-table-menu',
+ notifications: {
+ delete: {
+ success: 'delete-visit-success-notification',
+ failure: 'delete-visit-failure-notification',
+ },
+ },
+};
diff --git a/app/Dashboard/Households/HouseholdDetail/_components/HouseholderVisits/index.ts b/app/Dashboard/Households/HouseholdDetail/_components/HouseholderVisits/index.ts
new file mode 100644
index 00000000..795240fc
--- /dev/null
+++ b/app/Dashboard/Households/HouseholdDetail/_components/HouseholderVisits/index.ts
@@ -0,0 +1 @@
+export * from './HouseholderVisits';
diff --git a/app/Dashboard/Projects/ProjectDetail/_components/CreateProjectDocument/CreateProjectDocumentForm/CreateProjectDocumentForm.tsx b/app/Dashboard/Projects/ProjectDetail/_components/CreateProjectDocument/CreateProjectDocumentForm/CreateProjectDocumentForm.tsx
index 60e6b5f8..18283070 100644
--- a/app/Dashboard/Projects/ProjectDetail/_components/CreateProjectDocument/CreateProjectDocumentForm/CreateProjectDocumentForm.tsx
+++ b/app/Dashboard/Projects/ProjectDetail/_components/CreateProjectDocument/CreateProjectDocumentForm/CreateProjectDocumentForm.tsx
@@ -4,12 +4,13 @@ import {
ControlledFileUpload,
showNotification,
} from '@camp/design';
+import type { Document } from '@camp/domain';
import {
createResolver,
documentFileValidator,
documentSchema,
} from '@camp/domain';
-import { fileStorageApi } from '@camp/file-storage-api';
+import { fileStorageClient } from '@camp/file-storage-client';
import { messages } from '@camp/messages';
import { tid } from '@camp/test';
import { Button, createStyles, Group, Stack, TextInput } from '@mantine/core';
@@ -18,9 +19,9 @@ import { useForm } from 'react-hook-form';
import { createProjectDocumentFormIds as ids } from './CreateProjectDocumentForm.ids';
interface FormSchema {
- date: Date;
+ date?: Date;
description: string;
- documents: File[];
+ documents: Document[];
}
interface Props {
@@ -80,8 +81,8 @@ export const CreateProjectDocumentForm = ({ dismiss }: Props) => {
required
label={t.documentsInput.label}
helper={t.documentsInput.maxSize}
- upload={fileStorageApi.upload}
- unUpload={fileStorageApi.unUpload}
+ upload={fileStorageClient.upload}
+ unUpload={fileStorageClient.unUpload}
filter={(files): File[] => {
const res = files.map(f => {
const parsed = documentFileValidator.safeParse(f);
diff --git a/app/messages.ts b/app/messages.ts
index 1623882d..7d52864a 100644
--- a/app/messages.ts
+++ b/app/messages.ts
@@ -18,6 +18,15 @@ export const messages = {
unsupportedType: 'فایل انتخاب شده پشتیبانی نمیشود',
maxSizeExceeded: 'حداکثر سایز فایل انتخابی ۲۰ مگابایت هست.',
},
+ visits: {
+ delete: {
+ title: 'حذف بازدید',
+ success: (name: string) => `بازدید “${name}” با موفقیت حذف شد.`,
+ failed: (name: string) =>
+ `مشکلی در مرحله حذف بازدید "${name}" به وجود آمده است. لطفا دوباره تلاش کنید.`,
+ cantDeleteLst: 'اسناد نمیتواند خالی باشند',
+ },
+ },
household: {
delete: {
title: 'حذف خانوار',
@@ -92,6 +101,7 @@ export const messages = {
},
companyName: 'نامی مناسب برای خیریه',
actions: {
+ download: 'دانلود',
dismiss: 'انصراف',
open: 'باز کردن',
delete: 'حذف',
@@ -222,6 +232,49 @@ export const messages = {
Unknown: 'نامشخص',
},
householder: {
+ visits: {
+ delete: {
+ modal: {
+ title: 'حذف',
+ children: (name: string) =>
+ `آیا از حذف بازدید "${name}" مطمئن هستید؟`,
+ cancel: 'انصراف',
+ confirm: 'حذف',
+ },
+ },
+ table: {
+ columns: {
+ order: 'ردیف',
+ documents: 'اسناد',
+ date: 'تاریخ',
+ description: 'توضیحات',
+ },
+ },
+ form: {
+ nameInput: {
+ placeholder: 'برای مثال: مرادی',
+ label: 'نام بازدید کننده',
+ },
+ dateInput: {
+ label: 'تاریخ',
+ placeholder: '۱۴۰۰/۰۱/۰۱',
+ },
+ descriptionInput: {
+ label: 'توضیحات',
+ placeholder: 'توضیحات',
+ },
+ documentsInput: {
+ label: 'اسناد',
+ maxSize: 'فایل باید کمتر از ۲۰ مگابایت باشد',
+ },
+ submitBtn: 'ایجاد سند',
+ },
+ detail: {
+ title: 'اطلاعات بازدید',
+ },
+ title: 'بازدیدها',
+ addVisit: 'افزودن بازدید',
+ },
detail: {
title: 'اطلاعات هویت',
},
@@ -341,6 +394,7 @@ export const messages = {
edit: 'ویرایش',
complete: 'تکمیل',
tabs: {
+ visitsTitle: 'بازدیدها',
householderTitle: 'سرپرست',
membersTitle: 'اعضا',
},
diff --git a/configs/cspell/charity.en.txt b/configs/cspell/charity.en.txt
index 04748928..b37b43ae 100644
--- a/configs/cspell/charity.en.txt
+++ b/configs/cspell/charity.en.txt
@@ -52,3 +52,6 @@ indexify
pkey
alian
Prray
+clsx
+downloadjs
+matroska
\ No newline at end of file
diff --git a/libs/data-layer/ApiOperations.ts b/libs/data-layer/ApiOperations.ts
index e5e68d3b..af16e8a5 100644
--- a/libs/data-layer/ApiOperations.ts
+++ b/libs/data-layer/ApiOperations.ts
@@ -3,6 +3,10 @@
/* cspell:disable */
import type * as SchemaTypes from '@camp/domain';
+export type ApiDocumentKeysFragment = { __typename?: 'document', id: string, storage_id: string };
+
+export type ApiDocumentDetailFragment = { __typename?: 'document', url: string };
+
export type ApiHouseholdKeysFragment = { __typename?: 'household', id: string };
export type ApiHouseholdDetailFragment = { __typename?: 'household', name: string, status: SchemaTypes.HouseholdStatusEnum, severity: SchemaTypes.HouseholdSeverityEnum, code?: string | null, created_at: string, updated_at: string, members_count?: number | null };
@@ -23,6 +27,12 @@ export type ApiProjectDetailFragment = { __typename?: 'project', name: string, d
export type ApiProjectListItemFragment = { __typename?: 'project', name: string, description?: string | null, start_date?: string | null, due_date?: string | null, status: SchemaTypes.ProjectStatusEnum, created_at: string, updated_at: string };
+export type ApiVisitKeysFragment = { __typename?: 'visit', id: string, household_id: string };
+
+export type ApiVisitDetailFragment = { __typename?: 'visit', name: string, visitor?: string | null, date: string, id: string, description?: string | null, status: SchemaTypes.VisitStatusEnum, documents: Array<{ __typename?: 'document', url: string, id: string, storage_id: string }> };
+
+export type ApiVisitListItemFragment = { __typename?: 'visit', date: string, description?: string | null, documents: Array<{ __typename?: 'document', url: string, id: string, storage_id: string }> };
+
export type ApiCompleteHouseholdMutationVariables = SchemaTypes.Exact<{
id: SchemaTypes.Scalars['uuid']['input'];
}>;
@@ -46,6 +56,22 @@ export type ApiCreateProjectMutationVariables = SchemaTypes.Exact<{
export type ApiCreateProjectMutation = { __typename?: 'mutation_root', insert_project_one?: { __typename?: 'project', id: string, name: string, description?: string | null, start_date?: string | null, due_date?: string | null, status: SchemaTypes.ProjectStatusEnum, created_at: string, updated_at: string } | null };
+export type ApiCreateVisitMutationVariables = SchemaTypes.Exact<{
+ input: SchemaTypes.ApiVisitInsertInput;
+}>;
+
+
+export type ApiCreateVisitMutation = { __typename?: 'mutation_root', insert_visit_one?: { __typename?: 'visit', id: string, household_id: string, date: string, description?: string | null, documents: Array<{ __typename?: 'document', url: string, id: string, storage_id: string }> } | null };
+
+export type ApiNewVisitFragment = { __typename?: 'visit', id: string, household_id: string, date: string, description?: string | null, documents: Array<{ __typename?: 'document', url: string, id: string, storage_id: string }> };
+
+export type ApiDeleteDocumentMutationVariables = SchemaTypes.Exact<{
+ id: SchemaTypes.Scalars['uuid']['input'];
+}>;
+
+
+export type ApiDeleteDocumentMutation = { __typename?: 'mutation_root', delete_document_by_pk?: { __typename?: 'document', id: string, storage_id: string, url: string } | null };
+
export type ApiDeleteHouseholdMutationVariables = SchemaTypes.Exact<{
id: SchemaTypes.Scalars['uuid']['input'];
}>;
@@ -60,6 +86,13 @@ export type ApiDeleteMemberMutationVariables = SchemaTypes.Exact<{
export type ApiDeleteMemberMutation = { __typename?: 'mutation_root', delete_member_by_pk?: { __typename?: 'member', name: string, id: string, household_id: string } | null };
+export type ApiDeleteVisitMutationVariables = SchemaTypes.Exact<{
+ id: SchemaTypes.Scalars['uuid']['input'];
+}>;
+
+
+export type ApiDeleteVisitMutation = { __typename?: 'mutation_root', delete_visit_by_pk?: { __typename?: 'visit', name: string, id: string, household_id: string, documents: Array<{ __typename?: 'document', url: string, id: string, storage_id: string }> } | null };
+
export type ApiEditHouseholdMutationVariables = SchemaTypes.Exact<{
id: SchemaTypes.Scalars['uuid']['input'];
update: SchemaTypes.ApiHouseholdSetInput;
@@ -105,6 +138,15 @@ export type ApiHouseholderQueryVariables = SchemaTypes.Exact<{
export type ApiHouseholderQuery = { __typename?: 'query_root', householder_by_pk?: { __typename?: 'householder', id: string, name: string, father_name?: string | null, surname?: string | null, nationality?: SchemaTypes.NationalityEnum | null, religion?: SchemaTypes.ReligionEnum | null, city?: SchemaTypes.CityEnum | null, gender?: SchemaTypes.GenderEnum | null, status?: string | null, national_id?: string | null, dob?: string | null } | null };
+export type ApiVisitsQueryVariables = SchemaTypes.Exact<{
+ order_by?: SchemaTypes.InputMaybe | SchemaTypes.ApiVisitOrderBy>;
+ limit?: SchemaTypes.InputMaybe;
+ offset?: SchemaTypes.InputMaybe;
+}>;
+
+
+export type ApiVisitsQuery = { __typename?: 'query_root', visit: Array<{ __typename?: 'visit', id: string, household_id: string, date: string, description?: string | null, documents: Array<{ __typename?: 'document', url: string, id: string, storage_id: string }> }>, visit_aggregate: { __typename?: 'visit_aggregate', aggregate?: { __typename?: 'visit_aggregate_fields', count: number } | null } };
+
export type ApiMemberListQueryVariables = SchemaTypes.Exact<{
household_id: SchemaTypes.Scalars['uuid']['input'];
}>;
@@ -126,3 +168,10 @@ export type ApiProjectQueryVariables = SchemaTypes.Exact<{
export type ApiProjectQuery = { __typename?: 'query_root', project_by_pk?: { __typename?: 'project', id: string, name: string, description?: string | null, status: SchemaTypes.ProjectStatusEnum, start_date?: string | null, due_date?: string | null, created_at: string, updated_at: string, households: Array<{ __typename?: 'household_project', household: { __typename?: 'household', id: string, name: string, status: SchemaTypes.HouseholdStatusEnum, severity: SchemaTypes.HouseholdSeverityEnum, code?: string | null, created_at: string, updated_at: string, members_count?: number | null } }> } | null };
+
+export type ApiVisitDetailQueryVariables = SchemaTypes.Exact<{
+ id: SchemaTypes.Scalars['uuid']['input'];
+}>;
+
+
+export type ApiVisitDetailQuery = { __typename?: 'query_root', visit_by_pk?: { __typename?: 'visit', id: string, household_id: string, name: string, visitor?: string | null, date: string, description?: string | null, status: SchemaTypes.VisitStatusEnum, documents: Array<{ __typename?: 'document', url: string, id: string, storage_id: string }> } | null };
diff --git a/libs/data-layer/operations/fragments/document.fragment.ts b/libs/data-layer/operations/fragments/document.fragment.ts
new file mode 100644
index 00000000..6d3cd5f6
--- /dev/null
+++ b/libs/data-layer/operations/fragments/document.fragment.ts
@@ -0,0 +1,14 @@
+import { gql } from '@apollo/client';
+
+export const DocumentKeysFragment = gql`
+ fragment DocumentKeys on document {
+ id
+ storage_id
+ }
+`;
+
+export const DocumentDetailFragment = gql`
+ fragment DocumentDetail on document {
+ url
+ }
+`;
diff --git a/libs/data-layer/operations/fragments/document.mapper.ts b/libs/data-layer/operations/fragments/document.mapper.ts
new file mode 100644
index 00000000..7f1b3335
--- /dev/null
+++ b/libs/data-layer/operations/fragments/document.mapper.ts
@@ -0,0 +1,22 @@
+import type {
+ ApiDocumentDetailFragment,
+ ApiDocumentKeysFragment,
+} from '@camp/data-layer';
+import type { DocumentDetail, DocumentKeys } from '@camp/domain';
+
+export const getDocumentKeys = (
+ data: ApiDocumentKeysFragment,
+): DocumentKeys => {
+ return {
+ storageId: data.storage_id,
+ id: data.id,
+ };
+};
+
+export const getDocumentDetail = (
+ data: ApiDocumentDetailFragment,
+): DocumentDetail => {
+ return {
+ url: data.url,
+ };
+};
diff --git a/libs/data-layer/operations/fragments/index.ts b/libs/data-layer/operations/fragments/index.ts
index ea9629d7..477746d7 100644
--- a/libs/data-layer/operations/fragments/index.ts
+++ b/libs/data-layer/operations/fragments/index.ts
@@ -1,3 +1,5 @@
+export * from './document.fragment';
+export * from './document.mapper';
export * from './household.fragments';
export * from './household.mapper';
export * from './householder.fragments';
@@ -7,3 +9,5 @@ export * from './member.mapper';
export * from './project.fragment';
export * from './project.mapper';
export * from './scalar.mapper';
+export * from './visit.fragment';
+export * from './visit.mapper';
diff --git a/libs/data-layer/operations/fragments/visit.fragment.ts b/libs/data-layer/operations/fragments/visit.fragment.ts
new file mode 100644
index 00000000..6d5431b8
--- /dev/null
+++ b/libs/data-layer/operations/fragments/visit.fragment.ts
@@ -0,0 +1,36 @@
+import { gql } from '@apollo/client';
+
+export const VisitKeysFragment = gql`
+ fragment VisitKeys on visit {
+ id
+ household_id
+ }
+`;
+
+export const VisitDetailFragment = gql`
+ fragment VisitDetail on visit {
+ name
+ visitor
+ date
+ id
+ description
+ status
+ documents {
+ url
+ id
+ storage_id
+ }
+ }
+`;
+
+export const VisitListItemFragment = gql`
+ fragment VisitListItem on visit {
+ date
+ description
+ documents {
+ url
+ id
+ storage_id
+ }
+ }
+`;
diff --git a/libs/data-layer/operations/fragments/visit.mapper.ts b/libs/data-layer/operations/fragments/visit.mapper.ts
new file mode 100644
index 00000000..2d4316a3
--- /dev/null
+++ b/libs/data-layer/operations/fragments/visit.mapper.ts
@@ -0,0 +1,43 @@
+import type {
+ ApiVisitDetailFragment,
+ ApiVisitKeysFragment,
+ ApiVisitListItemFragment,
+} from '@camp/data-layer';
+import type { VisitDetail, VisitKeys, VisitListItem } from '@camp/domain';
+
+import { toDate } from './scalar.mapper';
+
+export const getVisitKeys = (data: ApiVisitKeysFragment): VisitKeys => {
+ return {
+ id: data.id,
+ householdId: data.household_id,
+ };
+};
+
+export const getVisitDetail = (data: ApiVisitDetailFragment): VisitDetail => {
+ return {
+ name: data.name,
+ status: data.status,
+ date: toDate(data.date)!,
+ description: data.description ?? undefined,
+ documents: data.documents.map(d => ({
+ storageId: d.id,
+ url: d.url,
+ id: d.id,
+ })),
+ };
+};
+
+export const getVisitListItem = (
+ data: ApiVisitListItemFragment,
+): VisitListItem => {
+ return {
+ date: toDate(data.date)!,
+ description: data.description ?? undefined,
+ documents: data.documents.map(d => ({
+ storageId: d.id,
+ url: d.url,
+ id: d.id,
+ })),
+ };
+};
diff --git a/libs/data-layer/operations/mutations/index.ts b/libs/data-layer/operations/mutations/index.ts
index bfe95c33..44fcca2a 100644
--- a/libs/data-layer/operations/mutations/index.ts
+++ b/libs/data-layer/operations/mutations/index.ts
@@ -1,8 +1,11 @@
export * from './useCompleteHousehold';
export * from './useCreateHouseholdMutation';
export * from './useCreateProjectMutation';
+export * from './useCreateVisitMutation';
+export * from './useDeleteDocumentMutation';
export * from './useDeleteHouseholdMutation';
export * from './useDeleteMemberMutation';
+export * from './useDeleteVisitMutation';
export * from './useEditHouseholdMutation';
export * from './useUpsertHouseholderMutation';
export * from './useUpsertMemberMutation';
diff --git a/libs/data-layer/operations/mutations/useCreateVisitMutation.ts b/libs/data-layer/operations/mutations/useCreateVisitMutation.ts
new file mode 100644
index 00000000..1438dafb
--- /dev/null
+++ b/libs/data-layer/operations/mutations/useCreateVisitMutation.ts
@@ -0,0 +1,114 @@
+import { gql } from '@apollo/client';
+import { mergeDeep } from '@apollo/client/utilities';
+import type { MutationOptions } from '@camp/api-client';
+import { useMutation } from '@camp/api-client';
+import type {
+ ApiCreateVisitMutation,
+ ApiCreateVisitMutationVariables,
+} from '@camp/data-layer';
+import type { StorageFile, VisitKeys, VisitListItem } from '@camp/domain';
+
+import {
+ getVisitKeys,
+ getVisitListItem,
+ VisitKeysFragment,
+ VisitListItemFragment,
+} from '../fragments';
+
+const Document = gql`
+ mutation CreateVisit($input: visit_insert_input!) {
+ insert_visit_one(object: $input) {
+ ...VisitKeys
+ ...VisitListItem
+ }
+ }
+ ${VisitKeysFragment}
+ ${VisitListItemFragment}
+`;
+
+export interface CreateVisit {
+ visit: (VisitKeys & VisitListItem) | undefined;
+}
+
+const toClient = (data: ApiCreateVisitMutation | null): CreateVisit => {
+ const visit = data?.insert_visit_one;
+
+ return {
+ visit: visit
+ ? {
+ ...getVisitKeys(visit),
+ ...getVisitListItem(visit),
+ }
+ : undefined,
+ };
+};
+
+interface Variables {
+ name: string;
+ visitor?: string;
+ householdId: string;
+ date: Date;
+ description?: string;
+ documents: StorageFile[];
+}
+
+const toApiVariables = (
+ variables: Variables,
+): ApiCreateVisitMutationVariables => ({
+ input: {
+ name: variables.name,
+ description: variables.description,
+ visitor: variables.visitor,
+ household_id: variables.householdId,
+ documents: {
+ data: variables.documents.map(d => ({ url: d.url, storage_id: d.id })),
+ },
+ date: variables.date.toISOString(),
+ },
+});
+
+export function useCreateVisitMutation(
+ options?: MutationOptions,
+) {
+ return useMutation(Document, {
+ ...options,
+ toClient,
+ toApiVariables,
+
+ update(cache, result, opts) {
+ const newVisit = result.data?.insert_visit_one;
+ if (!newVisit) return;
+
+ cache.modify({
+ fields: {
+ visit(existingVisitsRefs = []) {
+ const newVisitRef = cache.writeFragment({
+ data: newVisit,
+ fragment: gql`
+ fragment NewVisit on visit {
+ ...VisitKeys
+ ...VisitListItem
+ }
+ ${VisitKeysFragment}
+ ${VisitListItemFragment}
+ `,
+ fragmentName: 'NewVisit',
+ });
+ return [newVisitRef!, ...existingVisitsRefs];
+ },
+ visit_aggregate(existingAggregate) {
+ return mergeDeep(existingAggregate, {
+ aggregate: {
+ count: existingAggregate.aggregate?.count
+ ? existingAggregate.aggregate.count + 1
+ : undefined,
+ },
+ });
+ },
+ },
+ });
+
+ return options?.update?.(cache, result, opts);
+ },
+ });
+}
diff --git a/libs/data-layer/operations/mutations/useDeleteDocumentMutation.ts b/libs/data-layer/operations/mutations/useDeleteDocumentMutation.ts
new file mode 100644
index 00000000..92fde3e3
--- /dev/null
+++ b/libs/data-layer/operations/mutations/useDeleteDocumentMutation.ts
@@ -0,0 +1,88 @@
+import { gql } from '@apollo/client';
+import { mergeDeep } from '@apollo/client/utilities';
+import type { MutationOptions } from '@camp/api-client';
+import { useMutation } from '@camp/api-client';
+import type {
+ ApiDeleteDocumentMutation,
+ ApiDeleteDocumentMutationVariables,
+} from '@camp/data-layer';
+import type { Document as Doc, DocumentKeys } from '@camp/domain';
+import { isNull } from '@fullstacksjs/toolbox';
+
+import {
+ DocumentDetailFragment,
+ DocumentKeysFragment,
+ getDocumentDetail,
+ getDocumentKeys,
+} from '../fragments';
+
+const Document = gql`
+ mutation DeleteDocument($id: uuid!) {
+ delete_document_by_pk(id: $id) {
+ ...DocumentKeys
+ ...DocumentDetail
+ }
+ }
+ ${DocumentKeysFragment}
+ ${DocumentDetailFragment}
+`;
+
+export interface DeleteDocument {
+ document: (Doc & DocumentKeys) | undefined;
+}
+
+const toClient = (data: ApiDeleteDocumentMutation | null): DeleteDocument => {
+ const deleted = data?.delete_document_by_pk;
+
+ return {
+ document: !isNull(deleted)
+ ? {
+ ...getDocumentDetail(deleted),
+ ...getDocumentKeys(deleted),
+ }
+ : undefined,
+ };
+};
+
+interface Variables {
+ id: string;
+}
+
+const toApiVariables = (
+ variables: Variables,
+): ApiDeleteDocumentMutationVariables => ({
+ id: variables.id,
+});
+
+export const useDeleteDocumentMutation = (
+ options?: MutationOptions,
+) => {
+ return useMutation(Document, {
+ ...options,
+ toClient,
+ toApiVariables,
+ update(cache, { data }) {
+ const document = data?.delete_document_by_pk;
+ if (!document) return;
+
+ cache.evict({ id: cache.identify(document) });
+ cache.gc();
+
+ cache.evict({ fieldName: 'document' });
+
+ cache.modify({
+ fields: {
+ document_aggregate(existingAggregate) {
+ return mergeDeep(existingAggregate, {
+ aggregate: {
+ count: existingAggregate.aggregate?.count
+ ? existingAggregate.aggregate.count - 1
+ : undefined,
+ },
+ });
+ },
+ },
+ });
+ },
+ });
+};
diff --git a/libs/data-layer/operations/mutations/useDeleteVisitMutation.ts b/libs/data-layer/operations/mutations/useDeleteVisitMutation.ts
new file mode 100644
index 00000000..675bf3c1
--- /dev/null
+++ b/libs/data-layer/operations/mutations/useDeleteVisitMutation.ts
@@ -0,0 +1,96 @@
+import { gql } from '@apollo/client';
+import { mergeDeep } from '@apollo/client/utilities';
+import type { MutationOptions } from '@camp/api-client';
+import { useMutation } from '@camp/api-client';
+import type {
+ ApiDeleteVisitMutation,
+ ApiDeleteVisitMutationVariables,
+} from '@camp/data-layer';
+import { debug, DebugScopes } from '@camp/debug';
+import type { Visit, VisitKeys } from '@camp/domain';
+import { fileStorageClient } from '@camp/file-storage-client';
+import { isNull } from '@fullstacksjs/toolbox';
+import Prray from 'prray';
+
+import { getVisitKeys, VisitKeysFragment } from '../fragments';
+
+const Document = gql`
+ mutation DeleteVisit($id: uuid!) {
+ delete_visit_by_pk(id: $id) {
+ ...VisitKeys
+ name
+ documents {
+ url
+ id
+ storage_id
+ }
+ }
+ }
+ ${VisitKeysFragment}
+`;
+
+export interface DeleteVisit {
+ visit: (Pick & VisitKeys) | undefined;
+}
+
+const toClient = (data: ApiDeleteVisitMutation | null): DeleteVisit => {
+ const deleted = data?.delete_visit_by_pk;
+
+ return {
+ visit: !isNull(deleted)
+ ? {
+ ...getVisitKeys(deleted),
+ name: deleted.name,
+ }
+ : undefined,
+ };
+};
+
+interface Variables {
+ id: string;
+}
+
+const toApiVariables = (
+ variables: Variables,
+): ApiDeleteVisitMutationVariables => ({
+ id: variables.id,
+});
+
+export const useDeleteVisitMutation = (
+ options?: MutationOptions,
+) => {
+ return useMutation(Document, {
+ ...options,
+ toClient,
+ toApiVariables,
+ onCompleted: (data, ctx) => {
+ Prray.from(data?.delete_visit_by_pk?.documents ?? [])
+ .forEachAsync(d => fileStorageClient.unUpload(d.storage_id))
+ .catch(e => debug.error(DebugScopes.All, e))
+ .finally(() => options?.onCompleted?.(data, ctx));
+ },
+ update(cache, { data }) {
+ const visit = data?.delete_visit_by_pk;
+ if (!visit) return;
+
+ cache.evict({ id: cache.identify(visit) });
+ cache.gc();
+
+ cache.evict({ fieldName: 'visit' });
+
+ cache.modify({
+ fields: {
+ visit_aggregate(existingAggregate) {
+ return mergeDeep(existingAggregate, {
+ aggregate: {
+ count: existingAggregate.aggregate?.count
+ ? existingAggregate.aggregate.count - 1
+ : undefined,
+ },
+ });
+ },
+ },
+ });
+ },
+ });
+};
diff --git a/libs/data-layer/operations/queries/index.ts b/libs/data-layer/operations/queries/index.ts
index c5a01430..481a9e46 100644
--- a/libs/data-layer/operations/queries/index.ts
+++ b/libs/data-layer/operations/queries/index.ts
@@ -1,6 +1,8 @@
export * from './useHouseholderQuery';
+export * from './useHouseholderVisitsQuery';
export * from './useHouseholdListQuery';
export * from './useHouseholdQuery';
export * from './useMemberListQuery';
export * from './useProjectListQuery';
export * from './useProjectQuery';
+export * from './useVisitDetailQuery';
diff --git a/libs/data-layer/operations/queries/useHouseholderVisitsQuery.ts b/libs/data-layer/operations/queries/useHouseholderVisitsQuery.ts
new file mode 100644
index 00000000..bb7e6e03
--- /dev/null
+++ b/libs/data-layer/operations/queries/useHouseholderVisitsQuery.ts
@@ -0,0 +1,80 @@
+import { gql } from '@apollo/client';
+import type { QueryHookOptions } from '@camp/api-client';
+import { useQuery } from '@camp/api-client';
+import type { VisitKeys, VisitListItem } from '@camp/domain';
+import { ApiOrderBy } from '@camp/domain';
+import type { Nullable } from '@fullstacksjs/toolbox';
+import { isEmpty, isNotNull } from '@fullstacksjs/toolbox';
+import type { PaginationState, SortingState } from '@tanstack/react-table';
+
+import type {
+ ApiVisitsQuery,
+ ApiVisitsQueryVariables,
+} from '../../ApiOperations';
+import {
+ getVisitKeys,
+ getVisitListItem,
+ VisitKeysFragment,
+ VisitListItemFragment,
+} from '../fragments';
+
+export const VisitsDocument = gql`
+ query Visits($order_by: [visit_order_by!], $limit: Int, $offset: Int) {
+ visit(order_by: $order_by, limit: $limit, offset: $offset) {
+ ...VisitKeys
+ ...VisitListItem
+ }
+ visit_aggregate {
+ aggregate {
+ count
+ }
+ }
+ }
+ ${VisitKeysFragment}
+ ${VisitListItemFragment}
+`;
+
+export interface VisitsDto {
+ visits: (VisitKeys & VisitListItem)[];
+ totalCount: Nullable;
+}
+
+const toClient = (data: ApiVisitsQuery | null): VisitsDto => {
+ return {
+ visits:
+ data?.visit.filter(isNotNull).map(d => ({
+ ...getVisitKeys(d),
+ ...getVisitListItem(d),
+ })) ?? [],
+ totalCount: data?.visit_aggregate.aggregate?.count,
+ };
+};
+
+interface Variables {
+ orderBy: SortingState;
+ range: PaginationState;
+}
+
+const toApiVariables = (data: Variables): ApiVisitsQueryVariables => {
+ return {
+ order_by: isEmpty(Object.keys(data.orderBy))
+ ? { created_at: ApiOrderBy.Desc }
+ : data.orderBy.reduce((acc, item) => {
+ return {
+ ...acc,
+ [item.id]: item.desc ? ApiOrderBy.Desc : ApiOrderBy.Asc,
+ };
+ }, {}),
+ limit: data.range.pageSize,
+ offset: data.range.pageSize * data.range.pageIndex,
+ };
+};
+
+export const useHouseholderVisitsQuery = (
+ options: QueryHookOptions,
+) =>
+ useQuery(VisitsDocument, {
+ ...options,
+ mapper: toClient,
+ mapVariables: toApiVariables,
+ });
diff --git a/libs/data-layer/operations/queries/useVisitDetailQuery.ts b/libs/data-layer/operations/queries/useVisitDetailQuery.ts
new file mode 100644
index 00000000..65d6ef46
--- /dev/null
+++ b/libs/data-layer/operations/queries/useVisitDetailQuery.ts
@@ -0,0 +1,60 @@
+import { gql } from '@apollo/client';
+import type { QueryHookOptions } from '@camp/api-client';
+import { useQuery } from '@camp/api-client';
+import type {
+ ApiVisitDetailQuery,
+ ApiVisitDetailQueryVariables,
+} from '@camp/data-layer';
+import type { VisitDetail, VisitKeys } from '@camp/domain';
+import type { Nullable } from '@fullstacksjs/toolbox';
+
+import {
+ getVisitDetail,
+ getVisitKeys,
+ VisitDetailFragment,
+ VisitKeysFragment,
+} from '../fragments';
+
+export const VisitDocument = gql`
+ query VisitDetail($id: uuid!) {
+ visit_by_pk(id: $id) {
+ ...VisitKeys
+ ...VisitDetail
+ }
+ }
+ ${VisitKeysFragment}
+ ${VisitDetailFragment}
+`;
+
+export interface VisitDto {
+ visit: Nullable;
+}
+
+interface Variables {
+ id: string;
+}
+
+const toClient = (data: Nullable): VisitDto => {
+ return {
+ visit: data?.visit_by_pk
+ ? {
+ ...getVisitKeys(data.visit_by_pk),
+ ...getVisitDetail(data.visit_by_pk),
+ }
+ : undefined,
+ };
+};
+
+const toApiVariables = (data: Variables): ApiVisitDetailQueryVariables => {
+ return { id: data.id };
+};
+
+export const useVisitDetailQuery = (
+ options: QueryHookOptions,
+) =>
+ useQuery(VisitDocument, {
+ ...options,
+ skip: !options.variables?.id,
+ mapper: toClient,
+ mapVariables: toApiVariables,
+ });
diff --git a/libs/design/DataTable/DataTable.tsx b/libs/design/DataTable/DataTable.tsx
index 0803c711..f6471a67 100644
--- a/libs/design/DataTable/DataTable.tsx
+++ b/libs/design/DataTable/DataTable.tsx
@@ -34,6 +34,7 @@ export const DataTable = ({
{header.isPlaceholder
? null
diff --git a/libs/design/DateSummery/DateSummery.tsx b/libs/design/DateSummery/DateSummery.tsx
index 05b9bb3f..bbfc8db8 100644
--- a/libs/design/DateSummery/DateSummery.tsx
+++ b/libs/design/DateSummery/DateSummery.tsx
@@ -6,7 +6,7 @@ interface Props {
endDate?: Date;
}
-const formatToPersian = (d: Date) =>
+export const formatToPersian = (d: Date) =>
new Intl.DateTimeFormat('fa-IR', { dateStyle: 'short' }).format(d);
export const DateSummery = ({ startDate, endDate }: Props) => {
diff --git a/libs/design/FileUpload/ControlledFileUpload.tsx b/libs/design/FileUpload/ControlledFileUpload.tsx
index 0e57e82c..477a45b3 100644
--- a/libs/design/FileUpload/ControlledFileUpload.tsx
+++ b/libs/design/FileUpload/ControlledFileUpload.tsx
@@ -1,7 +1,8 @@
+import type { StorageFile } from '@camp/domain';
+import { useEffect, useState } from 'react';
import type { Control, FieldValues, Path, PathValue } from 'react-hook-form';
-import { Controller } from 'react-hook-form';
+import { useController } from 'react-hook-form';
-import type { RemoteDocument } from '../../domain';
import type { FileUploadProps } from './FileUpload';
import { FileUpload } from './FileUpload';
@@ -17,37 +18,28 @@ export const ControlledFileUpload = ({
defaultValue,
...fileUploadProps
}: Props): JSX.Element => {
+ const { field } = useController({ control, name });
+ const [files, setFiles] = useState([]);
+
+ useEffect(() => {
+ field.onChange({
+ target: {
+ value: files,
+ name: field.name,
+ },
+ });
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [files]);
+
return (
- (
- {
- if (Array.isArray(field.value))
- field.onChange({
- target: {
- value: (field.value as RemoteDocument[]).filter(
- doc => doc.id !== deletedDoc.id,
- ),
- name: field.name,
- },
- });
- }}
- onAdd={remote => {
- if (Array.isArray(field.value))
- field.onChange({
- target: {
- value: [...field.value, remote],
- name: field.name,
- },
- });
- }}
- {...fileUploadProps}
- {...field}
- />
- )}
+ {
+ setFiles(fs => fs.filter(doc => doc.id !== deletedDoc.id));
+ }}
+ onAdd={remote => {
+ setFiles(fs => [...fs, remote]);
+ }}
+ {...fileUploadProps}
/>
);
};
diff --git a/libs/design/FileUpload/File.tsx b/libs/design/FileUpload/File.tsx
index f5efb637..4d0e6292 100644
--- a/libs/design/FileUpload/File.tsx
+++ b/libs/design/FileUpload/File.tsx
@@ -1,10 +1,9 @@
+import type { FileState } from '@camp/domain';
+import { isFailed, isSuccess } from '@camp/domain';
import { TrashIcon } from '@camp/icons';
import { ActionIcon, Box, Group, Text } from '@mantine/core';
import { useState } from 'react';
-import type { FileState } from './FileState';
-import { isFailed, isSuccess } from './FileState';
-
interface Props {
file: FileState;
onRemove?: () => Promise | void;
@@ -43,7 +42,11 @@ export const File = ({ file, onRemove }: Props) => {
-
+
{file.file.name}
diff --git a/libs/design/FileUpload/FileList.tsx b/libs/design/FileUpload/FileList.tsx
index a4c9959f..c2d94331 100644
--- a/libs/design/FileUpload/FileList.tsx
+++ b/libs/design/FileUpload/FileList.tsx
@@ -1,8 +1,8 @@
+import type { FileState } from '@camp/domain';
import { isNullOrEmptyArray } from '@fullstacksjs/toolbox';
import { Box } from '@mantine/core';
import { File } from './File';
-import type { FileState } from './FileState';
interface Props {
files?: FileState[];
diff --git a/libs/design/FileUpload/FileUpload.stories.tsx b/libs/design/FileUpload/FileUpload.stories.tsx
index ab5679dc..74d4b1d4 100644
--- a/libs/design/FileUpload/FileUpload.stories.tsx
+++ b/libs/design/FileUpload/FileUpload.stories.tsx
@@ -1,6 +1,6 @@
+import type { FailedFile, SuccessFile } from '@camp/domain';
import type { Meta, StoryObj } from '@storybook/react';
-import type { FailedFile, SuccessFile } from './FileState';
import type { FileUploadProps } from './FileUpload';
import { FileUpload } from './FileUpload';
diff --git a/libs/design/FileUpload/FileUpload.tsx b/libs/design/FileUpload/FileUpload.tsx
index 1be17796..888de1e5 100644
--- a/libs/design/FileUpload/FileUpload.tsx
+++ b/libs/design/FileUpload/FileUpload.tsx
@@ -1,5 +1,11 @@
import { debug } from '@camp/debug';
-import type { RemoteDocument } from '@camp/domain';
+import type {
+ FailedFile,
+ FileState,
+ StorageFile,
+ SuccessFile,
+} from '@camp/domain';
+import { isSuccess } from '@camp/domain';
import { randomInt } from '@fullstacksjs/toolbox';
import type { InputWrapperProps } from '@mantine/core';
import { Input, Stack, Text } from '@mantine/core';
@@ -10,14 +16,12 @@ import { useDropzone } from 'react-dropzone';
import { FileList } from './FileList';
import { FileSelect } from './FileSelect';
-import type { FailedFile, FileState, SuccessFile } from './FileState';
-import { isSuccess } from './FileState';
type Action =
| {
type: 'Upload';
id: number;
- remote: RemoteDocument;
+ remote: StorageFile;
status: SuccessFile['status'];
}
| {
@@ -76,11 +80,11 @@ export interface FileUploadProps
defaultFiles?: (FailedFile | SuccessFile)[];
helper?: string;
concurrency?: number;
- upload?: (file: File) => Promise;
- unUpload?: (id: RemoteDocument['id']) => Promise;
+ upload?: (file: File) => Promise;
+ unUpload?: (id: StorageFile['id']) => Promise;
filter?: (files: File[]) => File[];
- onAdd?: (doc: RemoteDocument) => void;
- onDelete?: (doc: RemoteDocument) => void;
+ onAdd?: (doc: StorageFile) => void;
+ onDelete?: (doc: StorageFile) => void;
className?: string;
dropText?: string;
variant?: FileUploadVariant;
@@ -110,23 +114,26 @@ export const FileUpload = ({
dispatch({ type: 'Add', files: fileStates });
- void Prray.from(fileStates).forEachAsync(
- f =>
- upload?.(f.file)
- .then(doc => {
- onAdd?.(doc);
- dispatch({
- type: 'Upload',
- id: f.id,
- status: 'Success',
- remote: doc,
- });
- })
- .catch(() => {
- dispatch({ type: 'Upload', id: f.id, status: 'Failed' });
- }),
- { concurrency },
- );
+ Prray.from(fileStates)
+ .forEachAsync(
+ f =>
+ upload?.(f.file)
+ .then(doc => {
+ onAdd?.(doc);
+ dispatch({
+ type: 'Upload',
+ id: f.id,
+ status: 'Success',
+ remote: doc,
+ });
+ })
+ .catch(e => {
+ debug.error(e);
+ dispatch({ type: 'Upload', id: f.id, status: 'Failed' });
+ }),
+ { concurrency },
+ )
+ .catch(e => debug.error('FileUpload', e));
};
const { getRootProps, getInputProps } = useDropzone({
diff --git a/libs/design/ImagePreview/ImagePreview.tsx b/libs/design/ImagePreview/ImagePreview.tsx
new file mode 100644
index 00000000..781ad965
--- /dev/null
+++ b/libs/design/ImagePreview/ImagePreview.tsx
@@ -0,0 +1,30 @@
+import type { ImageProps } from '@mantine/core';
+import { Box, Center, Image } from '@mantine/core';
+
+import { CameraOffIcon } from '../../icons';
+
+interface Props extends ImageProps {
+ size: number;
+ pad?: number;
+}
+
+export const ImagePreview = ({ size, pad = 10, src, ...rest }: Props) => {
+ return (
+ ({
+ width: size + pad * 2,
+ border: `1px dashed ${theme.colors.bg[5]}`,
+ borderRadius: '8px',
+ padding: pad,
+ })}
+ >
+
+ {src == null ? (
+
+ ) : (
+
+ )}
+
+
+ );
+};
diff --git a/libs/design/ImagePreview/index.ts b/libs/design/ImagePreview/index.ts
new file mode 100644
index 00000000..af310632
--- /dev/null
+++ b/libs/design/ImagePreview/index.ts
@@ -0,0 +1 @@
+export * from './ImagePreview';
diff --git a/libs/design/Table/TableHead.tsx b/libs/design/Table/TableHead.tsx
index f7f6750a..06dba2c0 100644
--- a/libs/design/Table/TableHead.tsx
+++ b/libs/design/Table/TableHead.tsx
@@ -1,4 +1,4 @@
-interface Props extends React.ThHTMLAttributes {
+interface Props extends React.ThHTMLAttributes {
children: React.ReactNode;
}
diff --git a/libs/design/Table/TableRow.tsx b/libs/design/Table/TableRow.tsx
index 51439619..b93c4deb 100644
--- a/libs/design/Table/TableRow.tsx
+++ b/libs/design/Table/TableRow.tsx
@@ -1,9 +1,7 @@
import type { BoxProps } from '@mantine/core';
import { Box } from '@mantine/core';
-interface Props extends BoxProps {
- onClick?: (e: React.MouseEvent) => any;
-}
+interface Props extends BoxProps, React.HTMLAttributes {}
export const TableRow = (props: Props) => {
return ;
diff --git a/libs/design/index.ts b/libs/design/index.ts
index d665e2b0..bdcd283f 100644
--- a/libs/design/index.ts
+++ b/libs/design/index.ts
@@ -12,6 +12,7 @@ export * from './DetailCard';
export * from './EmptyState';
export * from './FileUpload';
export * from './FullPageLoader';
+export * from './ImagePreview';
export * from './Modal';
export * from './NavLink';
export * from './Notification';
diff --git a/libs/design/theme/palette.ts b/libs/design/theme/palette.ts
index 8bf603c8..284fd7c3 100644
--- a/libs/design/theme/palette.ts
+++ b/libs/design/theme/palette.ts
@@ -23,6 +23,7 @@ interface SemanticColor {
export const palette: Record = {
fg: {
+ first: ['#E8EAED', '#E8EAED', '#E8EAED', '#E8EAED'],
default: '#212529',
muted: '#8E959E',
subtle: '#ADB5BD',
@@ -30,7 +31,7 @@ export const palette: Record = {
bg: {
default: '#FCFCFC',
emphasized: '#FFFFFF',
- muted: '#C4C8CC',
+ muted: '#CED4DA',
subtle: '#DEE2E6',
},
secondary: {
diff --git a/libs/domain/ApiSchema.ts b/libs/domain/ApiSchema.ts
index e6c372dc..45fff61d 100644
--- a/libs/domain/ApiSchema.ts
+++ b/libs/domain/ApiSchema.ts
@@ -853,9 +853,11 @@ export type ApiDisabilityStatusUpdates = {
/** columns and relationships of "document" */
export type ApiDocument = {
__typename?: 'document';
- householder_id: Scalars['uuid']['output'];
+ householder_id?: Maybe;
id: Scalars['uuid']['output'];
+ storage_id: Scalars['String']['output'];
url: Scalars['String']['output'];
+ visit_id?: Maybe;
};
/** aggregated selection of "document" */
@@ -912,20 +914,26 @@ export type ApiDocumentBoolExp = {
_or?: InputMaybe>;
householder_id?: InputMaybe;
id?: InputMaybe;
+ storage_id?: InputMaybe;
url?: InputMaybe;
+ visit_id?: InputMaybe;
};
/** unique or primary key constraints on table "document" */
export enum ApiDocumentConstraint {
/** unique or primary key constraint on columns "id" */
- DocumentPkey = 'document_pkey'
+ DocumentPkey = 'document_pkey',
+ /** unique or primary key constraint on columns "storage_id" */
+ DocumentStorageIdKey = 'document_storage_id_key'
}
/** input type for inserting data into table "document" */
export type ApiDocumentInsertInput = {
householder_id?: InputMaybe;
id?: InputMaybe;
+ storage_id?: InputMaybe;
url?: InputMaybe;
+ visit_id?: InputMaybe;
};
/** aggregate max on columns */
@@ -933,14 +941,18 @@ export type ApiDocumentMaxFields = {
__typename?: 'document_max_fields';
householder_id?: Maybe;
id?: Maybe;
+ storage_id?: Maybe;
url?: Maybe;
+ visit_id?: Maybe;
};
/** order by max() on columns of table "document" */
export type ApiDocumentMaxOrderBy = {
householder_id?: InputMaybe;
id?: InputMaybe;
+ storage_id?: InputMaybe;
url?: InputMaybe;
+ visit_id?: InputMaybe;
};
/** aggregate min on columns */
@@ -948,14 +960,18 @@ export type ApiDocumentMinFields = {
__typename?: 'document_min_fields';
householder_id?: Maybe;
id?: Maybe;
+ storage_id?: Maybe;
url?: Maybe;
+ visit_id?: Maybe;
};
/** order by min() on columns of table "document" */
export type ApiDocumentMinOrderBy = {
householder_id?: InputMaybe;
id?: InputMaybe;
+ storage_id?: InputMaybe;
url?: InputMaybe;
+ visit_id?: InputMaybe;
};
/** response of any mutation on the table "document" */
@@ -978,7 +994,9 @@ export type ApiDocumentOnConflict = {
export type ApiDocumentOrderBy = {
householder_id?: InputMaybe;
id?: InputMaybe;
+ storage_id?: InputMaybe;
url?: InputMaybe;
+ visit_id?: InputMaybe;
};
/** primary key columns input for table: document */
@@ -993,14 +1011,20 @@ export enum ApiDocumentSelectColumn {
/** column name */
Id = 'id',
/** column name */
- Url = 'url'
+ StorageId = 'storage_id',
+ /** column name */
+ Url = 'url',
+ /** column name */
+ VisitId = 'visit_id'
}
/** input type for updating data in table "document" */
export type ApiDocumentSetInput = {
householder_id?: InputMaybe;
id?: InputMaybe;
+ storage_id?: InputMaybe;
url?: InputMaybe;
+ visit_id?: InputMaybe;
};
/** Streaming cursor of the table "document" */
@@ -1015,7 +1039,9 @@ export type ApiDocumentStreamCursorInput = {
export type ApiDocumentStreamCursorValueInput = {
householder_id?: InputMaybe;
id?: InputMaybe;
+ storage_id?: InputMaybe;
url?: InputMaybe;
+ visit_id?: InputMaybe;
};
/** update columns of table "document" */
@@ -1025,7 +1051,11 @@ export enum ApiDocumentUpdateColumn {
/** column name */
Id = 'id',
/** column name */
- Url = 'url'
+ StorageId = 'storage_id',
+ /** column name */
+ Url = 'url',
+ /** column name */
+ VisitId = 'visit_id'
}
export type ApiDocumentUpdates = {
@@ -3674,6 +3704,10 @@ export type ApiMutationRoot = {
delete_nationality?: Maybe;
/** delete single row from the table: "nationality" */
delete_nationality_by_pk?: Maybe;
+ /** delete data from the table: "neighborhood" */
+ delete_neighborhood?: Maybe;
+ /** delete single row from the table: "neighborhood" */
+ delete_neighborhood_by_pk?: Maybe;
/** delete data from the table: "project" */
delete_project?: Maybe;
/** delete single row from the table: "project" */
@@ -3694,6 +3728,14 @@ export type ApiMutationRoot = {
delete_subsidy?: Maybe;
/** delete single row from the table: "subsidy" */
delete_subsidy_by_pk?: Maybe;
+ /** delete data from the table: "visit" */
+ delete_visit?: Maybe;
+ /** delete single row from the table: "visit" */
+ delete_visit_by_pk?: Maybe;
+ /** delete data from the table: "visit_status" */
+ delete_visit_status?: Maybe;
+ /** delete single row from the table: "visit_status" */
+ delete_visit_status_by_pk?: Maybe;
/** insert data into the table: "accommodation_type" */
insert_accommodation_type?: Maybe;
/** insert a single row into the table: "accommodation_type" */
@@ -3762,6 +3804,10 @@ export type ApiMutationRoot = {
insert_nationality?: Maybe;
/** insert a single row into the table: "nationality" */
insert_nationality_one?: Maybe;
+ /** insert data into the table: "neighborhood" */
+ insert_neighborhood?: Maybe;
+ /** insert a single row into the table: "neighborhood" */
+ insert_neighborhood_one?: Maybe;
/** insert data into the table: "project" */
insert_project?: Maybe;
/** insert a single row into the table: "project" */
@@ -3782,6 +3828,14 @@ export type ApiMutationRoot = {
insert_subsidy?: Maybe;
/** insert a single row into the table: "subsidy" */
insert_subsidy_one?: Maybe;
+ /** insert data into the table: "visit" */
+ insert_visit?: Maybe;
+ /** insert a single row into the table: "visit" */
+ insert_visit_one?: Maybe;
+ /** insert data into the table: "visit_status" */
+ insert_visit_status?: Maybe;
+ /** insert a single row into the table: "visit_status" */
+ insert_visit_status_one?: Maybe;
/** update data of the table: "accommodation_type" */
update_accommodation_type?: Maybe;
/** update single row of the table: "accommodation_type" */
@@ -3884,6 +3938,12 @@ export type ApiMutationRoot = {
update_nationality_by_pk?: Maybe;
/** update multiples rows of table: "nationality" */
update_nationality_many?: Maybe>>;
+ /** update data of the table: "neighborhood" */
+ update_neighborhood?: Maybe;
+ /** update single row of the table: "neighborhood" */
+ update_neighborhood_by_pk?: Maybe;
+ /** update multiples rows of table: "neighborhood" */
+ update_neighborhood_many?: Maybe>>;
/** update data of the table: "project" */
update_project?: Maybe;
/** update single row of the table: "project" */
@@ -3914,6 +3974,18 @@ export type ApiMutationRoot = {
update_subsidy_by_pk?: Maybe;
/** update multiples rows of table: "subsidy" */
update_subsidy_many?: Maybe>>;
+ /** update data of the table: "visit" */
+ update_visit?: Maybe;
+ /** update single row of the table: "visit" */
+ update_visit_by_pk?: Maybe;
+ /** update multiples rows of table: "visit" */
+ update_visit_many?: Maybe>>;
+ /** update data of the table: "visit_status" */
+ update_visit_status?: Maybe;
+ /** update single row of the table: "visit_status" */
+ update_visit_status_by_pk?: Maybe;
+ /** update multiples rows of table: "visit_status" */
+ update_visit_status_many?: Maybe>>;
};
@@ -4122,6 +4194,18 @@ export type ApiMutationRootApiDeleteNationalityByPkArgs = {
};
+/** mutation root */
+export type ApiMutationRootApiDeleteNeighborhoodArgs = {
+ where: ApiNeighborhoodBoolExp;
+};
+
+
+/** mutation root */
+export type ApiMutationRootApiDeleteNeighborhoodByPkArgs = {
+ name: Scalars['String']['input'];
+};
+
+
/** mutation root */
export type ApiMutationRootApiDeleteProjectArgs = {
where: ApiProjectBoolExp;
@@ -4182,6 +4266,30 @@ export type ApiMutationRootApiDeleteSubsidyByPkArgs = {
};
+/** mutation root */
+export type ApiMutationRootApiDeleteVisitArgs = {
+ where: ApiVisitBoolExp;
+};
+
+
+/** mutation root */
+export type ApiMutationRootApiDeleteVisitByPkArgs = {
+ id: Scalars['uuid']['input'];
+};
+
+
+/** mutation root */
+export type ApiMutationRootApiDeleteVisitStatusArgs = {
+ where: ApiVisitStatusBoolExp;
+};
+
+
+/** mutation root */
+export type ApiMutationRootApiDeleteVisitStatusByPkArgs = {
+ value: Scalars['String']['input'];
+};
+
+
/** mutation root */
export type ApiMutationRootApiInsertAccommodationTypeArgs = {
objects: Array;
@@ -4420,6 +4528,20 @@ export type ApiMutationRootApiInsertNationalityOneArgs = {
};
+/** mutation root */
+export type ApiMutationRootApiInsertNeighborhoodArgs = {
+ objects: Array;
+ on_conflict?: InputMaybe;
+};
+
+
+/** mutation root */
+export type ApiMutationRootApiInsertNeighborhoodOneArgs = {
+ object: ApiNeighborhoodInsertInput;
+ on_conflict?: InputMaybe;
+};
+
+
/** mutation root */
export type ApiMutationRootApiInsertProjectArgs = {
objects: Array;
@@ -4490,6 +4612,34 @@ export type ApiMutationRootApiInsertSubsidyOneArgs = {
};
+/** mutation root */
+export type ApiMutationRootApiInsertVisitArgs = {
+ objects: Array;
+ on_conflict?: InputMaybe;
+};
+
+
+/** mutation root */
+export type ApiMutationRootApiInsertVisitOneArgs = {
+ object: ApiVisitInsertInput;
+ on_conflict?: InputMaybe;
+};
+
+
+/** mutation root */
+export type ApiMutationRootApiInsertVisitStatusArgs = {
+ objects: Array;
+ on_conflict?: InputMaybe;
+};
+
+
+/** mutation root */
+export type ApiMutationRootApiInsertVisitStatusOneArgs = {
+ object: ApiVisitStatusInsertInput;
+ on_conflict?: InputMaybe;
+};
+
+
/** mutation root */
export type ApiMutationRootApiUpdateAccommodationTypeArgs = {
_set?: InputMaybe;
@@ -4836,6 +4986,26 @@ export type ApiMutationRootApiUpdateNationalityManyArgs = {
};
+/** mutation root */
+export type ApiMutationRootApiUpdateNeighborhoodArgs = {
+ _set?: InputMaybe;
+ where: ApiNeighborhoodBoolExp;
+};
+
+
+/** mutation root */
+export type ApiMutationRootApiUpdateNeighborhoodByPkArgs = {
+ _set?: InputMaybe;
+ pk_columns: ApiNeighborhoodPkColumnsInput;
+};
+
+
+/** mutation root */
+export type ApiMutationRootApiUpdateNeighborhoodManyArgs = {
+ updates: Array;
+};
+
+
/** mutation root */
export type ApiMutationRootApiUpdateProjectArgs = {
_set?: InputMaybe;
@@ -4935,6 +5105,46 @@ export type ApiMutationRootApiUpdateSubsidyManyArgs = {
updates: Array;
};
+
+/** mutation root */
+export type ApiMutationRootApiUpdateVisitArgs = {
+ _set?: InputMaybe;
+ where: ApiVisitBoolExp;
+};
+
+
+/** mutation root */
+export type ApiMutationRootApiUpdateVisitByPkArgs = {
+ _set?: InputMaybe;
+ pk_columns: ApiVisitPkColumnsInput;
+};
+
+
+/** mutation root */
+export type ApiMutationRootApiUpdateVisitManyArgs = {
+ updates: Array;
+};
+
+
+/** mutation root */
+export type ApiMutationRootApiUpdateVisitStatusArgs = {
+ _set?: InputMaybe;
+ where: ApiVisitStatusBoolExp;
+};
+
+
+/** mutation root */
+export type ApiMutationRootApiUpdateVisitStatusByPkArgs = {
+ _set?: InputMaybe;
+ pk_columns: ApiVisitStatusPkColumnsInput;
+};
+
+
+/** mutation root */
+export type ApiMutationRootApiUpdateVisitStatusManyArgs = {
+ updates: Array;
+};
+
/** columns and relationships of "nationality" */
export type ApiNationality = {
__typename?: 'nationality';
@@ -5071,6 +5281,140 @@ export type ApiNationalityUpdates = {
where: ApiNationalityBoolExp;
};
+/** columns and relationships of "neighborhood" */
+export type ApiNeighborhood = {
+ __typename?: 'neighborhood';
+ city: Scalars['String']['output'];
+ name: Scalars['String']['output'];
+};
+
+/** aggregated selection of "neighborhood" */
+export type ApiNeighborhoodAggregate = {
+ __typename?: 'neighborhood_aggregate';
+ aggregate?: Maybe;
+ nodes: Array;
+};
+
+/** aggregate fields of "neighborhood" */
+export type ApiNeighborhoodAggregateFields = {
+ __typename?: 'neighborhood_aggregate_fields';
+ count: Scalars['Int']['output'];
+ max?: Maybe;
+ min?: Maybe;
+};
+
+
+/** aggregate fields of "neighborhood" */
+export type ApiNeighborhoodAggregateFieldsApiCountArgs = {
+ columns?: InputMaybe>;
+ distinct?: InputMaybe;
+};
+
+/** Boolean expression to filter rows from the table "neighborhood". All fields are combined with a logical 'AND'. */
+export type ApiNeighborhoodBoolExp = {
+ _and?: InputMaybe>;
+ _not?: InputMaybe;
+ _or?: InputMaybe>;
+ city?: InputMaybe;
+ name?: InputMaybe;
+};
+
+/** unique or primary key constraints on table "neighborhood" */
+export enum ApiNeighborhoodConstraint {
+ /** unique or primary key constraint on columns "name" */
+ NeighborhoodPkey = 'neighborhood_pkey'
+}
+
+/** input type for inserting data into table "neighborhood" */
+export type ApiNeighborhoodInsertInput = {
+ city?: InputMaybe;
+ name?: InputMaybe;
+};
+
+/** aggregate max on columns */
+export type ApiNeighborhoodMaxFields = {
+ __typename?: 'neighborhood_max_fields';
+ city?: Maybe;
+ name?: Maybe;
+};
+
+/** aggregate min on columns */
+export type ApiNeighborhoodMinFields = {
+ __typename?: 'neighborhood_min_fields';
+ city?: Maybe;
+ name?: Maybe;
+};
+
+/** response of any mutation on the table "neighborhood" */
+export type ApiNeighborhoodMutationResponse = {
+ __typename?: 'neighborhood_mutation_response';
+ /** number of rows affected by the mutation */
+ affected_rows: Scalars['Int']['output'];
+ /** data from the rows affected by the mutation */
+ returning: Array;
+};
+
+/** on_conflict condition type for table "neighborhood" */
+export type ApiNeighborhoodOnConflict = {
+ constraint: ApiNeighborhoodConstraint;
+ update_columns?: Array;
+ where?: InputMaybe;
+};
+
+/** Ordering options when selecting data from "neighborhood". */
+export type ApiNeighborhoodOrderBy = {
+ city?: InputMaybe;
+ name?: InputMaybe;
+};
+
+/** primary key columns input for table: neighborhood */
+export type ApiNeighborhoodPkColumnsInput = {
+ name: Scalars['String']['input'];
+};
+
+/** select columns of table "neighborhood" */
+export enum ApiNeighborhoodSelectColumn {
+ /** column name */
+ City = 'city',
+ /** column name */
+ Name = 'name'
+}
+
+/** input type for updating data in table "neighborhood" */
+export type ApiNeighborhoodSetInput = {
+ city?: InputMaybe;
+ name?: InputMaybe;
+};
+
+/** Streaming cursor of the table "neighborhood" */
+export type ApiNeighborhoodStreamCursorInput = {
+ /** Stream column input with initial value */
+ initial_value: ApiNeighborhoodStreamCursorValueInput;
+ /** cursor ordering */
+ ordering?: InputMaybe;
+};
+
+/** Initial value of the column from where the streaming should start */
+export type ApiNeighborhoodStreamCursorValueInput = {
+ city?: InputMaybe;
+ name?: InputMaybe;
+};
+
+/** update columns of table "neighborhood" */
+export enum ApiNeighborhoodUpdateColumn {
+ /** column name */
+ City = 'city',
+ /** column name */
+ Name = 'name'
+}
+
+export type ApiNeighborhoodUpdates = {
+ /** sets the columns of the filtered rows to the given values */
+ _set?: InputMaybe;
+ /** filter the rows which have to be updated */
+ where: ApiNeighborhoodBoolExp;
+};
+
/** column ordering options */
export enum ApiOrderBy {
/** in ascending order, nulls last */
@@ -5581,6 +5925,12 @@ export type ApiQueryRoot = {
nationality_aggregate: ApiNationalityAggregate;
/** fetch data from the table: "nationality" using primary key columns */
nationality_by_pk?: Maybe;
+ /** fetch data from the table: "neighborhood" */
+ neighborhood: Array;
+ /** fetch aggregated fields from the table: "neighborhood" */
+ neighborhood_aggregate: ApiNeighborhoodAggregate;
+ /** fetch data from the table: "neighborhood" using primary key columns */
+ neighborhood_by_pk?: Maybe;
/** fetch data from the table: "project" */
project: Array;
/** fetch aggregated fields from the table: "project" */
@@ -5611,6 +5961,18 @@ export type ApiQueryRoot = {
subsidy_aggregate: ApiSubsidyAggregate;
/** fetch data from the table: "subsidy" using primary key columns */
subsidy_by_pk?: Maybe;
+ /** fetch data from the table: "visit" */
+ visit: Array;
+ /** fetch aggregated fields from the table: "visit" */
+ visit_aggregate: ApiVisitAggregate;
+ /** fetch data from the table: "visit" using primary key columns */
+ visit_by_pk?: Maybe;
+ /** fetch data from the table: "visit_status" */
+ visit_status: Array;
+ /** fetch aggregated fields from the table: "visit_status" */
+ visit_status_aggregate: ApiVisitStatusAggregate;
+ /** fetch data from the table: "visit_status" using primary key columns */
+ visit_status_by_pk?: Maybe;
};
@@ -6006,6 +6368,29 @@ export type ApiQueryRootApiNationalityByPkArgs = {
};
+export type ApiQueryRootApiNeighborhoodArgs = {
+ distinct_on?: InputMaybe>;
+ limit?: InputMaybe;
+ offset?: InputMaybe;
+ order_by?: InputMaybe>;
+ where?: InputMaybe;
+};
+
+
+export type ApiQueryRootApiNeighborhoodAggregateArgs = {
+ distinct_on?: InputMaybe>;
+ limit?: InputMaybe;
+ offset?: InputMaybe;
+ order_by?: InputMaybe>;
+ where?: InputMaybe;
+};
+
+
+export type ApiQueryRootApiNeighborhoodByPkArgs = {
+ name: Scalars['String']['input'];
+};
+
+
export type ApiQueryRootApiProjectArgs = {
distinct_on?: InputMaybe>;
limit?: InputMaybe;
@@ -6120,6 +6505,52 @@ export type ApiQueryRootApiSubsidyByPkArgs = {
id: Scalars['uuid']['input'];
};
+
+export type ApiQueryRootApiVisitArgs = {
+ distinct_on?: InputMaybe>;
+ limit?: InputMaybe;
+ offset?: InputMaybe;
+ order_by?: InputMaybe>;
+ where?: InputMaybe;
+};
+
+
+export type ApiQueryRootApiVisitAggregateArgs = {
+ distinct_on?: InputMaybe>;
+ limit?: InputMaybe;
+ offset?: InputMaybe;
+ order_by?: InputMaybe>;
+ where?: InputMaybe;
+};
+
+
+export type ApiQueryRootApiVisitByPkArgs = {
+ id: Scalars['uuid']['input'];
+};
+
+
+export type ApiQueryRootApiVisitStatusArgs = {
+ distinct_on?: InputMaybe>;
+ limit?: InputMaybe;
+ offset?: InputMaybe;
+ order_by?: InputMaybe>;
+ where?: InputMaybe;
+};
+
+
+export type ApiQueryRootApiVisitStatusAggregateArgs = {
+ distinct_on?: InputMaybe>;
+ limit?: InputMaybe;
+ offset?: InputMaybe;
+ order_by?: InputMaybe>;
+ where?: InputMaybe;
+};
+
+
+export type ApiQueryRootApiVisitStatusByPkArgs = {
+ value: Scalars['String']['input'];
+};
+
/** columns and relationships of "religion" */
export type ApiReligion = {
__typename?: 'religion';
@@ -6581,6 +7012,14 @@ export type ApiSubscriptionRoot = {
nationality_by_pk?: Maybe;
/** fetch data from the table in a streaming manner: "nationality" */
nationality_stream: Array;
+ /** fetch data from the table: "neighborhood" */
+ neighborhood: Array;
+ /** fetch aggregated fields from the table: "neighborhood" */
+ neighborhood_aggregate: ApiNeighborhoodAggregate;
+ /** fetch data from the table: "neighborhood" using primary key columns */
+ neighborhood_by_pk?: Maybe;
+ /** fetch data from the table in a streaming manner: "neighborhood" */
+ neighborhood_stream: Array;
/** fetch data from the table: "project" */
project: Array;
/** fetch aggregated fields from the table: "project" */
@@ -6621,6 +7060,22 @@ export type ApiSubscriptionRoot = {
subsidy_by_pk?: Maybe;
/** fetch data from the table in a streaming manner: "subsidy" */
subsidy_stream: Array;
+ /** fetch data from the table: "visit" */
+ visit: Array;
+ /** fetch aggregated fields from the table: "visit" */
+ visit_aggregate: ApiVisitAggregate;
+ /** fetch data from the table: "visit" using primary key columns */
+ visit_by_pk?: Maybe;
+ /** fetch data from the table: "visit_status" */
+ visit_status: Array;
+ /** fetch aggregated fields from the table: "visit_status" */
+ visit_status_aggregate: ApiVisitStatusAggregate;
+ /** fetch data from the table: "visit_status" using primary key columns */
+ visit_status_by_pk?: Maybe;
+ /** fetch data from the table in a streaming manner: "visit_status" */
+ visit_status_stream: Array;
+ /** fetch data from the table in a streaming manner: "visit" */
+ visit_stream: Array;
};
@@ -7135,6 +7590,36 @@ export type ApiSubscriptionRootApiNationalityStreamArgs = {
};
+export type ApiSubscriptionRootApiNeighborhoodArgs = {
+ distinct_on?: InputMaybe>;
+ limit?: InputMaybe;
+ offset?: InputMaybe;
+ order_by?: InputMaybe>;
+ where?: InputMaybe;
+};
+
+
+export type ApiSubscriptionRootApiNeighborhoodAggregateArgs = {
+ distinct_on?: InputMaybe>;
+ limit?: InputMaybe