Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"typecheck": "tsc -b --noEmit",
"preview": "vite preview",
"typecheck": "tsc -b",
"check": "biome check .",
"check:fix": "biome check --write .",
"test": "vitest run",
Expand Down
33 changes: 10 additions & 23 deletions src/components/layout/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { ActionIcon, Anchor, SegmentedControl, Text } from '@mantine/core';
import { ActionIcon, Anchor, Text } from '@mantine/core';
import { IconSearch, IconSettings } from '@tabler/icons-react';
import { useLocation, useNavigate } from 'react-router';
import { useAppContext } from '../../context/AppContext.tsx';
import classes from './Header.module.css';

export function Header() {
const { mode, setMode } = useAppContext();
const navigate = useNavigate();
const { pathname } = useLocation();

Expand Down Expand Up @@ -59,26 +57,15 @@ export function Header() {
<img src="/utc-logo.svg" alt="ut.code();" style={{ height: 28, padding: '4px 0' }} />
</Anchor>
{showControls && (
<>
<SegmentedControl
value={mode}
onChange={(v) => setMode(v as 'view' | 'edit')}
data={[
{ value: 'edit', label: 'Edit' },
{ value: 'view', label: 'View' },
]}
size="sm"
/>
<ActionIcon
variant="subtle"
color="gray"
size="lg"
onClick={handleSearch}
aria-label="検索"
>
<IconSearch size={20} />
</ActionIcon>
</>
<ActionIcon
variant="subtle"
color="gray"
size="lg"
onClick={handleSearch}
aria-label="検索"
>
<IconSearch size={20} />
</ActionIcon>
)}
<ActionIcon
variant="subtle"
Expand Down
211 changes: 134 additions & 77 deletions src/components/main/Calendar.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { Box, Button, Group, Paper, Text, UnstyledButton } from '@mantine/core';
import { ActionIcon, Box, Button, Group, Paper, Text, UnstyledButton } from '@mantine/core';
import {
IconChevronDown,
IconChevronLeft,
IconChevronRight,
IconChevronUp,
} from '@tabler/icons-react';
import { Fragment } from 'react';
import { useAppContext } from '../../context/AppContext.tsx';
import {
Expand All @@ -11,9 +17,14 @@ import {
import classes from './Calendar.module.css';
import { PeriodCell } from './PeriodCell.tsx';

export function Calendar() {
const { mode, credits, togglePeriod, setPeriods, groupedByPeriod } = useAppContext();
const isEdit = mode === 'edit';
interface Props {
collapsed: boolean;
onToggle: () => void;
isDesktop: boolean;
}

export function Calendar({ collapsed, onToggle, isDesktop }: Props) {
const { credits, togglePeriod, setPeriods, groupedByPeriod } = useAppContext();

const selectBlank = () => {
const blank: string[] = [];
Expand All @@ -26,84 +37,130 @@ export function Calendar() {
setPeriods(blank);
};

if (collapsed && isDesktop) {
return (
<Paper
radius="xl"
p="xs"
h="100%"
style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 6 }}
>
<ActionIcon variant="subtle" color="gray" size="sm" onClick={onToggle}>
<IconChevronLeft size={16} />
</ActionIcon>
<Text fw={600} style={{ writingMode: 'vertical-rl', fontSize: 13 }}>
時間割
</Text>
</Paper>
);
}

return (
<Paper m={20} radius="xl" p="md" pos="relative" id="calendar-container">
<Text fz="xl" fw={600} pb={4} mb="xs" style={{ borderBottom: '1px solid #000' }}>
時間割
</Text>
<Box pos="absolute" right={30} top={12} fz="sm">
現在
<Text component="strong" fz="1.4em" fw={700} c="syllabusGreen" px="xs" display="inline">
{credits}
<Paper
radius="xl"
p="md"
id="calendar-container"
h={isDesktop ? '100%' : undefined}
style={{ overflowY: isDesktop ? 'auto' : undefined }}
>
<Group
justify="space-between"
pb={collapsed ? 0 : 4}
mb={collapsed ? 0 : 'xs'}
style={{ borderBottom: collapsed ? 'none' : '1px solid #000' }}
>
<Text fz="xl" fw={600}>
時間割
</Text>
単位
</Box>
<Group gap="xs" align="center">
{!collapsed && (
<Box fz="sm">
現在
<Text
component="strong"
fz="1.4em"
fw={700}
c="syllabusGreen"
px="xs"
display="inline"
>
{credits}
</Text>
単位
</Box>
)}
<ActionIcon variant="subtle" color="gray" size="sm" onClick={onToggle}>
{isDesktop ? (
<IconChevronRight size={16} />
) : collapsed ? (
<IconChevronDown size={16} />
) : (
<IconChevronUp size={16} />
)}
</ActionIcon>
</Group>
</Group>

<div className={`${classes.grid} ${isEdit ? classes.gridEdit : classes.gridView}`}>
{/* Corner */}
<div />
{/* Day headers */}
{DAY_JP_LIST.map((dayJp, di) => {
const dayEn = DAY_EN_LIST[di];
const periods = HEADER_ID_TO_PERIODS.get(`${dayEn}-all`) ?? [];
const isToday = dayEn === TODAY_EN;
return (
<UnstyledButton
key={dayJp}
className={`${classes.headerCell} ${isToday && !isEdit ? classes.today : ''}`}
onClick={isEdit ? () => togglePeriod(periods) : undefined}
>
{dayJp}
</UnstyledButton>
);
})}
{/* Time rows */}
{TIME_LIST.map((time) => (
<Fragment key={time}>
<UnstyledButton
className={classes.headerCell}
onClick={
isEdit
? () => togglePeriod(HEADER_ID_TO_PERIODS.get(`all-${time}`) ?? [])
: undefined
}
>
{time}
</UnstyledButton>
{DAY_JP_LIST.map((dayJp) => (
<div key={`${dayJp}${time}`} className={classes.cell}>
<PeriodCell period={`${dayJp}${time}`} />
</div>
{!collapsed && (
<>
<div className={`${classes.grid} ${classes.gridEdit}`}>
{/* Corner */}
<div />
{/* Day headers */}
{DAY_JP_LIST.map((dayJp, di) => {
const dayEn = DAY_EN_LIST[di];
const periods = HEADER_ID_TO_PERIODS.get(`${dayEn}-all`) ?? [];
const isToday = dayEn === TODAY_EN;
return (
<UnstyledButton
key={dayJp}
className={`${classes.headerCell} ${isToday ? classes.today : ''}`}
onClick={() => togglePeriod(periods)}
>
{dayJp}
</UnstyledButton>
);
})}
{/* Time rows */}
{TIME_LIST.map((time) => (
<Fragment key={time}>
<UnstyledButton
className={classes.headerCell}
onClick={() => togglePeriod(HEADER_ID_TO_PERIODS.get(`all-${time}`) ?? [])}
>
{time}
</UnstyledButton>
{DAY_JP_LIST.map((dayJp) => (
<div key={`${dayJp}${time}`} className={classes.cell}>
<PeriodCell period={`${dayJp}${time}`} />
</div>
))}
</Fragment>
))}
</Fragment>
))}
{/* 集中 */}
<UnstyledButton
className={classes.headerCell}
onClick={isEdit ? () => togglePeriod(['集中']) : undefined}
>
</UnstyledButton>
<div className={`${classes.cell} ${classes.intensiveCell}`}>
<PeriodCell period="集中" isIntensive />
</div>
</div>
{/* 集中 */}
<UnstyledButton className={classes.headerCell} onClick={() => togglePeriod(['集中'])}>
</UnstyledButton>
<div className={`${classes.cell} ${classes.intensiveCell}`}>
<PeriodCell period="集中" isIntensive />
</div>
</div>

{isEdit && (
<Group gap="xs" px="xs" pt="xs" pb={50}>
<Button size="xs" variant="outline" onClick={selectBlank}>
空きコマを選択
</Button>
<Button
size="xs"
variant="subtle"
color="gray"
td="underline"
onClick={() => setPeriods([])}
>
コマ選択クリア
</Button>
</Group>
<Group gap="xs" px="xs" pt="xs" pb="xl">
<Button size="xs" variant="outline" onClick={selectBlank}>
空きコマを選択
</Button>
<Button
size="xs"
variant="subtle"
color="gray"
td="underline"
onClick={() => setPeriods([])}
>
コマ選択クリア
</Button>
</Group>
</>
)}
</Paper>
);
Expand Down
45 changes: 8 additions & 37 deletions src/components/main/ConditionFilter.module.css
Original file line number Diff line number Diff line change
@@ -1,44 +1,15 @@
.semesterGrid {
.filterGrid {
display: grid;
grid-template-columns: repeat(4, 2.5rem);
grid-template-rows: repeat(2, 2.5rem);
grid-auto-flow: column dense;
gap: 4px;
grid-template-columns: 1fr 1fr;
gap: 16px 32px;
}

/* S_, A_ span 2 columns */
.semesterGrid > :nth-child(3n + 1) {
grid-column: span 2;
.fullWidth {
grid-column: 1 / -1;
}

.categoryGrid {
display: grid;
grid-template-columns: repeat(8, 2.5rem);
gap: 4px;
}

/* foundation, requirement, thematic, intermediate span 2 */
.categoryGrid > :nth-child(-n + 4) {
grid-column: span 2;
}

@media (max-width: 470px) {
.categoryGrid {
grid-template-columns: repeat(4, 2.5rem);
@media (max-width: 480px) {
.filterGrid {
grid-template-columns: 1fr;
}
}

/* Ternary (evaluation) */
.ternaryGroup {
display: grid;
grid-template-columns: 3rem 1.5rem 1.5rem;
align-items: center;
gap: 4px;
}

.evalHeader {
display: grid;
grid-template-columns: 3rem 1.5rem 1.5rem;
gap: 4px;
margin-bottom: 2px;
}
Loading
Loading