diff --git a/app/admin/_components/admin-sidebar.tsx b/app/admin/_components/admin-sidebar.tsx
index e66c622..0492b98 100644
--- a/app/admin/_components/admin-sidebar.tsx
+++ b/app/admin/_components/admin-sidebar.tsx
@@ -19,8 +19,8 @@ import {
useSidebar,
} from '@kit/ui/shadcn-sidebar';
-import { AppLogo } from '~/components/app-logo';
-import { ProfileAccountDropdownContainer } from '~/components/personal-account-dropdown-container';
+import { AppLogo } from '@kit/shared/components/app-logo';
+import { ProfileAccountDropdownContainer } from '@kit/shared/components/personal-account-dropdown-container';
export function AdminSidebar({
accounts,
diff --git a/app/api/billing/webhook/route.ts b/app/api/billing/webhook/route.ts
index a38754a..d641654 100644
--- a/app/api/billing/webhook/route.ts
+++ b/app/api/billing/webhook/route.ts
@@ -4,7 +4,7 @@ import { enhanceRouteHandler } from '@kit/next/routes';
import { getLogger } from '@kit/shared/logger';
import { getSupabaseServerAdminClient } from '@kit/supabase/server-admin-client';
-import billingConfig from '~/config/billing.config';
+import { billingConfig } from '@kit/shared/config';
/**
* @description Handle the webhooks from Stripe related to checkouts
diff --git a/app/auth/callback/error/page.tsx b/app/auth/callback/error/page.tsx
index ef70846..1d10580 100644
--- a/app/auth/callback/error/page.tsx
+++ b/app/auth/callback/error/page.tsx
@@ -7,9 +7,11 @@ import { Alert, AlertDescription, AlertTitle } from '@kit/ui/alert';
import { Button } from '@kit/ui/button';
import { Trans } from '@kit/ui/trans';
-import pathsConfig from '~/config/paths.config';
+import { pathsConfig } from '@kit/shared/config';
+
import { withI18n } from '~/lib/i18n/with-i18n';
+
interface AuthCallbackErrorPageProps {
searchParams: Promise<{
error: string;
diff --git a/app/auth/callback/layout.tsx b/app/auth/callback/layout.tsx
index ff7b33b..1efc0a4 100644
--- a/app/auth/callback/layout.tsx
+++ b/app/auth/callback/layout.tsx
@@ -1,6 +1,6 @@
import { AuthLayoutShell } from '@kit/auth/shared';
-import { AppLogo } from '~/components/app-logo';
+import { AppLogo } from '@kit/shared/components/app-logo';
function AuthLayout({ children }: React.PropsWithChildren) {
return
;
diff --git a/app/auth/callback/route.ts b/app/auth/callback/route.ts
index e8aa56a..0786a08 100644
--- a/app/auth/callback/route.ts
+++ b/app/auth/callback/route.ts
@@ -4,7 +4,8 @@ import type { NextRequest } from 'next/server';
import { createAuthCallbackService } from '@kit/supabase/auth';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
-import pathsConfig from '~/config/paths.config';
+import { pathsConfig } from '@kit/shared/config';
+
export async function GET(request: NextRequest) {
const service = createAuthCallbackService(getSupabaseServerClient());
diff --git a/app/auth/confirm/layout.tsx b/app/auth/confirm/layout.tsx
index ff7b33b..1efc0a4 100644
--- a/app/auth/confirm/layout.tsx
+++ b/app/auth/confirm/layout.tsx
@@ -1,6 +1,6 @@
import { AuthLayoutShell } from '@kit/auth/shared';
-import { AppLogo } from '~/components/app-logo';
+import { AppLogo } from '@kit/shared/components/app-logo';
function AuthLayout({ children }: React.PropsWithChildren) {
return
;
diff --git a/app/auth/confirm/route.ts b/app/auth/confirm/route.ts
index ac5cc8b..db0ef3f 100644
--- a/app/auth/confirm/route.ts
+++ b/app/auth/confirm/route.ts
@@ -3,7 +3,8 @@ import { NextRequest, NextResponse } from 'next/server';
import { createAuthCallbackService } from '@kit/supabase/auth';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
-import pathsConfig from '~/config/paths.config';
+import { pathsConfig } from '@kit/shared/config';
+
export async function GET(request: NextRequest) {
const service = createAuthCallbackService(getSupabaseServerClient());
diff --git a/app/auth/membership-confirmation/_components/membership-confirmation-notification.tsx b/app/auth/membership-confirmation/_components/membership-confirmation-notification.tsx
index bd681a5..4229e32 100644
--- a/app/auth/membership-confirmation/_components/membership-confirmation-notification.tsx
+++ b/app/auth/membership-confirmation/_components/membership-confirmation-notification.tsx
@@ -2,7 +2,7 @@
import React from 'react';
-import pathsConfig from '@/config/paths.config';
+import { pathsConfig } from '@kit/shared/config';
import { useTranslation } from 'react-i18next';
import { usePersonalAccountData } from '@kit/accounts/hooks/use-personal-account-data';
diff --git a/app/auth/membership-confirmation/layout.tsx b/app/auth/membership-confirmation/layout.tsx
index e3d32c7..c024555 100644
--- a/app/auth/membership-confirmation/layout.tsx
+++ b/app/auth/membership-confirmation/layout.tsx
@@ -1,5 +1,6 @@
import { withI18n } from '~/lib/i18n/with-i18n';
+
async function SiteLayout(props: React.PropsWithChildren) {
return (
diff --git a/app/auth/membership-confirmation/page.tsx b/app/auth/membership-confirmation/page.tsx
index c6aef9b..f4ae5fe 100644
--- a/app/auth/membership-confirmation/page.tsx
+++ b/app/auth/membership-confirmation/page.tsx
@@ -1,7 +1,6 @@
import { redirect } from 'next/navigation';
-import pathsConfig from '@/config/paths.config';
-
+import { pathsConfig } from '@kit/shared/config';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
import { withI18n } from '~/lib/i18n/with-i18n';
diff --git a/app/auth/password-reset/layout.tsx b/app/auth/password-reset/layout.tsx
index ff7b33b..1efc0a4 100644
--- a/app/auth/password-reset/layout.tsx
+++ b/app/auth/password-reset/layout.tsx
@@ -1,6 +1,6 @@
import { AuthLayoutShell } from '@kit/auth/shared';
-import { AppLogo } from '~/components/app-logo';
+import { AppLogo } from '@kit/shared/components/app-logo';
function AuthLayout({ children }: React.PropsWithChildren) {
return
{children};
diff --git a/app/auth/password-reset/page.tsx b/app/auth/password-reset/page.tsx
index 1447373..aaa1a5f 100644
--- a/app/auth/password-reset/page.tsx
+++ b/app/auth/password-reset/page.tsx
@@ -5,10 +5,12 @@ import { Button } from '@kit/ui/button';
import { Heading } from '@kit/ui/heading';
import { Trans } from '@kit/ui/trans';
-import pathsConfig from '~/config/paths.config';
+import { pathsConfig } from '@kit/shared/config';
+
import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
import { withI18n } from '~/lib/i18n/with-i18n';
+
export const generateMetadata = async () => {
const { t } = await createI18nServerInstance();
diff --git a/app/auth/sign-in/layout.tsx b/app/auth/sign-in/layout.tsx
index ff7b33b..1efc0a4 100644
--- a/app/auth/sign-in/layout.tsx
+++ b/app/auth/sign-in/layout.tsx
@@ -1,6 +1,6 @@
import { AuthLayoutShell } from '@kit/auth/shared';
-import { AppLogo } from '~/components/app-logo';
+import { AppLogo } from '@kit/shared/components/app-logo';
function AuthLayout({ children }: React.PropsWithChildren) {
return
{children};
diff --git a/app/auth/sign-in/page.tsx b/app/auth/sign-in/page.tsx
index 4fc9223..3728b38 100644
--- a/app/auth/sign-in/page.tsx
+++ b/app/auth/sign-in/page.tsx
@@ -1,14 +1,12 @@
import Link from 'next/link';
-import { register } from 'module';
import { SignInMethodsContainer } from '@kit/auth/sign-in';
+import { authConfig, pathsConfig } from '@kit/shared/config';
import { Button } from '@kit/ui/button';
import { Heading } from '@kit/ui/heading';
import { Trans } from '@kit/ui/trans';
-import authConfig from '~/config/auth.config';
-import pathsConfig from '~/config/paths.config';
import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
import { withI18n } from '~/lib/i18n/with-i18n';
diff --git a/app/auth/sign-up/layout.tsx b/app/auth/sign-up/layout.tsx
index ff7b33b..1efc0a4 100644
--- a/app/auth/sign-up/layout.tsx
+++ b/app/auth/sign-up/layout.tsx
@@ -1,6 +1,6 @@
import { AuthLayoutShell } from '@kit/auth/shared';
-import { AppLogo } from '~/components/app-logo';
+import { AppLogo } from '@kit/shared/components/app-logo';
function AuthLayout({ children }: React.PropsWithChildren) {
return
{children};
diff --git a/app/auth/sign-up/page.tsx b/app/auth/sign-up/page.tsx
index 61acc81..5c0a4e2 100644
--- a/app/auth/sign-up/page.tsx
+++ b/app/auth/sign-up/page.tsx
@@ -1,13 +1,13 @@
import Link from 'next/link';
import { SignUpMethodsContainer } from '@kit/auth/sign-up';
+import { authConfig, pathsConfig } from '@kit/shared/config';
import { Button } from '@kit/ui/button';
import { Heading } from '@kit/ui/heading';
import { Trans } from '@kit/ui/trans';
-import authConfig from '~/config/auth.config';
-import pathsConfig from '~/config/paths.config';
import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
+
import { withI18n } from '~/lib/i18n/with-i18n';
export const generateMetadata = async () => {
diff --git a/app/auth/update-account/_lib/server/update-account.ts b/app/auth/update-account/_lib/server/update-account.ts
index a7a424a..e2fcd0f 100644
--- a/app/auth/update-account/_lib/server/update-account.ts
+++ b/app/auth/update-account/_lib/server/update-account.ts
@@ -8,7 +8,8 @@ import { AccountSubmitData, createAuthApi } from '@kit/auth/api';
import { enhanceAction } from '@kit/next/actions';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
-import pathsConfig from '~/config/paths.config';
+import { pathsConfig } from '@kit/shared/config';
+
import { UpdateAccountSchema } from '../schemas/update-account.schema';
diff --git a/app/auth/update-account/layout.tsx b/app/auth/update-account/layout.tsx
index 8212f3c..d9687a8 100644
--- a/app/auth/update-account/layout.tsx
+++ b/app/auth/update-account/layout.tsx
@@ -1,5 +1,6 @@
import { withI18n } from '~/lib/i18n/with-i18n';
+
async function SiteLayout(props: React.PropsWithChildren) {
return (
diff --git a/app/auth/update-account/page.tsx b/app/auth/update-account/page.tsx
index c594570..28a6395 100644
--- a/app/auth/update-account/page.tsx
+++ b/app/auth/update-account/page.tsx
@@ -1,11 +1,11 @@
import { redirect } from 'next/navigation';
-import { BackButton } from '@/components/back-button';
-import { MedReportLogo } from '@/components/med-report-logo';
-import pathsConfig from '@/config/paths.config';
import { signOutAction } from '@/lib/actions/sign-out';
import { getSupabaseServerClient } from '@/packages/supabase/src/clients/server-client';
+import { BackButton } from '@kit/shared/components/back-button';
+import { MedReportLogo } from '@kit/shared/components/med-report-logo';
+import { pathsConfig } from '@kit/shared/config';
import { Trans } from '@kit/ui/trans';
import { withI18n } from '~/lib/i18n/with-i18n';
diff --git a/app/auth/verify/layout.tsx b/app/auth/verify/layout.tsx
index ff7b33b..1efc0a4 100644
--- a/app/auth/verify/layout.tsx
+++ b/app/auth/verify/layout.tsx
@@ -1,6 +1,6 @@
import { AuthLayoutShell } from '@kit/auth/shared';
-import { AppLogo } from '~/components/app-logo';
+import { AppLogo } from '@kit/shared/components/app-logo';
function AuthLayout({ children }: React.PropsWithChildren) {
return
{children};
diff --git a/app/auth/verify/page.tsx b/app/auth/verify/page.tsx
index 2bddfa4..abc9203 100644
--- a/app/auth/verify/page.tsx
+++ b/app/auth/verify/page.tsx
@@ -4,7 +4,8 @@ import { MultiFactorChallengeContainer } from '@kit/auth/mfa';
import { checkRequiresMultiFactorAuthentication } from '@kit/supabase/check-requires-mfa';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
-import pathsConfig from '~/config/paths.config';
+import { pathsConfig } from '@kit/shared/config';
+
import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
import { withI18n } from '~/lib/i18n/with-i18n';
diff --git a/app/doctor/_components/analysis-view.tsx b/app/doctor/_components/analysis-view.tsx
new file mode 100644
index 0000000..2d43d7f
--- /dev/null
+++ b/app/doctor/_components/analysis-view.tsx
@@ -0,0 +1,242 @@
+'use client';
+
+import { useState } from 'react';
+
+import { zodResolver } from '@hookform/resolvers/zod';
+import { useQueryClient } from '@tanstack/react-query';
+import { useForm } from 'react-hook-form';
+
+import { giveFeedbackAction } from '@kit/doctor/actions/doctor-server-actions';
+import {
+ getDOBWithAgeStringFromPersonalCode,
+ getResultSetName,
+} from '@kit/doctor/lib/helpers';
+import {
+ AnalysisResponse,
+ DoctorFeedback,
+ Order,
+ Patient,
+} from '@kit/doctor/schema/doctor-analysis-detail-view.schema';
+import {
+ DoctorAnalysisFeedbackForm,
+ doctorAnalysisFeedbackSchema,
+} from '@kit/doctor/schema/doctor-analysis.schema';
+import ConfirmationModal from '@kit/shared/components/confirmation-modal';
+import { getFullName } from '@kit/shared/utils';
+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 Analysis from '~/home/(user)/(dashboard)/analysis-results/_components/analysis';
+import { bmiFromMetric } from '~/lib/utils';
+
+export default function AnalysisView({
+ patient,
+ order,
+ analyses,
+ feedback,
+}: {
+ patient: Patient;
+ order: Order;
+ analyses: AnalysisResponse[];
+ feedback?: DoctorFeedback;
+}) {
+ const [isConfirmOpen, setIsConfirmOpen] = useState(false);
+
+ const { data: user } = useUser();
+
+ const isInProgress =
+ !!feedback?.status &&
+ feedback?.doctor_user_id &&
+ feedback?.status !== 'COMPLETED';
+ const isReadOnly =
+ !isInProgress ||
+ (!!feedback?.doctor_user_id && feedback?.doctor_user_id !== user?.id);
+
+ const form = useForm({
+ resolver: zodResolver(doctorAnalysisFeedbackSchema),
+ defaultValues: {
+ feedbackValue: feedback?.value ?? '',
+ userId: patient.userId,
+ },
+ });
+
+ const queryClient = useQueryClient();
+
+ if (!patient || !order || !analyses) {
+ return null;
+ }
+
+ const onSubmit = async (
+ data: DoctorAnalysisFeedbackForm,
+ status: 'DRAFT' | 'COMPLETED',
+ ) => {
+ try {
+ const feedbackPromise = giveFeedbackAction({
+ ...data,
+ analysisOrderId: order.analysisOrderId,
+ status,
+ });
+
+ toast.promise(() => feedbackPromise, {
+ success:
,
+ error:
,
+ loading:
,
+ });
+
+ queryClient.invalidateQueries({
+ predicate: (query) => query.queryKey.includes('doctor-jobs'),
+ });
+
+ return setIsConfirmOpen(false);
+ } catch (error) {
+ toast.error(
);
+ }
+ };
+
+ const handleDraftSubmit = () => {
+ const formData = form.getValues();
+ onSubmit(formData, 'DRAFT');
+ };
+
+ const handleCompleteSubmit = () => {
+ setIsConfirmOpen(true);
+ };
+
+ const confirmComplete = () => {
+ const formData = form.getValues();
+ onSubmit(formData, 'COMPLETED');
+ };
+
+ return (
+ <>
+
+
+
+
+
+
+
+
{getFullName(patient.firstName, patient.lastName)}
+
+
+
+
{patient.personalCode ?? ''}
+
+
+
+
{getDOBWithAgeStringFromPersonalCode(patient.personalCode)}
+
+
+
+
{patient.height}
+
+
+
+
{patient.weight}
+
+
+
+
{bmiFromMetric(patient?.height ?? 0, patient?.weight ?? 0)}
+
+
+
+
+
+
+
+
{patient.phone}
+
+
+
+
{patient.email}
+
+
+
+
+
+
+ {analyses.map((analysisData) => {
+ return (
+
+ );
+ })}
+
+
+
+
+
+
+
{feedback?.value ?? '-'}
+
+ {!isReadOnly && (
+
+
+ )}
+
setIsConfirmOpen(false)}
+ onConfirm={confirmComplete}
+ titleKey="doctor:confirmFeedbackModal.title"
+ descriptionKey="doctor:confirmFeedbackModal.description"
+ />
+ >
+ );
+}
diff --git a/app/doctor/_components/doctor-dashboard.tsx b/app/doctor/_components/doctor-dashboard.tsx
new file mode 100644
index 0000000..0ff9df2
--- /dev/null
+++ b/app/doctor/_components/doctor-dashboard.tsx
@@ -0,0 +1,36 @@
+'use client';
+
+import {
+ getOpenResponsesAction,
+ getOtherResponsesAction,
+ getUserDoneResponsesAction,
+ getUserInProgressResponsesAction,
+} from '@kit/doctor/actions/table-data-fetching-actions';
+import ResultsTableWrapper from './results-table-wrapper';
+
+export default function Dashboard() {
+ return (
+ <>
+
+
+
+
+ >
+ );
+}
diff --git a/packages/features/doctor/src/components/doctor-guard.tsx b/app/doctor/_components/doctor-guard.tsx
similarity index 92%
rename from packages/features/doctor/src/components/doctor-guard.tsx
rename to app/doctor/_components/doctor-guard.tsx
index afff737..c52a139 100644
--- a/packages/features/doctor/src/components/doctor-guard.tsx
+++ b/app/doctor/_components/doctor-guard.tsx
@@ -1,7 +1,7 @@
import { notFound } from 'next/navigation';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
-import { isDoctor } from '../lib/server/utils/is-doctor';
+import { isDoctor } from '@kit/doctor/lib/server/utils/is-doctor';
type LayoutOrPageComponent = React.ComponentType;
diff --git a/app/doctor/_components/doctor-sidebar.tsx b/app/doctor/_components/doctor-sidebar.tsx
index 2baba9d..99e0c12 100644
--- a/app/doctor/_components/doctor-sidebar.tsx
+++ b/app/doctor/_components/doctor-sidebar.tsx
@@ -20,8 +20,10 @@ import {
} from '@kit/ui/shadcn-sidebar';
import { Trans } from '@kit/ui/trans';
-import { AppLogo } from '~/components/app-logo';
-import { ProfileAccountDropdownContainer } from '~/components/personal-account-dropdown-container';
+import { AppLogo } from '@kit/shared/components/app-logo';
+import { ProfileAccountDropdownContainer } from '@kit/shared/components/personal-account-dropdown-container';
+
+import { pathsConfig } from '@kit/shared/config';
export function DoctorSidebar({
accounts,
@@ -33,7 +35,11 @@ export function DoctorSidebar({
return (
-
+
@@ -44,10 +50,49 @@ export function DoctorSidebar({
-
-
+
+
- Dashboard
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/doctor/_components/mobile-navigation.tsx b/app/doctor/_components/mobile-navigation.tsx
index 43f0c75..081b7d8 100644
--- a/app/doctor/_components/mobile-navigation.tsx
+++ b/app/doctor/_components/mobile-navigation.tsx
@@ -2,13 +2,14 @@ import Link from 'next/link';
import { Menu } from 'lucide-react';
+import { pathsConfig } from '@kit/shared/config';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@kit/ui/dropdown-menu';
-import pathsConfig from '../../../config/paths.config';
+import { Trans } from '@kit/ui/trans';
export function DoctorMobileNavigation() {
return (
@@ -19,9 +20,30 @@ export function DoctorMobileNavigation() {
- Home
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
);
diff --git a/app/doctor/_components/results-table-wrapper.tsx b/app/doctor/_components/results-table-wrapper.tsx
new file mode 100644
index 0000000..60f58f7
--- /dev/null
+++ b/app/doctor/_components/results-table-wrapper.tsx
@@ -0,0 +1,120 @@
+'use client';
+
+import { useState } from 'react';
+
+import { useQuery, useQueryClient } from '@tanstack/react-query';
+
+import {
+ PaginatedData,
+ ResponseTable,
+ ServerActionResponse,
+} from '@kit/doctor/schema/doctor-analysis.schema';
+import TableSkeleton from '@kit/shared/components/table-skeleton';
+import { Trans } from '@kit/ui/trans';
+import { cn } from '@kit/ui/utils';
+
+import ResultsTable from './results-table';
+
+const PAGE_SIZE = 10;
+
+export default function ResultsTableWrapper({
+ titleKey,
+ action,
+ queryKey,
+}: {
+ titleKey: string;
+ action: ({
+ page,
+ pageSize,
+ }: {
+ page: number;
+ pageSize: number;
+ }) => Promise>>;
+ queryKey: string;
+}) {
+ const [page, setPage] = useState(1);
+
+ const {
+ data: jobs,
+ isLoading,
+ isError,
+ isFetching,
+ } = useQuery({
+ queryKey: [queryKey, 'doctor-jobs', page],
+ queryFn: async () => await action({ page: page, pageSize: PAGE_SIZE }),
+ placeholderData: (previousData) => previousData,
+ });
+
+ const queryClient = useQueryClient();
+
+ const onJobUpdate = () => {
+ queryClient.invalidateQueries({
+ predicate: (query) => query.queryKey.includes('doctor-jobs'),
+ });
+ };
+
+ const createPageChangeHandler = (setPage: (page: number) => void) => {
+ return async ({ page }: { page: number; pageSize: number }) => {
+ setPage(page);
+ return { success: true, data: null };
+ };
+ };
+
+ if (isLoading) {
+ return (
+ <>
+
+
+
+
+
+ >
+ );
+ }
+
+ if (isError) {
+ return (
+ <>
+
+
+
+
+ >
+ );
+ }
+
+ return (
+ <>
+
+
+
+
+
+ >
+ );
+}
diff --git a/app/doctor/_components/results-table.tsx b/app/doctor/_components/results-table.tsx
new file mode 100644
index 0000000..f596723
--- /dev/null
+++ b/app/doctor/_components/results-table.tsx
@@ -0,0 +1,322 @@
+'use client';
+
+import { useTransition } from 'react';
+
+import Link from 'next/link';
+import { useRouter } from 'next/navigation';
+
+import { format } from 'date-fns';
+import { Eye, LoaderCircle } from 'lucide-react';
+
+import {
+ selectJobAction,
+ unselectJobAction,
+} from '@kit/doctor/actions/doctor-server-actions';
+import { getResultSetName } from '@kit/doctor/lib/helpers';
+import { ResponseTable } from '@kit/doctor/schema/doctor-analysis.schema';
+import { pathsConfig } from '@kit/shared/config';
+import { getFullName } from '@kit/shared/utils';
+import { useUser } from '@kit/supabase/hooks/use-user';
+import { Button } from '@kit/ui/button';
+import { toast } from '@kit/ui/sonner';
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from '@kit/ui/table';
+import { Trans } from '@kit/ui/trans';
+
+function DoctorCell({
+ doctorUserId,
+ doctorName,
+ analysisOrderId,
+ userId,
+ isRemovable,
+ onJobUpdate,
+ linkTo,
+}: {
+ doctorUserId?: string;
+ doctorName?: string;
+ analysisOrderId: number;
+ userId: string;
+ isRemovable?: boolean;
+ linkTo: string;
+ onJobUpdate: () => void;
+}) {
+ const [isPending, startTransition] = useTransition();
+ const router = useRouter();
+
+ const handleSelectJob = () => {
+ startTransition(async () => {
+ const result = await selectJobAction({
+ analysisOrderId,
+ userId,
+ });
+
+ if (result?.success) {
+ onJobUpdate();
+ router.push(linkTo);
+ } else {
+ toast.error('common.genericServerError');
+ }
+ });
+ };
+
+ const handleUnselectJob = () => {
+ startTransition(async () => {
+ const result = await unselectJobAction({
+ analysisOrderId,
+ });
+
+ if (result?.success) {
+ onJobUpdate();
+ } else {
+ toast.error('common.genericServerError');
+ }
+ });
+ };
+
+ if (isRemovable) {
+ return (
+
+ );
+ }
+
+ if (!doctorUserId) {
+ return (
+
+ );
+ }
+
+ return <>{doctorName}>;
+}
+
+export default function ResultsTable({
+ results = [],
+ pagination = {
+ currentPage: 1,
+ totalPages: 1,
+ totalCount: 0,
+ pageSize: 10,
+ },
+ fetchAction,
+ onJobUpdate,
+}: {
+ results: ResponseTable[] | null;
+ pagination?: {
+ currentPage: number;
+ totalPages: number;
+ totalCount: number;
+ pageSize: number;
+ };
+ fetchAction: ({
+ page,
+ pageSize,
+ }: {
+ page: number;
+ pageSize: number;
+ }) => Promise<{
+ success: boolean;
+ data: null;
+ }>;
+ onJobUpdate: () => void;
+}) {
+ const [isPending, startTransition] = useTransition();
+ const { data: currentUser } = useUser();
+
+ const fetchPage = async (page: number) => {
+ startTransition(async () => {
+ const result = await fetchAction({
+ page,
+ pageSize: pagination.pageSize,
+ });
+ if (!result.success) {
+ toast.error('common.genericServerError');
+ }
+ });
+ };
+
+ const handleNextPage = () => {
+ if (pagination.currentPage < pagination.totalPages) {
+ fetchPage(pagination.currentPage + 1);
+ }
+ };
+
+ const handlePrevPage = () => {
+ if (pagination.currentPage > 1) {
+ fetchPage(pagination.currentPage - 1);
+ }
+ };
+
+ const handleJobUpdate = () => {
+ onJobUpdate();
+ fetchPage(pagination.currentPage);
+ };
+
+ if (!results?.length) {
+ return (
+
+ .
+
+ );
+ }
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {results
+ ?.sort((a, b) =>
+ (a.created_at ?? '') > (b.created_at ?? '') ? -1 : 1,
+ )
+ .map((result) => {
+ const isCompleted = result.feedback?.status === 'COMPLETED';
+ const isCurrentDoctorJob =
+ !!result.doctor?.primary_owner_user_id &&
+ result.doctor?.primary_owner_user_id === currentUser?.id;
+
+ const resultsReceived = result.elements.length || 0;
+ const elementsInOrder = Array.from(
+ new Set(result.analysis_order_id.analysis_element_ids),
+ )?.length;
+
+ return (
+
+
+
+
+
+
+
+ {getFullName(
+ result.patient?.name,
+ result.patient?.last_name,
+ )}
+
+
+
+
+ {result.order_number}
+
+ {result.firstSampleGivenAt
+ ? format(result.firstSampleGivenAt, 'dd.MM.yyyy HH:mm')
+ : '-'}
+
+
+
+
+
+
+
+
+ );
+ })}
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/app/doctor/analysis/[id]/page.tsx b/app/doctor/analysis/[id]/page.tsx
new file mode 100644
index 0000000..3b74f13
--- /dev/null
+++ b/app/doctor/analysis/[id]/page.tsx
@@ -0,0 +1,38 @@
+import { cache } from 'react';
+
+import { getAnalysisResultsForDoctor } from '@kit/doctor/services/doctor-analysis.service';
+import { PageBody, PageHeader } from '@kit/ui/page';
+import AnalysisView from '../../_components/analysis-view';
+import { DoctorGuard } from '../../_components/doctor-guard';
+
+async function AnalysisPage({
+ params,
+}: {
+ params: Promise<{
+ id: string;
+ }>;
+}) {
+ const { id } = await params;
+ const analysisResultDetails = await loadResult(Number(id));
+
+ if (!analysisResultDetails) {
+ return null;
+ }
+
+ return (
+ <>
+
+
+
+
+ >
+ );
+}
+
+export default DoctorGuard(AnalysisPage);
+const loadResult = cache(getAnalysisResultsForDoctor);
diff --git a/app/doctor/completed-jobs/page.tsx b/app/doctor/completed-jobs/page.tsx
new file mode 100644
index 0000000..1035fff
--- /dev/null
+++ b/app/doctor/completed-jobs/page.tsx
@@ -0,0 +1,22 @@
+import { PageBody, PageHeader } from '@kit/ui/page';
+
+import { getUserDoneResponsesAction } from '@kit/doctor/actions/table-data-fetching-actions';
+import ResultsTableWrapper from '../_components/results-table-wrapper';
+import { DoctorGuard } from '../_components/doctor-guard';
+
+async function CompletedJobsPage() {
+ return (
+ <>
+
+
+
+
+ >
+ );
+}
+
+export default DoctorGuard(CompletedJobsPage);
diff --git a/app/doctor/my-jobs/page.tsx b/app/doctor/my-jobs/page.tsx
new file mode 100644
index 0000000..6fdfc78
--- /dev/null
+++ b/app/doctor/my-jobs/page.tsx
@@ -0,0 +1,21 @@
+import { getUserInProgressResponsesAction } from '@kit/doctor/actions/table-data-fetching-actions';
+import { PageBody, PageHeader } from '@kit/ui/page';
+import ResultsTableWrapper from '../_components/results-table-wrapper';
+import { DoctorGuard } from '../_components/doctor-guard';
+
+async function MyReviewsPage() {
+ return (
+ <>
+
+
+
+
+ >
+ );
+}
+
+export default DoctorGuard(MyReviewsPage);
diff --git a/app/doctor/open-jobs/page.tsx b/app/doctor/open-jobs/page.tsx
new file mode 100644
index 0000000..b1abf22
--- /dev/null
+++ b/app/doctor/open-jobs/page.tsx
@@ -0,0 +1,22 @@
+import { getOpenResponsesAction } from '@kit/doctor/actions/table-data-fetching-actions';
+import { PageBody, PageHeader } from '@kit/ui/page';
+
+import { DoctorGuard } from '../_components/doctor-guard';
+import ResultsTableWrapper from '../_components/results-table-wrapper';
+
+async function OpenJobsPage() {
+ return (
+ <>
+
+
+
+
+ >
+ );
+}
+
+export default DoctorGuard(OpenJobsPage);
diff --git a/app/doctor/page.tsx b/app/doctor/page.tsx
index f789fca..90ca14b 100644
--- a/app/doctor/page.tsx
+++ b/app/doctor/page.tsx
@@ -1,14 +1,13 @@
-import { DoctorGuard } from '@kit/doctor/components/doctor-guard';
import { PageBody, PageHeader } from '@kit/ui/page';
-import { Trans } from '@kit/ui/trans';
+import Dashboard from './_components/doctor-dashboard';
+import { DoctorGuard } from './_components/doctor-guard';
-function DoctorPage() {
+async function DoctorPage() {
return (
<>
- } />
-
+
- TBD
+
>
);
diff --git a/app/global-error.tsx b/app/global-error.tsx
index 5abc327..97e601e 100644
--- a/app/global-error.tsx
+++ b/app/global-error.tsx
@@ -10,7 +10,7 @@ import { Heading } from '@kit/ui/heading';
import { Trans } from '@kit/ui/trans';
import { SiteHeader } from '~/(marketing)/_components/site-header';
-import { RootProviders } from '~/components/root-providers';
+import { RootProviders } from '@kit/shared/components/root-providers';
const GlobalErrorPage = ({
error,
diff --git a/app/home/(user)/(dashboard)/analysis-results/_components/analysis-level-bar.tsx b/app/home/(user)/(dashboard)/analysis-results/_components/analysis-level-bar.tsx
index 66ceeac..b808403 100644
--- a/app/home/(user)/(dashboard)/analysis-results/_components/analysis-level-bar.tsx
+++ b/app/home/(user)/(dashboard)/analysis-results/_components/analysis-level-bar.tsx
@@ -1,9 +1,9 @@
-import React, { useMemo } from 'react';
+import { useMemo } from 'react';
import { ArrowDown } from 'lucide-react';
import { cn } from '@kit/ui/utils';
-import { UserAnalysisElement } from '@/packages/features/accounts/src/types/accounts';
+import { AnalysisResultForDisplay } from './analysis';
export enum AnalysisResultLevel {
VERY_LOW = 0,
@@ -63,7 +63,7 @@ const AnalysisLevelBar = ({
normLowerIncluded?: boolean;
normUpperIncluded?: boolean;
level: AnalysisResultLevel;
- results: UserAnalysisElement;
+ results: AnalysisResultForDisplay;
}) => {
const { norm_lower: lower, norm_upper: upper, response_value: value } = results;
diff --git a/app/home/(user)/(dashboard)/analysis-results/_components/analysis.tsx b/app/home/(user)/(dashboard)/analysis-results/_components/analysis.tsx
index 91af308..0535ffe 100644
--- a/app/home/(user)/(dashboard)/analysis-results/_components/analysis.tsx
+++ b/app/home/(user)/(dashboard)/analysis-results/_components/analysis.tsx
@@ -16,6 +16,18 @@ import AnalysisLevelBar, {
AnalysisResultLevel,
} from './analysis-level-bar';
+export type AnalysisResultForDisplay = Pick<
+ UserAnalysisElement,
+ | 'norm_status'
+ | 'response_value'
+ | 'unit'
+ | 'norm_lower_included'
+ | 'norm_upper_included'
+ | 'norm_lower'
+ | 'norm_upper'
+ | 'response_time'
+>;
+
export enum AnalysisStatus {
NORMAL = 0,
MEDIUM = 1,
@@ -26,8 +38,8 @@ const Analysis = ({
analysisElement,
results,
}: {
- analysisElement: AnalysisElement;
- results?: UserAnalysisElement;
+ analysisElement: Pick;
+ results?: AnalysisResultForDisplay;
}) => {
const name = analysisElement.analysis_name_lab || '';
const status = results?.norm_status || AnalysisStatus.NORMAL;
diff --git a/app/home/(user)/(dashboard)/analysis-results/page.tsx b/app/home/(user)/(dashboard)/analysis-results/page.tsx
index 27041df..1ac3d5e 100644
--- a/app/home/(user)/(dashboard)/analysis-results/page.tsx
+++ b/app/home/(user)/(dashboard)/analysis-results/page.tsx
@@ -9,17 +9,18 @@ import { Trans } from '@kit/ui/makerkit/trans';
import { PageBody } from '@kit/ui/page';
import { Button } from '@kit/ui/shadcn/button';
-import pathsConfig from '~/config/paths.config';
+import { pathsConfig } from '@kit/shared/config';
+
import { getAnalysisElements } from '~/lib/services/analysis-element.service';
import {
PAGE_VIEW_ACTION,
createPageViewLog,
} from '~/lib/services/audit/pageView.service';
import { AnalysisOrder, getAnalysisOrders } from '~/lib/services/order.service';
-import { ButtonTooltip } from '~/components/ui/button-tooltip';
+import { ButtonTooltip } from '@kit/shared/components/ui/button-tooltip';
-import Analysis from './_components/analysis';
import { loadUserAnalysis } from '../../_lib/server/load-user-analysis';
+import Analysis from './_components/analysis';
export const generateMetadata = async () => {
const i18n = await createI18nServerInstance();
diff --git a/app/home/(user)/(dashboard)/booking/page.tsx b/app/home/(user)/(dashboard)/booking/page.tsx
index 32b2058..def99c9 100644
--- a/app/home/(user)/(dashboard)/booking/page.tsx
+++ b/app/home/(user)/(dashboard)/booking/page.tsx
@@ -4,6 +4,7 @@ import { Trans } from '@kit/ui/trans';
import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
import { withI18n } from '~/lib/i18n/with-i18n';
+
import { HomeLayoutPageHeader } from '../../_components/home-page-header';
import OrderCards from '../../_components/order-cards';
diff --git a/app/home/(user)/(dashboard)/layout.tsx b/app/home/(user)/(dashboard)/layout.tsx
index 5a3c534..8344158 100644
--- a/app/home/(user)/(dashboard)/layout.tsx
+++ b/app/home/(user)/(dashboard)/layout.tsx
@@ -2,16 +2,16 @@ import { use } from 'react';
import { cookies } from 'next/headers';
+import { retrieveCart } from '@lib/data/cart';
+import { StoreCart } from '@medusajs/types';
import { z } from 'zod';
import { UserWorkspaceContextProvider } from '@kit/accounts/components';
+import { AppLogo } from '@kit/shared/components/app-logo';
+import { personalAccountNavigationConfig } from '@kit/shared/config';
import { Page, PageMobileNavigation, PageNavigation } from '@kit/ui/page';
import { SidebarProvider } from '@kit/ui/shadcn-sidebar';
-import { StoreCart } from '@medusajs/types';
-import { retrieveCart } from '@lib/data/cart';
-import { AppLogo } from '~/components/app-logo';
-import { personalAccountNavigationConfig } from '~/config/personal-account-navigation.config';
import { withI18n } from '~/lib/i18n/with-i18n';
// home imports
diff --git a/app/home/(user)/(dashboard)/order-analysis-package/page.tsx b/app/home/(user)/(dashboard)/order-analysis-package/page.tsx
index 8be76c5..5ce65fa 100644
--- a/app/home/(user)/(dashboard)/order-analysis-package/page.tsx
+++ b/app/home/(user)/(dashboard)/order-analysis-package/page.tsx
@@ -2,8 +2,9 @@ import { Scale } from 'lucide-react';
import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
import { withI18n } from '~/lib/i18n/with-i18n';
+
import { Button } from '@kit/ui/button';
-import SelectAnalysisPackages from '@/components/select-analysis-packages';
+import SelectAnalysisPackages from '@kit/shared/components/select-analysis-packages';
import { PageBody } from '@kit/ui/page';
import { Trans } from '@kit/ui/trans';
diff --git a/app/home/(user)/(dashboard)/order-analysis/page.tsx b/app/home/(user)/(dashboard)/order-analysis/page.tsx
index 6358f18..8e0f179 100644
--- a/app/home/(user)/(dashboard)/order-analysis/page.tsx
+++ b/app/home/(user)/(dashboard)/order-analysis/page.tsx
@@ -1,5 +1,6 @@
import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
import { withI18n } from '~/lib/i18n/with-i18n';
+
import { PageBody } from '@kit/ui/page';
import { Trans } from '@kit/ui/trans';
import { HomeLayoutPageHeader } from '../../_components/home-page-header';
diff --git a/app/home/(user)/(dashboard)/order-health-analysis/page.tsx b/app/home/(user)/(dashboard)/order-health-analysis/page.tsx
index 6a8524f..a4a4478 100644
--- a/app/home/(user)/(dashboard)/order-health-analysis/page.tsx
+++ b/app/home/(user)/(dashboard)/order-health-analysis/page.tsx
@@ -1,5 +1,6 @@
import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
import { withI18n } from '~/lib/i18n/with-i18n';
+
import { PageBody } from '@kit/ui/page';
import { Trans } from '@kit/ui/trans';
import { HomeLayoutPageHeader } from '../../_components/home-page-header';
diff --git a/app/home/(user)/(dashboard)/order/[orderId]/confirmed/page.tsx b/app/home/(user)/(dashboard)/order/[orderId]/confirmed/page.tsx
index f49e5cb..21e1829 100644
--- a/app/home/(user)/(dashboard)/order/[orderId]/confirmed/page.tsx
+++ b/app/home/(user)/(dashboard)/order/[orderId]/confirmed/page.tsx
@@ -3,9 +3,11 @@ import { PageBody, PageHeader } from '@kit/ui/page';
import { createI18nServerInstance } from '@/lib/i18n/i18n.server';
import { withI18n } from '~/lib/i18n/with-i18n';
+
import { getOrder } from '~/lib/services/order.service';
import { retrieveOrder } from '@lib/data/orders';
-import pathsConfig from '~/config/paths.config';
+import { pathsConfig } from '@kit/shared/config';
+
import Divider from "@modules/common/components/divider"
import OrderDetails from '@/app/home/(user)/_components/order/order-details';
import OrderItems from '@/app/home/(user)/_components/order/order-items';
diff --git a/app/home/(user)/(dashboard)/order/[orderId]/page.tsx b/app/home/(user)/(dashboard)/order/[orderId]/page.tsx
index b9e8597..4b717d5 100644
--- a/app/home/(user)/(dashboard)/order/[orderId]/page.tsx
+++ b/app/home/(user)/(dashboard)/order/[orderId]/page.tsx
@@ -3,9 +3,11 @@ import { PageBody, PageHeader } from '@kit/ui/page';
import { createI18nServerInstance } from '@/lib/i18n/i18n.server';
import { withI18n } from '~/lib/i18n/with-i18n';
+
import { getOrder } from '~/lib/services/order.service';
import { retrieveOrder } from '@lib/data/orders';
-import pathsConfig from '~/config/paths.config';
+import { pathsConfig } from '@kit/shared/config';
+
import Divider from "@modules/common/components/divider"
import OrderDetails from '@/app/home/(user)/_components/order/order-details';
import OrderItems from '@/app/home/(user)/_components/order/order-items';
diff --git a/app/home/(user)/(dashboard)/order/page.tsx b/app/home/(user)/(dashboard)/order/page.tsx
index 57b11c5..5bb1ae7 100644
--- a/app/home/(user)/(dashboard)/order/page.tsx
+++ b/app/home/(user)/(dashboard)/order/page.tsx
@@ -4,14 +4,15 @@ import { listOrders } from '~/medusa/lib/data/orders';
import { createI18nServerInstance } from '@/lib/i18n/i18n.server';
import { listProductTypes } from '@lib/data/products';
import { PageBody } from '@kit/ui/makerkit/page';
-import pathsConfig from '~/config/paths.config';
+import { pathsConfig } from '@kit/shared/config';
+
import { Trans } from '@kit/ui/trans';
import { HomeLayoutPageHeader } from '../../_components/home-page-header';
-import { withI18n } from '~/lib/i18n/with-i18n';
import { getAnalysisOrders } from '~/lib/services/order.service';
import OrderBlock from '../../_components/orders/order-block';
import React from 'react';
import { Divider } from '@medusajs/ui';
+import { withI18n } from '~/lib/i18n/with-i18n';
export async function generateMetadata() {
const { t } = await createI18nServerInstance();
diff --git a/app/home/(user)/_components/compare-packages-modal.tsx b/app/home/(user)/_components/compare-packages-modal.tsx
index 1b561ad..0fbc24d 100644
--- a/app/home/(user)/_components/compare-packages-modal.tsx
+++ b/app/home/(user)/_components/compare-packages-modal.tsx
@@ -19,11 +19,11 @@ import {
TableRow,
} from '@kit/ui/table';
import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
-import { withI18n } from '~/lib/i18n/with-i18n';
-import { PackageHeader } from '@/components/package-header';
-import { InfoTooltip } from '@/components/ui/info-tooltip';
+import { PackageHeader } from '@kit/shared/components/package-header';
+import { InfoTooltip } from '@kit/shared/components/ui/info-tooltip';
import { StoreProduct } from '@medusajs/types';
import { getAnalysisElementMedusaProductIds } from '@/utils/medusa-product';
+import { withI18n } from '@/lib/i18n/with-i18n';
const CheckWithBackground = () => {
return (
diff --git a/app/home/(user)/_components/dashboard.tsx b/app/home/(user)/_components/dashboard.tsx
index 181129d..b10238c 100644
--- a/app/home/(user)/_components/dashboard.tsx
+++ b/app/home/(user)/_components/dashboard.tsx
@@ -2,11 +2,9 @@
import Link from 'next/link';
-import { InfoTooltip } from '@/components/ui/info-tooltip';
import type { AccountWithParams } from '@/packages/features/accounts/src/server/api';
import { Database } from '@/packages/supabase/src/database.types';
import { BlendingModeIcon, RulerHorizontalIcon } from '@radix-ui/react-icons';
-import Isikukood from 'isikukood';
import {
Activity,
Clock9,
@@ -17,6 +15,8 @@ import {
User,
} from 'lucide-react';
+import { InfoTooltip } from '@kit/shared/components/ui/info-tooltip';
+import { getPersonParameters } from '@kit/shared/utils';
import { Button } from '@kit/ui/button';
import {
Card,
@@ -147,19 +147,6 @@ const dummyRecommendations = [
},
];
-const getPersonParameters = (personalCode: string) => {
- try {
- const person = new Isikukood(personalCode);
- return {
- gender: person.getGender(),
- age: person.getAge(),
- };
- } catch (error) {
- console.error(error);
- return null;
- }
-};
-
export default function Dashboard({
account,
bmiThresholds,
diff --git a/app/home/(user)/_components/home-account-selector.tsx b/app/home/(user)/_components/home-account-selector.tsx
index 315b3b8..5ea17c6 100644
--- a/app/home/(user)/_components/home-account-selector.tsx
+++ b/app/home/(user)/_components/home-account-selector.tsx
@@ -7,8 +7,8 @@ import { useRouter } from 'next/navigation';
import { AccountSelector } from '@kit/accounts/account-selector';
import { SidebarContext } from '@kit/ui/shadcn-sidebar';
-import featureFlagsConfig from '~/config/feature-flags.config';
-import pathsConfig from '~/config/paths.config';
+import { pathsConfig, featureFlagsConfig } from '@kit/shared/config';
+
const features = {
enableTeamCreation: featureFlagsConfig.enableTeamCreation,
diff --git a/app/home/(user)/_components/home-menu-navigation.tsx b/app/home/(user)/_components/home-menu-navigation.tsx
index 407ebf1..c6b91c2 100644
--- a/app/home/(user)/_components/home-menu-navigation.tsx
+++ b/app/home/(user)/_components/home-menu-navigation.tsx
@@ -2,9 +2,9 @@ import Link from 'next/link';
import { ShoppingCart } from 'lucide-react';
import { Trans } from '@kit/ui/trans';
-import { AppLogo } from '~/components/app-logo';
-import { ProfileAccountDropdownContainer } from '~/components/personal-account-dropdown-container';
-import { Search } from '~/components/ui/search';
+import { AppLogo } from '@kit/shared/components/app-logo';
+import { ProfileAccountDropdownContainer } from '@kit/shared/components/personal-account-dropdown-container';
+import { Search } from '@kit/shared/components/ui/search';
import { SIDEBAR_WIDTH_PROPERTY } from '@/packages/ui/src/shadcn/constants';
import { Button } from '@kit/ui/button';
diff --git a/app/home/(user)/_components/home-mobile-navigation.tsx b/app/home/(user)/_components/home-mobile-navigation.tsx
index 87fd7d5..a363a4c 100644
--- a/app/home/(user)/_components/home-mobile-navigation.tsx
+++ b/app/home/(user)/_components/home-mobile-navigation.tsx
@@ -2,8 +2,13 @@
import Link from 'next/link';
+import { StoreCart } from '@medusajs/types';
import { LogOut, Menu, ShoppingCart } from 'lucide-react';
+import {
+ featureFlagsConfig,
+ personalAccountNavigationConfig,
+} from '@kit/shared/config';
import { useSignOut } from '@kit/supabase/hooks/use-sign-out';
import {
DropdownMenu,
@@ -16,16 +21,15 @@ import {
} from '@kit/ui/dropdown-menu';
import { If } from '@kit/ui/if';
import { Trans } from '@kit/ui/trans';
-import { StoreCart } from '@medusajs/types';
-
-import featuresFlagConfig from '~/config/feature-flags.config';
-import { personalAccountNavigationConfig } from '~/config/personal-account-navigation.config';
// home imports
import { HomeAccountSelector } from '../_components/home-account-selector';
import type { UserWorkspace } from '../_lib/server/load-user-workspace';
-export function HomeMobileNavigation(props: { workspace: UserWorkspace, cart: StoreCart | null }) {
+export function HomeMobileNavigation(props: {
+ workspace: UserWorkspace;
+ cart: StoreCart | null;
+}) {
const signOut = useSignOut();
const Links = personalAccountNavigationConfig.routes.map((item, index) => {
@@ -47,7 +51,6 @@ export function HomeMobileNavigation(props: { workspace: UserWorkspace, cart: St
}
});
-
const cartItemsCount = props.cart?.items?.length ?? 0;
const hasCartItems = cartItemsCount > 0;
@@ -58,7 +61,7 @@ export function HomeMobileNavigation(props: { workspace: UserWorkspace, cart: St
-
+
@@ -113,7 +116,11 @@ function DropdownLink(
{props.Icon}
-
+
diff --git a/app/home/(user)/_components/home-sidebar.tsx b/app/home/(user)/_components/home-sidebar.tsx
index e1e53b6..fd254c8 100644
--- a/app/home/(user)/_components/home-sidebar.tsx
+++ b/app/home/(user)/_components/home-sidebar.tsx
@@ -1,3 +1,4 @@
+import { personalAccountNavigationConfig } from '@kit/shared/config';
import {
Sidebar,
SidebarContent,
@@ -6,8 +7,6 @@ import {
} from '@kit/ui/shadcn-sidebar';
import { Trans } from '@kit/ui/trans';
-import { personalAccountNavigationConfig } from '~/config/personal-account-navigation.config';
-
export function HomeSidebar() {
const collapsible = personalAccountNavigationConfig.sidebarCollapsedStyle;
diff --git a/app/home/(user)/_components/order-analyses-cards.tsx b/app/home/(user)/_components/order-analyses-cards.tsx
index f7575bc..7cf627f 100644
--- a/app/home/(user)/_components/order-analyses-cards.tsx
+++ b/app/home/(user)/_components/order-analyses-cards.tsx
@@ -13,7 +13,7 @@ import { StoreProduct, StoreProductVariant } from '@medusajs/types';
import { useState } from 'react';
import { handleAddToCart } from '~/lib/services/medusaCart.service';
import { useRouter } from 'next/navigation';
-import { InfoTooltip } from '~/components/ui/info-tooltip';
+import { InfoTooltip } from '@kit/shared/components/ui/info-tooltip';
import { Trans } from '@kit/ui/trans';
export default function OrderAnalysesCards({
diff --git a/app/home/(user)/_components/user-notifications.tsx b/app/home/(user)/_components/user-notifications.tsx
index f0dd414..e3d227f 100644
--- a/app/home/(user)/_components/user-notifications.tsx
+++ b/app/home/(user)/_components/user-notifications.tsx
@@ -1,16 +1,15 @@
import { NotificationsPopover } from '@kit/notifications/components';
-
-import featuresFlagConfig from '~/config/feature-flags.config';
+import { featureFlagsConfig } from '@kit/shared/config';
export function UserNotifications(props: { userId: string }) {
- if (!featuresFlagConfig.enableNotifications) {
+ if (!featureFlagsConfig.enableNotifications) {
return null;
}
return (
);
}
diff --git a/app/home/(user)/_lib/server/load-user-analysis.ts b/app/home/(user)/_lib/server/load-user-analysis.ts
index 94d293b..52cd529 100644
--- a/app/home/(user)/_lib/server/load-user-analysis.ts
+++ b/app/home/(user)/_lib/server/load-user-analysis.ts
@@ -4,12 +4,12 @@ import { createAccountsApi } from '@kit/accounts/api';
import { UserAnalysis } from '@kit/accounts/types/accounts';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
-export type UserAccount = Awaited>;
+export type UserAnalyses = Awaited>;
/**
- * @name loadUserAccount
+ * @name loadUserAnalysis
* @description
- * Load the user account. It's a cached per-request function that fetches the user workspace data.
+ * Load the user's analyses. It's a cached per-request function that fetches the user workspace data.
* It can be used across the server components to load the user workspace data.
*/
export const loadUserAnalysis = cache(analysisLoader);
diff --git a/app/home/(user)/_lib/server/load-user-workspace.ts b/app/home/(user)/_lib/server/load-user-workspace.ts
index 30ae4ed..d7ace83 100644
--- a/app/home/(user)/_lib/server/load-user-workspace.ts
+++ b/app/home/(user)/_lib/server/load-user-workspace.ts
@@ -1,9 +1,9 @@
import { cache } from 'react';
import { createAccountsApi } from '@kit/accounts/api';
+import { featureFlagsConfig } from '@kit/shared/config';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
-import featureFlagsConfig from '~/config/feature-flags.config';
import { requireUserInServerComponent } from '~/lib/server/require-user-in-server-component';
const shouldLoadAccounts = featureFlagsConfig.enableTeamAccounts;
diff --git a/app/home/(user)/billing/_components/personal-account-checkout-form.tsx b/app/home/(user)/billing/_components/personal-account-checkout-form.tsx
index 28e83fe..aacc8e5 100644
--- a/app/home/(user)/billing/_components/personal-account-checkout-form.tsx
+++ b/app/home/(user)/billing/_components/personal-account-checkout-form.tsx
@@ -19,7 +19,7 @@ import {
import { If } from '@kit/ui/if';
import { Trans } from '@kit/ui/trans';
-import billingConfig from '~/config/billing.config';
+import { billingConfig } from '@kit/shared/config';
import { createPersonalAccountCheckoutSession } from '../_lib/server/server-actions';
diff --git a/app/home/(user)/billing/_lib/server/server-actions.ts b/app/home/(user)/billing/_lib/server/server-actions.ts
index c029d1b..8bec904 100644
--- a/app/home/(user)/billing/_lib/server/server-actions.ts
+++ b/app/home/(user)/billing/_lib/server/server-actions.ts
@@ -5,10 +5,9 @@ import { redirect } from 'next/navigation';
import { enhanceAction } from '@kit/next/actions';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
-import featureFlagsConfig from '~/config/feature-flags.config';
-
import { PersonalAccountCheckoutSchema } from '../schema/personal-account-checkout.schema';
import { createUserBillingService } from './user-billing.service';
+import { featureFlagsConfig } from '@kit/shared/config';
/**
* @name enabled
diff --git a/app/home/(user)/billing/_lib/server/user-billing.service.ts b/app/home/(user)/billing/_lib/server/user-billing.service.ts
index 41506e8..dd1f00b 100644
--- a/app/home/(user)/billing/_lib/server/user-billing.service.ts
+++ b/app/home/(user)/billing/_lib/server/user-billing.service.ts
@@ -11,9 +11,9 @@ import { getBillingGatewayProvider } from '@kit/billing-gateway';
import { getLogger } from '@kit/shared/logger';
import { requireUser } from '@kit/supabase/require-user';
-import appConfig from '~/config/app.config';
-import billingConfig from '~/config/billing.config';
-import pathsConfig from '~/config/paths.config';
+import { appConfig, billingConfig } from '@kit/shared/config';
+import { pathsConfig } from '@kit/shared/config';
+
import { PersonalAccountCheckoutSchema } from '../schema/personal-account-checkout.schema';
diff --git a/app/home/(user)/billing/layout.tsx b/app/home/(user)/billing/layout.tsx
index bea4699..7b3c5cb 100644
--- a/app/home/(user)/billing/layout.tsx
+++ b/app/home/(user)/billing/layout.tsx
@@ -1,6 +1,6 @@
+import { featureFlagsConfig } from '@kit/shared/config';
import { notFound } from 'next/navigation';
-import featureFlagsConfig from '~/config/feature-flags.config';
function UserBillingLayout(props: React.PropsWithChildren) {
const isEnabled = featureFlagsConfig.enablePersonalAccountBilling;
diff --git a/app/home/(user)/billing/page.tsx b/app/home/(user)/billing/page.tsx
index 2637240..62484b4 100644
--- a/app/home/(user)/billing/page.tsx
+++ b/app/home/(user)/billing/page.tsx
@@ -8,12 +8,12 @@ import { If } from '@kit/ui/if';
import { PageBody } from '@kit/ui/page';
import { Trans } from '@kit/ui/trans';
-import billingConfig from '~/config/billing.config';
+import { billingConfig } from '@kit/shared/config';
import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
-import { withI18n } from '~/lib/i18n/with-i18n';
import { requireUserInServerComponent } from '~/lib/server/require-user-in-server-component';
// local imports
+import { withI18n } from '~/lib/i18n/with-i18n';
import { HomeLayoutPageHeader } from '../_components/home-page-header';
import { createPersonalAccountBillingPortalSession } from '../billing/_lib/server/server-actions';
import { PersonalAccountCheckoutForm } from './_components/personal-account-checkout-form';
diff --git a/app/home/(user)/settings/layout.tsx b/app/home/(user)/settings/layout.tsx
index 4972df9..0f55ab8 100644
--- a/app/home/(user)/settings/layout.tsx
+++ b/app/home/(user)/settings/layout.tsx
@@ -3,6 +3,7 @@ import { Trans } from '@kit/ui/trans';
import { withI18n } from '~/lib/i18n/with-i18n';
+
// local imports
import { HomeLayoutPageHeader } from '../_components/home-page-header';
diff --git a/app/home/(user)/settings/page.tsx b/app/home/(user)/settings/page.tsx
index 488c38f..38c0701 100644
--- a/app/home/(user)/settings/page.tsx
+++ b/app/home/(user)/settings/page.tsx
@@ -1,14 +1,16 @@
import { use } from 'react';
import { PersonalAccountSettingsContainer } from '@kit/accounts/personal-account-settings';
+import {
+ authConfig,
+ featureFlagsConfig,
+ pathsConfig,
+} from '@kit/shared/config';
import { PageBody } from '@kit/ui/page';
-import authConfig from '~/config/auth.config';
-import featureFlagsConfig from '~/config/feature-flags.config';
-import pathsConfig from '~/config/paths.config';
import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
-import { withI18n } from '~/lib/i18n/with-i18n';
import { requireUserInServerComponent } from '~/lib/server/require-user-in-server-component';
+import { withI18n } from '~/lib/i18n/with-i18n';
const features = {
enableAccountDeletion: featureFlagsConfig.enableAccountDeletion,
diff --git a/app/home/[account]/_components/team-account-accounts-selector.tsx b/app/home/[account]/_components/team-account-accounts-selector.tsx
index e1da477..4b6fc7b 100644
--- a/app/home/[account]/_components/team-account-accounts-selector.tsx
+++ b/app/home/[account]/_components/team-account-accounts-selector.tsx
@@ -7,8 +7,8 @@ import { useRouter } from 'next/navigation';
import { AccountSelector } from '@kit/accounts/account-selector';
import { SidebarContext } from '@kit/ui/shadcn-sidebar';
-import featureFlagsConfig from '~/config/feature-flags.config';
-import pathsConfig from '~/config/paths.config';
+import { pathsConfig, featureFlagsConfig } from '@kit/shared/config';
+
const features = {
enableTeamCreation: featureFlagsConfig.enableTeamCreation,
diff --git a/app/home/[account]/_components/team-account-benefit-statistics.tsx b/app/home/[account]/_components/team-account-benefit-statistics.tsx
index 95f35a6..af57ab3 100644
--- a/app/home/[account]/_components/team-account-benefit-statistics.tsx
+++ b/app/home/[account]/_components/team-account-benefit-statistics.tsx
@@ -10,8 +10,9 @@ import { Card, CardTitle } from '@kit/ui/card';
import { Button } from '@kit/ui/shadcn/button';
import { Trans } from '@kit/ui/trans';
-import pathsConfig from '~/config/paths.config';
-import { createPath } from '~/config/team-account-navigation.config';
+import { pathsConfig } from '@kit/shared/config';
+
+import { createPath } from '@kit/shared/config/team-account-navigation.config';
interface TeamAccountBenefitStatisticsProps {
accountSlug: string;
diff --git a/app/home/[account]/_components/team-account-layout-mobile-navigation.tsx b/app/home/[account]/_components/team-account-layout-mobile-navigation.tsx
index 3c34b7f..4e0a88f 100644
--- a/app/home/[account]/_components/team-account-layout-mobile-navigation.tsx
+++ b/app/home/[account]/_components/team-account-layout-mobile-navigation.tsx
@@ -6,6 +6,11 @@ import { useRouter } from 'next/navigation';
import { Home, LogOut, Menu } from 'lucide-react';
import { AccountSelector } from '@kit/accounts/account-selector';
+import {
+ featureFlagsConfig,
+ getTeamAccountSidebarConfig,
+ pathsConfig,
+} from '@kit/shared/config';
import { useSignOut } from '@kit/supabase/hooks/use-sign-out';
import {
Dialog,
@@ -23,10 +28,6 @@ import {
} from '@kit/ui/dropdown-menu';
import { Trans } from '@kit/ui/trans';
-import featureFlagsConfig from '~/config/feature-flags.config';
-import pathsConfig from '~/config/paths.config';
-import { getTeamAccountSidebarConfig } from '~/config/team-account-navigation.config';
-
type Accounts = Array<{
label: string | null;
value: string | null;
diff --git a/app/home/[account]/_components/team-account-layout-sidebar.tsx b/app/home/[account]/_components/team-account-layout-sidebar.tsx
index 4208b62..9c5b50b 100644
--- a/app/home/[account]/_components/team-account-layout-sidebar.tsx
+++ b/app/home/[account]/_components/team-account-layout-sidebar.tsx
@@ -8,8 +8,8 @@ import {
SidebarHeader,
} from '@kit/ui/shadcn-sidebar';
-import { ProfileAccountDropdownContainer } from '~/components//personal-account-dropdown-container';
-import { getTeamAccountSidebarConfig } from '~/config/team-account-navigation.config';
+import { ProfileAccountDropdownContainer } from '@kit/shared/components//personal-account-dropdown-container';
+import { getTeamAccountSidebarConfig } from '@kit/shared/config/team-account-navigation.config';
import { TeamAccountNotifications } from '~/home/[account]/_components/team-account-notifications';
import { TeamAccountAccountsSelector } from '../_components/team-account-accounts-selector';
diff --git a/app/home/[account]/_components/team-account-navigation-menu.tsx b/app/home/[account]/_components/team-account-navigation-menu.tsx
index 80c1bb9..f273d53 100644
--- a/app/home/[account]/_components/team-account-navigation-menu.tsx
+++ b/app/home/[account]/_components/team-account-navigation-menu.tsx
@@ -1,8 +1,8 @@
import { useMemo } from 'react';
-import { AppLogo } from '~/components/app-logo';
-import { ProfileAccountDropdownContainer } from '~/components/personal-account-dropdown-container';
-import { getTeamAccountSidebarConfig } from '~/config/team-account-navigation.config';
+import { AppLogo } from '@kit/shared/components/app-logo';
+import { ProfileAccountDropdownContainer } from '@kit/shared/components/personal-account-dropdown-container';
+import { getTeamAccountSidebarConfig } from '@kit/shared/config/team-account-navigation.config';
// local imports
import { TeamAccountWorkspace } from '../_lib/server/team-account-workspace.loader';
diff --git a/app/home/[account]/_components/team-account-notifications.tsx b/app/home/[account]/_components/team-account-notifications.tsx
index 5655ccf..5344490 100644
--- a/app/home/[account]/_components/team-account-notifications.tsx
+++ b/app/home/[account]/_components/team-account-notifications.tsx
@@ -1,19 +1,20 @@
import { NotificationsPopover } from '@kit/notifications/components';
-import featuresFlagConfig from '~/config/feature-flags.config';
+import { featureFlagsConfig } from '@kit/shared/config';
+
export function TeamAccountNotifications(params: {
userId: string;
accountId: string;
}) {
- if (!featuresFlagConfig.enableNotifications) {
+ if (!featureFlagsConfig.enableNotifications) {
return null;
}
return (
);
}
diff --git a/app/home/[account]/_components/team-account-statistics.tsx b/app/home/[account]/_components/team-account-statistics.tsx
index 98b0b83..a6bcf2a 100644
--- a/app/home/[account]/_components/team-account-statistics.tsx
+++ b/app/home/[account]/_components/team-account-statistics.tsx
@@ -8,8 +8,9 @@ import { ChevronRight, Euro, User } from 'lucide-react';
import { Card } from '@kit/ui/card';
import { Trans } from '@kit/ui/makerkit/trans';
-import pathsConfig from '~/config/paths.config';
-import { createPath } from '~/config/team-account-navigation.config';
+import { pathsConfig } from '@kit/shared/config';
+
+import { createPath } from '@kit/shared/config/team-account-navigation.config';
import TeamAccountBenefitStatistics from './team-account-benefit-statistics';
import TeamAccountHealthDetails from './team-account-health-details';
diff --git a/app/home/[account]/_lib/server/team-account-workspace.loader.ts b/app/home/[account]/_lib/server/team-account-workspace.loader.ts
index 7ca21ed..3ecaaee 100644
--- a/app/home/[account]/_lib/server/team-account-workspace.loader.ts
+++ b/app/home/[account]/_lib/server/team-account-workspace.loader.ts
@@ -7,7 +7,8 @@ import { redirect } from 'next/navigation';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
import { createTeamAccountsApi } from '@kit/team-accounts/api';
-import pathsConfig from '~/config/paths.config';
+import { pathsConfig } from '@kit/shared/config';
+
import { requireUserInServerComponent } from '~/lib/server/require-user-in-server-component';
export type TeamAccountWorkspace = Awaited<
diff --git a/app/home/[account]/billing/_components/team-account-checkout-form.tsx b/app/home/[account]/billing/_components/team-account-checkout-form.tsx
index 8bfc54c..ae0e711 100644
--- a/app/home/[account]/billing/_components/team-account-checkout-form.tsx
+++ b/app/home/[account]/billing/_components/team-account-checkout-form.tsx
@@ -16,7 +16,7 @@ import {
} from '@kit/ui/card';
import { Trans } from '@kit/ui/trans';
-import billingConfig from '~/config/billing.config';
+import { billingConfig } from '@kit/shared/config';
import { createTeamAccountCheckoutSession } from '../_lib/server/server-actions';
diff --git a/app/home/[account]/billing/_lib/server/server-actions.ts b/app/home/[account]/billing/_lib/server/server-actions.ts
index f7024a8..3b53c59 100644
--- a/app/home/[account]/billing/_lib/server/server-actions.ts
+++ b/app/home/[account]/billing/_lib/server/server-actions.ts
@@ -5,10 +5,9 @@ import { redirect } from 'next/navigation';
import { UpdateHealthBenefitData } from '@/packages/features/team-accounts/src/server/types';
import { enhanceAction } from '@kit/next/actions';
+import { featureFlagsConfig } from '@kit/shared/config';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
-import featureFlagsConfig from '~/config/feature-flags.config';
-
// billing imports
import {
TeamBillingPortalSchema,
diff --git a/app/home/[account]/billing/_lib/server/team-billing.service.ts b/app/home/[account]/billing/_lib/server/team-billing.service.ts
index e0decb6..555aa3a 100644
--- a/app/home/[account]/billing/_lib/server/team-billing.service.ts
+++ b/app/home/[account]/billing/_lib/server/team-billing.service.ts
@@ -13,9 +13,10 @@ import { requireUser } from '@kit/supabase/require-user';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
import { createTeamAccountsApi } from '@kit/team-accounts/api';
-import appConfig from '~/config/app.config';
-import billingConfig from '~/config/billing.config';
-import pathsConfig from '~/config/paths.config';
+import appConfig from '@kit/shared/config/app.config';
+import { billingConfig } from '@kit/shared/config';
+import { pathsConfig } from '@kit/shared/config';
+
import { TeamCheckoutSchema } from '../schema/team-billing.schema';
diff --git a/app/home/[account]/billing/layout.tsx b/app/home/[account]/billing/layout.tsx
index e6657eb..ec8a946 100644
--- a/app/home/[account]/billing/layout.tsx
+++ b/app/home/[account]/billing/layout.tsx
@@ -1,6 +1,6 @@
import { notFound } from 'next/navigation';
-import featureFlagsConfig from '~/config/feature-flags.config';
+import { featureFlagsConfig } from '@kit/shared/config';
function TeamAccountBillingLayout(props: React.PropsWithChildren) {
const isEnabled = featureFlagsConfig.enableTeamAccountBilling;
diff --git a/app/home/[account]/billing/page.tsx b/app/home/[account]/billing/page.tsx
index bc8288b..9291324 100644
--- a/app/home/[account]/billing/page.tsx
+++ b/app/home/[account]/billing/page.tsx
@@ -6,6 +6,7 @@ import { PageBody } from '@kit/ui/page';
import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
import { withI18n } from '~/lib/i18n/with-i18n';
+
import HealthBenefitForm from './_components/health-benefit-form';
interface TeamAccountBillingPageProps {
diff --git a/app/home/[account]/billing/return/page.tsx b/app/home/[account]/billing/return/page.tsx
index b9b0310..08274e3 100644
--- a/app/home/[account]/billing/return/page.tsx
+++ b/app/home/[account]/billing/return/page.tsx
@@ -2,13 +2,13 @@ import { notFound, redirect } from 'next/navigation';
import { getBillingGatewayProvider } from '@kit/billing-gateway';
import { BillingSessionStatus } from '@kit/billing-gateway/components';
+import { billingConfig } from '@kit/shared/config';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
-import billingConfig from '~/config/billing.config';
-import { withI18n } from '~/lib/i18n/with-i18n';
import { requireUserInServerComponent } from '~/lib/server/require-user-in-server-component';
import { EmbeddedCheckoutForm } from '../_components/embedded-checkout-form';
+import { withI18n } from '~/lib/i18n/with-i18n';
interface SessionPageProps {
searchParams: Promise<{
diff --git a/app/home/[account]/layout.tsx b/app/home/[account]/layout.tsx
index 9f0d255..42620f2 100644
--- a/app/home/[account]/layout.tsx
+++ b/app/home/[account]/layout.tsx
@@ -4,22 +4,20 @@ import { cookies } from 'next/headers';
import { z } from 'zod';
+import { AppLogo } from '@kit/shared/components/app-logo';
+import { getTeamAccountSidebarConfig } from '@kit/shared/config';
import {
CompanyGuard,
TeamAccountWorkspaceContextProvider,
} from '@kit/team-accounts/components';
import { Page, PageMobileNavigation, PageNavigation } from '@kit/ui/page';
import { SidebarProvider } from '@kit/ui/shadcn-sidebar';
-
-import { AppLogo } from '~/components/app-logo';
-import { getTeamAccountSidebarConfig } from '~/config/team-account-navigation.config';
-import { withI18n } from '~/lib/i18n/with-i18n';
-
// local imports
import { TeamAccountLayoutMobileNavigation } from './_components/team-account-layout-mobile-navigation';
import { TeamAccountLayoutSidebar } from './_components/team-account-layout-sidebar';
import { TeamAccountNavigationMenu } from './_components/team-account-navigation-menu';
import { loadTeamWorkspace } from './_lib/server/team-account-workspace.loader';
+import { withI18n } from '~/lib/i18n/with-i18n';
type TeamWorkspaceLayoutProps = React.PropsWithChildren<{
params: Promise<{ account: string }>;
diff --git a/app/home/[account]/members/page.tsx b/app/home/[account]/members/page.tsx
index bb471bf..4eddcef 100644
--- a/app/home/[account]/members/page.tsx
+++ b/app/home/[account]/members/page.tsx
@@ -22,6 +22,7 @@ import { Trans } from '@kit/ui/trans';
import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
import { withI18n } from '~/lib/i18n/with-i18n';
+
// local imports
import { TeamAccountLayoutPageHeader } from '../_components/team-account-layout-page-header';
import { loadMembersPageData } from './_lib/server/members-page.loader';
diff --git a/app/home/[account]/page.tsx b/app/home/[account]/page.tsx
index 46a0c2b..4c79849 100644
--- a/app/home/[account]/page.tsx
+++ b/app/home/[account]/page.tsx
@@ -10,10 +10,10 @@ import { PageBody } from '@kit/ui/page';
import { Trans } from '@kit/ui/trans';
import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
-import { withI18n } from '~/lib/i18n/with-i18n';
import { Dashboard } from './_components/dashboard';
import { TeamAccountLayoutPageHeader } from './_components/team-account-layout-page-header';
+import { withI18n } from '~/lib/i18n/with-i18n';
interface TeamAccountHomePageProps {
params: Promise<{ account: string }>;
diff --git a/app/home/[account]/settings/page.tsx b/app/home/[account]/settings/page.tsx
index 3a1a492..3d0e9a6 100644
--- a/app/home/[account]/settings/page.tsx
+++ b/app/home/[account]/settings/page.tsx
@@ -1,3 +1,4 @@
+import { featureFlagsConfig, pathsConfig } from '@kit/shared/config';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
import { createTeamAccountsApi } from '@kit/team-accounts/api';
import { TeamAccountSettingsContainer } from '@kit/team-accounts/components';
@@ -5,8 +6,6 @@ import { AppBreadcrumbs } from '@kit/ui/app-breadcrumbs';
import { PageBody } from '@kit/ui/page';
import { Trans } from '@kit/ui/trans';
-import featuresFlagConfig from '~/config/feature-flags.config';
-import pathsConfig from '~/config/paths.config';
import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
// local imports
@@ -43,7 +42,7 @@ async function TeamAccountSettingsPage(props: TeamAccountSettingsPageProps) {
};
const features = {
- enableTeamDeletion: featuresFlagConfig.enableTeamDeletion,
+ enableTeamDeletion: featureFlagsConfig.enableTeamDeletion,
};
return (
diff --git a/app/join/page.tsx b/app/join/page.tsx
index 5237a6a..82a0db4 100644
--- a/app/join/page.tsx
+++ b/app/join/page.tsx
@@ -13,11 +13,13 @@ import { Button } from '@kit/ui/button';
import { Heading } from '@kit/ui/heading';
import { Trans } from '@kit/ui/trans';
-import { AppLogo } from '~/components/app-logo';
-import pathsConfig from '~/config/paths.config';
+import { AppLogo } from '@kit/shared/components/app-logo';
+import { pathsConfig } from '@kit/shared/config';
+
import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
import { withI18n } from '~/lib/i18n/with-i18n';
+
interface JoinTeamAccountPageProps {
searchParams: Promise<{
invite_token?: string;
diff --git a/app/layout.tsx b/app/layout.tsx
index 20e1057..4ea28c2 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -1,13 +1,13 @@
import { headers } from 'next/headers';
+import { RootProviders } from '@kit/shared/components/root-providers';
import { Toaster } from '@kit/ui/sonner';
-import { RootProviders } from '~/components/root-providers';
import { getFontsClassName } from '~/lib/fonts';
-import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
import { generateRootMetadata } from '~/lib/root-metdata';
import { getRootTheme } from '~/lib/root-theme';
+import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
import '../styles/globals.css';
export const generateMetadata = () => {
diff --git a/app/not-found.tsx b/app/not-found.tsx
index 5422bc1..5b6bd81 100644
--- a/app/not-found.tsx
+++ b/app/not-found.tsx
@@ -7,8 +7,8 @@ import { Heading } from '@kit/ui/heading';
import { Trans } from '@kit/ui/trans';
import { SiteHeader } from '~/(marketing)/_components/site-header';
-import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
import { withI18n } from '~/lib/i18n/with-i18n';
+import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
export const generateMetadata = async () => {
const i18n = await createI18nServerInstance();
diff --git a/app/robots.ts b/app/robots.ts
index 495a819..72512d9 100644
--- a/app/robots.ts
+++ b/app/robots.ts
@@ -1,6 +1,6 @@
import { MetadataRoute } from 'next';
-import appConfig from '~/config/app.config';
+import appConfig from '@kit/shared/config/app.config';
export default function robots(): MetadataRoute.Robots {
return {
diff --git a/app/select-package/page.tsx b/app/select-package/page.tsx
index d0fcd41..41a73ac 100644
--- a/app/select-package/page.tsx
+++ b/app/select-package/page.tsx
@@ -1,6 +1,6 @@
import Link from 'next/link';
-import SelectAnalysisPackages from '@/components/select-analysis-packages';
+import SelectAnalysisPackages from '@kit/shared/components/select-analysis-packages';
import { CaretRightIcon } from '@radix-ui/react-icons';
import { Scale } from 'lucide-react';
@@ -10,6 +10,7 @@ import { Trans } from '@kit/ui/trans';
import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
import { withI18n } from '~/lib/i18n/with-i18n';
+
import { MedReportLogo } from '../../components/med-report-logo';
import pathsConfig from '../../config/paths.config';
import ComparePackagesModal from '../home/(user)/_components/compare-packages-modal';
diff --git a/app/sitemap.xml/route.ts b/app/sitemap.xml/route.ts
index 9c6fb67..fe2641b 100644
--- a/app/sitemap.xml/route.ts
+++ b/app/sitemap.xml/route.ts
@@ -2,7 +2,7 @@ import { getServerSideSitemap } from 'next-sitemap';
import { createCmsClient } from '@kit/cms';
-import appConfig from '~/config/app.config';
+import appConfig from '@kit/shared/config/app.config';
/**
* @description The maximum age of the sitemap in seconds.
diff --git a/app/update-password/page.tsx b/app/update-password/page.tsx
index f78bbd7..293e42f 100644
--- a/app/update-password/page.tsx
+++ b/app/update-password/page.tsx
@@ -1,11 +1,11 @@
import { UpdatePasswordForm } from '@kit/auth/password-reset';
import { AuthLayoutShell } from '@kit/auth/shared';
+import { AppLogo } from '@kit/shared/components/app-logo';
+import { pathsConfig } from '@kit/shared/config';
-import { AppLogo } from '~/components/app-logo';
-import pathsConfig from '~/config/paths.config';
import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
-import { withI18n } from '~/lib/i18n/with-i18n';
import { requireUserInServerComponent } from '~/lib/server/require-user-in-server-component';
+import { withI18n } from '~/lib/i18n/with-i18n';
export const generateMetadata = async () => {
const { t } = await createI18nServerInstance();
diff --git a/components/med-report-logo.tsx b/components/med-report-logo.tsx
deleted file mode 100644
index efbea64..0000000
--- a/components/med-report-logo.tsx
+++ /dev/null
@@ -1,11 +0,0 @@
-import { cn } from "@/lib/utils";
-import { MedReportSmallLogo } from "@/public/assets/med-report-small-logo";
-
-export const MedReportLogo = ({ className, compact = false }: { className?: string, compact?: boolean }) => (
-
-
- {!compact &&
- MedReport
- }
-
-);
diff --git a/lib/i18n/i18n.server.ts b/lib/i18n/i18n.server.ts
index 9074d2b..4545b57 100644
--- a/lib/i18n/i18n.server.ts
+++ b/lib/i18n/i18n.server.ts
@@ -10,8 +10,8 @@ import {
initializeServerI18n,
parseAcceptLanguageHeader,
} from '@kit/i18n/server';
+import { featureFlagsConfig } from '@kit/shared/config';
-import featuresFlagConfig from '~/config/feature-flags.config';
import {
I18N_COOKIE_NAME,
getI18nSettings,
@@ -24,7 +24,7 @@ import { i18nResolver } from './i18n.resolver';
* @name priority
* @description The language priority setting from the feature flag configuration.
*/
-const priority = featuresFlagConfig.languagePriority;
+const priority = featureFlagsConfig.languagePriority;
/**
* @name createI18nServerInstance
diff --git a/lib/i18n/i18n.settings.ts b/lib/i18n/i18n.settings.ts
index e41095f..92606a4 100644
--- a/lib/i18n/i18n.settings.ts
+++ b/lib/i18n/i18n.settings.ts
@@ -40,6 +40,7 @@ export const defaultI18nNamespaces = [
'cart',
'orders',
'analysis-results',
+ 'doctor',
];
/**
diff --git a/lib/root-metdata.ts b/lib/root-metdata.ts
index cf221a6..99a5bc3 100644
--- a/lib/root-metdata.ts
+++ b/lib/root-metdata.ts
@@ -2,7 +2,7 @@ import { Metadata } from 'next';
import { headers } from 'next/headers';
-import appConfig from '~/config/app.config';
+import { appConfig } from '@kit/shared/config';
/**
* @name generateRootMetadata
diff --git a/lib/utils.ts b/lib/utils.ts
index 592732f..a5ffd5b 100644
--- a/lib/utils.ts
+++ b/lib/utils.ts
@@ -1,5 +1,6 @@
import { Database } from '@/packages/supabase/src/database.types';
import { type ClassValue, clsx } from 'clsx';
+import Isikukood, { Gender } from 'isikukood';
import { twMerge } from 'tailwind-merge';
import { BmiCategory } from './types/bmi';
@@ -83,3 +84,10 @@ export function getBmiBackgroundColor(bmiStatus: BmiCategory | null): string {
return 'bg-success';
}
}
+
+export function getGenderStringFromPersonalCode(personalCode: string) {
+ const person = new Isikukood(personalCode);
+ if (person.getGender() === Gender.FEMALE) return 'common:female';
+ if (person.getGender() === Gender.MALE) return 'common:male';
+ return 'common:unknown';
+}
diff --git a/middleware.ts b/middleware.ts
index 5632285..09aff36 100644
--- a/middleware.ts
+++ b/middleware.ts
@@ -4,12 +4,12 @@ import { NextResponse, URLPattern } from 'next/server';
import { CsrfError, createCsrfProtect } from '@edge-csrf/nextjs';
import { isSuperAdmin } from '@kit/admin';
+import { isDoctor } from '@kit/doctor';
+import { appConfig, pathsConfig } from '@kit/shared/config';
import { checkRequiresMultiFactorAuthentication } from '@kit/supabase/check-requires-mfa';
import { createMiddlewareClient } from '@kit/supabase/middleware-client';
-import { middleware as medusaMiddleware } from "~/medusa/middleware";
-import appConfig from '~/config/app.config';
-import pathsConfig from '~/config/paths.config';
+import { middleware as medusaMiddleware } from '~/medusa/middleware';
const CSRF_SECRET_COOKIE = 'csrfSecret';
const NEXT_ACTION_HEADER = 'next-action';
@@ -132,6 +132,34 @@ async function adminMiddleware(request: NextRequest, response: NextResponse) {
return response;
}
+async function doctorMiddleware(request: NextRequest, response: NextResponse) {
+ const isDoctorPath = request.nextUrl.pathname.startsWith('/doctor');
+
+ if (!isDoctorPath) {
+ return;
+ }
+
+ const {
+ data: { user },
+ error,
+ } = await getUser(request, response);
+
+ if (!user || error) {
+ return NextResponse.redirect(
+ new URL(pathsConfig.auth.signIn, request.nextUrl.origin).href,
+ );
+ }
+
+ const client = createMiddlewareClient(request, response);
+ const userIsDoctor = await isDoctor(client);
+
+ if (!userIsDoctor) {
+ return NextResponse.redirect(new URL('/404', request.nextUrl.origin).href);
+ }
+
+ return response;
+}
+
/**
* Define URL patterns and their corresponding handlers.
*/
@@ -141,6 +169,10 @@ function getPatterns() {
pattern: new URLPattern({ pathname: '/admin/*?' }),
handler: adminMiddleware,
},
+ {
+ pattern: new URLPattern({ pathname: '/doctor/*?' }),
+ handler: doctorMiddleware,
+ },
{
pattern: new URLPattern({ pathname: '/auth/update-account' }),
handler: async (req: NextRequest, res: NextResponse) => {
diff --git a/packages/billing/montonio/src/services/montonio-order-handler.service.ts b/packages/billing/montonio/src/services/montonio-order-handler.service.ts
index ee1b630..7b23ae4 100644
--- a/packages/billing/montonio/src/services/montonio-order-handler.service.ts
+++ b/packages/billing/montonio/src/services/montonio-order-handler.service.ts
@@ -59,7 +59,7 @@ export class MontonioOrderHandlerService {
if (error instanceof AxiosError) {
console.error(error.response?.data);
}
- console.error("Failed to create payment link, params=${JSON.stringify(params)}", error);
+ console.error(`Failed to create payment link, params=${JSON.stringify(params)}`, error);
throw new Error("Failed to create payment link");
}
}
diff --git a/packages/features/doctor/package.json b/packages/features/doctor/package.json
index 48e3d23..9fab3a4 100644
--- a/packages/features/doctor/package.json
+++ b/packages/features/doctor/package.json
@@ -31,7 +31,10 @@
},
"exports": {
".": "./src/index.ts",
- "./components/*": "./src/components/*.tsx"
+ "./services/*": "./src/lib/server/services/*.ts",
+ "./actions/*": "./src/lib/server/actions/*.ts",
+ "./schema/*": "./src/lib/server/schema/*.ts",
+ "./lib/*": "./src/lib/*.ts"
},
"typesVersions": {
"*": {
@@ -40,4 +43,4 @@
]
}
}
-}
+}
\ No newline at end of file
diff --git a/packages/features/doctor/src/lib/helpers.ts b/packages/features/doctor/src/lib/helpers.ts
new file mode 100644
index 0000000..14e1065
--- /dev/null
+++ b/packages/features/doctor/src/lib/helpers.ts
@@ -0,0 +1,21 @@
+import { formatDate, getPersonParameters } from '@kit/shared/utils';
+
+export function getResultSetName(
+ title: string,
+ isPackage: boolean,
+ nrOfElements: number,
+) {
+ return !isPackage && nrOfElements > 1 ? 'doctor:analyses' : title;
+}
+
+export function getDOBWithAgeStringFromPersonalCode(
+ personalCode: string | null,
+) {
+ if (!personalCode) return 'common:unknown';
+
+ const person = getPersonParameters(personalCode);
+
+ if (!person) return 'common:unknown';
+
+ return `${formatDate(person.dob.toString())} (${person.age})`;
+}
diff --git a/packages/features/doctor/src/lib/server/actions/doctor-server-actions.ts b/packages/features/doctor/src/lib/server/actions/doctor-server-actions.ts
new file mode 100644
index 0000000..363de2f
--- /dev/null
+++ b/packages/features/doctor/src/lib/server/actions/doctor-server-actions.ts
@@ -0,0 +1,110 @@
+'use server';
+
+import { revalidatePath, revalidateTag } from 'next/cache';
+
+import { enhanceAction } from '@kit/next/actions';
+import { getLogger } from '@kit/shared/logger';
+
+import {
+ DoctorAnalysisFeedbackTable,
+ DoctorJobSelect,
+ DoctorJobUnselect,
+ doctorAnalysisFeedbackSchema,
+ doctorJobSelectSchema,
+ doctorJobUnselectSchema,
+} from '../schema/doctor-analysis.schema';
+import {
+ selectJob,
+ submitFeedback,
+ unselectJob,
+} from '../services/doctor-analysis.service';
+import { doctorAction } from '../utils/doctor-action';
+
+/**
+ * @name selectJobAction
+ * @description Creates a feedback item that ties doctor to the job and enables giving feedback.
+ */
+export const selectJobAction = doctorAction(
+ enhanceAction(
+ async ({ analysisOrderId, userId }: DoctorJobSelect) => {
+ const logger = await getLogger();
+
+ logger.info({ analysisOrderId }, `Selecting new job`);
+
+ await selectJob(analysisOrderId, userId);
+
+ logger.info({ analysisOrderId }, `Successfully selected`);
+
+ return { success: true };
+ },
+ {
+ schema: doctorJobSelectSchema,
+ },
+ ),
+);
+
+/**
+ * @name unselectJobAction
+ * @description Removes the current user from a feedback item.
+ */
+export const unselectJobAction = doctorAction(
+ enhanceAction(
+ async ({ analysisOrderId }: DoctorJobUnselect) => {
+ const logger = await getLogger();
+
+ logger.info({ analysisOrderId }, `Removing doctor from job`);
+
+ await unselectJob(analysisOrderId);
+
+ logger.info(
+ { analysisOrderId },
+ `Successfully removed current doctor from job`,
+ );
+
+ return { success: true };
+ },
+ {
+ schema: doctorJobUnselectSchema,
+ },
+ ),
+);
+
+/**
+ * @name giveFeedbackAction
+ * @description Creates or updates doctor analysis feedback.
+ */
+export const giveFeedbackAction = doctorAction(
+ enhanceAction(
+ async ({
+ feedbackValue,
+ userId,
+ analysisOrderId,
+ status,
+ }: {
+ feedbackValue: string;
+ userId: DoctorAnalysisFeedbackTable['user_id'];
+ analysisOrderId: DoctorAnalysisFeedbackTable['analysis_order_id'];
+ status: DoctorAnalysisFeedbackTable['status'];
+ }) => {
+ const logger = await getLogger();
+
+ logger.info(
+ { analysisOrderId },
+ `Submitting feedback for analysis order...`,
+ );
+
+ const result = await submitFeedback(
+ analysisOrderId,
+ userId,
+ feedbackValue,
+ status,
+ );
+ logger.info({ analysisOrderId }, `Successfully submitted feedback`);
+
+ return result;
+ },
+ {
+ schema: doctorAnalysisFeedbackSchema,
+ },
+ ),
+);
diff --git a/packages/features/doctor/src/lib/server/actions/table-data-fetching-actions.ts b/packages/features/doctor/src/lib/server/actions/table-data-fetching-actions.ts
new file mode 100644
index 0000000..577e82b
--- /dev/null
+++ b/packages/features/doctor/src/lib/server/actions/table-data-fetching-actions.ts
@@ -0,0 +1,69 @@
+'use server';
+
+import { getLogger } from '@kit/shared/logger';
+
+import {
+ getOpenResponses,
+ getOtherResponses,
+ getUserDoneResponses,
+ getUserInProgressResponses,
+} from '@kit/doctor/services/doctor-analysis.service';
+import { doctorAction } from '../utils/doctor-action';
+
+export const getUserDoneResponsesAction = doctorAction(
+ async ({ page, pageSize }: { page: number; pageSize: number }) => {
+ const logger = await getLogger();
+ try {
+ const data = await getUserDoneResponses({ page, pageSize });
+ return { success: true, data };
+ } catch (error) {
+ logger.error(`Error fetching data for user completed jobs`, error);
+ return { success: false, error: 'Failed to fetch data from the server.' };
+ }
+ },
+);
+
+export const getUserInProgressResponsesAction = doctorAction(
+ async ({ page, pageSize }: { page: number; pageSize: number }) => {
+ const logger = await getLogger();
+ try {
+ const data = await getUserInProgressResponses({ page, pageSize });
+ return { success: true, data };
+ } catch (error) {
+ logger.error(`Error fetching data for user's in progress jobs`, error);
+ return { success: false, error: 'Failed to fetch data from the server.' };
+ }
+ },
+);
+
+export const getOtherResponsesAction = doctorAction(
+ async ({ page, pageSize }: { page: number; pageSize: number }) => {
+ const logger = await getLogger();
+ try {
+ const data = await getOtherResponses({ page, pageSize });
+ return { success: true, data };
+ } catch (error) {
+ logger.error(
+ `Error fetching data for other analysis response jobs`,
+ error,
+ );
+ return {
+ success: false,
+ error: 'Failed to fetch data from the server.',
+ };
+ }
+ },
+);
+
+export const getOpenResponsesAction = doctorAction(
+ async ({ page, pageSize }: { page: number; pageSize: number }) => {
+ const logger = await getLogger();
+ try {
+ const data = await getOpenResponses({ page, pageSize });
+ return { success: true, data };
+ } catch (error) {
+ logger.error(`Error fetching open analysis response jobs`, error);
+ return { success: false, error: 'Failed to fetch data from the server.' };
+ }
+ },
+);
diff --git a/packages/features/doctor/src/lib/server/schema/doctor-analysis-detail-view.schema.ts b/packages/features/doctor/src/lib/server/schema/doctor-analysis-detail-view.schema.ts
new file mode 100644
index 0000000..a5ae622
--- /dev/null
+++ b/packages/features/doctor/src/lib/server/schema/doctor-analysis-detail-view.schema.ts
@@ -0,0 +1,78 @@
+import * as z from 'zod';
+
+export const AnalysisOrderIdSchema = z.object({
+ id: z.number(),
+ medusa_order_id: z.string(),
+ analysis_element_ids: z.array(z.number()).nullable(),
+});
+export type AnalysisOrderId = z.infer;
+
+export const DoctorFeedbackSchema = z
+ .object({
+ id: z.number(),
+ analysis_order_id: z.number(),
+ doctor_user_id: z.string().nullable(),
+ user_id: z.string(),
+ value: z.string().nullable(),
+ created_at: z.string(),
+ updated_at: z.string().nullable(),
+ created_by: z.string(),
+ updated_by: z.string().nullable(),
+ status: z.string(),
+ })
+ .optional()
+ .nullable();
+export type DoctorFeedback = z.infer;
+
+export const OrderSchema = z.object({
+ title: z.string(),
+ isPackage: z.boolean(),
+ analysisOrderId: z.number(),
+});
+export type Order = z.infer;
+
+export const PatientSchema = z.object({
+ userId: z.string(),
+ accountId: z.string(),
+ firstName: z.string(),
+ lastName: z.string().nullable(),
+ personalCode: z.string().nullable(),
+ phone: z.string().nullable(),
+ email: z.string().nullable(),
+ height: z.number().optional().nullable(),
+ weight: z.number().optional().nullable(),
+});
+export type Patient = z.infer;
+
+export const AnalysisResponsesSchema = z.object({
+ user_id: z.string(),
+ analysis_order_id: AnalysisOrderIdSchema,
+});
+export type AnalysisResponses = z.infer;
+
+export const AnalysisResponseSchema = z.object({
+ id: z.number(),
+ analysis_response_id: z.number(),
+ analysis_element_original_id: z.string(),
+ unit: z.string().nullable(),
+ response_value: z.number(),
+ response_time: z.string(),
+ norm_upper: z.number().nullable(),
+ norm_upper_included: z.boolean().nullable(),
+ norm_lower: z.number().nullable(),
+ norm_lower_included: z.boolean().nullable(),
+ norm_status: z.number().nullable(),
+ created_at: z.string(),
+ updated_at: z.string().nullable(),
+ analysis_name: z.string().nullable(),
+ analysis_responses: AnalysisResponsesSchema,
+});
+export type AnalysisResponse = z.infer;
+
+export const AnalysisResultDetailsSchema = z.object({
+ analysisResponse: z.array(AnalysisResponseSchema),
+ order: OrderSchema,
+ doctorFeedback: DoctorFeedbackSchema,
+ patient: PatientSchema,
+});
+export type AnalysisResultDetails = z.infer;
diff --git a/packages/features/doctor/src/lib/server/schema/doctor-analysis.schema.ts b/packages/features/doctor/src/lib/server/schema/doctor-analysis.schema.ts
new file mode 100644
index 0000000..faecafe
--- /dev/null
+++ b/packages/features/doctor/src/lib/server/schema/doctor-analysis.schema.ts
@@ -0,0 +1,135 @@
+import z from 'zod/v3';
+
+import { Database } from '@kit/supabase/database';
+
+export const doctorJobSelectSchema = z.object({
+ userId: z.string().uuid(),
+ analysisOrderId: z.number(),
+});
+export type DoctorJobSelect = z.infer;
+
+export const doctorJobUnselectSchema = z.object({
+ analysisOrderId: z.number(),
+});
+export type DoctorJobUnselect = z.infer;
+
+export const FeedbackStatus = z.enum(['STARTED', 'DRAFT', 'COMPLETED']);
+export const doctorAnalysisFeedbackFormSchema = z.object({
+ feedbackValue: z.string().min(15),
+ userId: z.string().uuid(),
+});
+export type DoctorAnalysisFeedbackForm = z.infer<
+ typeof doctorAnalysisFeedbackFormSchema
+>;
+export const doctorAnalysisFeedbackSchema = z.object({
+ feedbackValue: z.string().min(15),
+ userId: z.string().uuid(),
+ analysisOrderId: z.number(),
+ status: FeedbackStatus,
+});
+export type DoctorAnalysisFeedback = z.infer<
+ typeof doctorAnalysisFeedbackSchema
+>;
+
+export type DoctorAnalysisFeedbackTable =
+ Database['medreport']['Tables']['doctor_analysis_feedback']['Row'];
+
+export const AnalysisOrderIdSchema = z.object({
+ id: z.number(),
+ medusa_order_id: z.string(),
+ analysis_element_ids: z.array(z.number()).nullable(),
+});
+export type AnalysisOrderId = z.infer;
+
+export const ElementSchema = z.object({
+ id: z.number(),
+ analysis_response_id: z.number(),
+ analysis_element_original_id: z.string(),
+ unit: z.string().nullable(),
+ response_value: z.number(),
+ response_time: z.string(),
+ norm_upper: z.number().nullable(),
+ norm_upper_included: z.boolean().nullable(),
+ norm_lower: z.number().nullable(),
+ norm_lower_included: z.boolean().nullable(),
+ norm_status: z.number().nullable(),
+ created_at: z.string(),
+ updated_at: z.string().nullable(),
+ analysis_name: z.string().nullable(),
+});
+export type Element = z.infer;
+
+export const FeedbackSchema = z.object({
+ analysis_order_id: z.number(),
+ user_id: z.string(),
+ doctor_user_id: z.string().nullable(),
+ status: z.string(),
+});
+export type Feedback = z.infer;
+
+export const OrderSchema = z.object({
+ title: z.string().optional().nullable(),
+ isPackage: z.boolean(),
+ analysisOrderId: z.number(),
+});
+export type Order = z.infer;
+
+export const AccountSchema = z.object({
+ name: z.string(),
+ last_name: z.string().nullable(),
+ id: z.string(),
+ primary_owner_user_id: z.string(),
+});
+export type Account = z.infer;
+
+export const ResponseTableSchema = z.object({
+ id: z.number(),
+ analysis_order_id: AnalysisOrderIdSchema,
+ order_number: z.string(),
+ order_status: z.string(),
+ user_id: z.string(),
+ created_at: z.string(),
+ updated_at: z.string().nullable(),
+ elements: z.array(ElementSchema),
+ firstSampleGivenAt: z.string().nullable(),
+ patient: AccountSchema,
+ order: OrderSchema,
+ doctor: AccountSchema.optional().nullable(),
+ feedback: FeedbackSchema.optional(),
+});
+export type ResponseTable = z.infer;
+
+export type AnalysisResponseBase = {
+ analysis_order_id: number & {
+ id: number;
+ medusa_order_id: string;
+ analysis_element_ids: number[] | null;
+ };
+ created_at: string;
+ id: number;
+ order_number: string;
+ order_status: Database['medreport']['Tables']['analysis_orders']['Row']['status'];
+ updated_at: string | null;
+ user_id: string;
+};
+
+export interface PaginationParams {
+ page?: number;
+ pageSize?: number;
+}
+
+export interface PaginatedData {
+ data: T[];
+ pagination: {
+ currentPage: number;
+ totalPages: number;
+ totalCount: number;
+ pageSize: number;
+ };
+}
+
+export interface ServerActionResponse {
+ success: boolean;
+ data?: T;
+ error?: string;
+}
diff --git a/packages/features/doctor/src/lib/server/services/doctor-analysis.service.ts b/packages/features/doctor/src/lib/server/services/doctor-analysis.service.ts
new file mode 100644
index 0000000..fae6290
--- /dev/null
+++ b/packages/features/doctor/src/lib/server/services/doctor-analysis.service.ts
@@ -0,0 +1,583 @@
+import 'server-only';
+
+import { getSupabaseServerClient } from '@kit/supabase/server-client';
+
+import { AnalysisResultDetails } from '../schema/doctor-analysis-detail-view.schema';
+import {
+ AnalysisResponseBase,
+ DoctorAnalysisFeedbackTable,
+ PaginatedData,
+ PaginationParams,
+ ResponseTable,
+} from '../schema/doctor-analysis.schema';
+
+async function enrichAnalysisData(analysisResponses?: AnalysisResponseBase[]) {
+ const supabase = getSupabaseServerClient();
+
+ if (!analysisResponses?.length) return [];
+
+ const analysisResponseIds = analysisResponses.map((r) => r.id);
+ const medusaOrderIds = analysisResponses.map(
+ (r) => r.analysis_order_id.medusa_order_id,
+ );
+ const userIds = analysisResponses.map((response) => response.user_id);
+
+ const [
+ { data: doctorFeedbackItems },
+ { data: medusaOrderItems },
+ { data: analysisResponseElements },
+ { data: accounts },
+ ] = await Promise.all([
+ supabase
+ .schema('medreport')
+ .from('doctor_analysis_feedback')
+ .select('analysis_order_id, user_id, doctor_user_id, status')
+ .in(
+ 'analysis_order_id',
+ analysisResponses.map((r) => r.analysis_order_id.id),
+ ),
+ supabase
+ .schema('public')
+ .from('order_item')
+ .select('order_id, item_id(product_title, product_type)')
+ .in('order_id', medusaOrderIds),
+ supabase
+ .schema('medreport')
+ .from('analysis_response_elements')
+ .select('*')
+ .in('analysis_response_id', analysisResponseIds),
+ supabase
+ .schema('medreport')
+ .from('accounts')
+ .select('name, last_name, id, primary_owner_user_id')
+ .in('primary_owner_user_id', userIds),
+ ]);
+
+ const doctorUserIds =
+ doctorFeedbackItems
+ ?.map((item) => item.doctor_user_id)
+ .filter((value) => value !== null) || [];
+
+ const { data: doctorAccounts } = doctorUserIds.length
+ ? await supabase
+ .schema('medreport')
+ .from('accounts')
+ .select('name, last_name, id, primary_owner_user_id')
+ .in('primary_owner_user_id', doctorUserIds)
+ : { data: [] };
+
+ const allAccounts = [...(accounts ?? []), ...(doctorAccounts ?? [])];
+
+ return analysisResponses.map((analysisResponse) => {
+ const responseElements =
+ analysisResponseElements?.filter(
+ (element) => element.analysis_response_id === analysisResponse.id,
+ ) || [];
+
+ const firstSampleGivenAt = responseElements.length
+ ? responseElements.reduce((earliest, current) =>
+ new Date(current.response_time) < new Date(earliest.response_time)
+ ? current
+ : earliest,
+ )?.response_time
+ : null;
+
+ const medusaOrder = medusaOrderItems?.find(
+ ({ order_id }) =>
+ order_id === analysisResponse.analysis_order_id.medusa_order_id,
+ );
+
+ const patientAccount = allAccounts?.find(
+ ({ primary_owner_user_id }) =>
+ analysisResponse.user_id === primary_owner_user_id,
+ );
+
+ const feedback = doctorFeedbackItems?.find(
+ ({ analysis_order_id }) =>
+ analysis_order_id === analysisResponse.analysis_order_id.id,
+ );
+
+ const doctorAccount = allAccounts?.find(
+ ({ primary_owner_user_id }) =>
+ feedback?.doctor_user_id === primary_owner_user_id,
+ );
+
+ const order = {
+ title: medusaOrder?.item_id.product_title,
+ isPackage:
+ medusaOrder?.item_id.product_type?.toLowerCase() === 'analysis package',
+ analysisOrderId: analysisResponse.analysis_order_id.id,
+ status: analysisResponse.order_status,
+ };
+
+ if (!patientAccount || !analysisResponse) {
+ throw new Error('Invalid data');
+ }
+
+ return {
+ ...analysisResponse,
+ elements: responseElements,
+ firstSampleGivenAt,
+ patient: patientAccount,
+ doctor: doctorAccount,
+ order,
+ feedback,
+ };
+ });
+}
+
+export async function getUserInProgressResponses({
+ page = 1,
+ pageSize = 10,
+}: PaginationParams): Promise> {
+ const supabase = getSupabaseServerClient();
+ const {
+ data: { user },
+ } = await supabase.auth.getUser();
+
+ if (!user) {
+ throw new Error('No user logged in.');
+ }
+
+ const { data: inProgressFeedback } = await supabase
+ .schema('medreport')
+ .from('doctor_analysis_feedback')
+ .select('analysis_order_id')
+ .eq('doctor_user_id', user.id)
+ .neq('status', 'COMPLETED');
+
+ if (!inProgressFeedback?.length) {
+ return {
+ data: [],
+ pagination: { currentPage: page, totalPages: 0, totalCount: 0, pageSize },
+ };
+ }
+
+ const analysisOrderIds = inProgressFeedback.map((f) => f.analysis_order_id);
+ const offset = (page - 1) * pageSize;
+
+ const {
+ data: analysisResponses,
+ error,
+ count,
+ } = await supabase
+ .schema('medreport')
+ .from('analysis_responses')
+ .select(
+ `
+ *,
+ analysis_order_id(id, medusa_order_id, analysis_element_ids)
+ `,
+ { count: 'exact' },
+ )
+ .in('analysis_order_id', analysisOrderIds)
+ .range(offset, offset + pageSize - 1)
+ .order('created_at', { ascending: false });
+
+ if (error) {
+ throw new Error('Something went wrong');
+ }
+
+ const enrichedData = await enrichAnalysisData(analysisResponses);
+ const totalCount = count || 0;
+ const totalPages = Math.ceil(totalCount / pageSize);
+
+ return {
+ data: enrichedData,
+ pagination: { currentPage: page, totalPages, totalCount, pageSize },
+ };
+}
+
+export async function getUserDoneResponses({
+ page = 1,
+ pageSize = 10,
+}: PaginationParams): Promise> {
+ const supabase = getSupabaseServerClient();
+ const {
+ data: { user },
+ } = await supabase.auth.getUser();
+
+ if (!user) {
+ throw new Error('No user logged in.');
+ }
+
+ const { data: completedFeedback } = await supabase
+ .schema('medreport')
+ .from('doctor_analysis_feedback')
+ .select('analysis_order_id')
+ .eq('doctor_user_id', user.id)
+ .eq('status', 'COMPLETED');
+
+ if (!completedFeedback?.length) {
+ return {
+ data: [],
+ pagination: { currentPage: page, totalPages: 0, totalCount: 0, pageSize },
+ };
+ }
+
+ const analysisOrderIds = completedFeedback.map((f) => f.analysis_order_id);
+ const offset = (page - 1) * pageSize;
+
+ const {
+ data: analysisResponses,
+ error,
+ count,
+ } = await supabase
+ .schema('medreport')
+ .from('analysis_responses')
+ .select(
+ `
+ *,
+ analysis_order_id(id, medusa_order_id, status, analysis_element_ids)
+ `,
+ { count: 'exact' },
+ )
+ .in('analysis_order_id', analysisOrderIds)
+ .range(offset, offset + pageSize - 1)
+ .order('created_at', { ascending: false });
+
+ if (error) throw error;
+
+ const enrichedData = await enrichAnalysisData(analysisResponses);
+ const totalCount = count || 0;
+ const totalPages = Math.ceil(totalCount / pageSize);
+
+ return {
+ data: enrichedData,
+ pagination: { currentPage: page, totalPages, totalCount, pageSize },
+ };
+}
+
+export async function getOpenResponses({
+ page = 1,
+ pageSize = 10,
+}: PaginationParams): Promise> {
+ const supabase = getSupabaseServerClient();
+
+ const { data: assignedOrderIds } = await supabase
+ .schema('medreport')
+ .from('doctor_analysis_feedback')
+ .select('analysis_order_id')
+ .not('doctor_user_id', 'is', null);
+
+ const assignedIds = assignedOrderIds?.map((f) => f.analysis_order_id) || [];
+ const offset = (page - 1) * pageSize;
+
+ let query = supabase
+ .schema('medreport')
+ .from('analysis_responses')
+ .select(
+ `
+ *,
+ analysis_order_id(id, medusa_order_id, analysis_element_ids)
+ `,
+ { count: 'exact' },
+ )
+ .order('created_at', { ascending: false });
+
+ if (assignedIds.length > 0) {
+ query = query.not('analysis_order_id', 'in', `(${assignedIds.join(',')})`);
+ }
+
+ const {
+ data: analysisResponses,
+ error,
+ count,
+ } = await query.range(offset, offset + pageSize - 1);
+ if (error) throw error;
+
+ const enrichedData = await enrichAnalysisData(analysisResponses);
+ const totalCount = count || 0;
+ const totalPages = Math.ceil(totalCount / pageSize);
+
+ return {
+ data: enrichedData,
+ pagination: { currentPage: page, totalPages, totalCount, pageSize },
+ };
+}
+
+export async function getOtherResponses({
+ page = 1,
+ pageSize = 10,
+}: PaginationParams): Promise> {
+ const supabase = getSupabaseServerClient();
+ const {
+ data: { user },
+ } = await supabase.auth.getUser();
+
+ if (!user) {
+ throw new Error('No user logged in.');
+ }
+
+ const { data: otherFeedback } = await supabase
+ .schema('medreport')
+ .from('doctor_analysis_feedback')
+ .select('analysis_order_id')
+ .neq('doctor_user_id', user.id);
+
+ if (!otherFeedback?.length) {
+ return {
+ data: [],
+ pagination: { currentPage: page, totalPages: 0, totalCount: 0, pageSize },
+ };
+ }
+
+ const analysisOrderIds = otherFeedback.map((f) => f.analysis_order_id);
+ const offset = (page - 1) * pageSize;
+
+ const {
+ data: analysisResponses,
+ error,
+ count,
+ } = await supabase
+ .schema('medreport')
+ .from('analysis_responses')
+ .select(
+ `
+ *,
+ analysis_order_id(id, medusa_order_id, analysis_element_ids)
+ `,
+ { count: 'exact' },
+ )
+ .in('analysis_order_id', analysisOrderIds)
+ .range(offset, offset + pageSize - 1)
+ .order('created_at', { ascending: false });
+
+ if (error) throw error;
+
+ const enrichedData = await enrichAnalysisData(analysisResponses);
+ const totalCount = count || 0;
+ const totalPages = Math.ceil(totalCount / pageSize);
+
+ return {
+ data: enrichedData,
+ pagination: { currentPage: page, totalPages, totalCount, pageSize },
+ };
+}
+
+export async function getAnalysisResultsForDoctor(
+ id: number,
+): Promise {
+ const supabase = getSupabaseServerClient();
+
+ const { data: analysisResponse, error } = await supabase
+ .schema('medreport')
+ .from(`analysis_response_elements`)
+ .select(
+ `*,
+ analysis_responses(user_id, analysis_order_id(id,medusa_order_id, analysis_element_ids))`,
+ )
+ .eq('analysis_response_id', id);
+
+ if (error) {
+ throw new Error('Something went wrong.');
+ }
+
+ const firstAnalysisResponse = analysisResponse?.[0];
+ const userId = firstAnalysisResponse?.analysis_responses.user_id;
+ const medusaOrderId =
+ firstAnalysisResponse?.analysis_responses?.analysis_order_id
+ ?.medusa_order_id;
+
+ if (!analysisResponse?.length || !userId || !medusaOrderId) {
+ throw new Error('Failed to retrieve full analysis data.');
+ }
+
+ const [
+ { data: medusaOrderItems, error: medusaOrderError },
+ { data: accountWithParams, error: accountError },
+ { data: doctorFeedback, error: feedbackError },
+ ] = await Promise.all([
+ supabase
+ .schema('public')
+ .from('order_item')
+ .select(`order_id, item_id(product_title, product_type)`)
+ .eq('order_id', medusaOrderId),
+ supabase
+ .schema('medreport')
+ .from('accounts')
+ .select(
+ `primary_owner_user_id, id, name, last_name, personal_code, phone, email,
+ account_params(height,weight)`,
+ )
+ .eq('is_personal_account', true)
+ .eq('primary_owner_user_id', userId)
+ .limit(1),
+ await supabase
+ .schema('medreport')
+ .from('doctor_analysis_feedback')
+ .select(`*`)
+ .eq(
+ 'analysis_order_id',
+ firstAnalysisResponse.analysis_responses.analysis_order_id.id,
+ )
+ .limit(1),
+ ]);
+
+ if (medusaOrderError || accountError || feedbackError) {
+ throw new Error('Something went wrong.');
+ }
+
+ if (!accountWithParams?.[0]) {
+ throw new Error('Account not found.');
+ }
+
+ const {
+ primary_owner_user_id,
+ id: accountId,
+ name,
+ email,
+ last_name,
+ personal_code,
+ phone,
+ account_params,
+ } = accountWithParams[0];
+
+ return {
+ analysisResponse,
+ order: {
+ title: medusaOrderItems?.[0]?.item_id.product_title ?? '-',
+ isPackage:
+ medusaOrderItems?.[0]?.item_id.product_type?.toLowerCase() ===
+ 'analysis package',
+ analysisOrderId:
+ firstAnalysisResponse.analysis_responses.analysis_order_id.id,
+ },
+ doctorFeedback: doctorFeedback?.[0],
+ patient: {
+ userId: primary_owner_user_id,
+ accountId,
+ firstName: name,
+ lastName: last_name,
+ personalCode: personal_code,
+ phone,
+ email,
+ height: account_params?.[0]?.height,
+ weight: account_params?.[0]?.weight,
+ },
+ };
+}
+
+export async function selectJob(analysisOrderId: number, userId: string) {
+ const supabase = getSupabaseServerClient();
+ const {
+ data: { user },
+ } = await supabase.auth.getUser();
+
+ if (!user?.id) {
+ throw new Error('No user logged in.');
+ }
+
+ const { data: existingFeedback, error: existingFeedbackError } =
+ await supabase
+ .schema('medreport')
+ .from('doctor_analysis_feedback')
+ .select('doctor_user_id')
+ .eq('analysis_order_id', analysisOrderId)
+ .limit(1);
+
+ const jobAssignedToUserId = existingFeedback?.[0]?.doctor_user_id;
+
+ if (!!jobAssignedToUserId && jobAssignedToUserId !== user.id) {
+ throw new Error('Job already assigned to another doctor.');
+ }
+
+ const { data, error } = await supabase
+ .schema('medreport')
+ .from('doctor_analysis_feedback')
+ .upsert(
+ {
+ doctor_user_id: user.id,
+ user_id: userId,
+ analysis_order_id: analysisOrderId,
+ },
+ { onConflict: 'analysis_order_id' },
+ );
+
+ if (error || existingFeedbackError) {
+ throw new Error('Something went wrong');
+ }
+
+ return data;
+}
+
+export async function unselectJob(analysisOrderId: number) {
+ const supabase = getSupabaseServerClient();
+ const {
+ data: { user },
+ } = await supabase.auth.getUser();
+
+ if (!user?.id) {
+ throw new Error('No user logged in.');
+ }
+
+ const { data: currentDoctorFeedback, error: currentDoctorFeedbackError } =
+ await supabase
+ .schema('medreport')
+ .from('doctor_analysis_feedback')
+ .select('id,doctor_user_id')
+ .eq('analysis_order_id', analysisOrderId)
+ .eq('doctor_user_id', user.id)
+ .limit(1);
+
+ if (!currentDoctorFeedback) {
+ throw new Error(
+ 'Current user not assigned to give feedback to this set of analyses.',
+ );
+ }
+
+ if (currentDoctorFeedbackError) {
+ throw new Error('Error retrieving job data');
+ }
+
+ const { data, error } = await supabase
+ .schema('medreport')
+ .from('doctor_analysis_feedback')
+ .update({
+ doctor_user_id: null,
+ })
+ .eq('analysis_order_id', analysisOrderId)
+ .eq('doctor_user_id', user.id);
+
+ if (error) {
+ throw new Error('Something went wrong');
+ }
+
+ return data;
+}
+
+export async function submitFeedback(
+ analysisOrderId: DoctorAnalysisFeedbackTable['analysis_order_id'],
+ userId: DoctorAnalysisFeedbackTable['user_id'],
+ value: DoctorAnalysisFeedbackTable['value'],
+ status: DoctorAnalysisFeedbackTable['status'],
+) {
+ const supabase = getSupabaseServerClient();
+ const {
+ data: { user },
+ } = await supabase.auth.getUser();
+
+ if (!user?.id) {
+ throw new Error('No user logged in.');
+ }
+
+ const { data, error } = await supabase
+ .schema('medreport')
+ .from('doctor_analysis_feedback')
+ .upsert(
+ {
+ doctor_user_id: user?.id,
+ user_id: userId,
+ analysis_order_id: analysisOrderId,
+ value,
+ status,
+ },
+ { onConflict: 'analysis_order_id' },
+ );
+
+ if (error) {
+ throw new Error('Something went wrong');
+ }
+
+ return data;
+}
diff --git a/packages/features/doctor/src/lib/server/utils/doctor-action.ts b/packages/features/doctor/src/lib/server/utils/doctor-action.ts
new file mode 100644
index 0000000..3aaa5ab
--- /dev/null
+++ b/packages/features/doctor/src/lib/server/utils/doctor-action.ts
@@ -0,0 +1,24 @@
+import { notFound } from 'next/navigation';
+
+import { getSupabaseServerClient } from '@kit/supabase/server-client';
+
+import { isDoctor } from './is-doctor';
+
+/**
+ * @name doctorAction
+ * @description Wrap a server action to ensure the user is a doctor.
+ * @param fn
+ */
+export function doctorAction(
+ fn: (params: Args) => Promise,
+) {
+ return async (params: Args): Promise => {
+ const isUserDoctor = await isDoctor(getSupabaseServerClient());
+
+ if (!isUserDoctor) {
+ notFound();
+ }
+
+ return await fn(params);
+ };
+}
diff --git a/packages/features/doctor/tsconfig.json b/packages/features/doctor/tsconfig.json
index 7383acd..254ef1f 100644
--- a/packages/features/doctor/tsconfig.json
+++ b/packages/features/doctor/tsconfig.json
@@ -3,8 +3,23 @@
"compilerOptions": {
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json"
},
- "include": ["*.ts", "src"],
+ "exports": {
+ ".": "./src/index.ts",
+ "./components/*": "./src/components/*.tsx",
+ "./services/*": "./src/lib/server/services/*.ts",
+ "./actions/*": "./src/lib/server/actions/*.ts"
+ },
+ "include": [
+ "*.ts",
+ "src",
+ "app"
+ ],
"exclude": [
"node_modules"
- ]
-}
+ ],
+ "paths": {
+ "@components/*": [
+ "./src/lib/*"
+ ],
+ }
+}
\ No newline at end of file
diff --git a/packages/features/notifications/src/components/success-notification.tsx b/packages/features/notifications/src/components/success-notification.tsx
index e961dfa..bbdc3f2 100644
--- a/packages/features/notifications/src/components/success-notification.tsx
+++ b/packages/features/notifications/src/components/success-notification.tsx
@@ -1,7 +1,7 @@
import Image from 'next/image';
import { redirect } from 'next/navigation';
-import { MedReportLogo } from '@/components/med-report-logo';
+import { MedReportLogo } from '@kit/shared/components/med-report-logo';
import { Button } from '@kit/ui/button';
import { Trans } from '@kit/ui/trans';
diff --git a/packages/shared/package.json b/packages/shared/package.json
index 46d4df4..27fc7b2 100644
--- a/packages/shared/package.json
+++ b/packages/shared/package.json
@@ -14,7 +14,9 @@
"./utils": "./src/utils.ts",
"./hooks": "./src/hooks/index.ts",
"./events": "./src/events/index.tsx",
- "./registry": "./src/registry/index.ts"
+ "./components/*": "./src/components/*.tsx",
+ "./registry": "./src/registry/index.ts",
+ "./config": "./src/config/index.ts"
},
"devDependencies": {
"@kit/eslint-config": "workspace:*",
@@ -32,4 +34,4 @@
]
}
}
-}
+}
\ No newline at end of file
diff --git a/components/analytics-provider.tsx b/packages/shared/src/components/analytics-provider.tsx
similarity index 100%
rename from components/analytics-provider.tsx
rename to packages/shared/src/components/analytics-provider.tsx
diff --git a/components/app-logo.tsx b/packages/shared/src/components/app-logo.tsx
similarity index 90%
rename from components/app-logo.tsx
rename to packages/shared/src/components/app-logo.tsx
index 82a1179..3a660fe 100644
--- a/components/app-logo.tsx
+++ b/packages/shared/src/components/app-logo.tsx
@@ -1,6 +1,6 @@
import Link from 'next/link';
-import { MedReportLogo } from './med-report-logo';
+import { MedReportLogo } from '@kit/shared/components/med-report-logo';
function LogoImage({
className,
diff --git a/components/auth-provider.tsx b/packages/shared/src/components/auth-provider.tsx
similarity index 97%
rename from components/auth-provider.tsx
rename to packages/shared/src/components/auth-provider.tsx
index 9efc533..4398714 100644
--- a/components/auth-provider.tsx
+++ b/packages/shared/src/components/auth-provider.tsx
@@ -8,7 +8,8 @@ import { useMonitoring } from '@kit/monitoring/hooks';
import { useAppEvents } from '@kit/shared/events';
import { useAuthChangeListener } from '@kit/supabase/hooks/use-auth-change-listener';
-import pathsConfig from '~/config/paths.config';
+import { pathsConfig } from '@kit/shared/config';
+
export function AuthProvider(props: React.PropsWithChildren) {
const dispatchEvent = useDispatchAppEventFromAuthEvent();
diff --git a/components/back-button.tsx b/packages/shared/src/components/back-button.tsx
similarity index 100%
rename from components/back-button.tsx
rename to packages/shared/src/components/back-button.tsx
diff --git a/packages/shared/src/components/confirmation-modal.tsx b/packages/shared/src/components/confirmation-modal.tsx
new file mode 100644
index 0000000..aaa5421
--- /dev/null
+++ b/packages/shared/src/components/confirmation-modal.tsx
@@ -0,0 +1,52 @@
+
+import { Button } from '@kit/ui/button';
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+} from '@kit/ui/dialog';
+import { Trans } from '@kit/ui/trans';
+
+export default function ConfirmationModal({
+ isOpen,
+ onClose,
+ onConfirm,
+ titleKey,
+ descriptionKey,
+ cancelKey = 'common:cancel',
+ confirmKey = 'common:confirm',
+}: {
+ isOpen: boolean;
+ onClose: () => void;
+ onConfirm: () => void;
+ titleKey: string;
+ descriptionKey: string;
+ cancelKey?: string;
+ confirmKey?: string;
+}) {
+ return (
+
+ );
+}
diff --git a/components/header-auth.tsx b/packages/shared/src/components/header-auth.tsx
similarity index 100%
rename from components/header-auth.tsx
rename to packages/shared/src/components/header-auth.tsx
diff --git a/packages/shared/src/components/med-report-logo.tsx b/packages/shared/src/components/med-report-logo.tsx
new file mode 100644
index 0000000..6e9e09e
--- /dev/null
+++ b/packages/shared/src/components/med-report-logo.tsx
@@ -0,0 +1,20 @@
+import { cn } from '@kit/ui/utils';
+
+import { MedReportSmallLogo } from '../../../../public/assets/med-report-small-logo';
+
+export const MedReportLogo = ({
+ className,
+ compact = false,
+}: {
+ className?: string;
+ compact?: boolean;
+}) => (
+
+
+ {!compact && (
+
+ MedReport
+
+ )}
+
+);
diff --git a/components/package-header.tsx b/packages/shared/src/components/package-header.tsx
similarity index 100%
rename from components/package-header.tsx
rename to packages/shared/src/components/package-header.tsx
diff --git a/components/personal-account-dropdown-container.tsx b/packages/shared/src/components/personal-account-dropdown-container.tsx
similarity index 88%
rename from components/personal-account-dropdown-container.tsx
rename to packages/shared/src/components/personal-account-dropdown-container.tsx
index fe68027..0973a6f 100644
--- a/components/personal-account-dropdown-container.tsx
+++ b/packages/shared/src/components/personal-account-dropdown-container.tsx
@@ -7,8 +7,8 @@ import { ApplicationRole } from '@kit/accounts/types/accounts';
import { useSignOut } from '@kit/supabase/hooks/use-sign-out';
import { useUser } from '@kit/supabase/hooks/use-user';
-import featuresFlagConfig from '~/config/feature-flags.config';
-import pathsConfig from '~/config/paths.config';
+import { pathsConfig, featureFlagsConfig } from '@kit/shared/config';
+
const paths = {
home: pathsConfig.app.home,
@@ -18,7 +18,7 @@ const paths = {
};
const features = {
- enableThemeToggle: featuresFlagConfig.enableThemeToggle,
+ enableThemeToggle: featureFlagsConfig.enableThemeToggle,
};
export function ProfileAccountDropdownContainer(props: {
diff --git a/components/react-query-provider.tsx b/packages/shared/src/components/react-query-provider.tsx
similarity index 100%
rename from components/react-query-provider.tsx
rename to packages/shared/src/components/react-query-provider.tsx
diff --git a/components/root-providers.tsx b/packages/shared/src/components/root-providers.tsx
similarity index 82%
rename from components/root-providers.tsx
rename to packages/shared/src/components/root-providers.tsx
index df246d8..9b72a16 100644
--- a/components/root-providers.tsx
+++ b/packages/shared/src/components/root-providers.tsx
@@ -9,17 +9,15 @@ import { ThemeProvider } from 'next-themes';
import { CaptchaProvider } from '@kit/auth/captcha/client';
import { I18nProvider } from '@kit/i18n/provider';
import { MonitoringProvider } from '@kit/monitoring/components';
+import { AnalyticsProvider } from '@kit/shared/components/analytics-provider';
+import { AuthProvider } from '@kit/shared/components/auth-provider';
+import { appConfig, authConfig, featureFlagsConfig } from '@kit/shared/config';
import { AppEventsProvider } from '@kit/shared/events';
import { If } from '@kit/ui/if';
import { VersionUpdater } from '@kit/ui/version-updater';
-import { AnalyticsProvider } from '~/components/analytics-provider';
-import { AuthProvider } from '~/components/auth-provider';
-import appConfig from '~/config/app.config';
-import authConfig from '~/config/auth.config';
-import featuresFlagConfig from '~/config/feature-flags.config';
-import { i18nResolver } from '~/lib/i18n/i18n.resolver';
-import { getI18nSettings } from '~/lib/i18n/i18n.settings';
+import { i18nResolver } from '../../../../lib/i18n/i18n.resolver';
+import { getI18nSettings } from '../../../../lib/i18n/i18n.settings';
import { ReactQueryProvider } from './react-query-provider';
@@ -77,7 +75,7 @@ export function RootProviders({
-
+
diff --git a/components/select-analysis-package.tsx b/packages/shared/src/components/select-analysis-package.tsx
similarity index 75%
rename from components/select-analysis-package.tsx
rename to packages/shared/src/components/select-analysis-package.tsx
index d6b74d2..69fa24c 100644
--- a/components/select-analysis-package.tsx
+++ b/packages/shared/src/components/select-analysis-package.tsx
@@ -1,10 +1,14 @@
-"use client";
+'use client';
import { useState } from 'react';
-import { useTranslation } from 'react-i18next';
+
import Image from 'next/image';
import { useRouter } from 'next/navigation';
+import { StoreProduct, StoreProductVariant } from '@medusajs/types';
+import { Button } from '@medusajs/ui';
+import { useTranslation } from 'react-i18next';
+
import {
Card,
CardContent,
@@ -13,13 +17,11 @@ import {
CardHeader,
} from '@kit/ui/card';
import { Trans } from '@kit/ui/trans';
-import { StoreProduct, StoreProductVariant } from '@medusajs/types';
-import { Button } from '@medusajs/ui';
-import { handleAddToCart } from '@/lib/services/medusaCart.service';
+import { handleAddToCart } from '../../../../lib/services/medusaCart.service';
+import { getAnalysisElementMedusaProductIds } from '../../../../utils/medusa-product';
import { PackageHeader } from './package-header';
import { ButtonTooltip } from './ui/button-tooltip';
-import { getAnalysisElementMedusaProductIds } from '@/utils/medusa-product';
export interface IAnalysisPackage {
titleKey: string;
@@ -32,15 +34,18 @@ export default function SelectAnalysisPackage({
analysisPackage,
countryCode,
}: {
- analysisPackage: StoreProduct
- countryCode: string,
+ analysisPackage: StoreProduct;
+ countryCode: string;
}) {
const router = useRouter();
- const { t, i18n: { language } } = useTranslation();
-
+ const {
+ t,
+ i18n: { language },
+ } = useTranslation();
+
const [isAddingToCart, setIsAddingToCart] = useState(false);
const handleSelect = async (selectedVariant: StoreProductVariant) => {
- if (!selectedVariant?.id) return null
+ if (!selectedVariant?.id) return null;
setIsAddingToCart(true);
await handleAddToCart({
@@ -49,10 +54,12 @@ export default function SelectAnalysisPackage({
});
setIsAddingToCart(false);
router.push('/home/cart');
- }
+ };
const titleKey = analysisPackage.title;
- const analysisElementMedusaProductIds = getAnalysisElementMedusaProductIds([analysisPackage]);
+ const analysisElementMedusaProductIds = getAnalysisElementMedusaProductIds([
+ analysisPackage,
+ ]);
const nrOfAnalyses = analysisElementMedusaProductIds.length;
const description = analysisPackage.description ?? '';
const subtitle = analysisPackage.subtitle ?? '';
@@ -84,18 +91,22 @@ export default function SelectAnalysisPackage({
-
- {subtitle}
-
+ {subtitle}
-
diff --git a/components/select-analysis-packages.tsx b/packages/shared/src/components/select-analysis-packages.tsx
similarity index 100%
rename from components/select-analysis-packages.tsx
rename to packages/shared/src/components/select-analysis-packages.tsx
diff --git a/packages/shared/src/components/table-skeleton.tsx b/packages/shared/src/components/table-skeleton.tsx
new file mode 100644
index 0000000..da9b1bb
--- /dev/null
+++ b/packages/shared/src/components/table-skeleton.tsx
@@ -0,0 +1,41 @@
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from '@kit/ui/table';
+
+export default function TableSkeleton({
+ rows = 1,
+ cols = 7,
+}: {
+ rows?: number;
+ cols?: number;
+}) {
+ return (
+
+
+
+ {Array.from({ length: cols }).map((_, i) => (
+
+
+
+ ))}
+
+
+
+ {Array.from({ length: rows }).map((_, r) => (
+
+ {Array.from({ length: cols }).map((_, c) => (
+
+
+
+ ))}
+
+ ))}
+
+
+ );
+}
diff --git a/components/ui/button-tooltip.tsx b/packages/shared/src/components/ui/button-tooltip.tsx
similarity index 100%
rename from components/ui/button-tooltip.tsx
rename to packages/shared/src/components/ui/button-tooltip.tsx
diff --git a/components/ui/info-tooltip.tsx b/packages/shared/src/components/ui/info-tooltip.tsx
similarity index 100%
rename from components/ui/info-tooltip.tsx
rename to packages/shared/src/components/ui/info-tooltip.tsx
diff --git a/components/ui/search.tsx b/packages/shared/src/components/ui/search.tsx
similarity index 100%
rename from components/ui/search.tsx
rename to packages/shared/src/components/ui/search.tsx
diff --git a/components/ui/submit-button.tsx b/packages/shared/src/components/ui/submit-button.tsx
similarity index 100%
rename from components/ui/submit-button.tsx
rename to packages/shared/src/components/ui/submit-button.tsx
diff --git a/config/app.config.ts b/packages/shared/src/config/app.config.ts
similarity index 100%
rename from config/app.config.ts
rename to packages/shared/src/config/app.config.ts
diff --git a/config/auth.config.ts b/packages/shared/src/config/auth.config.ts
similarity index 100%
rename from config/auth.config.ts
rename to packages/shared/src/config/auth.config.ts
diff --git a/config/billing.config.ts b/packages/shared/src/config/billing.config.ts
similarity index 100%
rename from config/billing.config.ts
rename to packages/shared/src/config/billing.config.ts
diff --git a/config/billing.sample.config.ts b/packages/shared/src/config/billing.sample.config.ts
similarity index 100%
rename from config/billing.sample.config.ts
rename to packages/shared/src/config/billing.sample.config.ts
diff --git a/config/feature-flags.config.ts b/packages/shared/src/config/feature-flags.config.ts
similarity index 97%
rename from config/feature-flags.config.ts
rename to packages/shared/src/config/feature-flags.config.ts
index 647e117..e73a9b0 100644
--- a/config/feature-flags.config.ts
+++ b/packages/shared/src/config/feature-flags.config.ts
@@ -56,7 +56,7 @@ const FeatureFlagsSchema = z.object({
}),
});
-const featuresFlagConfig = FeatureFlagsSchema.parse({
+const featureFlagsConfig = FeatureFlagsSchema.parse({
enableThemeToggle: getBoolean(
process.env.NEXT_PUBLIC_ENABLE_THEME_TOGGLE,
true,
@@ -101,7 +101,7 @@ const featuresFlagConfig = FeatureFlagsSchema.parse({
),
} satisfies z.infer);
-export default featuresFlagConfig;
+export default featureFlagsConfig;
function getBoolean(value: unknown, defaultValue: boolean) {
if (typeof value === 'string') {
diff --git a/packages/shared/src/config/index.ts b/packages/shared/src/config/index.ts
new file mode 100644
index 0000000..516ecc7
--- /dev/null
+++ b/packages/shared/src/config/index.ts
@@ -0,0 +1,21 @@
+import appConfig from './app.config';
+import authConfig from './auth.config';
+import billingConfig from './billing.config';
+import featureFlagsConfig from './feature-flags.config';
+import pathsConfig from './paths.config';
+import { personalAccountNavigationConfig } from './personal-account-navigation.config';
+import {
+ createPath,
+ getTeamAccountSidebarConfig,
+} from './team-account-navigation.config';
+
+export {
+ appConfig,
+ authConfig,
+ billingConfig,
+ createPath,
+ featureFlagsConfig,
+ getTeamAccountSidebarConfig,
+ pathsConfig,
+ personalAccountNavigationConfig,
+};
diff --git a/config/paths.config.ts b/packages/shared/src/config/paths.config.ts
similarity index 89%
rename from config/paths.config.ts
rename to packages/shared/src/config/paths.config.ts
index f076dbe..d21c445 100644
--- a/config/paths.config.ts
+++ b/packages/shared/src/config/paths.config.ts
@@ -30,8 +30,12 @@ const PathsSchema = z.object({
accountMembers: z.string().min(1),
accountBillingReturn: z.string().min(1),
joinTeam: z.string().min(1),
- doctor: z.string().min(1),
admin: z.string().min(1),
+ doctor: z.string().min(1),
+ myJobs: z.string().min(1),
+ completedJobs: z.string().min(1),
+ openJobs: z.string().min(1),
+ analysisDetails: z.string().min(1),
}),
});
@@ -67,6 +71,10 @@ const pathsConfig = PathsSchema.parse({
orderHealthAnalysis: '/home/order-health-analysis',
doctor: '/doctor',
admin: '/admin',
+ myJobs: '/doctor/my-jobs',
+ completedJobs: '/doctor/completed-jobs',
+ openJobs: '/doctor/open-jobs',
+ analysisDetails: 'doctor/analysis',
},
} satisfies z.infer);
diff --git a/config/personal-account-navigation.config.tsx b/packages/shared/src/config/personal-account-navigation.config.tsx
similarity index 97%
rename from config/personal-account-navigation.config.tsx
rename to packages/shared/src/config/personal-account-navigation.config.tsx
index f52c78e..521ba0a 100644
--- a/config/personal-account-navigation.config.tsx
+++ b/packages/shared/src/config/personal-account-navigation.config.tsx
@@ -9,10 +9,9 @@ import {
} from 'lucide-react';
import { z } from 'zod';
+import { pathsConfig } from '@kit/shared/config';
import { NavigationConfigSchema } from '@kit/ui/navigation-schema';
-import pathsConfig from '~/config/paths.config';
-
const iconClasses = 'w-4 stroke-[1.5px]';
const routes = [
diff --git a/config/team-account-navigation.config.tsx b/packages/shared/src/config/team-account-navigation.config.tsx
similarity index 93%
rename from config/team-account-navigation.config.tsx
rename to packages/shared/src/config/team-account-navigation.config.tsx
index b3a7c6b..b6f5e89 100644
--- a/config/team-account-navigation.config.tsx
+++ b/packages/shared/src/config/team-account-navigation.config.tsx
@@ -1,10 +1,8 @@
import { CreditCard, LayoutDashboard, Settings, Users } from 'lucide-react';
+import { featureFlagsConfig, pathsConfig } from '@kit/shared/config';
import { NavigationConfigSchema } from '@kit/ui/navigation-schema';
-import featureFlagsConfig from '~/config/feature-flags.config';
-import pathsConfig from '~/config/paths.config';
-
const iconClasses = 'w-4';
const getRoutes = (account: string) => [
diff --git a/packages/shared/src/utils.ts b/packages/shared/src/utils.ts
index 1b8d1af..971a03e 100644
--- a/packages/shared/src/utils.ts
+++ b/packages/shared/src/utils.ts
@@ -1,3 +1,6 @@
+import { format } from 'date-fns';
+import Isikukood, { Gender } from 'isikukood';
+
/**
* Check if the code is running in a browser environment.
*/
@@ -21,3 +24,36 @@ export function formatCurrency(params: {
currency: params.currencyCode,
}).format(Number(params.value));
}
+
+export function formatDateAndTime(date?: string) {
+ if (!date) return '-';
+
+ return format(date, 'dd.MM.yyyy HH:mm');
+}
+
+export function formatDate(date?: string) {
+ if (!date) return '-';
+
+ return format(date, 'dd.MM.yyyy');
+}
+
+export function getFullName(
+ firstName?: string | null,
+ lastName?: string | null,
+) {
+ return [firstName ?? '', lastName ?? ''].join(' ');
+}
+
+export const getPersonParameters = (personalCode: string) => {
+ try {
+ const person = new Isikukood(personalCode);
+ return {
+ gender: person.getGender(),
+ dob: person.getBirthday(),
+ age: person.getAge(),
+ };
+ } catch (error) {
+ console.error(error);
+ return null;
+ }
+};
diff --git a/packages/supabase/src/database.types.ts b/packages/supabase/src/database.types.ts
index 9093fe4..0ff6ae6 100644
--- a/packages/supabase/src/database.types.ts
+++ b/packages/supabase/src/database.types.ts
@@ -1012,6 +1012,53 @@ export type Database = {
},
]
}
+ doctor_analysis_feedback: {
+ Row: {
+ analysis_order_id: number
+ created_at: string
+ created_by: string
+ doctor_user_id: string | null
+ id: number
+ status: Database["medreport"]["Enums"]["analysis_feedback_status"]
+ updated_at: string
+ updated_by: string | null
+ user_id: string
+ value: string | null
+ }
+ Insert: {
+ analysis_order_id: number
+ created_at?: string
+ created_by?: string
+ doctor_user_id?: string | null
+ id?: number
+ status?: Database["medreport"]["Enums"]["analysis_feedback_status"]
+ updated_at?: string
+ updated_by?: string | null
+ user_id: string
+ value?: string | null
+ }
+ Update: {
+ analysis_order_id?: number
+ created_at?: string
+ created_by?: string
+ doctor_user_id?: string | null
+ id?: number
+ status?: Database["medreport"]["Enums"]["analysis_feedback_status"]
+ updated_at?: string
+ updated_by?: string | null
+ user_id?: string
+ value?: string | null
+ }
+ Relationships: [
+ {
+ foreignKeyName: "doctor_analysis_feedback_analysis_order_id_fkey"
+ columns: ["analysis_order_id"]
+ isOneToOne: false
+ referencedRelation: "analysis_orders"
+ referencedColumns: ["id"]
+ },
+ ]
+ }
invitations: {
Row: {
account_id: string
@@ -1863,22 +1910,6 @@ export type Database = {
}
Returns: undefined
}
- update_analysis_order_status: {
- Args: {
- order_id: number
- medusa_order_id_param: string
- status_param: Database["medreport"]["Enums"]["analysis_order_status"]
- }
- Returns: {
- analysis_element_ids: number[] | null
- analysis_ids: number[] | null
- created_at: string
- id: number
- medusa_order_id: string
- status: Database["medreport"]["Enums"]["analysis_order_status"]
- user_id: string
- }
- }
upsert_order: {
Args: {
target_account_id: string
@@ -1949,6 +1980,7 @@ export type Database = {
}
}
Enums: {
+ analysis_feedback_status: "STARTED" | "DRAFT" | "COMPLETED"
analysis_order_status:
| "QUEUED"
| "ON_HOLD"
@@ -7842,6 +7874,7 @@ export const Constants = {
},
medreport: {
Enums: {
+ analysis_feedback_status: ["STARTED", "DRAFT", "COMPLETED"],
analysis_order_status: [
"QUEUED",
"ON_HOLD",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 83aa88c..f7d8c1b 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -82,13 +82,13 @@ importers:
version: 1.1.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@medusajs/icons':
specifier: ^2.8.6
- version: 2.8.6(react@19.1.0)
+ version: 2.8.7(react@19.1.0)
'@medusajs/js-sdk':
specifier: latest
- version: 2.8.7(awilix@8.0.1)
+ version: 2.9.0(awilix@8.0.1)
'@medusajs/ui':
specifier: latest
- version: 4.0.17(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.8.3)
+ version: 4.0.19(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.8.3)
'@nosecone/next':
specifier: 1.0.0-beta.7
version: 1.0.0-beta.7(next@15.3.2(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))
@@ -182,10 +182,10 @@ importers:
version: link:tooling/typescript
'@medusajs/types':
specifier: latest
- version: 2.8.7(awilix@8.0.1)
+ version: 2.9.0(awilix@8.0.1)
'@medusajs/ui-preset':
specifier: latest
- version: 2.8.7(tailwindcss@4.1.7)
+ version: 2.9.0(tailwindcss@4.1.7)
'@next/bundle-analyzer':
specifier: 15.3.2
version: 15.3.2
@@ -251,7 +251,7 @@ importers:
version: link:../../tooling/typescript
'@types/node':
specifier: ^22.15.18
- version: 22.15.30
+ version: 22.15.32
packages/billing/core:
devDependencies:
@@ -275,7 +275,7 @@ importers:
devDependencies:
'@hookform/resolvers':
specifier: ^5.0.1
- version: 5.0.1(react-hook-form@7.58.0(react@19.1.0))
+ version: 5.1.1(react-hook-form@7.58.0(react@19.1.0))
'@kit/billing':
specifier: workspace:*
version: link:../core
@@ -469,7 +469,7 @@ importers:
version: link:../wordpress
'@types/node':
specifier: ^22.15.18
- version: 22.15.30
+ version: 22.15.32
packages/cms/keystatic:
dependencies:
@@ -500,7 +500,7 @@ importers:
version: link:../../ui
'@types/node':
specifier: ^22.15.18
- version: 22.15.30
+ version: 22.15.32
'@types/react':
specifier: 19.1.4
version: 19.1.4
@@ -539,7 +539,7 @@ importers:
version: link:../../ui
'@types/node':
specifier: ^22.15.18
- version: 22.15.30
+ version: 22.15.32
'@types/react':
specifier: 19.1.4
version: 19.1.4
@@ -610,7 +610,7 @@ importers:
devDependencies:
'@hookform/resolvers':
specifier: ^5.0.1
- version: 5.0.1(react-hook-form@7.58.0(react@19.1.0))
+ version: 5.1.1(react-hook-form@7.58.0(react@19.1.0))
'@kit/billing-gateway':
specifier: workspace:*
version: link:../../billing/gateway
@@ -685,7 +685,7 @@ importers:
devDependencies:
'@hookform/resolvers':
specifier: ^5.0.1
- version: 5.0.1(react-hook-form@7.58.0(react@19.1.0))
+ version: 5.1.1(react-hook-form@7.58.0(react@19.1.0))
'@kit/eslint-config':
specifier: workspace:*
version: link:../../../tooling/eslint
@@ -742,7 +742,7 @@ importers:
devDependencies:
'@hookform/resolvers':
specifier: ^5.0.1
- version: 5.0.1(react-hook-form@7.58.0(react@19.1.0))
+ version: 5.1.1(react-hook-form@7.58.0(react@19.1.0))
'@kit/eslint-config':
specifier: workspace:*
version: link:../../../tooling/eslint
@@ -850,10 +850,10 @@ importers:
version: 2.2.4(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)
'@medusajs/js-sdk':
specifier: latest
- version: 2.8.7(awilix@8.0.1)
+ version: 2.9.0(awilix@8.0.1)
'@medusajs/ui':
specifier: latest
- version: 4.0.17(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(typescript@5.8.3)
+ version: 4.0.19(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(typescript@5.8.3)
'@radix-ui/react-accordion':
specifier: ^1.2.1
version: 1.2.10(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)
@@ -899,10 +899,10 @@ importers:
version: 7.27.4
'@medusajs/types':
specifier: latest
- version: 2.8.7(awilix@8.0.1)
+ version: 2.9.0(awilix@8.0.1)
'@medusajs/ui-preset':
specifier: latest
- version: 2.8.7(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@17.0.21)(typescript@5.8.3)))
+ version: 2.9.0(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@17.0.21)(typescript@5.8.3)))
'@types/lodash':
specifier: ^4.14.195
version: 4.17.17
@@ -993,7 +993,7 @@ importers:
devDependencies:
'@hookform/resolvers':
specifier: ^5.0.1
- version: 5.0.1(react-hook-form@7.58.0(react@19.1.0))
+ version: 5.1.1(react-hook-form@7.58.0(react@19.1.0))
'@kit/accounts':
specifier: workspace:*
version: link:../accounts
@@ -1135,7 +1135,7 @@ importers:
version: link:../../../tooling/typescript
'@types/node':
specifier: ^22.15.18
- version: 22.15.30
+ version: 22.15.32
packages/mailers/nodemailer:
dependencies:
@@ -1175,7 +1175,7 @@ importers:
version: link:../../../tooling/typescript
'@types/node':
specifier: ^22.15.18
- version: 22.15.30
+ version: 22.15.32
packages/mailers/shared:
devDependencies:
@@ -1324,7 +1324,7 @@ importers:
devDependencies:
'@hookform/resolvers':
specifier: ^5.0.1
- version: 5.0.1(react-hook-form@7.58.0(react@19.1.0))
+ version: 5.1.1(react-hook-form@7.58.0(react@19.1.0))
'@kit/email-templates':
specifier: workspace:*
version: link:../email-templates
@@ -1429,7 +1429,7 @@ importers:
dependencies:
'@hookform/resolvers':
specifier: ^5.0.1
- version: 5.0.1(react-hook-form@7.58.0(react@19.1.0))
+ version: 5.1.1(react-hook-form@7.58.0(react@19.1.0))
'@radix-ui/react-accordion':
specifier: 1.2.10
version: 1.2.10(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
@@ -1476,7 +1476,7 @@ importers:
specifier: ^1.1.6
version: 1.1.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@radix-ui/react-slot':
- specifier: ^1.2.2
+ specifier: ^1.2.3
version: 1.2.3(@types/react@19.1.4)(react@19.1.0)
'@radix-ui/react-switch':
specifier: ^1.2.4
@@ -1510,7 +1510,7 @@ importers:
version: 2.15.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
tailwind-merge:
specifier: ^3.3.0
- version: 3.3.0
+ version: 3.3.1
devDependencies:
'@kit/eslint-config':
specifier: workspace:*
@@ -2037,11 +2037,6 @@ packages:
react: ^18 || ^19 || ^19.0.0-rc
react-dom: ^18 || ^19 || ^19.0.0-rc
- '@hookform/resolvers@5.0.1':
- resolution: {integrity: sha512-u/+Jp83luQNx9AdyW2fIPGY6Y7NG68eN2ZW8FOJYL+M0i4s49+refdJdOp/A9n9HFQtQs3HIDHQvX3ZET2o7YA==}
- peerDependencies:
- react-hook-form: ^7.55.0
-
'@hookform/resolvers@5.1.1':
resolution: {integrity: sha512-J/NVING3LMAEvexJkyTLjruSm7aOFx7QX21pzkiJfMoNG0wl5aFEjLTl7ay7IQb9EWY6AkrBy7tHL2Alijpdcg==}
peerDependencies:
@@ -2388,22 +2383,22 @@ packages:
react: ^17.0.2 || ^18.0.0 || ^19.0
react-dom: ^17.0.2 || ^18.0.0 || ^19.0
- '@medusajs/icons@2.8.6':
- resolution: {integrity: sha512-k3X1nA1L0eoR30tfAzCxTtpaE1h28K2qmuNyangOoBJObHkaD+gNIi3AG+2iLlmIrByzfCgzP0JvhzrtFFha4Q==}
- peerDependencies:
- react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
-
'@medusajs/icons@2.8.7':
resolution: {integrity: sha512-zGkAokqWBNJ1PcTktCPSMT5spIIjv8Pba88BXvfcbblG5cUbMSvvJ2v/BRODMFejQ9NqlboIeP0fo/9RzLpPHg==}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
- '@medusajs/js-sdk@2.8.7':
- resolution: {integrity: sha512-ZGYMQOM7GHuKtxOvJ+wgKyC/fzLlyMu5nij4hIWIf2osZy7d6dpvEglcV6w9B0UgSEADJh1SZ7a22HOJdjjJ9A==}
+ '@medusajs/icons@2.9.0':
+ resolution: {integrity: sha512-qzFyX8f87WjLBFr23aly5F8hmN/camZ2oVcqmP1XBK4HqOWWxrPPjABePomQixwm7XGkfQHZf+B2rnyIyjwfKA==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
+
+ '@medusajs/js-sdk@2.9.0':
+ resolution: {integrity: sha512-5L5dN235k/EyNelAYrUZevjxULfhyswAtRH5oy6PETbb4ExBdFi//vFXSnOuWjj2YnZtrTwymJkeZSOMYuMxSg==}
engines: {node: '>=20'}
- '@medusajs/types@2.8.7':
- resolution: {integrity: sha512-8m/H9KkDUQz4YD+XkD/C63RfE/2elcdWf5G/KOK2QViTK0Jsd/Iw8Yy+T60pm0Lq/QQ925AfGH/Ji8UYNXjT8g==}
+ '@medusajs/types@2.9.0':
+ resolution: {integrity: sha512-7YGHq7OuvmHfKCz9sXus7aMHIwZXt1B3QYOoH+7gziEA0JXB0WNhx2kbWt4qSjQxsrFtCkdQgqBiWHkgvkq2iA==}
engines: {node: '>=20'}
peerDependencies:
awilix: ^8.0.1
@@ -2415,13 +2410,13 @@ packages:
vite:
optional: true
- '@medusajs/ui-preset@2.8.7':
- resolution: {integrity: sha512-ro8BrYlqHh7iZvYKrxmJtLweJYYet+wYQQv0R3pyfxkkP0aQ09KDPo8yTwls11iuMC4cQHljekdaOyXtSR6ZiQ==}
+ '@medusajs/ui-preset@2.9.0':
+ resolution: {integrity: sha512-ykoM2UY2wKGZbVhiAW0bOvizqa5wxYk0C7aCyUu0DpoFS8KKSas8V4IfIBiav++0LUJjKZunJWzDu1qMyqQWTw==}
peerDependencies:
tailwindcss: '>=3.0.0'
- '@medusajs/ui@4.0.17':
- resolution: {integrity: sha512-N5KtZXvns13jDiCE3ZgZLINQnlECYLf4Q4GFdbRhCjAFKFBRGyyeNKX+Zo2wBUZA2Oi4kockdxFfsZfBHh/ZhA==}
+ '@medusajs/ui@4.0.19':
+ resolution: {integrity: sha512-iDy41IXHpYOLaM8aizZmuQjiQuFf0sKYK1CVwx1nsPvzXuuyJWGiTnoMiAhZ3NWgnf3dNDHFgnHlsU1k4zV2pQ==}
peerDependencies:
react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc
react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc
@@ -5740,9 +5735,6 @@ packages:
'@types/node@17.0.21':
resolution: {integrity: sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==}
- '@types/node@22.15.30':
- resolution: {integrity: sha512-6Q7lr06bEHdlfplU6YRbgG1SFBdlsfNC4/lX+SkhiTs0cpJkOElmWls8PxDFv4yY/xKb8Y6SO0OmSX4wgqTZbA==}
-
'@types/node@22.15.32':
resolution: {integrity: sha512-3jigKqgSjsH6gYZv2nEsqdXfZqIFGAV36XYYjf9KGZ3PSG+IhLecqPnI310RvjutyMwifE2hhhNEklOUrvx/wA==}
@@ -9635,9 +9627,6 @@ packages:
tailwind-merge@2.6.0:
resolution: {integrity: sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==}
- tailwind-merge@3.3.0:
- resolution: {integrity: sha512-fyW/pEfcQSiigd5SNn0nApUOxx0zB/dm6UDU/rEwc2c3sX2smWUNbapHv+QRqLGVp9GWX3THIa7MUGPo+YkDzQ==}
-
tailwind-merge@3.3.1:
resolution: {integrity: sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==}
@@ -10702,11 +10691,6 @@ snapshots:
react-dom: 19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106)
use-sync-external-store: 1.5.0(react@19.0.0-rc-66855b96-20241106)
- '@hookform/resolvers@5.0.1(react-hook-form@7.58.0(react@19.1.0))':
- dependencies:
- '@standard-schema/utils': 0.3.0
- react-hook-form: 7.58.0(react@19.1.0)
-
'@hookform/resolvers@5.1.1(react-hook-form@7.58.0(react@19.1.0))':
dependencies:
'@standard-schema/utils': 0.3.0
@@ -11271,21 +11255,21 @@ snapshots:
react: 19.1.0
react-dom: 19.1.0(react@19.1.0)
- '@medusajs/icons@2.8.6(react@19.1.0)':
- dependencies:
- react: 19.1.0
-
- '@medusajs/icons@2.8.7(react@19.0.0-rc-66855b96-20241106)':
- dependencies:
- react: 19.0.0-rc-66855b96-20241106
-
'@medusajs/icons@2.8.7(react@19.1.0)':
dependencies:
react: 19.1.0
- '@medusajs/js-sdk@2.8.7(awilix@8.0.1)':
+ '@medusajs/icons@2.9.0(react@19.0.0-rc-66855b96-20241106)':
dependencies:
- '@medusajs/types': 2.8.7(awilix@8.0.1)
+ react: 19.0.0-rc-66855b96-20241106
+
+ '@medusajs/icons@2.9.0(react@19.1.0)':
+ dependencies:
+ react: 19.1.0
+
+ '@medusajs/js-sdk@2.9.0(awilix@8.0.1)':
+ dependencies:
+ '@medusajs/types': 2.9.0(awilix@8.0.1)
fetch-event-stream: 0.1.5
qs: 6.14.0
transitivePeerDependencies:
@@ -11293,26 +11277,26 @@ snapshots:
- ioredis
- vite
- '@medusajs/types@2.8.7(awilix@8.0.1)':
+ '@medusajs/types@2.9.0(awilix@8.0.1)':
dependencies:
awilix: 8.0.1
bignumber.js: 9.3.0
- '@medusajs/ui-preset@2.8.7(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@17.0.21)(typescript@5.8.3)))':
+ '@medusajs/ui-preset@2.9.0(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@17.0.21)(typescript@5.8.3)))':
dependencies:
'@tailwindcss/forms': 0.5.10(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@17.0.21)(typescript@5.8.3)))
tailwindcss: 3.4.17(ts-node@10.9.2(@types/node@17.0.21)(typescript@5.8.3))
tailwindcss-animate: 1.0.7(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@17.0.21)(typescript@5.8.3)))
- '@medusajs/ui-preset@2.8.7(tailwindcss@4.1.7)':
+ '@medusajs/ui-preset@2.9.0(tailwindcss@4.1.7)':
dependencies:
'@tailwindcss/forms': 0.5.10(tailwindcss@4.1.7)
tailwindcss: 4.1.7
tailwindcss-animate: 1.0.7(tailwindcss@4.1.7)
- '@medusajs/ui@4.0.17(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(typescript@5.8.3)':
+ '@medusajs/ui@4.0.19(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(typescript@5.8.3)':
dependencies:
- '@medusajs/icons': 2.8.7(react@19.0.0-rc-66855b96-20241106)
+ '@medusajs/icons': 2.9.0(react@19.0.0-rc-66855b96-20241106)
'@tanstack/react-table': 8.20.5(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)
clsx: 1.2.1
copy-to-clipboard: 3.3.3
@@ -11332,9 +11316,9 @@ snapshots:
- '@types/react-dom'
- typescript
- '@medusajs/ui@4.0.17(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.8.3)':
+ '@medusajs/ui@4.0.19(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.8.3)':
dependencies:
- '@medusajs/icons': 2.8.7(react@19.1.0)
+ '@medusajs/icons': 2.9.0(react@19.1.0)
'@tanstack/react-table': 8.20.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
clsx: 1.2.1
copy-to-clipboard: 3.3.3
@@ -17302,10 +17286,6 @@ snapshots:
'@types/node@17.0.21': {}
- '@types/node@22.15.30':
- dependencies:
- undici-types: 6.21.0
-
'@types/node@22.15.32':
dependencies:
undici-types: 6.21.0
@@ -18799,16 +18779,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
- eslint-module-utils@2.12.0(@typescript-eslint/parser@8.32.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@9.28.0(jiti@2.4.2)):
- dependencies:
- debug: 3.2.7
- optionalDependencies:
- '@typescript-eslint/parser': 8.32.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3)
- eslint: 9.28.0(jiti@2.4.2)
- eslint-import-resolver-node: 0.3.9
- transitivePeerDependencies:
- - supports-color
-
eslint-module-utils@2.12.0(@typescript-eslint/parser@8.33.1(eslint@8.10.0)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.10.0):
dependencies:
debug: 3.2.7
@@ -18820,6 +18790,17 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ eslint-module-utils@2.12.0(@typescript-eslint/parser@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.28.0(jiti@2.4.2)):
+ dependencies:
+ debug: 3.2.7
+ optionalDependencies:
+ '@typescript-eslint/parser': 8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3)
+ eslint: 9.28.0(jiti@2.4.2)
+ eslint-import-resolver-node: 0.3.9
+ eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@9.28.0(jiti@2.4.2))
+ transitivePeerDependencies:
+ - supports-color
+
eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.32.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2)):
dependencies:
'@rtsao/scc': 1.1.0
@@ -18831,7 +18812,7 @@ snapshots:
doctrine: 2.1.0
eslint: 9.28.0(jiti@2.4.2)
eslint-import-resolver-node: 0.3.9
- eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.32.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@9.28.0(jiti@2.4.2))
+ eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.28.0(jiti@2.4.2))
hasown: 2.0.2
is-core-module: 2.16.1
is-glob: 4.0.3
@@ -22404,8 +22385,6 @@ snapshots:
tailwind-merge@2.6.0: {}
- tailwind-merge@3.3.0: {}
-
tailwind-merge@3.3.1: {}
tailwindcss-animate@1.0.7(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@17.0.21)(typescript@5.8.3))):
diff --git a/public/locales/en/common.json b/public/locales/en/common.json
index b029ffc..155f795 100644
--- a/public/locales/en/common.json
+++ b/public/locales/en/common.json
@@ -110,5 +110,10 @@
"reject": "Reject",
"accept": "Accept"
},
- "doctor": "Doctor"
+ "doctor": "Doctor",
+ "save": "Save",
+ "saveAsDraft": "Save as draft",
+ "confirm": "Confirm",
+ "previous": "Previous",
+ "next": "Next"
}
\ No newline at end of file
diff --git a/public/locales/en/doctor.json b/public/locales/en/doctor.json
new file mode 100644
index 0000000..0a73489
--- /dev/null
+++ b/public/locales/en/doctor.json
@@ -0,0 +1,43 @@
+{
+ "sidebar": {
+ "dashboard": "Overview",
+ "openReviews": "Open jobs",
+ "myReviews": "My jobs",
+ "completedReviews": "Completed jobs"
+ },
+ "openReviews": "Open jobs",
+ "myReviews": "My jobs",
+ "completedReviews": "Completed jobs",
+ "otherReviews": "Other jobs",
+ "resultsTable": {
+ "patientName": "Patient name",
+ "serviceName": "Service",
+ "orderNr": "Order number",
+ "time": "Time",
+ "assignedTo": "Doctor",
+ "resultsStatus": "Analysis results",
+ "waitingForNr": "Waiting for {{nr}}",
+ "responsesReceived": "Results complete"
+ },
+ "otherPatients": "Other patients",
+ "analyses": "Analyses",
+ "open": "Open",
+ "name": "Name",
+ "personalCode": "Personal code",
+ "dobAndAge": "Date of birth and age",
+ "bmi": "Body mass index",
+ "smoking": "Smoking",
+ "phone": "Phone",
+ "email": "Email",
+ "results": "Analysis results",
+ "feedback": "Feedback",
+ "selectJob": "Select",
+ "unselectJob": "Deselect",
+ "confirmFeedbackModal": {
+ "title": "Confirm publishing feedback",
+ "description": "When confirmed, the feedback will be published to the patient."
+ },
+ "updateFeedbackSuccess": "Feedback updated",
+ "updateFeedbackLoading": "Updating feedback...",
+ "updateFeedbackError": "Failed to update feedback"
+}
diff --git a/public/locales/et/common.json b/public/locales/et/common.json
index fd3a5e9..d9d85ac 100644
--- a/public/locales/et/common.json
+++ b/public/locales/et/common.json
@@ -45,8 +45,8 @@
"continue": "Continue",
"skip": "Skip",
"signedInAs": "Signed in as",
- "pageOfPages": "Page {{page}} of {{total}}",
- "noData": "No data available",
+ "pageOfPages": "Leht {{page}} / {{total}}",
+ "noData": "Andmed puuduvad",
"pageNotFoundHeading": "Ouch! :|",
"errorPageHeading": "Ouch! :|",
"notifications": "Notifications",
@@ -128,5 +128,10 @@
"balance": "Sinu MedReporti konto seis",
"expiredAt": "Kehtiv kuni {{expiredAt}}"
},
- "doctor": "Arst"
-}
+ "doctor": "Arst",
+ "save": "Salvesta",
+ "saveAsDraft": "Salvesta mustandina",
+ "confirm": "Kinnita",
+ "previous": "Eelmine",
+ "next": "Järgmine"
+}
\ No newline at end of file
diff --git a/public/locales/et/doctor.json b/public/locales/et/doctor.json
new file mode 100644
index 0000000..6945f43
--- /dev/null
+++ b/public/locales/et/doctor.json
@@ -0,0 +1,43 @@
+{
+ "sidebar": {
+ "dashboard": "Ülevaade",
+ "openReviews": "Vabad tööd",
+ "myReviews": "Minu tööd",
+ "completedReviews": "Tehtud tööd"
+ },
+ "openReviews": "Vabad tööd",
+ "myReviews": "Minu tööd",
+ "completedReviews": "Tehtud tööd",
+ "otherReviews": "Muud tööd",
+ "resultsTable": {
+ "patientName": "Patsiendi nimi",
+ "serviceName": "Teenus",
+ "orderNr": "Tellimuse number",
+ "time": "Proov antud",
+ "assignedTo": "Arst",
+ "resultsStatus": "Analüüsitulemused",
+ "waitingForNr": "Ootel {{nr}}",
+ "responsesReceived": "Tulemused koos"
+ },
+ "otherPatients": "Muud patsiendid",
+ "analyses": "Analüüsid",
+ "open": "Ava",
+ "name": "Nimi",
+ "personalCode": "Isikukood",
+ "dobAndAge": "Sünniaeg ja vanus",
+ "bmi": "Kehamassiindeks",
+ "smoking": "Suitsetamine",
+ "phone": "Telefon",
+ "email": "E-mail",
+ "results": "Analüüside tulemused",
+ "feedback": "Tagasiside",
+ "selectJob": "Vali",
+ "unselectJob": "Loobu",
+ "confirmFeedbackModal": {
+ "title": "Kinnita tagasiside avaldamine",
+ "description": "Tagasiside kinnitamisel avaldatakse see patsiendile."
+ },
+ "updateFeedbackSuccess": "Tagasiside uuendatud",
+ "updateFeedbackLoading": "Tagasiside uuendatakse...",
+ "updateFeedbackError": "Tagasiside uuendamine ebaõnnestus"
+}
\ No newline at end of file
diff --git a/public/locales/ru/common.json b/public/locales/ru/common.json
index e0e9728..c9bb217 100644
--- a/public/locales/ru/common.json
+++ b/public/locales/ru/common.json
@@ -108,5 +108,10 @@
"reject": "Reject",
"accept": "Accept"
},
- "doctor": "Doctor"
+ "doctor": "Doctor",
+ "save": "Save",
+ "saveAsDraft": "Save as draft",
+ "confirm": "Confirm",
+ "previous": "Previous",
+ "next": "Next"
}
\ No newline at end of file
diff --git a/public/locales/ru/doctor.json b/public/locales/ru/doctor.json
new file mode 100644
index 0000000..e88dd58
--- /dev/null
+++ b/public/locales/ru/doctor.json
@@ -0,0 +1,43 @@
+{
+ "sidebar": {
+ "dashboard": "Overview",
+ "openReviews": "Open jobs",
+ "myReviews": "My jobs",
+ "completedReviews": "Completed jobs"
+ },
+ "openReviews": "Open jobs",
+ "myReviews": "My jobs",
+ "completedReviews": "Completed jobs",
+ "otherReviews": "Other jobs",
+ "resultsTable": {
+ "patientName": "Patient name",
+ "serviceName": "Service",
+ "orderNr": "Order number",
+ "time": "Time",
+ "assignedTo": "Doctor",
+ "resultsStatus": "Analysis results",
+ "waitingForNr": "Waiting for {{nr}}",
+ "responsesReceived": "Results complete"
+ },
+ "otherPatients": "Other patients",
+ "analyses": "Analyses",
+ "open": "Open",
+ "name": "Name",
+ "personalCode": "Personal code",
+ "dobAndAge": "Date of birth and age",
+ "bmi": "Body mass index",
+ "smoking": "Smoking",
+ "phone": "Phone",
+ "email": "Email",
+ "results": "Analysis results",
+ "feedback": "Feedback",
+ "selectJob": "Select",
+ "unselectJob": "Deselect",
+ "confirmFeedbackModal": {
+ "title": "Confirm publishing feedback",
+ "description": "When confirmed, the feedback will be published to the patient."
+ },
+ "updateFeedbackSuccess": "Feedback updated",
+ "updateFeedbackLoading": "Updating feedback...",
+ "updateFeedbackError": "Failed to update feedback"
+}
\ No newline at end of file
diff --git a/supabase/migrations/20250814110834_doctor_analysis_permission_and_table.sql b/supabase/migrations/20250814110834_doctor_analysis_permission_and_table.sql
new file mode 100644
index 0000000..82b1186
--- /dev/null
+++ b/supabase/migrations/20250814110834_doctor_analysis_permission_and_table.sql
@@ -0,0 +1,95 @@
+create policy "doctor_select"
+on "medreport"."analysis_responses"
+as PERMISSIVE
+for SELECT
+to authenticated
+using (medreport.is_doctor());
+
+create policy "doctor_select"
+on "medreport"."analysis_response_elements"
+as PERMISSIVE
+for SELECT
+to authenticated
+using (medreport.is_doctor());
+
+create policy "doctor_select"
+on "medreport"."accounts"
+as PERMISSIVE
+for SELECT
+to authenticated
+using (medreport.is_doctor());
+
+create type medreport.analysis_feedback_status as enum ('STARTED', 'DRAFT', 'COMPLETED');
+
+CREATE TABLE medreport.doctor_analysis_feedback (
+ id BIGINT GENERATED BY DEFAULT as IDENTITY NOT NULL,
+ analysis_order_id BIGINT NOT NULL REFERENCES medreport.analysis_orders(id),
+ doctor_user_id UUID NULL REFERENCES auth.users(id),
+ user_id UUID NOT NULL REFERENCES auth.users(id),
+ value TEXT,
+ status medreport.analysis_feedback_status DEFAULT 'STARTED' NOT NULL,
+ created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
+ updated_at TIMESTAMPTZ DEFAULT NOW(),
+ created_by UUID DEFAULT auth.uid() REFERENCES auth.users(id) NOT NULL,
+ updated_by UUID DEFAULT auth.uid() REFERENCES auth.users(id),
+ PRIMARY KEY (id)
+);
+
+CREATE UNIQUE INDEX doctor_analysis_feedback_analysis_order_id_key ON medreport.doctor_analysis_feedback USING btree (analysis_order_id);
+
+CREATE OR REPLACE FUNCTION medreport.update_doctor_analysis_feedback_updated_fields()
+RETURNS TRIGGER AS $$
+BEGIN
+ NEW.updated_at = NOW();
+ NEW.updated_by = auth.uid();
+ RETURN NEW;
+END;
+$$ LANGUAGE plpgsql SECURITY DEFINER;
+
+CREATE TRIGGER trigger_update_doctor_analysis_feedback_updated_fields
+ AFTER UPDATE ON medreport.doctor_analysis_feedback
+ FOR EACH ROW
+ EXECUTE FUNCTION medreport.update_doctor_analysis_feedback_updated_fields();
+
+CREATE OR REPLACE FUNCTION medreport.set_doctor_analysis_feedback_created_fields()
+RETURNS TRIGGER AS $$
+BEGIN
+ NEW.created_at = NOW();
+ NEW.created_by = auth.uid();
+ RETURN NEW;
+END;
+$$ LANGUAGE plpgsql SECURITY DEFINER;
+
+CREATE TRIGGER trigger_insert_doctor_analysis_feedback_created_fields
+ AFTER INSERT ON medreport.doctor_analysis_feedback
+ FOR EACH ROW
+ EXECUTE FUNCTION medreport.set_doctor_analysis_feedback_created_fields();
+
+CREATE INDEX idx_doctor_analysis_feedback_analysis_order_id ON medreport.doctor_analysis_feedback(analysis_order_id);
+CREATE INDEX idx_doctor_analysis_feedback_doctor_user_id ON medreport.doctor_analysis_feedback(doctor_user_id);
+
+grant select,insert, update, delete on table medreport.doctor_analysis_feedback to authenticated;
+
+ALTER TABLE medreport.doctor_analysis_feedback ENABLE ROW LEVEL SECURITY;
+
+create policy "user_select"
+on "medreport"."doctor_analysis_feedback"
+as PERMISSIVE
+for SELECT
+to authenticated
+using (auth.uid() = user_id);
+
+create policy "doctor_all"
+on "medreport"."doctor_analysis_feedback"
+as PERMISSIVE
+for ALL
+to authenticated
+using (medreport.is_doctor());
+
+create policy "doctor_select"
+on "medreport"."account_params"
+as PERMISSIVE
+for SELECT
+to authenticated
+using (medreport.is_doctor());
+
diff --git a/tsconfig.json b/tsconfig.json
index 8abaf1e..c090f42 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -3,18 +3,42 @@
"compilerOptions": {
"baseUrl": ".",
"paths": {
- "@kit/ui/*": ["packages/ui/src/*"],
- "@kit/ui": ["packages/ui/src/index.ts"],
- "@lib/*": ["packages/features/medusa-storefront/src/lib/*"],
- "@modules/*": ["packages/features/medusa-storefront/src/modules/*"],
- "@styles/*": ["packages/features/medusa-storefront/src/styles/*"],
- "types/*": ["packages/features/medusa-storefront/src/types/*"],
- "~/config/*": ["./config/*"],
- "~/components/*": ["./components/*"],
- "~/lib/*": ["./lib/*"],
- "~/medusa/*": ["./packages/features/medusa-storefront/src/*"],
- "@/*": ["./*"],
- "~/*": ["./app/*"]
+ "@kit/ui/*": [
+ "packages/ui/src/*"
+ ],
+ "@kit/ui": [
+ "packages/ui/src/index.ts"
+ ],
+ "@lib/*": [
+ "packages/features/medusa-storefront/src/lib/*"
+ ],
+ "@modules/*": [
+ "packages/features/medusa-storefront/src/modules/*"
+ ],
+ "@styles/*": [
+ "packages/features/medusa-storefront/src/styles/*"
+ ],
+ "types/*": [
+ "packages/features/medusa-storefront/src/types/*"
+ ],
+ "~/config/*": [
+ "./config/*"
+ ],
+ "~/components/*": [
+ "./components/*"
+ ],
+ "~/lib/*": [
+ "./lib/*"
+ ],
+ "~/medusa/*": [
+ "./packages/features/medusa-storefront/src/*"
+ ],
+ "@/*": [
+ "./*"
+ ],
+ "~/*": [
+ "./app/*"
+ ]
},
"plugins": [
{
@@ -33,5 +57,8 @@
"lib/**/*.ts",
"app"
],
- "exclude": ["node_modules", ".next"]
-}
+ "exclude": [
+ "node_modules",
+ ".next"
+ ]
+}
\ No newline at end of file