Skip to content

Commit

Permalink
Merge pull request #11 from Opetushallitus/OK-508_hakujen-sivutus-sel…
Browse files Browse the repository at this point in the history
…aimessa

OK-508: Hakujen sivutus selaimessa
  • Loading branch information
SalamaGofore authored May 15, 2024
2 parents 8532d56 + b7b4c29 commit 3476aac
Show file tree
Hide file tree
Showing 37 changed files with 1,461 additions and 468 deletions.
4 changes: 3 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,10 @@ jobs:
sudo cp mkcert-v*-linux-amd64 /usr/local/bin/mkcert
- name: Create dev certificates
run: npm run create-dev-certs
- name: Build app
run: npm run build-test
- name: Start the app
run: npm run dev-test &
run: npm run start-test &
- name: Run Playwright tests
run: npx playwright test
- uses: actions/upload-artifact@v4
Expand Down
7 changes: 5 additions & 2 deletions next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,15 @@ const cspHeader = `
upgrade-insecure-requests;
`;

const isProd = process.env.NODE_ENV === 'production';
const isStandalone = process.env.STANDALONE === 'true';

const basePath = '/valintojen-toteuttaminen';

const nextConfig = {
basePath,
eslint: {
ignoreDuringBuilds: true,
},
async headers() {
return [
{
Expand All @@ -38,7 +41,7 @@ const nextConfig = {
VIRKAILIJA_URL: process.env.VIRKAILIJA_URL,
APP_URL: process.env.APP_URL,
},
output: isProd ? 'standalone' : undefined,
output: isStandalone ? 'standalone' : undefined,
};

export default nextConfig;
461 changes: 306 additions & 155 deletions package-lock.json

Large diffs are not rendered by default.

10 changes: 8 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@
"private": true,
"scripts": {
"dev": "touch .env.development.local && NODE_EXTRA_CA_CERTS=\"$(mkcert -CAROOT)/rootCA.pem\" node --env-file=.env --env-file=.env.development.local dev-server.mjs",
"dev-test": "APP_URL=https://localhost:3404 VIRKAILIJA_URL=http://localhost:3104 npm run dev",
"build": "next build; cp run.sh .next/standalone",
"dev-test": "TEST=true APP_URL=https://localhost:3404 VIRKAILIJA_URL=http://localhost:3104 npm run dev",
"build": "STANDALONE=true next build; cp run.sh .next/standalone",
"build-test": "APP_URL=https://localhost:3404 VIRKAILIJA_URL=http://localhost:3104 next build",
"start": "NODE_ENV=production npm run dev",
"start-test": "NODE_ENV=production npm run dev-test",
"lint": "next lint",
"prettier-eslint:fix": "prettier-eslint \"**/*.{js,ts,mjs,cjs,jsx,tsx}\" --write",
"test:ui": "playwright test",
"test": "vitest",
"coverage": "vitest run --coverage",
"create-dev-certs": "mkdir -p certificates && cd certificates && mkcert localhost && mkcert -install",
"prepare": "husky",
"typecheck": "tsc"
Expand All @@ -26,11 +29,13 @@
"@tanstack/react-query-devtools": "^5.29.2",
"cookie": "^0.6.0",
"next": "^14.2.2",
"nuqs": "^1.17.2",
"react": "^18",
"react-dom": "^18"
},
"devDependencies": {
"@axe-core/playwright": "^4.9.0",
"@axe-core/react": "^4.9.0",
"@playwright/test": "^1.42.0",
"@testing-library/react": "^14.2.2",
"@types/cookie": "^0.6.0",
Expand All @@ -40,6 +45,7 @@
"@typescript-eslint/eslint-plugin": "^7.7.0",
"@typescript-eslint/parser": "^7.7.0",
"@vitejs/plugin-react": "^4.2.1",
"@vitest/coverage-istanbul": "^1.6.0",
"autoprefixer": "^10.0.1",
"eslint": "^8",
"eslint-config-next": "14.1.0",
Expand Down
2 changes: 1 addition & 1 deletion playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export default defineConfig({
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
workers: process.env.CI ? 2 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'list',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
Expand Down
5 changes: 5 additions & 0 deletions src/app/(root)/@controls/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { FullSpinner } from '@/app/components/full-spinner';

export default function Loading() {
return <FullSpinner />;
}
200 changes: 200 additions & 0 deletions src/app/(root)/@controls/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
'use client';
import React, { ChangeEvent, Suspense, useMemo } from 'react';

import {
FormControl,
Select,
MenuItem,
SelectChangeEvent,
FormLabel,
OutlinedInput,
Box,
InputAdornment,
CircularProgress,
} from '@mui/material';

import { Tila, getHakuAlkamisKaudet } from '@/app/lib/kouta-types';
import { Search } from '@mui/icons-material';
import { useHakuSearchParams } from '@/app/hooks/useHakuSearch';
import { useHakutavat } from '@/app/hooks/useHakutavat';

const HakutapaSelect = ({
value: selectedHakutapa,
onChange,
}: {
value: string;
onChange: (e: SelectChangeEvent) => void;
}) => {
const { data: hakutavat } = useHakutavat();
return (
<Select
labelId="hakutapa-select-label"
name="hakutapa-select"
value={selectedHakutapa ?? ''}
onChange={onChange}
displayEmpty={true}
>
<MenuItem value="">Valitse...</MenuItem>
{hakutavat.map((tapa) => {
return (
<MenuItem value={tapa.koodiUri} key={tapa.koodiUri}>
{tapa.nimi.fi}
</MenuItem>
); //TODO: translate
})}
</Select>
);
};

const SelectFallback = () => (
<Select
disabled={true}
startAdornment={
<InputAdornment position="start">
<CircularProgress
sx={{ height: '24px !important', width: '24px !important' }}
/>
</InputAdornment>
}
/>
);

const HakutapaInput = ({
value,
onChange,
}: {
value: string;
onChange: (e: SelectChangeEvent) => void;
}) => {
return (
<FormControl sx={{ flex: '1 0 180px', textAlign: 'left' }}>
<FormLabel id="hakutapa-select-label">Hakutapa</FormLabel>
<Suspense fallback={<SelectFallback />}>
<HakutapaSelect value={value} onChange={onChange} />
</Suspense>
</FormControl>
);
};

export default function HakuControls() {
const alkamiskaudet = useMemo(getHakuAlkamisKaudet, []);

const {
searchPhrase,
setSearchPhrase,
selectedAlkamisKausi,
setSelectedAlkamisKausi,
selectedHakutapa,
setSelectedHakutapa,
tila,
setTila,
} = useHakuSearchParams();

const handleSearchChange = (e: ChangeEvent<HTMLInputElement>) => {
setSearchPhrase(e.target.value);
};

const changeTila = (e: SelectChangeEvent) => {
setTila(e.target.value);
};

const changeHakutapa = (e: SelectChangeEvent) => {
setSelectedHakutapa(e.target.value);
};

const changeAlkamisKausi = (e: SelectChangeEvent) => {
setSelectedAlkamisKausi(e.target.value);
};

return (
<Box
display="flex"
flexDirection="row"
justifyContent="stretch"
gap={2}
marginBottom={2}
flexWrap="wrap"
alignItems="flex-end"
>
<FormControl
sx={{
flexGrow: 4,
minWidth: '180px',
textAlign: 'left',
}}
>
<FormLabel htmlFor="haku-search">Hae hakuja</FormLabel>
<OutlinedInput
id="haku-search"
name="haku-search"
defaultValue={searchPhrase}
onChange={handleSearchChange}
autoFocus={true}
type="text"
placeholder="Hae hakuja"
endAdornment={
<InputAdornment position="end">
<Search />
</InputAdornment>
}
/>
</FormControl>
<FormControl
sx={{
width: 'auto',
minWidth: '140px',
textAlign: 'left',
}}
>
<FormLabel id="tila-select-label">Tila</FormLabel>
<Select
labelId="tila-select-label"
name="tila-select"
value={tila ?? ''}
onChange={changeTila}
displayEmpty={true}
>
<MenuItem value="">Valitse...</MenuItem>
{Object.values(Tila).map((tila) => {
return (
<MenuItem value={tila} key={tila}>
{tila}
</MenuItem>
); //TODO: translate
})}
</Select>
</FormControl>
<Box
display="flex"
justifyContent="stretch"
gap={2}
flex="1 0 400px"
alignItems="flex-end"
>
<HakutapaInput value={selectedHakutapa} onChange={changeHakutapa} />
<FormControl sx={{ textAlign: 'left', flex: '1 0 180px' }}>
<FormLabel id="alkamiskausi-select-label">
Koulutuksen alkamiskausi
</FormLabel>
<Select
labelId="alkamiskausi-select-label"
name="alkamiskausi-select"
value={selectedAlkamisKausi ?? ''}
onChange={changeAlkamisKausi}
displayEmpty={true}
>
<MenuItem value="">Valitse...</MenuItem>
{alkamiskaudet.map((kausi) => {
const vuosiKausi = `${kausi.alkamisVuosi} ${kausi.alkamisKausiNimi}`;
return (
<MenuItem value={kausi.value} key={kausi.value}>
{vuosiKausi}
</MenuItem>
); //TODO: translate
})}
</Select>
</FormControl>
</Box>
</Box>
);
}
File renamed without changes.
76 changes: 76 additions & 0 deletions src/app/(root)/haku-table-pagination-wrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
'use client';
import React from 'react';
import {
FormControl,
Select,
MenuItem,
FormLabel,
Typography,
Box,
styled,
Pagination,
} from '@mui/material';
import { DEFAULT_PAGE_SIZE, PAGE_SIZES } from '@/app/lib/constants';

export const StyledPagination = styled(Pagination)({
display: 'flex',
});

export type HakuTablePaginationWrapperProps = {
totalCount: number;
pageNumber: number;
setPageNumber: (page: number) => void;
pageSize: number;
setPageSize: (page: number) => void;
children: React.ReactNode;
};

export const HakuTablePaginationWrapper = ({
totalCount,
pageNumber,
pageSize,
setPageNumber,
setPageSize,
children,
}: HakuTablePaginationWrapperProps) => {
return totalCount === 0 ? (
<p>Ei hakutuloksia</p>
) : (
<>
<Box display="flex" justifyContent="space-between" alignItems="center">
<Typography sx={{ textAlign: 'left' }}>Hakuja: {totalCount}</Typography>
<FormControl>
<FormLabel id="page-size-select-label">Näytä per sivu:</FormLabel>
<Select
labelId="page-size-select-label"
name="page-size-select"
value={pageSize.toString()}
onChange={(e) => {
const newValue = parseInt(e.target.value, 10);
setPageSize(isNaN(newValue) ? DEFAULT_PAGE_SIZE : newValue);
}}
>
{PAGE_SIZES.map((size) => {
return (
<MenuItem value={size} key={size}>
{size}
</MenuItem>
);
})}
</Select>
</FormControl>
</Box>
<Box display="flex" flexDirection="column" rowGap={1} alignItems="center">
{children}
<StyledPagination
aria-label="Sivutus"
count={Math.ceil(totalCount / pageSize)}
page={pageNumber}
onChange={(_e: unknown, value: number) => {
setPageNumber(value);
}}
/>
</Box>
</>
);
};
Loading

0 comments on commit 3476aac

Please sign in to comment.