Skip to content

Tonnam/feat(v2)/expaned course card dialog #737

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 17 commits into
base: v2
Choose a base branch
from
Open
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
14 changes: 14 additions & 0 deletions packages/ui/src/components/atom/seat-chip/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { tv, type VariantProps } from 'tailwind-variants'
export { default as SeatChip } from './seat-chip.svelte'

export const chipVariants = tv({
variants: {
status: {
full: 'bg-sunday text-on-sunday px-4',
avaliable: 'bg-wednesday text-on-wednesday px-4',
close: 'bg-surface-container-low text-neutral-500 px-4',
},
},
})

export type Status = VariantProps<typeof chipVariants>['status']
45 changes: 45 additions & 0 deletions packages/ui/src/components/atom/seat-chip/seat-chip.stories.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<script module lang="ts">
import { defineMeta } from '@storybook/addon-svelte-csf'

import { SeatChip } from './index.js'

const { Story } = defineMeta<typeof SeatChip>({
title: 'Atom/SeatChip',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add space in Title

Suggested change
title: 'Atom/SeatChip',
title: 'Atom/Seat Chip',

component: SeatChip,
tags: ['autodocs'],
argTypes: {
status: {
control: {
type: 'select',
},
options: ['full', 'avaliable', 'close'],
defaultValue: 'full',
},
class: {
control: false,
},
},
})
</script>

<!--👇 We create a “template” of how args map to rendering -->

<!-- 👇 Each story then reuses that template -->

<Story name="full" args={{ status: 'full', closable: false }}>
{#snippet children(args)}
<SeatChip {...args}>28/28</SeatChip>
{/snippet}
</Story>

<Story name="avaliable" args={{ status: 'avaliable', closable: false }}>
{#snippet children(args)}
<SeatChip {...args}>10/28</SeatChip>
{/snippet}
</Story>

<Story name="close" args={{ status: 'close', closable: false }}>
{#snippet children(args)}
<SeatChip {...args}>1/28</SeatChip>
{/snippet}
</Story>
19 changes: 19 additions & 0 deletions packages/ui/src/components/atom/seat-chip/seat-chip.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<script lang="ts">
import { cn } from '../../../../../utils'
import { Chip } from '../chip'
import { chipVariants, type Status } from './index.js'

let className: string | undefined | null = undefined
export let status: Status
export let closable: boolean = false
export let onClose: () => void = () => {}
export { className as class }
</script>

<Chip class={cn(chipVariants({ status, className }))} {closable} {onClose}>
{#if status == 'close'}
ปิด
{:else}
<slot />
{/if}
</Chip>
Comment on lines +1 to +19
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this component should accept registered and max seat instead of slot

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

status can be removed as it can be inferred from closed, regis, max

Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<script lang="ts">
import { ArrowRight, Dot } from 'lucide-svelte'

import { Button } from '../../atom/button/index'
import { type Course } from './index'

export let course: Course
</script>

<div class="flex justify-between">
<div class="flex gap-8">
<div class="space-y-1">
<p class="text-on-surface text-h2">{course.code} {course.name}</p>
<p
class="flex text-on-surface-placeholder text-body2 font-normal font-sarabun"
>
{course.credits} หน่วยกิต
<span><Dot class="text-surface-container-lowest" /></span>
{course.reviews} รีวิว
</p>
</div>
<Button variant="outlined">
ข้อมูลรายวิชา <ArrowRight
class="text-on-primary-container size-4 ml-1 stroke-[3]"
/>
</Button>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<script lang="ts">
import { Chip } from '../../atom/chip/index'
import * as Select from '../select/index'

export let haveSection: { value: string; label: string }[]
export let selectedSections: string[] = []

function removeSection(sectionValue: string) {
selectedSections = selectedSections.filter((item) => item !== sectionValue)
}

function getSectionLabel(sectionValue: string) {
return (
haveSection.find((item) => item.value === sectionValue)?.label ||
sectionValue
)
}
</script>

<div class="flex-col items-end">
<p class="text-caption text-on-surface-placeholder font-normal mb-1">
แสดงเซคชัน
</p>
<Select.Root type="multiple" name="sections" bind:value={selectedSections}>
<div class="relative w-[276px]">
{#if selectedSections.length}
<div
class="absolute flex max-w-[274px] w-auto pl-2 top-1/2 -translate-y-1/2 items-center gap-1 truncate"
>
{#each selectedSections as temp}
<Chip class="z-10" closable onClose={() => removeSection(temp)}>
{getSectionLabel(temp)}
</Chip>
{/each}
</div>
{/if}
<Select.Trigger
aria-label="Select sections"
class="w-[276px] h-9 z-0"
placeholder="เลือก"
>
{#if !selectedSections.length}เลือก{/if}
</Select.Trigger>
</div>
<Select.Content role="listbox">
<Select.Group>
{#each haveSection as section}
<Select.Item
value={section.value}
label={section.label}
aria-label={section.label}
role="option"
check={true}
>
{section.label}
</Select.Item>
{/each}
</Select.Group>
</Select.Content>
</Select.Root>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<script lang="ts">
import { Check, Plus } from 'lucide-svelte'

import { Button } from '../../atom/button/index'
import { SeatChip } from '../../atom/seat-chip/index'
import * as Table from '../../atom/table/index'

export let section
</script>

{#each section.schedule as schedule, i}
<Table.Row class={i === 0 ? 'border-b-0' : ''}>
{#if i === 0}
<Table.Cell rowspan={section.schedule.length}
>{section.section}</Table.Cell
>
<Table.Cell rowspan={section.schedule.length}>
<SeatChip class="text-body2 px-4 py-0.5" status={section.seats.status}>
{section.seats.count}
</SeatChip>
</Table.Cell>
{/if}
<Table.Cell>{section.instructors[0]}</Table.Cell>
<Table.Cell>{section.group}</Table.Cell>
<Table.Cell>{schedule.day} {schedule.time}</Table.Cell>
<Table.Cell>{schedule.room}</Table.Cell>
<Table.Cell>{schedule.type}</Table.Cell>
{#if i === 0 && section.selectable}
<Table.Cell>
<Button
variant={section.seats.status === 'avaliable' ? 'outlined' : 'solid'}
>
เลือก {#if section.seats.status === 'avaliable'}
<Plus />
{:else}
<Check />
{/if}
</Button>
</Table.Cell>
{/if}
</Table.Row>
{/each}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<script lang="ts">
import * as Table from '../../atom/table/index'
import { type Course } from './index'
import SectionRow from './SectionRow.svelte'

export let course: Course
export let selectedSections: string[]

$: sectionsToShow = selectedSections.length
? course.sections.filter((section) =>
selectedSections.includes(section.section.toString()),
)
: course.sections
// console.log('selectedSection', selectedSections)
// console.log('selection to show', sectionsToShow)
</script>

<Table.Root>
<Table.Header>
<Table.Row>
<Table.Head>เซคชัน</Table.Head>
<Table.Head>จำนวนที่รับ</Table.Head>
<Table.Head>ผู้สอน</Table.Head>
<Table.Head>กลุ่ม</Table.Head>
<Table.Head>วันเวลาเรียน</Table.Head>
<Table.Head>ห้องเรียน</Table.Head>
<Table.Head>รูปแบบ</Table.Head>
<Table.Head></Table.Head>
</Table.Row>
</Table.Header>
<Table.Body>
{console.log('select', selectedSections)}
{console.log('show', sectionsToShow)}
{#each sectionsToShow as section}
<SectionRow {section} />
{/each}
</Table.Body>
</Table.Root>
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<script module lang="ts">
import { defineMeta } from '@storybook/addon-svelte-csf'

import ExpandedCourseCardDialog from './expanded-course-card-dialog.svelte'

const { Story } = defineMeta<typeof ExpandedCourseCardDialog>({
title: 'Molecule/Expanded Course Card Dialog',
component: ExpandedCourseCardDialog,
tags: ['autodocs'],
argTypes: {
haveSection: {
control: 'array',
description: 'list of section',
},
course: {
control: 'object',
description: 'course object',
},
},
})
</script>

<Story
name="Default"
args={{
haveSection: [
{ value: '1', label: 'เซค 1' },
{ value: '2', label: 'เซค 2' },
{ value: '3', label: 'เซค 3' },
],
course: {
code: '0123101',
name: 'PARAGRAPH WRITING',
credits: 3,
reviews: 14,
sections: [
{
section: 1,
seats: { status: 'full', count: '28/28' },
instructors: ['SSS'],
group: '4EE ONLY',
schedule: [
{
day: 'MON',
time: '16:00 - 17:00',
room: 'MAHIT 202',
type: 'LECT',
},
{
day: 'THU',
time: '16:00 - 17:00',
room: 'MAHIT 202',
type: 'LECT',
},
],
selectable: true,
},
{
section: 2,
seats: { status: 'avaliable', count: '20/28' },
instructors: ['SSS'],
group: '4EE ONLY',
schedule: [
{
day: 'MON',
time: '16:00 - 17:00',
room: 'MAHIT 202',
type: 'LECT',
},
{
day: 'THU',
time: '16:00 - 17:00',
room: 'MAHIT 202',
type: 'LECT',
},
],
selectable: true,
},
{
section: 3,
seats: { status: 'close', count: '28/28' },
instructors: ['SSS'],
group: '4EE ONLY',
schedule: [
{
day: 'THU',
time: '16:00 - 17:00',
room: 'MAHIT 202',
type: 'LECT',
},
{
day: 'THU',
time: '16:00 - 17:00',
room: 'MAHIT 202',
type: 'LECT',
},
],
},
],
},
}}
/>
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<script lang="ts">
import CourseInfo from './CourseInfo.svelte'
import { type Course } from './index'
import SectionFilter from './SectionFilter.svelte'
import SectionTable from './SectionTable.svelte'

let {
course,
haveSection,
}: { course: Course; haveSection: { value: string; label: string }[] } =
$props()
let value2 = $state<string[]>([])
</script>

<div class="w-fit h-fit rounded-xl p-6 space-y-4">
<div class="flex justify-between">
<CourseInfo {course} />
<SectionFilter bind:selectedSections={value2} {haveSection} />
</div>
<SectionTable {course} bind:selectedSections={value2} />
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
type CourseSection = {
section: number
seats: { status: 'full' | 'avaliable' | 'close'; count: string }
instructors: string[]
group: string
schedule: { day: string; time: string; room: string; type: string }[]
selectable?: boolean
}

export type Course = {
code: string
name: string
credits: number
reviews: number
sections: CourseSection[]
}
export { default as ExpandedCourseCardDialog } from './expanded-course-card-dialog.svelte'
Loading
Loading