-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #22 from LabGraphTeam/refactoring/analytics_table
feat: update Docker setup and add analytics pagination components
- Loading branch information
Showing
13 changed files
with
468 additions
and
313 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,30 +1,57 @@ | ||
# Etapa 1: Build | ||
FROM node:20-alpine AS builder | ||
# syntax=docker.io/docker/dockerfile:1 | ||
|
||
FROM node:20-alpine AS base | ||
|
||
# Install dependencies only when needed | ||
FROM base AS deps | ||
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. | ||
RUN apk add --no-cache libc6-compat | ||
WORKDIR /app | ||
|
||
# Copie apenas os arquivos necessários para instalar dependências | ||
COPY package*.json ./ | ||
# Install dependencies based on the preferred package manager | ||
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* .npmrc* ./ | ||
RUN \ | ||
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \ | ||
elif [ -f package-lock.json ]; then npm ci; \ | ||
elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \ | ||
else echo "Lockfile not found." && exit 1; \ | ||
fi | ||
|
||
# Instale dependências apenas uma vez e use cache do Docker | ||
RUN npm ci | ||
|
||
# Copie os arquivos do projeto | ||
# Rebuild the source code only when needed | ||
FROM base AS builder | ||
WORKDIR /app | ||
COPY --from=deps /app/node_modules ./node_modules | ||
COPY . . | ||
|
||
# Execute o build da aplicação | ||
RUN npm run build | ||
# Next.js collects completely anonymous telemetry data about general usage. | ||
# Learn more here: https://nextjs.org/telemetry | ||
# Uncomment the following line in case you want to disable telemetry during the build. | ||
# ENV NEXT_TELEMETRY_DISABLED=1 | ||
|
||
# Etapa 2: Produção | ||
FROM node:20-alpine AS runner | ||
RUN \ | ||
if [ -f yarn.lock ]; then yarn run build; \ | ||
elif [ -f package-lock.json ]; then npm run build; \ | ||
elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm run build; \ | ||
else echo "Lockfile not found." && exit 1; \ | ||
fi | ||
|
||
# Production image, copy all the files and run next | ||
FROM base AS runner | ||
WORKDIR /app | ||
|
||
# Copie apenas os arquivos necessários da etapa de build | ||
COPY --from=builder /app/.next /app/.next | ||
COPY --from=builder /app/package*.json /app/ | ||
COPY --from=builder /app/public /app/public | ||
ENV NODE_ENV=production | ||
# Uncomment the following line in case you want to disable telemetry during runtime. | ||
# ENV NEXT_TELEMETRY_DISABLED=1 | ||
|
||
RUN addgroup --system --gid 1001 nodejs | ||
RUN adduser --system --uid 1001 nextjs | ||
|
||
COPY --from=builder /app/public ./public | ||
|
||
# Automatically leverage output traces to reduce image size | ||
# https://nextjs.org/docs/advanced-features/output-file-tracing | ||
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ | ||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static | ||
|
||
# Instale apenas dependências de produção | ||
RUN npm ci --omit=dev | ||
RUN npm prune --production | ||
USER nextjs |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# Etapa 1: Build | ||
FROM node:20-alpine AS builder | ||
|
||
WORKDIR /app | ||
|
||
COPY package*.json ./ | ||
|
||
RUN npm ci | ||
|
||
COPY . . | ||
|
||
RUN npm run build | ||
|
||
FROM node:20-alpine AS runner | ||
|
||
WORKDIR /app | ||
|
||
COPY --from=builder /app/.next /app/.next | ||
COPY --from=builder /app/package*.json /app/ | ||
COPY --from=builder /app/public /app/public | ||
|
||
RUN npm ci --omit=dev | ||
RUN npm prune --production |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
import ListingTable from '@/components/analytics-table/listing-table'; | ||
import useDateSelector from '@/components/shared/date-selector/hooks/useDateSelector'; | ||
import Footer from '@/components/ui/footer'; | ||
import NavBar from '@/components/ui/navigation-bar'; | ||
import Head from 'next/head'; | ||
import { useEffect, useState } from 'react'; | ||
import { useAnalyticsData } from '@/components/analytics-table/hooks/useAnalyticsData'; | ||
import useWindowDimensions from '@/components/ui/hooks/useWindowDimensions'; | ||
import AnalyticsFilters from './util/analytics-filters'; | ||
import AnalyticsPagination from './util/analytics-pagination'; | ||
|
||
const AnalyticsTableIndex = () => { | ||
const dateSelector = useDateSelector(); | ||
const [currentPage, setCurrentPage] = useState(0); | ||
const [itemsPerPage, setItemsPerPage] = useState(7); | ||
const [analyticsType, setAnalyticsType] = useState('biochemistry-analytics'); | ||
const [level, setLevel] = useState('0'); | ||
const [isFiltered, setFiltered] = useState(false); | ||
|
||
|
||
const { width } = useWindowDimensions(); | ||
|
||
|
||
|
||
const { | ||
data: dataFetched, | ||
links: _links, | ||
isLoading, | ||
error, | ||
fetchData, | ||
buildUrl, | ||
totalPages, | ||
} = useAnalyticsData({ | ||
analyticsType, | ||
level, | ||
startDate: { | ||
day: dateSelector.startDay, | ||
month: dateSelector.startMonth, | ||
year: dateSelector.startYear, | ||
}, | ||
endDate: { | ||
day: dateSelector.endDay, | ||
month: dateSelector.endMonth, | ||
year: dateSelector.endYear, | ||
}, | ||
itemsPerPage, | ||
currentPage, | ||
}); | ||
|
||
useEffect(() => { | ||
const url = buildUrl(isFiltered); | ||
fetchData(url); | ||
}, [ | ||
isFiltered, | ||
analyticsType, | ||
level, | ||
itemsPerPage, | ||
currentPage, | ||
dateSelector.startDay, | ||
dateSelector.startMonth, | ||
dateSelector.startYear, | ||
dateSelector.endDay, | ||
dateSelector.endMonth, | ||
dateSelector.endYear, | ||
]); | ||
|
||
const handlePageChange = async (url: string): Promise<void> => { | ||
await fetchData(url); | ||
}; | ||
|
||
useEffect(() => { | ||
setItemsPerPage(width >= 1800 ? 12 : 8); | ||
console.log(width); | ||
}, [fetchData, width]); | ||
|
||
const analyticsOptions = [ | ||
{ value: 'biochemistry-analytics', label: 'BIOCHEMISTRY' }, | ||
{ value: 'hematology-analytics', label: 'HEMATOLOGY' }, | ||
{ value: 'coagulation-analytics', label: 'COAGULATION' }, | ||
]; | ||
|
||
const levelOptions = [ | ||
{ value: '0', label: '-' }, | ||
{ value: '1', label: '1' }, | ||
{ value: '2', label: '2' }, | ||
{ value: '3', label: '3' }, | ||
] | ||
|
||
return ( | ||
<div className='min-h bg-background'> | ||
<div className='min-h flex flex-col content-center items-center justify-center'> | ||
<Head> | ||
<title>{`LabGraph - ${analyticsType || 'Quality-Lab-Pro'}`}</title> | ||
</Head> | ||
<NavBar jsonData={dataFetched} fileName={analyticsType} /> | ||
<div className='w-full max-w-7xl'> | ||
<AnalyticsFilters | ||
dateSelector={dateSelector} | ||
analyticsOptions={analyticsOptions} | ||
analyticsType={analyticsType} | ||
setAnalyticsType={setAnalyticsType} | ||
levelOptions={levelOptions} | ||
level={level} | ||
setLevel={setLevel} | ||
setFiltered={setFiltered} | ||
/> | ||
{isLoading ? ( | ||
<div className='flex content-center items-center justify-center'> | ||
<div className='h-10 w-10 animate-spin rounded-full border-t-2 border-primary'></div> | ||
</div> | ||
) : error ? ( | ||
<div className='mtrelative rounded bg-danger px-4 py-3 text-white' role='alert'> | ||
{error} | ||
</div> | ||
) : ( | ||
<ListingTable | ||
items={dataFetched} | ||
pageInfos={_links} | ||
onPageChange={handlePageChange} | ||
/> | ||
)} | ||
</div> | ||
<AnalyticsPagination | ||
currentPage={currentPage} | ||
totalPages={totalPages} | ||
dataFetched={dataFetched} | ||
setCurrentPage={setCurrentPage} | ||
/> | ||
<div className='flex flex-col items-center justify-end'> | ||
<Footer /> | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
}; | ||
|
||
export default AnalyticsTableIndex; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import React from 'react'; | ||
import { ListingTableProps } from '../../features/types/ListiningTable'; | ||
import TableRow from './table-row'; | ||
import MobileItemCard from './mobile-item-card'; | ||
|
||
const ListingTable: React.FC<ListingTableProps> = ({ items }) => { | ||
return ( | ||
<div className='flex flex-col justify-between w-full h-min rounded-lg shadow-xl'> | ||
<table className='hidden bg-background md:table'> | ||
<thead className='bg-muted rounded-lg'> | ||
<tr> | ||
<th className='border-b border-border px-2 py-1 text-left text-[10px] font-semibold uppercase tracking-wider text-textSecondary md:text-xs'> | ||
Date | ||
</th> | ||
<th className='border-b border-border px-2 py-1 text-left text-[10px] font-semibold uppercase tracking-wider text-textSecondary md:text-xs'> | ||
Test | ||
</th> | ||
<th className='border-b border-border px-2 py-1 text-left text-[10px] font-semibold uppercase tracking-wider text-textSecondary md:text-xs'> | ||
Level | ||
</th> | ||
<th className='border-b border-border px-2 py-1 text-left text-[10px] font-semibold uppercase tracking-wider text-textSecondary md:text-xs'> | ||
Lot | ||
</th> | ||
<th className='border-b border-border px-2 py-1 text-left text-[10px] font-semibold uppercase tracking-wider text-textSecondary md:text-xs'> | ||
Standard Deviation | ||
</th> | ||
<th className='border-b border-border px-2 py-1 text-left text-[10px] font-semibold uppercase tracking-wider text-textSecondary md:text-xs'> | ||
Mean | ||
</th> | ||
<th className='border-b border-border px-2 py-1 text-left text-[10px] font-semibold uppercase tracking-wider text-textSecondary md:text-xs'> | ||
Values | ||
</th> | ||
<th className='border-b border-border px-2 py-1 text-left text-[10px] font-semibold uppercase tracking-wider text-textSecondary md:text-xs'> | ||
Unit | ||
</th> | ||
<th className='border-b border-border px-2 py-1 text-left text-[10px] font-semibold uppercase tracking-wider text-textSecondary md:text-xs'> | ||
Rules | ||
</th> | ||
</tr> | ||
</thead> | ||
<tbody> | ||
{items.map((item, index) => ( | ||
<TableRow key={index} item={item} /> | ||
))} | ||
</tbody> | ||
</table> | ||
|
||
<div className='grid content-center justify-center grid-cols-4 px-2 text-center md:hidden'> | ||
{items.map((item, index) => ( | ||
<MobileItemCard key={index} item={item} /> | ||
))} | ||
</div> | ||
|
||
{items.length === 0 && ( | ||
<div className='py-2 text-center bg-background text-muted'>No items to display</div> | ||
)} | ||
</div> | ||
); | ||
}; | ||
|
||
export default ListingTable; |
40 changes: 40 additions & 0 deletions
40
src/components/analytics-table/listing-table/mobile-item-card.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
|
||
import React from 'react'; | ||
import { ListingItem } from '@/components/charts/types/Chart'; | ||
|
||
interface MobileItemCardProps { | ||
item: ListingItem; | ||
} | ||
|
||
const MobileItemCard: React.FC<MobileItemCardProps> = ({ item }) => { | ||
return ( | ||
<div className='p-2 border rounded-md shadow-md border-border bg-surface'> | ||
<p className='text-[6px] font-semibold text-textSecondary'> | ||
Test: <span className='text-textPrimary'>{item.name}</span> | ||
</p> | ||
<p className='text-[6px] font-semibold text-textSecondary'> | ||
Level: <span className='text-textPrimary'>{item.level}</span> | ||
</p> | ||
<p className='text-[6px] font-semibold text-textSecondary'> | ||
Standard Deviation: <span className='text-textPrimary'>{item.sd.toFixed(2)}</span> | ||
</p> | ||
<p className='text-[6px] font-semibold text-textSecondary'> | ||
Mean: <span className='text-textPrimary'>{item.mean.toFixed(2)}</span> | ||
</p> | ||
<p className='text-[6px] font-semibold text-textSecondary'> | ||
Date: <span className='text-textPrimary'>{item.date}</span> | ||
</p> | ||
<p className='text-[6px] font-semibold text-textSecondary'> | ||
Values: <span className='text-textPrimary'>{item.value.toFixed(2)}</span> | ||
</p> | ||
<p className='text-[6px] font-semibold text-textSecondary'> | ||
Unit: <span className='text-textPrimary'>{item.unit_value}</span> | ||
</p> | ||
<p className='text-[6px] font-semibold text-textSecondary'> | ||
Rules: <span className='text-textPrimary'>{item.rules}</span> | ||
</p> | ||
</div> | ||
); | ||
}; | ||
|
||
export default MobileItemCard; |
Oops, something went wrong.