Compare commits

...

15 Commits
tests ... main

Author SHA1 Message Date
andrzej e66ed6975d add explainers re editing
Gitea/subman-nextjs/pipeline/head This commit looks good Details
2024-10-16 13:55:00 +02:00
andrzej 872c1fd53a describe why pubs are greyed out
Gitea/subman-nextjs/pipeline/head This commit looks good Details
2024-10-16 12:13:44 +02:00
andrzej c8c29fab75 fix build errors
Gitea/subman-nextjs/pipeline/head This commit looks good Details
2024-10-16 12:03:45 +02:00
andrzej 4459b9d644 grey out irrelevant pubs based on genre data
Gitea/subman-nextjs/pipeline/head Something is wrong with the build of this commit Details
2024-10-16 11:47:39 +02:00
andrzej ae4e1685c5 add more stories and pubs, show off pagination 2024-10-16 10:36:57 +02:00
andrzej c0f85e89fd style welcome screen
Gitea/subman-nextjs/pipeline/head This commit looks good Details
2024-10-12 19:27:03 +02:00
andrzej f939c3896a fix build errors
Gitea/subman-nextjs/pipeline/head This commit looks good Details
2024-10-12 19:19:03 +02:00
andrzej 39ba6901d1 better filter icon 2024-10-12 19:18:56 +02:00
andrzej e2b21601ea default to first *filterable* column
Gitea/subman-nextjs/pipeline/head There was a failure building this commit Details
2024-10-12 19:07:46 +02:00
andrzej 59a8f8bc41 make filter more intuitive, usable on mobile
Gitea/subman-nextjs/pipeline/head This commit looks good Details
2024-10-12 18:23:04 +02:00
andrzej 01b98a0a08 conditonal row styling
Gitea/subman-nextjs/pipeline/head This commit looks good Details
2024-10-04 22:23:24 +02:00
andrzej 3c3564bb29 only clean workspace when build fails 2024-10-04 22:23:02 +02:00
andrzej cda903175c make Jenkins install playwright browsers
Gitea/subman-nextjs/pipeline/head This commit looks good Details
2024-10-04 21:45:30 +02:00
andrzej e1044b58b7 Merge branch 'tests'
Gitea/subman-nextjs/pipeline/head There was a failure building this commit Details
2024-10-04 21:41:06 +02:00
andrzej a5c40a7982 fix: redirect after login
Gitea/subman-nextjs/pipeline/head This commit looks good Details
2024-10-04 18:47:41 +02:00
14 changed files with 123 additions and 124 deletions

4
Jenkinsfile vendored
View File

@ -11,12 +11,13 @@ agent any
sh 'echo "DATABASE_URL=${DATABASE_URL}" | cat >> .env' sh 'echo "DATABASE_URL=${DATABASE_URL}" | cat >> .env'
sh 'npm install' sh 'npm install'
sh 'npm run build' sh 'npm run build'
sh 'rm -r pack'
} }
} }
stage('test'){ stage('test'){
steps{ steps{
sh 'npx playwright install'
sh 'npx playwright test' sh 'npx playwright test'
sh 'rm -r pack'
} }
} }
stage('deploy'){ stage('deploy'){
@ -29,6 +30,7 @@ agent any
// Clean after build // Clean after build
always { always {
cleanWs(cleanWhenNotBuilt: true, cleanWs(cleanWhenNotBuilt: true,
cleanWhenFailure: false,
deleteDirs: true, deleteDirs: true,
disableDeferredWipeout: true, disableDeferredWipeout: true,
// notFailBuild: true, // notFailBuild: true,

Binary file not shown.

View File

@ -44,7 +44,7 @@ export default function LoginForm() {
return ( return (
<> <main className="flex flex-col items-center justify-around h-60 w-26">
{submitted ? <div className="flex flex-col items-center justify-around h-30 w-26"> {submitted ? <div className="flex flex-col items-center justify-around h-30 w-26">
<h1>Logging in...</h1> <h1>Logging in...</h1>
<Button asChild> <Button asChild>
@ -52,7 +52,7 @@ export default function LoginForm() {
</Button> </Button>
</div> : </div> :
<Form {...form}> <Form {...form}>
<form onSubmit={onSubmit} className="mt-20 flex flex-col items-center space-y-6"> <form onSubmit={onSubmit} className="flex flex-col items-center space-y-6">
<FormField <FormField
control={form.control} control={form.control}
name="email" name="email"
@ -83,7 +83,7 @@ export default function LoginForm() {
</form> </form>
</Form> </Form>
} }
</> </main>
) )
} }

View File

@ -3,21 +3,21 @@ import { Button } from "@/components/ui/button";
export default function Home() { export default function Home() {
return ( return (
<main className="flex flex-col items-center justify-around h-60 w-26"> <main className="flex flex-col gap-4 items-center justify-around h-60 w-26 m-6">
< div > < div >
<h1 className="text-3xl font-black"> <h1 className="text-3xl font-black">
Welcome to Subman Welcome to Subman!
</h1> </h1>
</div > </div >
<div> <div className="flex flex-col gap-3">
<p className="mb-6"> <p>
This app is for demonstration purposes only. Data is reset periodically. This app is for demonstration purposes only. Data is reset periodically.
</p> </p>
<p> <p>
USERNAME: demo@demo.demo <b>USERNAME:</b> demo@demo.demo
</p> </p>
<p> <p>
PASSWORD: password <b>PASSWORD:</b> password
</p> </p>
</div> </div>
<Button className="mt-6"> <Button className="mt-6">

View File

@ -1,12 +1,9 @@
"use client" "use client"
import { Dialog, DialogHeader, DialogTrigger, DialogContent, DialogClose, DialogTitle, DialogFooter, DialogDescription } from "@/components/ui/dialog"; import { DialogHeader, DialogTitle, DialogFooter, DialogDescription } from "@/components/ui/dialog";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { ComponentProps } from "react"; import { ComponentProps } from "react";
import { Genre, Pub } from "@prisma/client"; import { Genre, Pub } from "@prisma/client";
import { createPub } from "app/lib/create";
import PubForm from "app/ui/forms/pub"; import PubForm from "app/ui/forms/pub";
import { Plus } from "lucide-react";
import { useState } from "react";
import { PubWithGenres } from "./page"; import { PubWithGenres } from "./page";
export default function EditPubDialog({ genres, closeDialog, defaults, dbAction }: ComponentProps<"div"> & { genres: Genre[], closeDialog: () => void, defaults: PubWithGenres, dbAction: (data: Pub & { genres: number[] }) => Promise<{ success: string }> }) { export default function EditPubDialog({ genres, closeDialog, defaults, dbAction }: ComponentProps<"div"> & { genres: Genre[], closeDialog: () => void, defaults: PubWithGenres, dbAction: (data: Pub & { genres: number[] }) => Promise<{ success: string }> }) {
@ -17,7 +14,7 @@ export default function EditPubDialog({ genres, closeDialog, defaults, dbAction
<> <>
<DialogHeader> <DialogHeader>
<DialogTitle>Edit publication</DialogTitle> <DialogTitle>Edit publication</DialogTitle>
<DialogDescription>Modify an entry for an existing publication.</DialogDescription> <DialogDescription>Modify an entry for an existing publication. Remember - you can edit fields inline by double clicking on them!</DialogDescription>
</DialogHeader> </DialogHeader>
<PubForm dbAction={dbAction} genres={genres} closeDialog={closeDialog} defaults={defaults} /> <PubForm dbAction={dbAction} genres={genres} closeDialog={closeDialog} defaults={defaults} />
<DialogFooter> <DialogFooter>

View File

@ -14,7 +14,7 @@ export default function EditStoryDialog({ genres, closeDialog, defaults, dbActio
<> <>
<DialogHeader> <DialogHeader>
<DialogTitle>Edit story</DialogTitle> <DialogTitle>Edit story</DialogTitle>
<DialogDescription>Create an entry for a new story i.e. a thing you intend to submit for publication.</DialogDescription> <DialogDescription>Modify an entry for an existing story. Remember - you can edit fields inline by double-clicking on them!</DialogDescription>
</DialogHeader> </DialogHeader>
<StoryForm dbAction={dbAction} genres={genres} className="" closeDialog={closeDialog} defaults={defaults} /> <StoryForm dbAction={dbAction} genres={genres} className="" closeDialog={closeDialog} defaults={defaults} />
<DialogFooter> <DialogFooter>

View File

@ -5,7 +5,6 @@ import { Button } from "@/components/ui/button"
import { SubComplete } from "./page" import { SubComplete } from "./page"
import { selectCol } from "app/ui/tables/selectColumn" import { selectCol } from "app/ui/tables/selectColumn"
import TitleContainer from "app/ui/titleContainer" import TitleContainer from "app/ui/titleContainer"
import { CalendarArrowUp } from "lucide"

View File

@ -8,10 +8,12 @@ import { Pub, Response, Story } from "@prisma/client";
import SubmissionForm from "app/ui/forms/sub"; import SubmissionForm from "app/ui/forms/sub";
import { Plus } from "lucide-react"; import { Plus } from "lucide-react";
import { useState } from "react"; import { useState } from "react";
import { StoryWithGenres } from "app/story/page";
import { PubWithGenres } from "app/publication/page";
export default function CreateSubmissionDialog({ stories, pubs, responses }: ComponentProps<"div"> & { stories: Story[], pubs: Pub[], responses: Response[] }) { export default function CreateSubmissionDialog({ stories, pubs, responses }: ComponentProps<"div"> & { stories: StoryWithGenres[], pubs: PubWithGenres[], responses: Response[] }) {
const [isOpen, setIsOpen] = useState(false) const [isOpen, setIsOpen] = useState(false)
function closeDialog() { function closeDialog() {

View File

@ -1,22 +0,0 @@
"use server"
import { getPubs, getResponses, getStories } from "app/lib/get";
import SubmissionForm from "app/ui/forms/sub";
import prisma from "app/lib/db";
import { CreateContainer, CreateContainerContent, CreateContainerHeader } from "app/ui/createContainer";
import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";
export default async function Page() {
const stories = await getStories()
const pubs = await getPubs()
const responses = await getResponses()
return (
<CreateContainer>
<CreateContainerHeader>New submission</CreateContainerHeader>
<CreateContainerContent>
<SubmissionForm stories={stories} pubs={pubs} responses={responses} />
</CreateContainerContent>
</CreateContainer>
)
}

View File

@ -1,8 +1,10 @@
import { getGenres, getPubs, getResponses, getStories, getSubsComplete } from "app/lib/get" import { getGenres, getPubs, getPubsWithGenres, getResponses, getStories, getStoriesWithGenres, getSubsComplete } from "app/lib/get"
import { DataTable } from "app/ui/tables/data-table" import { DataTable } from "app/ui/tables/data-table"
import { columns } from "./columns" import { columns } from "./columns"
import { Pub, Response, Story, Sub } from "@prisma/client" import { Genre, Pub, Response, Story, Sub } from "@prisma/client"
import CreateSubmissionDialog from "./create" import CreateSubmissionDialog from "./create"
import { PubWithGenres } from "app/publication/page"
import { StoryWithGenres } from "app/story/page"
export type SubComplete = Sub & { export type SubComplete = Sub & {
pub: Pub, pub: Pub,
@ -12,10 +14,10 @@ export type SubComplete = Sub & {
export default async function Page() { export default async function Page() {
const subs: Array<SubComplete> = await getSubsComplete() const subs: Array<SubComplete> = await getSubsComplete()
const stories = await getStories() const stories: StoryWithGenres[] = await getStoriesWithGenres()
const pubs = await getPubs() const pubs: PubWithGenres[] = await getPubsWithGenres()
const responses = await getResponses() const responses: Response[] = await getResponses()
const genres = await getGenres() const genres: Genre[] = await getGenres()
return ( return (
<div className="container px-1 md:px-4 mx-auto"> <div className="container px-1 md:px-4 mx-auto">

View File

@ -706,6 +706,10 @@ body {
z-index: 100; z-index: 100;
} }
.m-6 {
margin: 1.5rem;
}
.m-auto { .m-auto {
margin: auto; margin: auto;
} }
@ -715,6 +719,11 @@ body {
margin-right: -0.25rem; margin-right: -0.25rem;
} }
.mx-0 {
margin-left: 0px;
margin-right: 0px;
}
.mx-1 { .mx-1 {
margin-left: 0.25rem; margin-left: 0.25rem;
margin-right: 0.25rem; margin-right: 0.25rem;
@ -765,10 +774,6 @@ body {
margin-top: 0.5rem; margin-top: 0.5rem;
} }
.mt-20 {
margin-top: 5rem;
}
.mt-3 { .mt-3 {
margin-top: 0.75rem; margin-top: 0.75rem;
} }
@ -781,10 +786,6 @@ body {
margin-top: 1.5rem; margin-top: 1.5rem;
} }
.mb-6 {
margin-bottom: 1.5rem;
}
.block { .block {
display: block; display: block;
} }
@ -854,6 +855,10 @@ body {
height: 1.25rem; height: 1.25rem;
} }
.h-60 {
height: 15rem;
}
.h-7 { .h-7 {
height: 1.75rem; height: 1.75rem;
} }
@ -887,10 +892,6 @@ body {
height: 100vh; height: 100vh;
} }
.h-60 {
height: 15rem;
}
.max-h-96 { .max-h-96 {
max-height: 24rem; max-height: 24rem;
} }
@ -976,14 +977,6 @@ body {
width: 100vw; width: 100vw;
} }
.w-14 {
width: 3.5rem;
}
.w-20 {
width: 5rem;
}
.min-w-\[8rem\] { .min-w-\[8rem\] {
min-width: 8rem; min-width: 8rem;
} }
@ -1141,10 +1134,6 @@ body {
justify-content: space-around; justify-content: space-around;
} }
.justify-items-center {
justify-items: center;
}
.gap-1 { .gap-1 {
gap: 0.25rem; gap: 0.25rem;
} }
@ -1153,6 +1142,10 @@ body {
gap: 0.5rem; gap: 0.5rem;
} }
.gap-3 {
gap: 0.75rem;
}
.gap-4 { .gap-4 {
gap: 1rem; gap: 1rem;
} }
@ -1632,10 +1625,6 @@ body {
color: rgb(255 255 255 / var(--tw-text-opacity)); color: rgb(255 255 255 / var(--tw-text-opacity));
} }
.underline {
text-decoration-line: underline;
}
.underline-offset-4 { .underline-offset-4 {
text-underline-offset: 4px; text-underline-offset: 4px;
} }

View File

@ -36,12 +36,14 @@ import { createSub } from "app/lib/create"
import { subSchema } from "./schemas" import { subSchema } from "./schemas"
import { useRouter } from "next/navigation" import { useRouter } from "next/navigation"
import { Ban } from "lucide-react" import { Ban } from "lucide-react"
import { Story } from "@prisma/client" import { Pub, Response, Story, Sub } from "@prisma/client"
import { StoryWithGenres } from "app/story/page"
import { PubWithGenres } from "app/publication/page"
export type SubForm = z.infer<typeof subSchema> export type SubForm = z.infer<typeof subSchema>
export default function SubmissionForm({ stories, pubs, responses, defaults, closeDialog }: { stories: any, pubs: any, responses: any, defaults?: any, closeDialog?: () => void }) { export default function SubmissionForm({ stories, pubs, responses, defaults, closeDialog }: { stories: StoryWithGenres[], pubs: PubWithGenres[], responses: Response[], defaults?: Sub, closeDialog?: () => void }) {
const form = useForm<z.infer<typeof subSchema>>({ const form = useForm<z.infer<typeof subSchema>>({
resolver: zodResolver(subSchema), resolver: zodResolver(subSchema),
defaultValues: { defaultValues: {
@ -51,19 +53,41 @@ export default function SubmissionForm({ stories, pubs, responses, defaults, clo
}) })
const [isSubCalendarOpen, setIsSubCalendarOpen] = useState(false); const [isSubCalendarOpen, setIsSubCalendarOpen] = useState(false);
const [isRespCalendarOpen, setIsRespCalendarOpen] = useState(false); const [isRespCalendarOpen, setIsRespCalendarOpen] = useState(false);
const [relevantPubIds, setRelevantPubIds] = useState(pubs.map(e => e.id));
function updateRelevantPubs(storyId: number) {
console.log("storyId: " + storyId)
console.log("stories: ", stories)
const story = stories.find(e => e.id == storyId)
console.log("story: ", story)
const storyGenreIds = story?.genres.map(e => e.id) ?? []
const relevantPubIds = pubs.filter(e => {
const pubGenreIds = e.genres.map(e => e.id)
for (let i = 0; i < storyGenreIds.length; i++) {
const storyGenreId = storyGenreIds[i];
if (pubGenreIds.includes(storyGenreId)) return true
}
}).map(e => e.id)
console.log("relevant pubs: ", relevantPubIds)
setRelevantPubIds(relevantPubIds)
}
const storiesSelectItems = stories.map((e: Story) => ( const storiesSelectItems = stories.map((e: Story) => (
<SelectItem value={e.id?.toString()} key={e.title}> <SelectItem value={e.id?.toString()} key={e.title}>
{e.title} {e.title}
</SelectItem> </SelectItem>
)) ))
const pubsSelectItems = pubs.map(e => ( const pubsSelectItems = pubs.map(e => {
<SelectItem value={e.id} key={e.title}> const isDisabled = !relevantPubIds.includes(e.id)
{e.title} return (
</SelectItem> <SelectItem disabled={isDisabled} value={e.id.toString()} key={e.title}>
)) {e.title}
</SelectItem>
)
})
const reponsesSelectItems = responses.map(e => ( const reponsesSelectItems = responses.map(e => (
<SelectItem value={e.id} key={e.title}> <SelectItem value={e.id.toString()} key={e.response}>
{e.response} {e.response}
</SelectItem> </SelectItem>
)) ))
@ -127,7 +151,8 @@ export default function SubmissionForm({ stories, pubs, responses, defaults, clo
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel className="text-sm md:text-base">Publication</FormLabel> <FormLabel className="text-sm md:text-base">Publication</FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value?.toString()}> <Select onOpenChange={() => updateRelevantPubs(form.getValues().storyId
)} onValueChange={field.onChange} defaultValue={field.value?.toString()}>
<FormControl> <FormControl>
<SelectTrigger> <SelectTrigger>
<SelectValue placeholder="Select something"> <SelectValue placeholder="Select something">
@ -139,7 +164,7 @@ export default function SubmissionForm({ stories, pubs, responses, defaults, clo
{pubsSelectItems} {pubsSelectItems}
</SelectContent> </SelectContent>
</Select> </Select>
<FormDescription className="text-xs md:text-base">The market you sent it to</FormDescription> <FormDescription className="text-xs md:text-base">The market you sent it to. Bad genre fits are greyed out.</FormDescription>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
)} )}

View File

@ -2,8 +2,6 @@
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import { import {
ContextMenu, ContextMenu,
ContextMenuContent,
ContextMenuItem,
ContextMenuTrigger, ContextMenuTrigger,
} from "@/components/ui/context-menu" } from "@/components/ui/context-menu"
import { import {
@ -15,7 +13,7 @@ import {
DropdownMenuRadioGroup DropdownMenuRadioGroup
} from "@/components/ui/dropdown-menu" } from "@/components/ui/dropdown-menu"
import { Input } from "@/components/ui/input" import { Input } from "@/components/ui/input"
import { Component, ComponentProps, use, useState } from "react" import { ComponentProps, useState } from "react"
import { import {
ColumnDef, ColumnDef,
flexRender, flexRender,
@ -38,7 +36,7 @@ import {
TableHeader, TableHeader,
TableRow, TableRow,
} from "@/components/ui/table" } from "@/components/ui/table"
import { EyeIcon, Trash2 } from "lucide-react" import { CircleHelp, EyeIcon, Filter, Trash2 } from "lucide-react"
import { usePathname, useSearchParams } from "next/navigation" import { usePathname, useSearchParams } from "next/navigation"
import FormContextMenu from "./contextMenu" import FormContextMenu from "./contextMenu"
import { deleteRecord, deleteRecords } from "app/lib/del" import { deleteRecord, deleteRecords } from "app/lib/del"
@ -129,16 +127,14 @@ export function DataTable<TData, TValue>({
const router = useRouter() const router = useRouter()
const [filterBy, setFilterBy] = useState(table.getAllColumns()[0]) const [filterBy, setFilterBy] = useState(table.getAllColumns().filter(e => e.getCanFilter())[0])
const [isContextMenuOpen, setIsContextMenuOpen] = useState(false) const [isContextMenuOpen, setIsContextMenuOpen] = useState(false)
return (<> return (<>
<div className="flex justify-between items-center py-1 md:py-4"> <div className="flex gap-2 justify-between items-center py-1 md:py-4">
<div className="flex gap-2"> <div className="flex gap-1">
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button variant="outline" className="hidden sm:block ml-auto"> <Button variant="outline" className="mx-0"> <p className="hidden md:block">Filter by</p><Filter className="block md:hidden" /> </Button>
Filter by
</Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end"> <DropdownMenuContent align="end">
{/*@ts-ignore*/} {/*@ts-ignore*/}
@ -308,36 +304,45 @@ export function DataTable<TData, TValue>({
</TableHeader> </TableHeader>
<TableBody> <TableBody>
{table.getRowModel().rows?.length ? ( {table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => ( table.getRowModel().rows.map((row) => {
<ContextMenu key={row.id + "contextMenu"}> const classes = () => {
<ContextMenuTrigger asChild> const classes = []
<TableRow if (row.getValue('response') === "Pending") classes.push("bg-accent")
key={row.id} if (row.getValue('response') === "Acceptance") classes.push("bg-primary")
data-state={row.getIsSelected() && "selected"} return classes.join(" ")
tabIndex={0} }
onDoubleClick={() => { return (
if (tableName === "sub") { <ContextMenu key={row.id + "contextMenu"}>
openEditDialog(row) <ContextMenuTrigger asChild>
<TableRow
key={row.id}
className={classes()}
data-state={row.getIsSelected() && "selected"}
tabIndex={0}
onDoubleClick={() => {
if (tableName === "sub") {
openEditDialog(row)
}
} }
} }
} >
> {row.getVisibleCells().map((cell) => (
{row.getVisibleCells().map((cell) => ( <TableCell key={cell.id}>
<TableCell key={cell.id}> {flexRender(cell.column.columnDef.cell, cell.getContext())}
{flexRender(cell.column.columnDef.cell, cell.getContext())} </TableCell>
</TableCell> ))}
))} <FormContextMenu
<FormContextMenu key={"formContextMenu" + row.id}
key={"formContextMenu" + row.id} row={row}
row={row} table={table}
table={table} openEditDialog={openEditDialog}
openEditDialog={openEditDialog} openDeleteDialog={openDeleteDialog}
openDeleteDialog={openDeleteDialog} />
/> </TableRow>
</TableRow> </ContextMenuTrigger>
</ContextMenuTrigger> </ContextMenu>
</ContextMenu> )
)) })
) : ( ) : (
<TableRow> <TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center"> <TableCell colSpan={columns.length} className="h-24 text-center">

View File

@ -16,7 +16,7 @@ export const selectCol = {
) )
}, },
enableColumnFilter: false,
cell: (props: CellContext<any, any>) => { cell: (props: CellContext<any, any>) => {
return ( return (
<div className="flex items-start justify-left"> <div className="flex items-start justify-left">