main <- develop
main <- develop
This commit is contained in:
@@ -41,7 +41,7 @@ export default async function syncAnalysisGroups() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
console.info('Getting latest public message id');
|
console.info('Getting latest public message id');
|
||||||
const lastCheckedDate = await getLastCheckedDate();
|
// const lastCheckedDate = await getLastCheckedDate(); never used?
|
||||||
|
|
||||||
const latestMessage = await getLatestPublicMessageListItem();
|
const latestMessage = await getLatestPublicMessageListItem();
|
||||||
if (!latestMessage) {
|
if (!latestMessage) {
|
||||||
|
|||||||
@@ -81,21 +81,19 @@ export default async function syncConnectedOnline() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let clinics;
|
|
||||||
let services;
|
|
||||||
let serviceProviders;
|
|
||||||
let jobTitleTranslations;
|
|
||||||
// Filter out "Dentas Demo OÜ" in prod or only sync "Dentas Demo OÜ" in any other environment
|
// Filter out "Dentas Demo OÜ" in prod or only sync "Dentas Demo OÜ" in any other environment
|
||||||
const isDemoClinic = (clinicId: number) =>
|
const isDemoClinic = (clinicId: number) =>
|
||||||
isProd ? clinicId !== 2 : clinicId === 2;
|
isProd ? clinicId !== 2 : clinicId === 2;
|
||||||
clinics = responseData.Data.T_Lic.filter(({ ID }) => isDemoClinic(ID));
|
const clinics = responseData.Data.T_Lic.filter(({ ID }) =>
|
||||||
services = responseData.Data.T_Service.filter(({ ClinicID }) =>
|
isDemoClinic(ID),
|
||||||
|
);
|
||||||
|
const services = responseData.Data.T_Service.filter(({ ClinicID }) =>
|
||||||
isDemoClinic(ClinicID),
|
isDemoClinic(ClinicID),
|
||||||
);
|
);
|
||||||
serviceProviders = responseData.Data.T_Doctor.filter(({ ClinicID }) =>
|
const serviceProviders = responseData.Data.T_Doctor.filter(({ ClinicID }) =>
|
||||||
isDemoClinic(ClinicID),
|
isDemoClinic(ClinicID),
|
||||||
);
|
);
|
||||||
jobTitleTranslations = createTranslationMap(
|
const jobTitleTranslations = createTranslationMap(
|
||||||
responseData.Data.P_JobTitleTranslations.filter(({ ClinicID }) =>
|
responseData.Data.P_JobTitleTranslations.filter(({ ClinicID }) =>
|
||||||
isDemoClinic(ClinicID),
|
isDemoClinic(ClinicID),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export const POST = async (request: NextRequest) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
validateApiKey(request);
|
validateApiKey(request);
|
||||||
} catch (e) {
|
} catch {
|
||||||
return NextResponse.json({}, { status: 401, statusText: 'Unauthorized' });
|
return NextResponse.json({}, { status: 401, statusText: 'Unauthorized' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export const POST = async (request: NextRequest) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
validateApiKey(request);
|
validateApiKey(request);
|
||||||
} catch (e) {
|
} catch {
|
||||||
return NextResponse.json({}, { status: 401, statusText: 'Unauthorized' });
|
return NextResponse.json({}, { status: 401, statusText: 'Unauthorized' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export const POST = async (request: NextRequest) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
validateApiKey(request);
|
validateApiKey(request);
|
||||||
} catch (e) {
|
} catch {
|
||||||
return NextResponse.json({}, { status: 401, statusText: 'Unauthorized' });
|
return NextResponse.json({}, { status: 401, statusText: 'Unauthorized' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export const POST = async (request: NextRequest) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
validateApiKey(request);
|
validateApiKey(request);
|
||||||
} catch (e) {
|
} catch {
|
||||||
return NextResponse.json({}, { status: 401, statusText: 'Unauthorized' });
|
return NextResponse.json({}, { status: 401, statusText: 'Unauthorized' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export const POST = async (request: NextRequest) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
validateApiKey(request);
|
validateApiKey(request);
|
||||||
} catch (e) {
|
} catch {
|
||||||
return NextResponse.json({}, { status: 401, statusText: 'Unauthorized' });
|
return NextResponse.json({}, { status: 401, statusText: 'Unauthorized' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export const POST = async (request: NextRequest) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
validateApiKey(request);
|
validateApiKey(request);
|
||||||
} catch (e) {
|
} catch {
|
||||||
return NextResponse.json({}, { status: 401, statusText: 'Unauthorized' });
|
return NextResponse.json({}, { status: 401, statusText: 'Unauthorized' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export async function POST(request: NextRequest) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
validateApiKey(request);
|
validateApiKey(request);
|
||||||
} catch (e) {
|
} catch {
|
||||||
return NextResponse.json({}, { status: 401, statusText: 'Unauthorized' });
|
return NextResponse.json({}, { status: 401, statusText: 'Unauthorized' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import { withI18n } from '~/lib/i18n/with-i18n';
|
|
||||||
|
|
||||||
async function SiteLayout(props: React.PropsWithChildren) {
|
async function SiteLayout(props: React.PropsWithChildren) {
|
||||||
return (
|
return (
|
||||||
<div className={'flex min-h-[100vh] flex-col items-center justify-center'}>
|
<div className={'flex min-h-[100vh] flex-col items-center justify-center'}>
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { updateCustomer } from '@lib/data/customer';
|
|||||||
|
|
||||||
import { AccountSubmitData, createAuthApi } from '@kit/auth/api';
|
import { AccountSubmitData, createAuthApi } from '@kit/auth/api';
|
||||||
import { enhanceAction } from '@kit/next/actions';
|
import { enhanceAction } from '@kit/next/actions';
|
||||||
import { pathsConfig } from '@kit/shared/config';
|
|
||||||
import { getSupabaseServerClient } from '@kit/supabase/server-client';
|
import { getSupabaseServerClient } from '@kit/supabase/server-client';
|
||||||
|
|
||||||
import { UpdateAccountSchemaServer } from '../schemas/update-account.schema';
|
import { UpdateAccountSchemaServer } from '../schemas/update-account.schema';
|
||||||
|
|||||||
156
app/doctor/_components/analysis-feedback.tsx
Normal file
156
app/doctor/_components/analysis-feedback.tsx
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
|
import { giveFeedbackAction } from '@/packages/features/doctor/src/lib/server/actions/doctor-server-actions';
|
||||||
|
import {
|
||||||
|
DoctorFeedback,
|
||||||
|
Order,
|
||||||
|
Patient,
|
||||||
|
} from '@/packages/features/doctor/src/lib/server/schema/doctor-analysis-detail-view.schema';
|
||||||
|
import {
|
||||||
|
DoctorAnalysisFeedbackForm,
|
||||||
|
doctorAnalysisFeedbackFormSchema,
|
||||||
|
} from '@/packages/features/doctor/src/lib/server/schema/doctor-analysis.schema';
|
||||||
|
import ConfirmationModal from '@/packages/shared/src/components/confirmation-modal';
|
||||||
|
import { useUser } from '@/packages/supabase/src/hooks/use-user';
|
||||||
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
|
import { useForm } from 'react-hook-form';
|
||||||
|
|
||||||
|
import { Trans } from '@kit/ui/makerkit/trans';
|
||||||
|
import { Button } from '@kit/ui/shadcn/button';
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
FormControl,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormMessage,
|
||||||
|
} from '@kit/ui/shadcn/form';
|
||||||
|
import { toast } from '@kit/ui/shadcn/sonner';
|
||||||
|
import { Textarea } from '@kit/ui/shadcn/textarea';
|
||||||
|
|
||||||
|
const AnalysisFeedback = ({
|
||||||
|
feedback,
|
||||||
|
patient,
|
||||||
|
order,
|
||||||
|
}: {
|
||||||
|
feedback?: DoctorFeedback;
|
||||||
|
patient: Patient;
|
||||||
|
order: Order;
|
||||||
|
}) => {
|
||||||
|
const [isDraftSubmitting, setIsDraftSubmitting] = useState(false);
|
||||||
|
const [isConfirmOpen, setIsConfirmOpen] = useState(false);
|
||||||
|
const { data: user } = useUser();
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
const form = useForm({
|
||||||
|
resolver: zodResolver(doctorAnalysisFeedbackFormSchema),
|
||||||
|
reValidateMode: 'onChange',
|
||||||
|
defaultValues: {
|
||||||
|
feedbackValue: feedback?.value ?? '',
|
||||||
|
userId: patient.userId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const isReadOnly =
|
||||||
|
!!feedback?.doctor_user_id && feedback?.doctor_user_id !== user?.id;
|
||||||
|
|
||||||
|
const handleDraftSubmit = async (e: React.FormEvent) => {
|
||||||
|
setIsDraftSubmitting(true);
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
form.formState.errors.feedbackValue = undefined;
|
||||||
|
const formData = form.getValues();
|
||||||
|
await onSubmit(formData, 'DRAFT');
|
||||||
|
setIsDraftSubmitting(false);
|
||||||
|
};
|
||||||
|
const handleCompleteSubmit = form.handleSubmit(async () => {
|
||||||
|
setIsConfirmOpen(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
const onSubmit = async (
|
||||||
|
data: DoctorAnalysisFeedbackForm,
|
||||||
|
status: 'DRAFT' | 'COMPLETED',
|
||||||
|
) => {
|
||||||
|
const result = await giveFeedbackAction({
|
||||||
|
...data,
|
||||||
|
analysisOrderId: order.analysisOrderId,
|
||||||
|
status,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
return toast.error(<Trans i18nKey="common:genericServerError" />);
|
||||||
|
}
|
||||||
|
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
predicate: (query) => query.queryKey.includes('doctor-jobs'),
|
||||||
|
});
|
||||||
|
|
||||||
|
toast.success(<Trans i18nKey={'doctor:updateFeedbackSuccess'} />);
|
||||||
|
|
||||||
|
return setIsConfirmOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const confirmComplete = form.handleSubmit(async (data) => {
|
||||||
|
await onSubmit(data, 'COMPLETED');
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h3>
|
||||||
|
<Trans i18nKey="doctor:feedback" />
|
||||||
|
</h3>
|
||||||
|
<p>{feedback?.value ?? '-'}</p>
|
||||||
|
{!isReadOnly && (
|
||||||
|
<Form {...form}>
|
||||||
|
<form className="space-y-4 lg:w-1/2">
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="feedbackValue"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormControl>
|
||||||
|
<Textarea {...field} disabled={isReadOnly} />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="xs:flex block justify-end gap-2 space-y-2">
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="outline"
|
||||||
|
onClick={handleDraftSubmit}
|
||||||
|
disabled={
|
||||||
|
isReadOnly || isDraftSubmitting || form.formState.isSubmitting
|
||||||
|
}
|
||||||
|
className="xs:w-auto w-full text-xs"
|
||||||
|
>
|
||||||
|
<Trans i18nKey="common:saveAsDraft" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
onClick={handleCompleteSubmit}
|
||||||
|
disabled={
|
||||||
|
isReadOnly || isDraftSubmitting || form.formState.isSubmitting
|
||||||
|
}
|
||||||
|
className="xs:w-1/4 w-full"
|
||||||
|
>
|
||||||
|
<Trans i18nKey="common:save" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
)}
|
||||||
|
<ConfirmationModal
|
||||||
|
isOpen={isConfirmOpen}
|
||||||
|
onClose={() => setIsConfirmOpen(false)}
|
||||||
|
onConfirm={confirmComplete}
|
||||||
|
titleKey="doctor:confirmFeedbackModal.title"
|
||||||
|
descriptionKey="doctor:confirmFeedbackModal.description"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AnalysisFeedback;
|
||||||
@@ -1,13 +1,8 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useState } from 'react';
|
|
||||||
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
import { capitalize } from 'lodash';
|
import { capitalize } from 'lodash';
|
||||||
import { useForm } from 'react-hook-form';
|
|
||||||
|
|
||||||
import { giveFeedbackAction } from '@kit/doctor/actions/doctor-server-actions';
|
|
||||||
import {
|
import {
|
||||||
getDOBWithAgeStringFromPersonalCode,
|
getDOBWithAgeStringFromPersonalCode,
|
||||||
getResultSetName,
|
getResultSetName,
|
||||||
@@ -18,28 +13,14 @@ import {
|
|||||||
Order,
|
Order,
|
||||||
Patient,
|
Patient,
|
||||||
} from '@kit/doctor/schema/doctor-analysis-detail-view.schema';
|
} from '@kit/doctor/schema/doctor-analysis-detail-view.schema';
|
||||||
import {
|
|
||||||
DoctorAnalysisFeedbackForm,
|
|
||||||
doctorAnalysisFeedbackFormSchema,
|
|
||||||
} from '@kit/doctor/schema/doctor-analysis.schema';
|
|
||||||
import ConfirmationModal from '@kit/shared/components/confirmation-modal';
|
|
||||||
import { useCurrentLocaleLanguageNames } from '@kit/shared/hooks';
|
import { useCurrentLocaleLanguageNames } from '@kit/shared/hooks';
|
||||||
import { getFullName } from '@kit/shared/utils';
|
import { getFullName } from '@kit/shared/utils';
|
||||||
import { useUser } from '@kit/supabase/hooks/use-user';
|
import { useUser } from '@kit/supabase/hooks/use-user';
|
||||||
import { Button } from '@kit/ui/button';
|
|
||||||
import {
|
|
||||||
Form,
|
|
||||||
FormControl,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormMessage,
|
|
||||||
} from '@kit/ui/form';
|
|
||||||
import { toast } from '@kit/ui/sonner';
|
|
||||||
import { Textarea } from '@kit/ui/textarea';
|
|
||||||
import { Trans } from '@kit/ui/trans';
|
import { Trans } from '@kit/ui/trans';
|
||||||
|
|
||||||
import { bmiFromMetric } from '~/lib/utils';
|
import { bmiFromMetric } from '~/lib/utils';
|
||||||
|
|
||||||
|
import AnalysisFeedback from './analysis-feedback';
|
||||||
import DoctorAnalysisWrapper from './doctor-analysis-wrapper';
|
import DoctorAnalysisWrapper from './doctor-analysis-wrapper';
|
||||||
import DoctorJobSelect from './doctor-job-select';
|
import DoctorJobSelect from './doctor-job-select';
|
||||||
|
|
||||||
@@ -54,10 +35,8 @@ export default function AnalysisView({
|
|||||||
analyses: AnalysisResponse[];
|
analyses: AnalysisResponse[];
|
||||||
feedback?: DoctorFeedback;
|
feedback?: DoctorFeedback;
|
||||||
}) {
|
}) {
|
||||||
const [isConfirmOpen, setIsConfirmOpen] = useState(false);
|
|
||||||
const [isDraftSubmitting, setIsDraftSubmitting] = useState(false);
|
|
||||||
|
|
||||||
const { data: user } = useUser();
|
const { data: user } = useUser();
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const languageNames = useCurrentLocaleLanguageNames();
|
const languageNames = useCurrentLocaleLanguageNames();
|
||||||
|
|
||||||
@@ -68,65 +47,11 @@ export default function AnalysisView({
|
|||||||
);
|
);
|
||||||
const isCurrentDoctorJob =
|
const isCurrentDoctorJob =
|
||||||
!!feedback?.doctor_user_id && feedback?.doctor_user_id === user?.id;
|
!!feedback?.doctor_user_id && feedback?.doctor_user_id === user?.id;
|
||||||
const isReadOnly =
|
|
||||||
!!feedback?.doctor_user_id && feedback?.doctor_user_id !== user?.id;
|
|
||||||
|
|
||||||
const form = useForm({
|
|
||||||
resolver: zodResolver(doctorAnalysisFeedbackFormSchema),
|
|
||||||
reValidateMode: 'onChange',
|
|
||||||
defaultValues: {
|
|
||||||
feedbackValue: feedback?.value ?? '',
|
|
||||||
userId: patient.userId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
|
|
||||||
if (!patient || !order || !analyses) {
|
if (!patient || !order || !analyses) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const onSubmit = async (
|
|
||||||
data: DoctorAnalysisFeedbackForm,
|
|
||||||
status: 'DRAFT' | 'COMPLETED',
|
|
||||||
) => {
|
|
||||||
const result = await giveFeedbackAction({
|
|
||||||
...data,
|
|
||||||
analysisOrderId: order.analysisOrderId,
|
|
||||||
status,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!result.success) {
|
|
||||||
return toast.error(<Trans i18nKey="common:genericServerError" />);
|
|
||||||
}
|
|
||||||
|
|
||||||
queryClient.invalidateQueries({
|
|
||||||
predicate: (query) => query.queryKey.includes('doctor-jobs'),
|
|
||||||
});
|
|
||||||
|
|
||||||
toast.success(<Trans i18nKey={'doctor:updateFeedbackSuccess'} />);
|
|
||||||
|
|
||||||
return setIsConfirmOpen(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDraftSubmit = async (e: React.FormEvent) => {
|
|
||||||
setIsDraftSubmitting(true);
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
form.formState.errors.feedbackValue = undefined;
|
|
||||||
const formData = form.getValues();
|
|
||||||
await onSubmit(formData, 'DRAFT');
|
|
||||||
setIsDraftSubmitting(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCompleteSubmit = form.handleSubmit(async () => {
|
|
||||||
setIsConfirmOpen(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
const confirmComplete = form.handleSubmit(async (data) => {
|
|
||||||
await onSubmit(data, 'COMPLETED');
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="xs:flex xs:justify-between">
|
<div className="xs:flex xs:justify-between">
|
||||||
@@ -228,59 +153,9 @@ export default function AnalysisView({
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
<h3>
|
{order.isPackage && (
|
||||||
<Trans i18nKey="doctor:feedback" />
|
<AnalysisFeedback order={order} patient={patient} feedback={feedback} />
|
||||||
</h3>
|
|
||||||
<p>{feedback?.value ?? '-'}</p>
|
|
||||||
{!isReadOnly && (
|
|
||||||
<Form {...form}>
|
|
||||||
<form className="space-y-4 lg:w-1/2">
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="feedbackValue"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormControl>
|
|
||||||
<Textarea {...field} disabled={isReadOnly} />
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="xs:flex block justify-end gap-2 space-y-2">
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant="outline"
|
|
||||||
onClick={handleDraftSubmit}
|
|
||||||
disabled={
|
|
||||||
isReadOnly || isDraftSubmitting || form.formState.isSubmitting
|
|
||||||
}
|
|
||||||
className="xs:w-1/4 w-full"
|
|
||||||
>
|
|
||||||
<Trans i18nKey="common:saveAsDraft" />
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
onClick={handleCompleteSubmit}
|
|
||||||
disabled={
|
|
||||||
isReadOnly || isDraftSubmitting || form.formState.isSubmitting
|
|
||||||
}
|
|
||||||
className="xs:w-1/4 w-full"
|
|
||||||
>
|
|
||||||
<Trans i18nKey="common:save" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</Form>
|
|
||||||
)}
|
)}
|
||||||
<ConfirmationModal
|
|
||||||
isOpen={isConfirmOpen}
|
|
||||||
onClose={() => setIsConfirmOpen(false)}
|
|
||||||
onConfirm={confirmComplete}
|
|
||||||
titleKey="doctor:confirmFeedbackModal.title"
|
|
||||||
descriptionKey="doctor:confirmFeedbackModal.description"
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ export default async function AnalysisResultsPage({
|
|||||||
)}
|
)}
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
{orderedAnalysisElements ? (
|
{orderedAnalysisElements ? (
|
||||||
orderedAnalysisElements.map((element, index) => (
|
orderedAnalysisElements.map((element) => (
|
||||||
<React.Fragment key={element.analysisIdOriginal}>
|
<React.Fragment key={element.analysisIdOriginal}>
|
||||||
<Analysis element={element} />
|
<Analysis element={element} />
|
||||||
{element.results?.nestedElements?.map(
|
{element.results?.nestedElements?.map(
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import { use } from 'react';
|
|
||||||
|
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
|
||||||
import { createI18nServerInstance } from '@/lib/i18n/i18n.server';
|
import { createI18nServerInstance } from '@/lib/i18n/i18n.server';
|
||||||
|
|||||||
@@ -28,17 +28,18 @@ async function OrderAnalysisPackagePage() {
|
|||||||
<PageBody>
|
<PageBody>
|
||||||
<div className="space-y-3 text-center">
|
<div className="space-y-3 text-center">
|
||||||
<h3>
|
<h3>
|
||||||
<Trans i18nKey={'marketing:selectPackage'} />
|
<Trans i18nKey="order-analysis-package:selectPackage" />
|
||||||
</h3>
|
</h3>
|
||||||
<ComparePackagesModal
|
<ComparePackagesModal
|
||||||
analysisPackages={analysisPackages}
|
analysisPackages={analysisPackages}
|
||||||
analysisPackageElements={analysisPackageElements}
|
analysisPackageElements={analysisPackageElements}
|
||||||
triggerElement={
|
triggerElement={
|
||||||
<Button variant="secondary" className="gap-2">
|
<Button variant="secondary" className="gap-2">
|
||||||
<Trans i18nKey={'marketing:comparePackages'} />
|
<Trans i18nKey="order-analysis-package:comparePackages" />
|
||||||
<Scale className="size-4 stroke-[1.5px]" />
|
<Scale className="size-4 stroke-[1.5px]" />
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
|
countryCode={countryCode}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<SelectAnalysisPackages
|
<SelectAnalysisPackages
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export const BookingProvider: React.FC<{
|
|||||||
);
|
);
|
||||||
setTimeSlots(response.timeSlots);
|
setTimeSlots(response.timeSlots);
|
||||||
setLocations(response.locations);
|
setLocations(response.locations);
|
||||||
} catch (error) {
|
} catch {
|
||||||
setTimeSlots(null);
|
setTimeSlots(null);
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoadingTimeSlots(false);
|
setIsLoadingTimeSlots(false);
|
||||||
|
|||||||
@@ -0,0 +1,115 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
|
||||||
|
import { AnalysisPackageWithVariant } from '@/packages/shared/src/components/select-analysis-package';
|
||||||
|
import { pathsConfig } from '@/packages/shared/src/config';
|
||||||
|
|
||||||
|
import { Spinner } from '@kit/ui/makerkit/spinner';
|
||||||
|
import { Trans } from '@kit/ui/makerkit/trans';
|
||||||
|
import { Button } from '@kit/ui/shadcn/button';
|
||||||
|
import { toast } from '@kit/ui/shadcn/sonner';
|
||||||
|
import { Table, TableBody, TableCell, TableRow } from '@kit/ui/shadcn/table';
|
||||||
|
|
||||||
|
import { handleAddToCart } from '~/lib/services/medusaCart.service';
|
||||||
|
import { cn } from '~/lib/utils';
|
||||||
|
|
||||||
|
const AddToCartButton = ({
|
||||||
|
onClick,
|
||||||
|
disabled,
|
||||||
|
isLoading,
|
||||||
|
}: {
|
||||||
|
onClick: () => void;
|
||||||
|
disabled: boolean;
|
||||||
|
isLoading: boolean;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<TableCell align="center" className="xs:px-2 px-1 py-6">
|
||||||
|
<Button
|
||||||
|
onClick={onClick}
|
||||||
|
disabled={disabled}
|
||||||
|
className="xs:p-6 xs:text-sm relative p-2 text-[10px]"
|
||||||
|
>
|
||||||
|
{isLoading && (
|
||||||
|
<div className="absolute inset-0 flex items-center justify-center">
|
||||||
|
<Spinner />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div
|
||||||
|
className={cn({
|
||||||
|
invisible: isLoading,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<Trans i18nKey="compare-packages-modal:selectThisPackage" />
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
</TableCell>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ComparePackagesAddToCartButtons = ({
|
||||||
|
countryCode,
|
||||||
|
standardPackage,
|
||||||
|
standardPlusPackage,
|
||||||
|
premiumPackage,
|
||||||
|
}: {
|
||||||
|
countryCode: string;
|
||||||
|
standardPackage: AnalysisPackageWithVariant;
|
||||||
|
standardPlusPackage: AnalysisPackageWithVariant;
|
||||||
|
premiumPackage: AnalysisPackageWithVariant;
|
||||||
|
}) => {
|
||||||
|
const [addedPackage, setAddedPackage] = useState<string | null>(null);
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const handleSelect = async ({ variantId }: AnalysisPackageWithVariant) => {
|
||||||
|
setAddedPackage(variantId);
|
||||||
|
try {
|
||||||
|
await handleAddToCart({
|
||||||
|
selectedVariant: { id: variantId },
|
||||||
|
countryCode,
|
||||||
|
});
|
||||||
|
setAddedPackage(null);
|
||||||
|
toast.success(
|
||||||
|
<Trans i18nKey={'order-analysis-package:analysisPackageAddedToCart'} />,
|
||||||
|
);
|
||||||
|
router.push(pathsConfig.app.cart);
|
||||||
|
} catch (e) {
|
||||||
|
toast.error(
|
||||||
|
<Trans
|
||||||
|
i18nKey={'order-analysis-package:analysisPackageAddToCartError'}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
setAddedPackage(null);
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Table>
|
||||||
|
<TableBody>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell className="w-[30vw] py-6" />
|
||||||
|
<AddToCartButton
|
||||||
|
onClick={() => handleSelect(standardPackage)}
|
||||||
|
disabled={!!addedPackage}
|
||||||
|
isLoading={addedPackage === standardPackage.variantId}
|
||||||
|
/>
|
||||||
|
<AddToCartButton
|
||||||
|
onClick={() => handleSelect(standardPlusPackage)}
|
||||||
|
disabled={!!addedPackage}
|
||||||
|
isLoading={addedPackage === standardPlusPackage.variantId}
|
||||||
|
/>
|
||||||
|
<AddToCartButton
|
||||||
|
onClick={() => handleSelect(premiumPackage)}
|
||||||
|
disabled={!!addedPackage}
|
||||||
|
isLoading={addedPackage === premiumPackage.variantId}
|
||||||
|
/>
|
||||||
|
</TableRow>
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ComparePackagesAddToCartButtons;
|
||||||
@@ -26,6 +26,9 @@ import {
|
|||||||
import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
|
import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
|
||||||
import { withI18n } from '~/lib/i18n/with-i18n';
|
import { withI18n } from '~/lib/i18n/with-i18n';
|
||||||
|
|
||||||
|
import ComparePackagesAddToCartButtons from './compare-packages-add-to-cart-buttons';
|
||||||
|
import DefaultPackageFeaturesRows from './default-package-features-rows';
|
||||||
|
|
||||||
export type AnalysisPackageElement = Pick<
|
export type AnalysisPackageElement = Pick<
|
||||||
StoreProduct,
|
StoreProduct,
|
||||||
'title' | 'id' | 'description'
|
'title' | 'id' | 'description'
|
||||||
@@ -35,7 +38,7 @@ export type AnalysisPackageElement = Pick<
|
|||||||
isIncludedInPremium: boolean;
|
isIncludedInPremium: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const CheckWithBackground = () => {
|
export const CheckWithBackground = () => {
|
||||||
return (
|
return (
|
||||||
<div className="bg-primary w-min rounded-full p-1 text-white">
|
<div className="bg-primary w-min rounded-full p-1 text-white">
|
||||||
<Check className="size-3 stroke-2" />
|
<Check className="size-3 stroke-2" />
|
||||||
@@ -53,7 +56,7 @@ const PackageTableHead = async ({
|
|||||||
const { title, price, nrOfAnalyses } = product;
|
const { title, price, nrOfAnalyses } = product;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableHead className="py-2">
|
<TableHead className="xs:content-normal content-start py-2">
|
||||||
<PackageHeader
|
<PackageHeader
|
||||||
title={t(title)}
|
title={t(title)}
|
||||||
tagColor="bg-cyan"
|
tagColor="bg-cyan"
|
||||||
@@ -69,10 +72,12 @@ const ComparePackagesModal = async ({
|
|||||||
analysisPackages,
|
analysisPackages,
|
||||||
analysisPackageElements,
|
analysisPackageElements,
|
||||||
triggerElement,
|
triggerElement,
|
||||||
|
countryCode,
|
||||||
}: {
|
}: {
|
||||||
analysisPackages: AnalysisPackageWithVariant[];
|
analysisPackages: AnalysisPackageWithVariant[];
|
||||||
analysisPackageElements: AnalysisPackageElement[];
|
analysisPackageElements: AnalysisPackageElement[];
|
||||||
triggerElement: JSX.Element;
|
triggerElement: JSX.Element;
|
||||||
|
countryCode: string;
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = await createI18nServerInstance();
|
const { t } = await createI18nServerInstance();
|
||||||
|
|
||||||
@@ -110,7 +115,7 @@ const ComparePackagesModal = async ({
|
|||||||
<p className="text-muted-foreground mx-auto w-3/5 text-sm">
|
<p className="text-muted-foreground mx-auto w-3/5 text-sm">
|
||||||
{t('product:healthPackageComparison.description')}
|
{t('product:healthPackageComparison.description')}
|
||||||
</p>
|
</p>
|
||||||
<div className="max-h-[80vh] overflow-y-auto rounded-md border">
|
<div className="max-h-[50vh] overflow-y-auto rounded-md border sm:max-h-[70vh]">
|
||||||
<Table>
|
<Table>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
@@ -121,6 +126,8 @@ const ComparePackagesModal = async ({
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
|
<DefaultPackageFeaturesRows />
|
||||||
|
|
||||||
{analysisPackageElements.map(
|
{analysisPackageElements.map(
|
||||||
({
|
({
|
||||||
title,
|
title,
|
||||||
@@ -136,7 +143,7 @@ const ComparePackagesModal = async ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<TableRow key={id}>
|
<TableRow key={id}>
|
||||||
<TableCell className="py-6 sm:max-w-[30vw]">
|
<TableCell className="py-6 sm:w-[30vw]">
|
||||||
{title}{' '}
|
{title}{' '}
|
||||||
{description && (
|
{description && (
|
||||||
<InfoTooltip
|
<InfoTooltip
|
||||||
@@ -164,6 +171,12 @@ const ComparePackagesModal = async ({
|
|||||||
</Table>
|
</Table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<ComparePackagesAddToCartButtons
|
||||||
|
countryCode={countryCode}
|
||||||
|
standardPackage={standardPackage}
|
||||||
|
premiumPackage={premiumPackage}
|
||||||
|
standardPlusPackage={standardPlusPackage}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { Trans } from '@kit/ui/makerkit/trans';
|
||||||
|
import { TableCell, TableRow } from '@kit/ui/shadcn/table';
|
||||||
|
|
||||||
|
import { withI18n } from '~/lib/i18n/with-i18n';
|
||||||
|
|
||||||
|
import { CheckWithBackground } from './compare-packages-modal';
|
||||||
|
|
||||||
|
const DefaultPackageFeaturesRows = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<TableRow key="digital-doctor-feedback">
|
||||||
|
<TableCell className="max-w-[30vw] py-6">
|
||||||
|
<Trans i18nKey="order-analysis-package:digitalDoctorFeedback" />
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="center" className="py-6">
|
||||||
|
<CheckWithBackground />
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="center" className="py-6">
|
||||||
|
<CheckWithBackground />
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="center" className="py-6">
|
||||||
|
<CheckWithBackground />
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
|
||||||
|
<TableRow key="give-analyses">
|
||||||
|
<TableCell className="py-6 sm:max-w-[30vw]">
|
||||||
|
<Trans i18nKey="order-analysis-package:giveAnalyses" />
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="center" className="py-6">
|
||||||
|
<CheckWithBackground />
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="center" className="py-6">
|
||||||
|
<CheckWithBackground />
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="center" className="py-6">
|
||||||
|
<CheckWithBackground />
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withI18n(DefaultPackageFeaturesRows);
|
||||||
@@ -26,13 +26,6 @@ export async function HomeMenuNavigation(props: {
|
|||||||
const balanceSummary = workspace?.id
|
const balanceSummary = workspace?.id
|
||||||
? await getAccountBalanceSummary(workspace.id)
|
? await getAccountBalanceSummary(workspace.id)
|
||||||
: null;
|
: null;
|
||||||
const totalValue = props.cart?.total
|
|
||||||
? formatCurrency({
|
|
||||||
currencyCode: props.cart.currency_code,
|
|
||||||
locale: language,
|
|
||||||
value: props.cart.total,
|
|
||||||
})
|
|
||||||
: 0;
|
|
||||||
|
|
||||||
const cartQuantityTotal =
|
const cartQuantityTotal =
|
||||||
props.cart?.items?.reduce((acc, item) => acc + item.quantity, 0) ?? 0;
|
props.cart?.items?.reduce((acc, item) => acc + item.quantity, 0) ?? 0;
|
||||||
|
|||||||
@@ -25,16 +25,21 @@ import {
|
|||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from '@kit/ui/dropdown-menu';
|
} from '@kit/ui/dropdown-menu';
|
||||||
import { If } from '@kit/ui/if';
|
import { If } from '@kit/ui/if';
|
||||||
|
import { cn } from '@kit/ui/shadcn';
|
||||||
|
import { Avatar, AvatarFallback, AvatarImage } from '@kit/ui/shadcn/avatar';
|
||||||
|
import { Button } from '@kit/ui/shadcn/button';
|
||||||
import { Trans } from '@kit/ui/trans';
|
import { Trans } from '@kit/ui/trans';
|
||||||
|
|
||||||
// home imports
|
// home imports
|
||||||
import type { UserWorkspace } from '../_lib/server/load-user-workspace';
|
import type { UserWorkspace } from '../_lib/server/load-user-workspace';
|
||||||
|
|
||||||
|
const PERSONAL_ACCOUNT_SLUG = 'personal';
|
||||||
|
|
||||||
export function HomeMobileNavigation(props: {
|
export function HomeMobileNavigation(props: {
|
||||||
workspace: UserWorkspace;
|
workspace: UserWorkspace;
|
||||||
cart: StoreCart | null;
|
cart: StoreCart | null;
|
||||||
}) {
|
}) {
|
||||||
const user = props.workspace.user;
|
const { user, accounts } = props.workspace;
|
||||||
|
|
||||||
const signOut = useSignOut();
|
const signOut = useSignOut();
|
||||||
const { data: personalAccountData } = usePersonalAccountData(user.id);
|
const { data: personalAccountData } = usePersonalAccountData(user.id);
|
||||||
@@ -85,10 +90,28 @@ export function HomeMobileNavigation(props: {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger>
|
<div className="flex justify-between gap-4">
|
||||||
<Menu className={'h-9'} />
|
<Link href={pathsConfig.app.cart}>
|
||||||
</DropdownMenuTrigger>
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
className="relative mr-0 h-10 cursor-pointer border-1 px-4 py-2"
|
||||||
|
>
|
||||||
|
<ShoppingCart className="stroke-[1.5px]" />
|
||||||
|
{hasCartItems && (
|
||||||
|
<>
|
||||||
|
(
|
||||||
|
<span className="text-success font-bold">
|
||||||
|
{cartQuantityTotal}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
<DropdownMenuTrigger>
|
||||||
|
<Menu className="h-6 w-6" />
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
</div>
|
||||||
<DropdownMenuContent sideOffset={10} className={'w-screen rounded-none'}>
|
<DropdownMenuContent sideOffset={10} className={'w-screen rounded-none'}>
|
||||||
<If condition={props.cart && hasCartItems}>
|
<If condition={props.cart && hasCartItems}>
|
||||||
<DropdownMenuGroup>
|
<DropdownMenuGroup>
|
||||||
@@ -148,6 +171,46 @@ export function HomeMobileNavigation(props: {
|
|||||||
</If>
|
</If>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
|
|
||||||
|
<If condition={accounts.length > 0}>
|
||||||
|
<span className="text-muted-foreground px-2 text-xs">
|
||||||
|
<Trans
|
||||||
|
i18nKey={'teams:yourTeams'}
|
||||||
|
values={{ teamsCount: accounts.length }}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{accounts.map((account) => (
|
||||||
|
<DropdownMenuItem key={account.value} asChild>
|
||||||
|
<Link
|
||||||
|
className={'s-full flex cursor-pointer items-center space-x-2'}
|
||||||
|
href={`${pathsConfig.app.home}/${account.value}`}
|
||||||
|
>
|
||||||
|
<div className={'flex items-center'}>
|
||||||
|
<Avatar className={'h-5 w-5 rounded-xs ' + account.image}>
|
||||||
|
<AvatarImage
|
||||||
|
{...(account.image && { src: account.image })}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<AvatarFallback
|
||||||
|
className={cn('rounded-md', {
|
||||||
|
['bg-background']:
|
||||||
|
PERSONAL_ACCOUNT_SLUG === account.value,
|
||||||
|
['group-hover:bg-background']:
|
||||||
|
PERSONAL_ACCOUNT_SLUG !== account.value,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{account.label ? account.label[0] : ''}
|
||||||
|
</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
|
||||||
|
<span className={'pl-3'}>{account.label}</span>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
))}
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
</If>
|
||||||
|
|
||||||
<SignOutDropdownItem onSignOut={() => signOut.mutateAsync()} />
|
<SignOutDropdownItem onSignOut={() => signOut.mutateAsync()} />
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import Link from 'next/link';
|
|||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { pathsConfig } from '@/packages/shared/src/config';
|
import { pathsConfig } from '@/packages/shared/src/config';
|
||||||
import { ComponentInstanceIcon } from '@radix-ui/react-icons';
|
import { ComponentInstanceIcon } from '@radix-ui/react-icons';
|
||||||
import { ChevronRight, HeartPulse } from 'lucide-react';
|
import { ChevronRight } from 'lucide-react';
|
||||||
|
|
||||||
import { Button } from '@kit/ui/button';
|
import { Button } from '@kit/ui/button';
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { InfoTooltip } from '@/packages/shared/src/components/ui/info-tooltip';
|
import { InfoTooltip } from '@/packages/shared/src/components/ui/info-tooltip';
|
||||||
import { HeartPulse } from 'lucide-react';
|
|
||||||
|
|
||||||
import { Button } from '@kit/ui/shadcn/button';
|
import { Button } from '@kit/ui/shadcn/button';
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export async function createInitialReservationAction(
|
|||||||
|
|
||||||
export async function cancelTtoBooking(bookingCode: string, clinicId: number) {
|
export async function cancelTtoBooking(bookingCode: string, clinicId: number) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
await fetch(
|
||||||
`${process.env.CONNECTED_ONLINE_URL}/${ConnectedOnlineMethodName.ConfirmedCancel}`,
|
`${process.env.CONNECTED_ONLINE_URL}/${ConnectedOnlineMethodName.ConfirmedCancel}`,
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ export const isValidOpenAiEnv = async () => {
|
|||||||
const client = new OpenAI();
|
const client = new OpenAI();
|
||||||
await client.models.list();
|
await client.models.list();
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
import {
|
import { pathsConfig } from '@/packages/shared/src/config';
|
||||||
getTeamAccountSidebarConfig,
|
|
||||||
pathsConfig,
|
|
||||||
} from '@/packages/shared/src/config';
|
|
||||||
|
|
||||||
import { AppLogo } from '@kit/shared/components/app-logo';
|
import { AppLogo } from '@kit/shared/components/app-logo';
|
||||||
import { ProfileAccountDropdownContainer } from '@kit/shared/components/personal-account-dropdown-container';
|
import { ProfileAccountDropdownContainer } from '@kit/shared/components/personal-account-dropdown-container';
|
||||||
@@ -28,25 +25,6 @@ export function TeamAccountNavigationMenu(props: {
|
|||||||
[rawAccounts],
|
[rawAccounts],
|
||||||
);
|
);
|
||||||
|
|
||||||
const routes = getTeamAccountSidebarConfig(account.slug).routes.reduce<
|
|
||||||
Array<{
|
|
||||||
path: string;
|
|
||||||
label: string;
|
|
||||||
Icon?: React.ReactNode;
|
|
||||||
end?: boolean | ((path: string) => boolean);
|
|
||||||
}>
|
|
||||||
>((acc, item) => {
|
|
||||||
if ('children' in item) {
|
|
||||||
return [...acc, ...item.children];
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('divider' in item) {
|
|
||||||
return acc;
|
|
||||||
}
|
|
||||||
|
|
||||||
return [...acc, item];
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'flex w-full flex-1 justify-between'}>
|
<div className={'flex w-full flex-1 justify-between'}>
|
||||||
<div className={'flex items-center space-x-8'}>
|
<div className={'flex items-center space-x-8'}>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
|
|
||||||
import { redirect } from 'next/navigation';
|
import { redirect } from 'next/navigation';
|
||||||
|
|
||||||
@@ -43,11 +43,13 @@ export default function TeamAccountStatistics({
|
|||||||
accountBenefitStatistics,
|
accountBenefitStatistics,
|
||||||
expensesOverview,
|
expensesOverview,
|
||||||
}: TeamAccountStatisticsProps) {
|
}: TeamAccountStatisticsProps) {
|
||||||
const currentDate = new Date();
|
const date = useMemo<DateRange | undefined>(() => {
|
||||||
const [date, setDate] = useState<DateRange | undefined>({
|
const currentDate = new Date();
|
||||||
from: new Date(currentDate.getFullYear(), currentDate.getMonth(), 1),
|
return {
|
||||||
to: new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 0),
|
from: new Date(currentDate.getFullYear(), currentDate.getMonth(), 1),
|
||||||
});
|
to: new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 0),
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
const {
|
const {
|
||||||
i18n: { language },
|
i18n: { language },
|
||||||
} = useTranslation();
|
} = useTranslation();
|
||||||
|
|||||||
@@ -397,7 +397,7 @@ export async function readPrivateMessageResponse({
|
|||||||
try {
|
try {
|
||||||
analysisOrder = await getAnalysisOrder({ analysisOrderId });
|
analysisOrder = await getAnalysisOrder({ analysisOrderId });
|
||||||
medusaOrderId = analysisOrder.medusa_order_id;
|
medusaOrderId = analysisOrder.medusa_order_id;
|
||||||
} catch (e) {
|
} catch {
|
||||||
if (IS_ENABLED_DELETE_PRIVATE_MESSAGE) {
|
if (IS_ENABLED_DELETE_PRIVATE_MESSAGE) {
|
||||||
await deletePrivateMessage(privateMessageId);
|
await deletePrivateMessage(privateMessageId);
|
||||||
}
|
}
|
||||||
@@ -571,7 +571,6 @@ export async function sendOrderToMedipost({
|
|||||||
phone: account.phone ?? '',
|
phone: account.phone ?? '',
|
||||||
},
|
},
|
||||||
orderId: medreportOrder.id,
|
orderId: medreportOrder.id,
|
||||||
orderCreatedAt: new Date(medreportOrder.created_at),
|
|
||||||
comment: '',
|
comment: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ export async function composeOrderXML({
|
|||||||
analysisElements,
|
analysisElements,
|
||||||
person,
|
person,
|
||||||
orderId,
|
orderId,
|
||||||
orderCreatedAt,
|
|
||||||
comment,
|
comment,
|
||||||
}: {
|
}: {
|
||||||
analyses: AnalysesWithGroupsAndElements;
|
analyses: AnalysesWithGroupsAndElements;
|
||||||
@@ -45,7 +44,6 @@ export async function composeOrderXML({
|
|||||||
phone: string;
|
phone: string;
|
||||||
};
|
};
|
||||||
orderId: number;
|
orderId: number;
|
||||||
orderCreatedAt: Date;
|
|
||||||
comment?: string;
|
comment?: string;
|
||||||
}) {
|
}) {
|
||||||
const analysisGroups: Tables<{ schema: 'medreport' }, 'analysis_groups'>[] =
|
const analysisGroups: Tables<{ schema: 'medreport' }, 'analysis_groups'>[] =
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { loadCurrentUserAccount } from '@/app/home/(user)/_lib/server/load-user-account';
|
import { loadCurrentUserAccount } from '@/app/home/(user)/_lib/server/load-user-account';
|
||||||
import { MontonioOrderHandlerService } from '@/packages/billing/montonio/src';
|
import { MontonioOrderHandlerService } from '@/packages/billing/montonio/src';
|
||||||
|
import { getLogger } from '@/packages/shared/src/logger';
|
||||||
import { addToCart, deleteLineItem, retrieveCart } from '@lib/data/cart';
|
import { addToCart, deleteLineItem, retrieveCart } from '@lib/data/cart';
|
||||||
import { getCartId } from '@lib/data/cookies';
|
import { getCartId } from '@lib/data/cookies';
|
||||||
import { StoreCartLineItem, StoreProductVariant } from '@medusajs/types';
|
import { StoreCartLineItem, StoreProductVariant } from '@medusajs/types';
|
||||||
@@ -44,12 +45,17 @@ export async function handleAddToCart({
|
|||||||
selectedVariant: Pick<StoreProductVariant, 'id'>;
|
selectedVariant: Pick<StoreProductVariant, 'id'>;
|
||||||
countryCode: string;
|
countryCode: string;
|
||||||
}) {
|
}) {
|
||||||
try {
|
const logger = await getLogger();
|
||||||
} catch (e) {
|
const ctx = {
|
||||||
console.error('medusa card error: ', e);
|
countryCode,
|
||||||
}
|
selectedVariant,
|
||||||
|
};
|
||||||
|
|
||||||
|
logger.info(ctx, 'Adding to cart...');
|
||||||
|
|
||||||
const { account } = await loadCurrentUserAccount();
|
const { account } = await loadCurrentUserAccount();
|
||||||
if (!account) {
|
if (!account) {
|
||||||
|
logger.error(ctx, 'Account not found');
|
||||||
throw new Error('Account not found');
|
throw new Error('Account not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export async function renderBookTimeFailedEmail({
|
|||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Text>
|
<Text>
|
||||||
Broneeringu {reservationId} Connected Online'i saatmine ei
|
Broneeringu {reservationId} Connected Online'i saatmine ei
|
||||||
õnnestunud, kliendile tuleb teha tagasimakse.
|
õnnestunud, kliendile tuleb teha tagasimakse.
|
||||||
</Text>
|
</Text>
|
||||||
<Text>Saadud error: {error}</Text>
|
<Text>Saadud error: {error}</Text>
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ export async function renderNewJobsAvailableEmail({
|
|||||||
</Text>
|
</Text>
|
||||||
<ul className="list-none text-[16px] leading-[24px]">
|
<ul className="list-none text-[16px] leading-[24px]">
|
||||||
{analysisResponseIds.map((analysisResponseId, index) => (
|
{analysisResponseIds.map((analysisResponseId, index) => (
|
||||||
<li>
|
<li key={index}>
|
||||||
<Link
|
<Link
|
||||||
key={analysisResponseId}
|
key={analysisResponseId}
|
||||||
href={`${process.env.NEXT_PUBLIC_SITE_URL}/doctor/analysis/${analysisResponseId}`}
|
href={`${process.env.NEXT_PUBLIC_SITE_URL}/doctor/analysis/${analysisResponseId}`}
|
||||||
|
|||||||
@@ -43,17 +43,9 @@ export function PersonalAccountDropdown({
|
|||||||
showProfileName = true,
|
showProfileName = true,
|
||||||
paths,
|
paths,
|
||||||
features,
|
features,
|
||||||
account,
|
|
||||||
accounts = [],
|
accounts = [],
|
||||||
}: {
|
}: {
|
||||||
user: User;
|
user: User;
|
||||||
|
|
||||||
account?: {
|
|
||||||
id: string | null;
|
|
||||||
name: string | null;
|
|
||||||
picture_url: string | null;
|
|
||||||
application_role: ApplicationRole | null;
|
|
||||||
};
|
|
||||||
accounts: {
|
accounts: {
|
||||||
label: string | null;
|
label: string | null;
|
||||||
value: string | null;
|
value: string | null;
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { Tables } from '@kit/supabase/database';
|
|||||||
import { getSupabaseServerAdminClient } from '@kit/supabase/server-admin-client';
|
import { getSupabaseServerAdminClient } from '@kit/supabase/server-admin-client';
|
||||||
import { getSupabaseServerClient } from '@kit/supabase/server-client';
|
import { getSupabaseServerClient } from '@kit/supabase/server-client';
|
||||||
import { createTeamAccountsApi } from '@kit/team-accounts/api';
|
import { createTeamAccountsApi } from '@kit/team-accounts/api';
|
||||||
import { Alert, AlertDescription, AlertTitle } from '@kit/ui/alert';
|
|
||||||
import { AppBreadcrumbs } from '@kit/ui/app-breadcrumbs';
|
import { AppBreadcrumbs } from '@kit/ui/app-breadcrumbs';
|
||||||
import { Badge } from '@kit/ui/badge';
|
import { Badge } from '@kit/ui/badge';
|
||||||
import { Button } from '@kit/ui/button';
|
import { Button } from '@kit/ui/button';
|
||||||
@@ -12,14 +11,6 @@ import { Heading } from '@kit/ui/heading';
|
|||||||
import { If } from '@kit/ui/if';
|
import { If } from '@kit/ui/if';
|
||||||
import { PageBody, PageHeader } from '@kit/ui/page';
|
import { PageBody, PageHeader } from '@kit/ui/page';
|
||||||
import { ProfileAvatar } from '@kit/ui/profile-avatar';
|
import { ProfileAvatar } from '@kit/ui/profile-avatar';
|
||||||
import {
|
|
||||||
Table,
|
|
||||||
TableBody,
|
|
||||||
TableCell,
|
|
||||||
TableHead,
|
|
||||||
TableHeader,
|
|
||||||
TableRow,
|
|
||||||
} from '@kit/ui/table';
|
|
||||||
|
|
||||||
import { AdminBanUserDialog } from './admin-ban-user-dialog';
|
import { AdminBanUserDialog } from './admin-ban-user-dialog';
|
||||||
import { AdminDeleteAccountDialog } from './admin-delete-account-dialog';
|
import { AdminDeleteAccountDialog } from './admin-delete-account-dialog';
|
||||||
@@ -224,148 +215,6 @@ async function TeamAccountPage(props: {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function SubscriptionsTable(props: { accountId: string }) {
|
|
||||||
const client = getSupabaseServerClient();
|
|
||||||
|
|
||||||
const { data: subscription, error } = await client
|
|
||||||
.schema('medreport')
|
|
||||||
.from('subscriptions')
|
|
||||||
.select('*, subscription_items !inner (*)')
|
|
||||||
.eq('account_id', props.accountId)
|
|
||||||
.maybeSingle();
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
return (
|
|
||||||
<Alert variant={'destructive'}>
|
|
||||||
<AlertTitle>There was an error loading subscription.</AlertTitle>
|
|
||||||
|
|
||||||
<AlertDescription>
|
|
||||||
Please check the logs for more information or try again later.
|
|
||||||
</AlertDescription>
|
|
||||||
</Alert>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={'flex flex-col gap-y-1'}>
|
|
||||||
<Heading level={6}>Subscription</Heading>
|
|
||||||
|
|
||||||
<If
|
|
||||||
condition={subscription}
|
|
||||||
fallback={
|
|
||||||
<span className={'text-muted-foreground text-sm'}>
|
|
||||||
This account does not currently have a subscription.
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{(subscription) => {
|
|
||||||
return (
|
|
||||||
<div className={'flex flex-col space-y-4'}>
|
|
||||||
<Table>
|
|
||||||
<TableHeader>
|
|
||||||
<TableHead>Subscription ID</TableHead>
|
|
||||||
|
|
||||||
<TableHead>Provider</TableHead>
|
|
||||||
|
|
||||||
<TableHead>Customer ID</TableHead>
|
|
||||||
|
|
||||||
<TableHead>Status</TableHead>
|
|
||||||
|
|
||||||
<TableHead>Created At</TableHead>
|
|
||||||
|
|
||||||
<TableHead>Period Starts At</TableHead>
|
|
||||||
|
|
||||||
<TableHead>Ends At</TableHead>
|
|
||||||
</TableHeader>
|
|
||||||
|
|
||||||
<TableBody>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>
|
|
||||||
<span>{subscription.id}</span>
|
|
||||||
</TableCell>
|
|
||||||
|
|
||||||
<TableCell>
|
|
||||||
<span>{subscription.billing_provider}</span>
|
|
||||||
</TableCell>
|
|
||||||
|
|
||||||
<TableCell>
|
|
||||||
<span>{subscription.billing_customer_id}</span>
|
|
||||||
</TableCell>
|
|
||||||
|
|
||||||
<TableCell>
|
|
||||||
<span>{subscription.status}</span>
|
|
||||||
</TableCell>
|
|
||||||
|
|
||||||
<TableCell>
|
|
||||||
<span>{subscription.created_at}</span>
|
|
||||||
</TableCell>
|
|
||||||
|
|
||||||
<TableCell>
|
|
||||||
<span>{subscription.period_starts_at}</span>
|
|
||||||
</TableCell>
|
|
||||||
|
|
||||||
<TableCell>
|
|
||||||
<span>{subscription.period_ends_at}</span>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
|
|
||||||
<Table>
|
|
||||||
<TableHeader>
|
|
||||||
<TableHead>Product ID</TableHead>
|
|
||||||
|
|
||||||
<TableHead>Variant ID</TableHead>
|
|
||||||
|
|
||||||
<TableHead>Quantity</TableHead>
|
|
||||||
|
|
||||||
<TableHead>Price</TableHead>
|
|
||||||
|
|
||||||
<TableHead>Interval</TableHead>
|
|
||||||
|
|
||||||
<TableHead>Type</TableHead>
|
|
||||||
</TableHeader>
|
|
||||||
|
|
||||||
<TableBody>
|
|
||||||
{subscription.subscription_items.map((item) => {
|
|
||||||
return (
|
|
||||||
<TableRow key={item.variant_id}>
|
|
||||||
<TableCell>
|
|
||||||
<span>{item.product_id}</span>
|
|
||||||
</TableCell>
|
|
||||||
|
|
||||||
<TableCell>
|
|
||||||
<span>{item.variant_id}</span>
|
|
||||||
</TableCell>
|
|
||||||
|
|
||||||
<TableCell>
|
|
||||||
<span>{item.quantity}</span>
|
|
||||||
</TableCell>
|
|
||||||
|
|
||||||
<TableCell>
|
|
||||||
<span>{item.price_amount}</span>
|
|
||||||
</TableCell>
|
|
||||||
|
|
||||||
<TableCell>
|
|
||||||
<span>{item.interval}</span>
|
|
||||||
</TableCell>
|
|
||||||
|
|
||||||
<TableCell>
|
|
||||||
<span>{item.type}</span>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</If>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getMemberships(userId: string) {
|
async function getMemberships(userId: string) {
|
||||||
const client = getSupabaseServerClient();
|
const client = getSupabaseServerClient();
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export function MultiFactorChallengeContainer({
|
|||||||
try {
|
try {
|
||||||
await fetch('/api/after-mfa', { method: 'POST' });
|
await fetch('/api/after-mfa', { method: 'POST' });
|
||||||
router.replace(paths.redirectPath);
|
router.replace(paths.redirectPath);
|
||||||
} catch (err) {
|
} catch {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ export const AnalysisResponseSchema = z.object({
|
|||||||
export type AnalysisResponse = z.infer<typeof AnalysisResponseSchema>;
|
export type AnalysisResponse = z.infer<typeof AnalysisResponseSchema>;
|
||||||
|
|
||||||
export const AnalysisResultDetailsSchema = z.object({
|
export const AnalysisResultDetailsSchema = z.object({
|
||||||
analysisResponse: z.array(AnalysisResponseSchema).nullable(),
|
analysisResponse: z.array(AnalysisResponseSchema),
|
||||||
order: OrderSchema,
|
order: OrderSchema,
|
||||||
doctorFeedback: DoctorFeedbackSchema,
|
doctorFeedback: DoctorFeedbackSchema,
|
||||||
patient: PatientSchema,
|
patient: PatientSchema,
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import { sdk } from '@lib/config';
|
|||||||
import medusaError from '@lib/util/medusa-error';
|
import medusaError from '@lib/util/medusa-error';
|
||||||
import { HttpTypes, StoreCart } from '@medusajs/types';
|
import { HttpTypes, StoreCart } from '@medusajs/types';
|
||||||
|
|
||||||
|
import { getLogger } from '@kit/shared/logger';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getAuthHeaders,
|
getAuthHeaders,
|
||||||
getCacheOptions,
|
getCacheOptions,
|
||||||
@@ -135,13 +137,21 @@ export async function addToCart({
|
|||||||
quantity: number;
|
quantity: number;
|
||||||
countryCode: string;
|
countryCode: string;
|
||||||
}) {
|
}) {
|
||||||
|
const logger = await getLogger();
|
||||||
|
const ctx = {
|
||||||
|
variantId,
|
||||||
|
quantity,
|
||||||
|
countryCode,
|
||||||
|
};
|
||||||
if (!variantId) {
|
if (!variantId) {
|
||||||
|
logger.error(ctx, 'Missing variant ID when adding to cart');
|
||||||
throw new Error('Missing variant ID when adding to cart');
|
throw new Error('Missing variant ID when adding to cart');
|
||||||
}
|
}
|
||||||
|
|
||||||
const cart = await getOrSetCart(countryCode);
|
const cart = await getOrSetCart(countryCode);
|
||||||
|
|
||||||
if (!cart) {
|
if (!cart) {
|
||||||
|
logger.error(ctx, 'Error retrieving or creating cart');
|
||||||
throw new Error('Error retrieving or creating cart');
|
throw new Error('Error retrieving or creating cart');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export function CompanyGuard<Params extends object>(
|
|||||||
Component: LayoutOrPageComponent<Params>,
|
Component: LayoutOrPageComponent<Params>,
|
||||||
) {
|
) {
|
||||||
return async function AdminGuardServerComponentWrapper(params: Params) {
|
return async function AdminGuardServerComponentWrapper(params: Params) {
|
||||||
//@ts-ignore
|
// @ts-expect-error incorrectly typed params
|
||||||
const { account } = await params.params;
|
const { account } = await params.params;
|
||||||
const client = getSupabaseServerClient();
|
const client = getSupabaseServerClient();
|
||||||
const [isUserSuperAdmin, isUserCompanyAdmin] = await Promise.all([
|
const [isUserSuperAdmin, isUserCompanyAdmin] = await Promise.all([
|
||||||
|
|||||||
@@ -149,7 +149,6 @@ export const updateInvitationAction = enhanceAction(
|
|||||||
export const acceptInvitationAction = enhanceAction(
|
export const acceptInvitationAction = enhanceAction(
|
||||||
async (data: FormData, user) => {
|
async (data: FormData, user) => {
|
||||||
const client = getSupabaseServerClient();
|
const client = getSupabaseServerClient();
|
||||||
const accountBalanceService = new AccountBalanceService();
|
|
||||||
|
|
||||||
const { inviteToken, nextPath } = AcceptInvitationSchema.parse(
|
const { inviteToken, nextPath } = AcceptInvitationSchema.parse(
|
||||||
Object.fromEntries(data),
|
Object.fromEntries(data),
|
||||||
|
|||||||
@@ -18,14 +18,16 @@ export const PackageHeader = ({
|
|||||||
return (
|
return (
|
||||||
<div className="space-y-1 text-center">
|
<div className="space-y-1 text-center">
|
||||||
<p className="text-sm sm:text-lg sm:font-medium">{title}</p>
|
<p className="text-sm sm:text-lg sm:font-medium">{title}</p>
|
||||||
<h2 className="text-xl sm:text-4xl">
|
<h2 className="xs:text-xl text-lg sm:text-4xl">
|
||||||
{formatCurrency({
|
{formatCurrency({
|
||||||
currencyCode: 'eur',
|
currencyCode: 'eur',
|
||||||
locale: language,
|
locale: language,
|
||||||
value: price,
|
value: price,
|
||||||
})}
|
})}
|
||||||
</h2>
|
</h2>
|
||||||
<Badge className={cn('text-xs', tagColor)}>{analysesNr}</Badge>
|
<Badge className={cn('xs:text-xs text-[10px]', tagColor)}>
|
||||||
|
{analysesNr}
|
||||||
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -46,11 +46,10 @@ export function ProfileAccountDropdownContainer(props: {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<PersonalAccountDropdown
|
<PersonalAccountDropdown
|
||||||
className={'w-full'}
|
className="w-full"
|
||||||
paths={paths}
|
paths={paths}
|
||||||
features={features}
|
features={features}
|
||||||
user={userData}
|
user={userData}
|
||||||
account={props.account}
|
|
||||||
accounts={props.accounts}
|
accounts={props.accounts}
|
||||||
signOutRequested={() => signOut.mutateAsync()}
|
signOutRequested={() => signOut.mutateAsync()}
|
||||||
showProfileName={props.showProfileName}
|
showProfileName={props.showProfileName}
|
||||||
|
|||||||
@@ -117,6 +117,7 @@ export default function SelectAnalysisPackage({
|
|||||||
<Button
|
<Button
|
||||||
className="w-full text-[10px] sm:text-sm"
|
className="w-full text-[10px] sm:text-sm"
|
||||||
onClick={handleSelect}
|
onClick={handleSelect}
|
||||||
|
disabled={isAddingToCart}
|
||||||
>
|
>
|
||||||
{isAddingToCart ? (
|
{isAddingToCart ? (
|
||||||
<Spinner />
|
<Spinner />
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { JSX, ReactNode } from 'react';
|
import React, { JSX } from 'react';
|
||||||
|
|
||||||
import { cn } from '@kit/ui/utils';
|
import { cn } from '@kit/ui/utils';
|
||||||
|
|
||||||
|
|||||||
@@ -72,7 +72,7 @@
|
|||||||
"myOrders": "My orders",
|
"myOrders": "My orders",
|
||||||
"analysisResults": "Analysis results",
|
"analysisResults": "Analysis results",
|
||||||
"orderAnalysisPackage": "Order analysis package",
|
"orderAnalysisPackage": "Order analysis package",
|
||||||
"orderAnalysis": "Order analysis",
|
"orderAnalysis": "Order single analysis",
|
||||||
"orderHealthAnalysis": "Order health check",
|
"orderHealthAnalysis": "Order health check",
|
||||||
"account": "Account",
|
"account": "Account",
|
||||||
"companyMembers": "Manage employees",
|
"companyMembers": "Manage employees",
|
||||||
|
|||||||
@@ -5,5 +5,7 @@
|
|||||||
"selectPackage": "Select package",
|
"selectPackage": "Select package",
|
||||||
"comparePackages": "Compare packages",
|
"comparePackages": "Compare packages",
|
||||||
"analysisPackageAddedToCart": "Analysis package added to cart",
|
"analysisPackageAddedToCart": "Analysis package added to cart",
|
||||||
"analysisPackageAddToCartError": "Adding analysis package to cart failed"
|
"analysisPackageAddToCartError": "Adding analysis package to cart failed",
|
||||||
|
"digitalDoctorFeedback": "Digital doctor feedback",
|
||||||
|
"giveAnalyses": "Analyses"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,7 +72,7 @@
|
|||||||
"myOrders": "Minu tellimused",
|
"myOrders": "Minu tellimused",
|
||||||
"analysisResults": "Analüüside vastused",
|
"analysisResults": "Analüüside vastused",
|
||||||
"orderAnalysisPackage": "Telli analüüside pakett",
|
"orderAnalysisPackage": "Telli analüüside pakett",
|
||||||
"orderAnalysis": "Telli analüüs",
|
"orderAnalysis": "Telli üksikanalüüs",
|
||||||
"orderHealthAnalysis": "Telli terviseuuring",
|
"orderHealthAnalysis": "Telli terviseuuring",
|
||||||
"account": "Konto",
|
"account": "Konto",
|
||||||
"companyMembers": "Töötajate haldamine",
|
"companyMembers": "Töötajate haldamine",
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
"recommendedForYou": "Soovitused sulle",
|
"recommendedForYou": "Soovitused sulle",
|
||||||
"heroCard": {
|
"heroCard": {
|
||||||
"orderAnalysis": {
|
"orderAnalysis": {
|
||||||
"title": "Telli analüüs",
|
"title": "Telli üksikanalüüs",
|
||||||
"description": "Telli endale sobiv analüüs"
|
"description": "Telli endale sobiv analüüs"
|
||||||
},
|
},
|
||||||
"benefits": {
|
"benefits": {
|
||||||
|
|||||||
@@ -5,5 +5,7 @@
|
|||||||
"selectPackage": "Vali pakett",
|
"selectPackage": "Vali pakett",
|
||||||
"comparePackages": "Võrdle pakette",
|
"comparePackages": "Võrdle pakette",
|
||||||
"analysisPackageAddedToCart": "Analüüsi pakett lisatud ostukorvi",
|
"analysisPackageAddedToCart": "Analüüsi pakett lisatud ostukorvi",
|
||||||
"analysisPackageAddToCartError": "Analüüsi paketi lisamine ostukorvi ebaõnnestus"
|
"analysisPackageAddToCartError": "Analüüsi paketi lisamine ostukorvi ebaõnnestus",
|
||||||
|
"digitalDoctorFeedback": "Digitaalne arsti kokkuvõte",
|
||||||
|
"giveAnalyses": "Analüüside võtmine"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,5 +5,7 @@
|
|||||||
"selectPackage": "Выберите пакет",
|
"selectPackage": "Выберите пакет",
|
||||||
"comparePackages": "Сравнить пакеты",
|
"comparePackages": "Сравнить пакеты",
|
||||||
"analysisPackageAddedToCart": "Пакет анализов добавлен в корзину",
|
"analysisPackageAddedToCart": "Пакет анализов добавлен в корзину",
|
||||||
"analysisPackageAddToCartError": "Не удалось добавить пакет анализов в корзину"
|
"analysisPackageAddToCartError": "Не удалось добавить пакет анализов в корзину",
|
||||||
|
"digitalDoctorFeedback": "Digital doctor feedback",
|
||||||
|
"giveAnalyses": "Analyses"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
grant execute on function medreport.update_analysis_order_status (
|
||||||
|
bigint,
|
||||||
|
text,
|
||||||
|
medreport.analysis_order_status
|
||||||
|
) to authenticated, service_role;
|
||||||
|
|
||||||
|
create policy "doctor_update_analysis_order"
|
||||||
|
on "medreport"."analysis_orders"
|
||||||
|
as PERMISSIVE
|
||||||
|
for UPDATE
|
||||||
|
to authenticated
|
||||||
|
using (medreport.is_doctor());
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
DO $$
|
||||||
|
DECLARE
|
||||||
|
target_schema text := 'public';
|
||||||
|
table_name text;
|
||||||
|
BEGIN
|
||||||
|
FOR table_name IN
|
||||||
|
SELECT c.relname
|
||||||
|
FROM pg_class c
|
||||||
|
JOIN pg_namespace n ON n.oid = c.relnamespace
|
||||||
|
WHERE n.nspname = target_schema
|
||||||
|
AND c.relkind = 'r'
|
||||||
|
LOOP
|
||||||
|
EXECUTE format('ALTER TABLE %I.%I ENABLE ROW LEVEL SECURITY;', target_schema, table_name);
|
||||||
|
END LOOP;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
REVOKE USAGE ON SCHEMA public FROM anon, authenticated;
|
||||||
Reference in New Issue
Block a user