Skip to content
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
2 changes: 1 addition & 1 deletion apps/frontend/src/api/apiClient.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import axios, { type AxiosInstance } from 'axios';
import { Anthology } from '../types';
import { Anthology } from 'types/anthology';

const defaultBaseUrl =
import.meta.env.VITE_API_BASE_URL ?? 'http://localhost:3000';
Expand Down
15 changes: 10 additions & 5 deletions apps/frontend/src/containers/archived-publications/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import {
STATIC_ARCHIVED,
RECENTLY_EDITED,
MOCK_LAST_MODIFIED,
Anthology,
} from '@utils/mock-data';
import { Anthology, AnthologyStatus } from '../../types/anthology';

// Import SVG icons
import DocumentIcon from '../../assets/icons/document.svg';
Expand All @@ -25,7 +25,7 @@ export default function ArchivedPublications() {
.then((res) => res.json())
.then((data) => {
const archivedOnly = (data as Anthology[]).filter(
(item) => item.status === 'archived',
(item) => item.status === AnthologyStatus.ARCHIVED,
);
if (archivedOnly.length > 0) {
setArchived(archivedOnly);
Expand All @@ -39,8 +39,10 @@ export default function ArchivedPublications() {
const filteredPublications = archived.filter((pub) => {
const query = searchQuery.toLowerCase();
const titleMatch = pub.title.toLowerCase().includes(query);
const authors = pub.authors || [];
const authorMatch = authors.some((a) => a.toLowerCase().includes(query));
const authors = pub.getAuthors() || [];
const authorMatch = authors.some((a) =>
a.name.toLowerCase().includes(query),
);
return titleMatch || authorMatch;
});

Expand Down Expand Up @@ -152,7 +154,10 @@ export default function ArchivedPublications() {
<div className="publication-card-details">
<h3 className="publication-card-title">{pub.title}</h3>
<p className="publication-card-author">
{pub.authors?.join(', ') || 'Author Name'}
{pub
.getAuthors()
.map((a) => a.name)
.join(', ') || 'Author Name'}
</p>
<div className="publication-card-meta">
<span className="publication-card-modified">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import React, { useState, useEffect } from 'react';
import { useParams } from 'react-router-dom';
import apiClient from '../../../api/apiClient';
import { STATIC_ARCHIVED } from '@utils/mock-data';
import { Anthology, AnthologyStatus, AnthologyPubLevel } from '../../../types';
import {
Anthology,
AnthologyStatus,
AnthologyPubLevel,
} from '../../../types/anthology';
import './publication-view.css';

import imgFrame69 from '../../../assets/images/frame-69.png';
Expand Down Expand Up @@ -159,42 +163,44 @@ const assets = [
{ name: 'name_of_file', type: 'PDF', size: '6.2 MB' },
];

const mockAnthology: Anthology = {
id: 0,
title: 'Untitled Publication (Mock)',
subtitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit',
byline: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit',
description:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.',
published_year: 2025,
programs: 'YLAB',
inventory: 79,
status: AnthologyStatus.CAN_BE_SHARED,
pub_level: AnthologyPubLevel.PERFECT_BOUND,
photo_url: undefined,
genre: 'Fantasy, Science Fiction, Mystery',
theme: 'Short Stories, Creative Writing',
isbn: '979-8-88694-087-9',
shopify_url: 'https://example.com',
praise_quotes:
'"Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur."',
foreword_author: 'Agnes Ugoji',
age_category: '9-12',
dimensions: '7" x 7"',
binding_type: 'Perfect Bound',
page_count: 132,
print_run: 500,
printed_by: 'Marquis',
number_of_students: 53,
printing_cost: '$2,906.40',
weight: '6.2 oz / 176 g',
inventory_locations: {
'Devs/Comms Office (1865 Columbus)': 79,
'The Hub (1989 Columbus)': 400,
'Tutoring Center (3035 Office)': 0,
Archived: 3,
},
};
const mockAnthology: Anthology = new Anthology(
/* id: */ 0,
/* title: */ 'Untitled Publication (Mock)',
/* published_year: */ 2025,
/* status: */ AnthologyStatus.CAN_BE_SHARED,
/* stories: */ [],
/* subtitle: */ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit',
/* byline: */ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit',
/* description: */
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.',
/* updated_at: */ 'Jan 2, 1960',
/* photo_url: */ undefined,
/* foreword_author: */ 'Agnes Ugoji',
/* praise_quotes: */
'"Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur."',
/* age_category: */ '9-12',
/* isbn: */ '979-8-88694-087-9',
/* shopify_url: */ 'https://example.com',
/* binding_type: */ 'Perfect Bound',
/* dimensions: */ '7" x 7"',
/* printing_cost: */ '$2,906.40',
/* print_run: */ 500,
/* weight: */ '6.2 oz / 176 g',
/* page_count: */ 132,
/* printed_by: */ 'Marquis',
/* pub_level: */ AnthologyPubLevel.PERFECT_BOUND,
/* publishing_permission:*/ 'All',
/* programs: */ ['YLAB'],
/*sponsors:*/ ['Richard K. Lubin Family Foundation'],
/* number_of_students: */ 53,
/* total_inventory: */ 79,
/* inventory_locations: */ [
{ id: 1, name: 'Devs/Comms Office (1865 Columbus)', num_copies: 79 },
{ id: 2, name: 'The Hub (1989 Columbus)', num_copies: 400 },
{ id: 3, name: 'Tutoring Center (3035 Office)', num_copies: 0 },
{ id: 4, name: 'Archived', num_copies: 3 },
],
);

const PublicationView: React.FC = () => {
const { id } = useParams<{ id: string }>();
Expand Down Expand Up @@ -271,14 +277,18 @@ const PublicationView: React.FC = () => {
}
};

const genreTags = parseTags(anthology.genre).map((g) => ({
label: g,
className: getTagClass(g),
}));
const themeTags = parseTags(anthology.theme).map((t) => ({
label: t,
className: 'tag-neutral',
}));
const genreTags = parseTags(anthology.getGenres().map((g) => g.name)).map(
(g) => ({
label: g,
className: getTagClass(g),
}),
);
const themeTags = parseTags(anthology.getThemes().map((t) => t.name)).map(
(t) => ({
label: t,
className: 'tag-neutral',
}),
);
const programValue = Array.isArray(anthology.programs)
? anthology.programs.join(', ')
: anthology.programs || 'Empty';
Expand All @@ -293,8 +303,12 @@ const PublicationView: React.FC = () => {
value: anthology.byline || 'Empty',
},
{
label: 'Theme',
value: anthology.theme || 'Empty',
label: anthology.getThemes().length === 1 ? 'Theme' : 'Themes',
value:
anthology
.getThemes()
.map((t) => t.name)
.join(', ') || 'Empty',
},
{
label: 'Praise/Pull Quotes',
Expand Down Expand Up @@ -325,13 +339,18 @@ const PublicationView: React.FC = () => {
];

const inventoryItems = [
{ label: 'Total Inventory', value: anthology.inventory?.toString() || '0' },
...Object.entries(anthology.inventory_locations || {}).map(
([location, count]) => ({
label: location,
value: count.toString(),
}),
),
{
label: 'Total Inventory',
value:
anthology.inventory_locations
?.map((i) => i.num_copies)
.reduce((total, current) => total + current, 0)
.toString() || '0',
},
...(anthology.inventory_locations ?? []).map((location) => ({
label: location.name,
value: (location.num_copies ?? 0).toString(),
})),
];

return (
Expand Down Expand Up @@ -360,7 +379,12 @@ const PublicationView: React.FC = () => {
<div className="publication-info">
<div className="publication-title-section">
<h1 className="publication-title">{anthology.title}</h1>
<p className="publication-author">Author Name(s)</p>
<p className="publication-author">
{anthology
.getAuthors()
.map((a) => a.name)
.join(', ') || 'Author Name'}
</p>
<div className="publication-description">
<p className="description-text">
{isExpanded
Expand Down
46 changes: 0 additions & 46 deletions apps/frontend/src/types.ts

This file was deleted.

90 changes: 90 additions & 0 deletions apps/frontend/src/types/anthology.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { Author } from './author';
import { Genre } from './genre';
import { InventoryLocation } from './inventory-location';
import { Theme } from './theme';
import { Story } from './story';

export enum AnthologyStatus {
ARCHIVED = 'Archived',
NOT_STARTED = 'NotStarted',
DRAFTING = 'Drafting',
CAN_BE_SHARED = 'CanBeShared',
}

export enum AnthologyPubLevel {
ZINE = 'Zine',
CHAPBOOK = 'Chapbook',
PERFECT_BOUND = 'PerfectBound',
SIGNATURE = 'Signature',
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

After the client meeting we made some changes to the anthology backend entity so that general book info and additional metadata such as production info are stored separately. For Anthology, the only fields needed are id, title, description, published_year, programs, status, pub_level, photo_url, isbn, and shopify_url. For the other production related information, I would create an additional interface called ProductionInfo that stores id, anthology_id, design_files_link, cover_image_file_link, binding_type, dimensions, printing_cost, print_run, weight_in_grams, page_count, and printed_by. You can see more details on the updated backend entities in our ticket notes for this week!

export class Anthology {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I would change Anthology to an interface and make the methods free-standing getters since a publication will need be edited in the tracker view!

constructor(
public id: number,
public title: string,
public published_year: number,
public status: AnthologyStatus,
public stories?: Story[],
public subtitle?: string,
public byline?: string,
public description?: string,
public updated_at?: string,
public photo_url?: string,

// Additional fields from metadata
public foreword_author?: string,
public praise_quotes?: string,
public age_category?: string,
public isbn?: string,
public shopify_url?: string,
public binding_type?: string,
public dimensions?: string,
public printing_cost?: string,
public print_run?: number,
public weight?: string,
public page_count?: number,
public printed_by?: string,
public pub_level?: AnthologyPubLevel,
public publishing_permission?: string,
public programs?: string[],
public sponsors?: string[],
public number_of_students?: number,
public total_inventory?: number,
public inventory_locations?: InventoryLocation[],

// cached aggregated data from stories
private _authors?: Author[],
private _genres?: Genre[],
private _themes?: Theme[],
) {}

getAuthors(): Author[] {
if (this._authors !== undefined) {
return this._authors;
}

const authors = this.stories?.flatMap((story) => story.authors) ?? [];
this._authors = authors;
return authors;
}

getGenres(): Genre[] {
if (this._genres !== undefined) {
return this._genres;
}

const genres = this.stories?.flatMap((story) => story.genres ?? []) ?? [];
this._genres = genres;
return genres;
}

getThemes(): Theme[] {
if (this._themes !== undefined) {
return this._themes;
}

const themes = this.stories?.flatMap((story) => story.themes ?? []) ?? [];
this._themes = themes;
return themes;
}
}
6 changes: 6 additions & 0 deletions apps/frontend/src/types/author.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface Author {
id: number;
name: string;
bio?: string;
grade?: number;
}
4 changes: 4 additions & 0 deletions apps/frontend/src/types/genre.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface Genre {
id: number;
name: string;
}
5 changes: 5 additions & 0 deletions apps/frontend/src/types/inventory-location.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface InventoryLocation {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We now have an inventory-holding join table in the backend that has an id, inventory_id, anthology_id, and num_copies. However since we only have to display this info on the frontend it might be convenient to just store id, inventory_name, anthology_name, and num_copies in InventoryLocation.

id: number;
name: string;
num_copies: number;
}
Loading