@mbehenri/openmrs-esm-opentms-meet-app 1.0.2 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
- import React, { useCallback, useEffect, useMemo } from "react";
2
- import { useState } from "react";
1
+ import React, { useCallback, useEffect, useMemo } from 'react';
2
+ import { useState } from 'react';
3
3
  import {
4
4
  Button,
5
5
  DataTable,
@@ -15,7 +15,7 @@ import {
15
15
  TableRow,
16
16
  Tile,
17
17
  Pagination,
18
- } from "@carbon/react";
18
+ } from '@carbon/react';
19
19
 
20
20
  import {
21
21
  type ToastType,
@@ -27,14 +27,14 @@ import {
27
27
  formatDatetime,
28
28
  parseDate,
29
29
  usePagination,
30
- } from "@openmrs/esm-framework";
31
- import { EmptyLayout } from "../EmptyLayout";
32
- import styles from "./tab.scss";
33
- import DoctorService from "../../services/doctor";
34
- import env from "../../repositories/env";
35
- import { Search } from "@carbon/react";
36
- import { useTranslation } from "react-i18next";
37
- import { getPageSizes } from "../../utils";
30
+ } from '@openmrs/esm-framework';
31
+ import { EmptyLayout } from '../EmptyLayout';
32
+ import styles from './tab.scss';
33
+ import DoctorService from '../../services/doctor';
34
+ import env from '../../repositories/env';
35
+ import { Search } from '@carbon/react';
36
+ import { useTranslation } from 'react-i18next';
37
+ import { getPageSizes } from '../../utils';
38
38
 
39
39
  const DemandTab: React.FC = (/* {} */) => {
40
40
  //I. hooks
@@ -42,9 +42,9 @@ const DemandTab: React.FC = (/* {} */) => {
42
42
  const { t } = useTranslation();
43
43
  const [error, setError] = useState(false);
44
44
  const layout = useLayoutType();
45
- const responsiveSize = isDesktop(layout) ? "sm" : "lg";
45
+ const responsiveSize = isDesktop(layout) ? 'sm' : 'lg';
46
46
  const [demands, setDemands] = useState([]);
47
- const [searchString, setSearchString] = useState("");
47
+ const [searchString, setSearchString] = useState('');
48
48
  const [processing, setProcessing] = useState(false);
49
49
  const [pageSize, setPageSize] = useState(5);
50
50
 
@@ -53,21 +53,21 @@ const DemandTab: React.FC = (/* {} */) => {
53
53
 
54
54
  // update env variable
55
55
 
56
- env.API_BASE_URL = conf["API_BASE_URL"];
57
- env.API_SECURE = conf["API_SECURE"];
56
+ env.API_BASE_URL = conf['API_BASE_URL'];
57
+ env.API_SECURE = conf['API_SECURE'];
58
58
  const doctorService = useMemo(() => DoctorService.getInstance(), []);
59
59
 
60
60
  //const [reload, setReload] = useState("");
61
61
  // colonnes du tableau
62
62
  const headers: Array<typeof DataTableHeader> = useMemo(
63
63
  () => [
64
- { key: "numero", header: "" },
65
- { key: "patient", header: "Patient" },
66
- { key: "service", header: "Service" },
67
- { key: "date", header: "Date" },
68
- { key: "action", header: "Action" },
64
+ { key: 'numero', header: '' },
65
+ { key: 'patient', header: 'Patient' },
66
+ { key: 'service', header: 'Service' },
67
+ { key: 'date', header: 'Date' },
68
+ { key: 'action', header: 'Action' },
69
69
  ],
70
- []
70
+ [],
71
71
  );
72
72
 
73
73
  // chargement des démandes
@@ -110,13 +110,13 @@ const DemandTab: React.FC = (/* {} */) => {
110
110
  return {
111
111
  ...demand,
112
112
  date: formatDatetime(parseDate(demand.date), {
113
- mode: "wide",
113
+ mode: 'wide',
114
114
  }),
115
115
  numero: i + 1,
116
116
  };
117
117
  })
118
118
  .filter((demand) => {
119
- if (searchString === "") {
119
+ if (searchString === '') {
120
120
  return true;
121
121
  }
122
122
  if (demand.date && `${demand.date}`.includes(searchString)) {
@@ -127,7 +127,7 @@ const DemandTab: React.FC = (/* {} */) => {
127
127
  }
128
128
  return false;
129
129
  }),
130
- [demands, searchString]
130
+ [demands, searchString],
131
131
  );
132
132
 
133
133
  // pagination des demandes
@@ -139,14 +139,14 @@ const DemandTab: React.FC = (/* {} */) => {
139
139
  if (!processing) {
140
140
  setProcessing(true);
141
141
  const res = await doctorService.rejectDemand(demand.id);
142
- let message = "";
143
- let type: ToastType = "error";
142
+ let message = '';
143
+ let type: ToastType = 'error';
144
144
  if (res) {
145
145
  message = `the demand initiated by ${demand.patient} have been rejected`;
146
146
  await doctorService.getDemands().then((demands) => {
147
147
  if (demands) {
148
148
  setDemands(demands);
149
- type = "success";
149
+ type = 'success';
150
150
  } else {
151
151
  setError(true);
152
152
  }
@@ -158,7 +158,7 @@ const DemandTab: React.FC = (/* {} */) => {
158
158
  showToast({ description: message, kind: type });
159
159
  }
160
160
  },
161
- [doctorService, processing]
161
+ [doctorService, processing],
162
162
  );
163
163
 
164
164
  // fonction de validation
@@ -167,7 +167,7 @@ const DemandTab: React.FC = (/* {} */) => {
167
167
  if (!processing) {
168
168
  setProcessing(true);
169
169
  const dispose = showModal(
170
- "opencare-validate-demand-form",
170
+ 'opentms-validate-demand-form',
171
171
  {
172
172
  demand,
173
173
  onClose: () => {
@@ -183,11 +183,11 @@ const DemandTab: React.FC = (/* {} */) => {
183
183
  }
184
184
  });
185
185
  setProcessing(false);
186
- }
186
+ },
187
187
  );
188
188
  }
189
189
  },
190
- [doctorService, processing]
190
+ [doctorService, processing],
191
191
  );
192
192
 
193
193
  // II. returns
@@ -200,57 +200,37 @@ const DemandTab: React.FC = (/* {} */) => {
200
200
  }
201
201
 
202
202
  if (demands.length == 0) {
203
- return <EmptyLayout headerTitle={"Demands"} displayText={"demands"} />;
203
+ return <EmptyLayout headerTitle={'Demands'} displayText={'demands'} />;
204
204
  }
205
205
 
206
206
  return (
207
207
  <Layer className={styles.container}>
208
208
  <Tile className={styles.headerContainer}>
209
- <div
210
- className={
211
- isDesktop(layout) ? styles.desktopHeading : styles.tabletHeading
212
- }
213
- >
209
+ <div className={isDesktop(layout) ? styles.desktopHeading : styles.tabletHeading}>
214
210
  <h4>Demands</h4>
215
211
  </div>
216
212
  <span className={styles.totalDemand}>
217
- Total :
218
- <span className={styles.totalDemandLength}>{demands.length}</span>
213
+ Total :<span className={styles.totalDemandLength}>{demands.length}</span>
219
214
  </span>
220
215
  </Tile>
221
216
  <div className={styles.toolbar}>
222
217
  <Search
223
218
  className={styles.searchbar}
224
219
  labelText=""
225
- placeholder={t("filterTable", "Filter table")}
220
+ placeholder={t('filterTable', 'Filter table')}
226
221
  onChange={(event) => setSearchString(event.target.value)}
227
222
  size={responsiveSize}
228
223
  />
229
224
  </div>
230
- <DataTable
231
- rows={results}
232
- headers={headers}
233
- isSortable
234
- size={responsiveSize}
235
- useZebraStyles
236
- >
237
- {({
238
- rows,
239
- headers,
240
- getHeaderProps,
241
- getRowProps,
242
- getTableProps,
243
- getTableContainerProps,
244
- }) => (
225
+ <DataTable rows={results} headers={headers} isSortable size={responsiveSize} useZebraStyles>
226
+ {({ rows, headers, getHeaderProps, getRowProps, getTableProps, getTableContainerProps }) => (
245
227
  <>
246
228
  <TableContainer {...getTableContainerProps()}>
247
229
  <Table {...getTableProps()}>
248
230
  <TableHead>
249
231
  <TableRow>
250
232
  {headers.map((header) => (
251
- <TableHeader {...getHeaderProps({ header })}>
252
- {header.header}
253
- </TableHeader>
233
+ <TableHeader {...getHeaderProps({ header })}>{header.header}</TableHeader>
254
234
  ))}
255
235
  </TableRow>
256
236
  </TableHead>
@@ -1,6 +1,6 @@
1
- import React, { useCallback, useEffect, useRef, useState } from "react";
2
- import styles from "./index.scss";
3
- import logo from "../../assets/img/Logo.png";
1
+ import React, { useCallback, useEffect, useRef, useState } from 'react';
2
+ import styles from './index.scss';
3
+ import logo from '../../assets/img/Logo.png';
4
4
 
5
5
  interface MeetIframeProps {
6
6
  username: string;
@@ -8,11 +8,7 @@ interface MeetIframeProps {
8
8
  url: string;
9
9
  }
10
10
 
11
- export const MeetIframe: React.FC<MeetIframeProps> = ({
12
- url,
13
- token,
14
- username,
15
- }) => {
11
+ export const MeetIframe: React.FC<MeetIframeProps> = ({ url, token, username }) => {
16
12
  const iframeRef = useRef<HTMLIFrameElement>(null);
17
13
 
18
14
  const [showPreloader, setShowPreloader] = useState(false);
@@ -30,22 +26,16 @@ export const MeetIframe: React.FC<MeetIframeProps> = ({
30
26
 
31
27
  const iframeDocument = iframeWindow.document;
32
28
 
33
- const isLoggedIn = iframeDocument.querySelector(".user-menu");
29
+ const isLoggedIn = iframeDocument.querySelector('.user-menu');
34
30
  if (isLoggedIn) {
35
31
  //console.log("Utilisateur déjà connecté.");
36
32
  //setShowPreloader(false);
37
33
  return; // Pas besoin de soumettre le formulaire
38
34
  }
39
35
 
40
- const userInput = iframeDocument.querySelector(
41
- 'input[name="user"]'
42
- ) as HTMLInputElement | null;
43
- const passwordInput = iframeDocument.querySelector(
44
- 'input[name="password"]'
45
- ) as HTMLInputElement | null;
46
- const loginForm = iframeDocument.querySelector(
47
- "form"
48
- ) as HTMLFormElement | null;
36
+ const userInput = iframeDocument.querySelector('input[name="user"]') as HTMLInputElement | null;
37
+ const passwordInput = iframeDocument.querySelector('input[name="password"]') as HTMLInputElement | null;
38
+ const loginForm = iframeDocument.querySelector('form') as HTMLFormElement | null;
49
39
 
50
40
  if (userInput && passwordInput && loginForm) {
51
41
  userInput.value = username;
@@ -55,7 +45,7 @@ export const MeetIframe: React.FC<MeetIframeProps> = ({
55
45
  setTimeout(() => {
56
46
  loginForm.submit();
57
47
  // eslint-disable-next-line no-console
58
- console.log("Formulaire de connexion soumis.");
48
+ console.log('Formulaire de connexion soumis.');
59
49
  }, 3000); // Délai de 3 secondes
60
50
  //console.log('username', userInput.value, 'password', passwordInput.value);
61
51
  return;
@@ -65,7 +55,7 @@ export const MeetIframe: React.FC<MeetIframeProps> = ({
65
55
  }
66
56
  //setShowPreloader(false);
67
57
  },
68
- [token, username]
58
+ [token, username],
69
59
  );
70
60
 
71
61
  const handleLoad = useCallback(
@@ -75,7 +65,7 @@ export const MeetIframe: React.FC<MeetIframeProps> = ({
75
65
  }
76
66
  setShowPreloader(false);
77
67
  },
78
- [handleLogin, needContinue]
68
+ [handleLogin, needContinue],
79
69
  );
80
70
 
81
71
  useEffect(() => {
@@ -85,31 +75,22 @@ export const MeetIframe: React.FC<MeetIframeProps> = ({
85
75
  const load = () => handleLoad(iframe);
86
76
 
87
77
  // Associer l'événement de chargement
88
- iframe.addEventListener("load", load);
78
+ iframe.addEventListener('load', load);
89
79
 
90
80
  // Nettoyage de l'événement lors du démontage du composant
91
81
  return () => {
92
- iframe.removeEventListener("load", load);
82
+ iframe.removeEventListener('load', load);
93
83
  };
94
84
  }
95
85
  }, [handleLoad]);
96
86
 
97
87
  return (
98
88
  <div className={styles.contentViewWrapper}>
99
- <div
100
- className={styles.preloader}
101
- style={{ display: showPreloader ? "flex" : "none" }}
102
- >
89
+ <div className={styles.preloader} style={{ display: showPreloader ? 'flex' : 'none' }}>
103
90
  <span className={styles.spinner}></span>
104
- <img src={logo} alt="opencare logo" className="logo" />
91
+ <img src={logo} alt="opentms logo" className="logo" />
105
92
  </div>
106
- <iframe
107
- ref={iframeRef}
108
- className={styles.viewer}
109
- title="Web Meeting"
110
- src={url}
111
- allow="camera;microphone"
112
- />
93
+ <iframe ref={iframeRef} className={styles.viewer} title="Web Meeting" src={url} allow="camera;microphone" />
113
94
  </div>
114
95
  );
115
96
  };
@@ -1,11 +1,11 @@
1
1
  export const meetingDashboardMeta = {
2
- name: "meeting",
3
- slot: "opencare-dashboard-slot",
4
- title: "Meeting",
2
+ name: 'meeting',
3
+ slot: 'opentms-dashboard-slot',
4
+ title: 'Meeting',
5
5
  };
6
6
 
7
7
  export const PaymentDashboardMeta = {
8
- name: "payment",
9
- slot: "opencare-dashboard-slot",
10
- title: "Payment",
8
+ name: 'payment',
9
+ slot: 'opentms-dashboard-slot',
10
+ title: 'Payment',
11
11
  };
package/src/index.ts CHANGED
@@ -35,7 +35,7 @@ export function startupApp() {
35
35
  * This named export tells the app shell that the default export of `root.component.tsx`
36
36
  * should be rendered when the route matches `root`. The full route
37
37
  * will be `openmrsSpaBase() + 'root'`, which is usually
38
- * `/openmrs/spa/opencare`.
38
+ * `/openmrs/spa/opentms`.
39
39
  */
40
40
  /* export const root = getSyncLifecycle(
41
41
  Root,
@@ -0,0 +1,10 @@
1
+ import { type TypeRepository } from '../TypeRepository';
2
+ import ProdOpentmsRepository from './prodRepository';
3
+ import OpentmsRepository from './repository';
4
+
5
+ export function getOpentmsRepository(t: TypeRepository = 'fake'): OpentmsRepository {
6
+ if (t === 'fake') {
7
+ return new OpentmsRepository();
8
+ }
9
+ return new ProdOpentmsRepository();
10
+ }
@@ -1,31 +1,25 @@
1
- import { BadResponse } from "../errors";
2
- import env from "../env";
3
- import OpencareRepository from "./repository";
1
+ import { BadResponse } from '../errors';
2
+ import env from '../env';
3
+ import OpentmsRepository from './repository';
4
4
 
5
- class ProdOpencareRepository extends OpencareRepository {
5
+ class ProdOpentmsRepository extends OpentmsRepository {
6
6
  async getDemands(): Promise<Array<any>> {
7
7
  let myHeaders = new Headers();
8
- myHeaders.append("Accept", "application/json,");
9
- myHeaders.append("Content-Type", "application/json");
8
+ myHeaders.append('Accept', 'application/json,');
9
+ myHeaders.append('Content-Type', 'application/json');
10
10
  //myHeaders.append("Authorization", `Basic ${TALK_BASE64}`);
11
11
 
12
12
  let requestOptions = {
13
- method: "GET",
13
+ method: 'GET',
14
14
  headers: myHeaders,
15
15
  };
16
- const demandProgressStatus = "2";
17
- return await fetch(
18
- `${env.API_BASE_URL}/demand?status=${demandProgressStatus}`,
19
- requestOptions
20
- )
16
+ const demandProgressStatus = '2';
17
+ return await fetch(`${env.API_BASE_URL}/demand?status=${demandProgressStatus}`, requestOptions)
21
18
  .then((response) => {
22
19
  if (response.ok) {
23
20
  return response.json();
24
21
  }
25
- throw new BadResponse(
26
- `Impossible de recupérer les démandes (${response.status})`,
27
- "Opencare"
28
- );
22
+ throw new BadResponse(`Impossible de recupérer les démandes (${response.status})`, 'Opentms');
29
23
  })
30
24
  .then(({ results }) => {
31
25
  const res: Array<any> = results;
@@ -33,34 +27,26 @@ class ProdOpencareRepository extends OpencareRepository {
33
27
  });
34
28
  }
35
29
 
36
- async getAppointments(
37
- patientUuid: string,
38
- doctor?: string
39
- ): Promise<Array<any>> {
30
+ async getAppointments(patientUuid: string, doctor?: string): Promise<Array<any>> {
40
31
  let myHeaders = new Headers();
41
- myHeaders.append("Accept", "application/json,");
42
- myHeaders.append("Content-Type", "application/json");
32
+ myHeaders.append('Accept', 'application/json,');
33
+ myHeaders.append('Content-Type', 'application/json');
43
34
  //myHeaders.append("Authorization", `Basic ${TALK_BASE64}`);
44
35
 
45
36
  let requestOptions = {
46
- method: "GET",
37
+ method: 'GET',
47
38
  headers: myHeaders,
48
39
  };
49
40
 
50
41
  return await fetch(
51
- `${env.API_BASE_URL}/patient/${patientUuid}/appointment${
52
- doctor ? `?doctor=${doctor}` : ""
53
- }`,
54
- requestOptions
42
+ `${env.API_BASE_URL}/patient/${patientUuid}/appointment${doctor ? `?doctor=${doctor}` : ''}`,
43
+ requestOptions,
55
44
  )
56
45
  .then((response) => {
57
46
  if (response.ok) {
58
47
  return response.json();
59
48
  }
60
- throw new BadResponse(
61
- `Impossible de recupérer les rencontres (${response.status})`,
62
- "Opencare"
63
- );
49
+ throw new BadResponse(`Impossible de recupérer les rencontres (${response.status})`, 'Opentms');
64
50
  })
65
51
  .then(({ results }) => {
66
52
  const res: Array<any> = results;
@@ -68,11 +54,11 @@ class ProdOpencareRepository extends OpencareRepository {
68
54
  return {
69
55
  uuid: appointment.uuid,
70
56
  startDateTime: appointment.startDateTime,
71
- location: "",
57
+ location: '',
72
58
  service: appointment.service,
73
59
  status: appointment.statusProgress,
74
- appointmentKind: "Scheduled",
75
- comments: "",
60
+ appointmentKind: 'Scheduled',
61
+ comments: '',
76
62
  linkRoom: appointment.linkRoom,
77
63
  patientUuid,
78
64
  };
@@ -82,25 +68,19 @@ class ProdOpencareRepository extends OpencareRepository {
82
68
 
83
69
  async rejectDemand(demand_id: string): Promise<void> {
84
70
  let myHeaders = new Headers();
85
- myHeaders.append("Accept", "application/json,");
86
- myHeaders.append("Content-Type", "application/json");
71
+ myHeaders.append('Accept', 'application/json,');
72
+ myHeaders.append('Content-Type', 'application/json');
87
73
  //myHeaders.append("Authorization", `Basic ${TALK_BASE64}`);
88
74
 
89
75
  let requestOptions = {
90
- method: "PUT",
76
+ method: 'PUT',
91
77
  headers: myHeaders,
92
78
  };
93
- await fetch(
94
- `${env.API_BASE_URL}/demand/${demand_id}/reject`,
95
- requestOptions
96
- ).then((response) => {
79
+ await fetch(`${env.API_BASE_URL}/demand/${demand_id}/reject`, requestOptions).then((response) => {
97
80
  if (response.ok) {
98
81
  return response.json();
99
82
  }
100
- throw new BadResponse(
101
- `Impossible de rejeter la demande (${response.status})`,
102
- "Opencare"
103
- );
83
+ throw new BadResponse(`Impossible de rejeter la demande (${response.status})`, 'Opentms');
104
84
  });
105
85
  }
106
86
 
@@ -108,11 +88,11 @@ class ProdOpencareRepository extends OpencareRepository {
108
88
  demand_id: string,
109
89
  doctor_id: string,
110
90
  startDate: Date = new Date(),
111
- duration = 30
91
+ duration = 30,
112
92
  ): Promise<void> {
113
93
  const myHeaders = new Headers();
114
- myHeaders.append("Accept", "application/json,");
115
- myHeaders.append("Content-Type", "application/json");
94
+ myHeaders.append('Accept', 'application/json,');
95
+ myHeaders.append('Content-Type', 'application/json');
116
96
 
117
97
  const raw = JSON.stringify({
118
98
  doctor_id: doctor_id,
@@ -121,33 +101,27 @@ class ProdOpencareRepository extends OpencareRepository {
121
101
  });
122
102
 
123
103
  const requestOptions = {
124
- method: "POST",
104
+ method: 'POST',
125
105
  headers: myHeaders,
126
106
  body: raw,
127
107
  };
128
108
 
129
- await fetch(
130
- `${env.API_BASE_URL}/demand/${demand_id}/validate`,
131
- requestOptions
132
- ).then((response) => {
109
+ await fetch(`${env.API_BASE_URL}/demand/${demand_id}/validate`, requestOptions).then((response) => {
133
110
  if (response.ok) {
134
111
  return response.json();
135
112
  }
136
- throw new BadResponse(
137
- `Impossible de valider la demande (${response.status})`,
138
- "Opencare"
139
- );
113
+ throw new BadResponse(`Impossible de valider la demande (${response.status})`, 'Opentms');
140
114
  });
141
115
  }
142
116
 
143
117
  async getProviders(): Promise<Array<any>> {
144
118
  let myHeaders = new Headers();
145
- myHeaders.append("Accept", "application/json,");
146
- myHeaders.append("Content-Type", "application/json");
119
+ myHeaders.append('Accept', 'application/json,');
120
+ myHeaders.append('Content-Type', 'application/json');
147
121
  //myHeaders.append("Authorization", `Basic ${TALK_BASE64}`);
148
122
 
149
123
  let requestOptions = {
150
- method: "GET",
124
+ method: 'GET',
151
125
  headers: myHeaders,
152
126
  };
153
127
 
@@ -156,10 +130,7 @@ class ProdOpencareRepository extends OpencareRepository {
156
130
  if (response.ok) {
157
131
  return response.json();
158
132
  }
159
- throw new BadResponse(
160
- `Impossible de recupérer les rencontres (${response.status})`,
161
- "Opencare"
162
- );
133
+ throw new BadResponse(`Impossible de recupérer les rencontres (${response.status})`, 'Opentms');
163
134
  })
164
135
  .then(({ results }) => {
165
136
  const res: Array<any> = results;
@@ -173,4 +144,4 @@ class ProdOpencareRepository extends OpencareRepository {
173
144
  }
174
145
  }
175
146
 
176
- export default ProdOpencareRepository;
147
+ export default ProdOpentmsRepository;
@@ -1,14 +1,11 @@
1
1
  /* eslint-disable @typescript-eslint/no-empty-function */
2
2
 
3
- class OpencareRepository {
3
+ class OpentmsRepository {
4
4
  async getDemands(): Promise<Array<any>> {
5
5
  return [];
6
6
  }
7
7
 
8
- async getAppointments(
9
- patientUuid: string,
10
- doctor?: string
11
- ): Promise<Array<any>> {
8
+ async getAppointments(patientUuid: string, doctor?: string): Promise<Array<any>> {
12
9
  return [];
13
10
  }
14
11
 
@@ -18,7 +15,7 @@ class OpencareRepository {
18
15
  demand_id: string,
19
16
  doctor_id: string,
20
17
  startDate: Date = new Date(),
21
- duration = 30
18
+ duration = 30,
22
19
  ): Promise<void> {}
23
20
 
24
21
  async getProviders(): Promise<Array<any>> {
@@ -27,8 +24,8 @@ class OpencareRepository {
27
24
 
28
25
  // must be different for production for the duration of the appointment
29
26
  async getTokenNextcloud(username: string): Promise<any> {
30
- return "TALK_PASSWORD";
27
+ return 'TALK_PASSWORD';
31
28
  }
32
29
  }
33
30
 
34
- export default OpencareRepository;
31
+ export default OpentmsRepository;