Stubbing out meal plan page, created meal component

This commit is contained in:
Z. Charles Dziura 2025-06-09 11:49:14 -04:00
parent 0ff95a4e16
commit 63368aec0b
20 changed files with 9235 additions and 8975 deletions

145
.gitignore vendored
View file

@ -220,3 +220,148 @@ e2e/*.map
.DS_Store/
# End of https://www.toptal.com/developers/gitignore/api/angular
# Created by https://www.toptal.com/developers/gitignore/api/node
# Edit at https://www.toptal.com/developers/gitignore?templates=node
### Node ###
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
### Node Patch ###
# Serverless Webpack directories
.webpack/
# Optional stylelint cache
# SvelteKit build / generate output
.svelte-kit
# End of https://www.toptal.com/developers/gitignore/api/node

3
webapp/.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,3 @@
{
"typescript.tsdk": "node_modules/typescript/lib"
}

View file

@ -15,14 +15,15 @@
"@angular/platform-browser": "^20.0.0",
"@angular/router": "^20.0.0",
"rxjs": "~7.8.0",
"tslib": "^2.3.0"
"tslib": "^2.3.0",
"uuid": "^11.1.0"
},
"devDependencies": {
"@angular/build": "^20.0.1",
"@angular/cli": "^20.0.1",
"@angular/compiler-cli": "^20.0.0",
"@types/jasmine": "~5.1.0",
"jasmine-core": "~5.7.0",
"jasmine-core": "~5.8.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.2.0",
"karma-coverage": "~2.2.0",
@ -5088,9 +5089,9 @@
}
},
"node_modules/htmlparser2/node_modules/entities": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-6.0.0.tgz",
"integrity": "sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==",
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
"integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
@ -5527,9 +5528,9 @@
}
},
"node_modules/jasmine-core": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.7.1.tgz",
"integrity": "sha512-QnurrtpKsPoixxG2R3d1xP0St/2kcX5oTZyDyQJMY+Vzi/HUlu1kGm+2V8Tz+9lV991leB1l0xcsyz40s9xOOw==",
"version": "5.8.0",
"resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.8.0.tgz",
"integrity": "sha512-Q9dqmpUAfptwyueW3+HqBOkSuYd9I/clZSSfN97wXE/Nr2ROFNCwIBEC1F6kb3QXS9Fcz0LjFYSDQT+BiwjuhA==",
"dev": true,
"license": "MIT"
},
@ -7056,9 +7057,9 @@
}
},
"node_modules/parse5-html-rewriting-stream/node_modules/entities": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-6.0.0.tgz",
"integrity": "sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==",
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
"integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
@ -7082,9 +7083,9 @@
}
},
"node_modules/parse5/node_modules/entities": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-6.0.0.tgz",
"integrity": "sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==",
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
"integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
@ -8440,6 +8441,19 @@
"node": ">= 0.4.0"
}
},
"node_modules/uuid": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz",
"integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==",
"funding": [
"https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan"
],
"license": "MIT",
"bin": {
"uuid": "dist/esm/bin/uuid"
}
},
"node_modules/validate-npm-package-license": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",

View file

@ -17,14 +17,15 @@
"@angular/platform-browser": "^20.0.0",
"@angular/router": "^20.0.0",
"rxjs": "~7.8.0",
"tslib": "^2.3.0"
"tslib": "^2.3.0",
"uuid": "^11.1.0"
},
"devDependencies": {
"@angular/build": "^20.0.1",
"@angular/cli": "^20.0.1",
"@angular/compiler-cli": "^20.0.0",
"@types/jasmine": "~5.1.0",
"jasmine-core": "~5.7.0",
"jasmine-core": "~5.8.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.2.0",
"karma-coverage": "~2.2.0",

View file

@ -1,8 +1,14 @@
import { Routes } from '@angular/router';
import { Plan } from './routes/plan/plan';
export const routes: Routes = [
{
path: '',
loadComponent: () => import('./routes/home/home').then(c => c.Home)
}
pathMatch: 'full',
redirectTo: '/plan'
},
{
path: 'plan',
component: Plan
},
];

View file

@ -8,6 +8,4 @@ import { NavRail } from './components/nav-rail/nav-rail';
styleUrl: './app.scss',
templateUrl: './app.html',
})
export class App {
protected title = 'webapp';
}
export class App {}

View file

@ -9,7 +9,7 @@ nav {
display: flex;
flex-direction: column;
list-style: none;
padding: 1.2em;
padding: 1.8em 1.2em;
.nav-list__item {
align-items: center;
@ -18,7 +18,7 @@ nav {
inline-size: min-content;
&:not(:last-of-type) {
margin-bottom: 1.2em;
margin-bottom: 1.8em;
}
.nav-list__item-label {

View file

@ -0,0 +1,15 @@
export class TMeal {
constructor(readonly id: string, readonly type: MealType, readonly foods: Array<IFood>, readonly name?: string) {}
get totalCalories(): number {
return this.foods.map(food => food.calories).reduce((a, b) => a + b);
}
}
export type MealType = 'breakfast' | 'lunch' | 'dinner' | 'snack';
export interface IFood {
name: string;
servings: number;
calories: number;
}

View file

@ -1 +0,0 @@
<p>home works!</p>

View file

@ -1,14 +0,0 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-home',
imports: [],
templateUrl: './home.html',
styleUrl: './home.scss',
})
export class Home implements OnInit {
constructor() {}
ngOnInit(): void {
}
}

View file

@ -0,0 +1,3 @@
<header>
{{ name }}
</header>

View file

@ -0,0 +1,8 @@
:host {
display: grid;
grid-template-areas:
"head"
"body"
"foot";
grid-template-rows: min-content auto min-content;
}

View file

@ -1,18 +1,18 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { Home } from './home';
import { Meal } from './meal';
describe('Home', () => {
let component: Home;
let fixture: ComponentFixture<Home>;
describe('Meal', () => {
let component: Meal;
let fixture: ComponentFixture<Meal>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [Home]
imports: [Meal]
})
.compileComponents();
fixture = TestBed.createComponent(Home);
fixture = TestBed.createComponent(Meal);
component = fixture.componentInstance;
fixture.detectChanges();
});

View file

@ -0,0 +1,19 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { MealType } from 'models/meal.model';
@Component({
selector: 'app-meal',
imports: [],
templateUrl: './meal.html',
styleUrl: './meal.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class Meal {
@Input() type: MealType | undefined;
@Input('name') _name?: string;
get name(): string {
const name = this._name ?? (this.type as string);
return name.charAt(0).toLocaleUpperCase() + name.slice(1);
}
}

View file

@ -0,0 +1,6 @@
<header>Header!</header>
<section class="meal-plan">
@for (meal of meals(); track meal.id) {
<app-meal [type]="meal.type" [name]="meal.name"></app-meal>
}
</section>

View file

@ -0,0 +1,16 @@
:host {
display: grid;
grid-template-areas:
"head"
"body";
grid-template-rows: min-content auto;
height: 100%;
}
header {
grid-area: 'head';
}
.meal-plan {
grid-area: "body";
}

View file

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { Plan } from './plan';
describe('Plan', () => {
let component: Plan;
let fixture: ComponentFixture<Plan>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [Plan]
})
.compileComponents();
fixture = TestBed.createComponent(Plan);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View file

@ -0,0 +1,15 @@
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { v7 as uuidv7 } from 'uuid';
import { TMeal } from '../../models/meal.model';
import { Meal } from './components/meal/meal';
@Component({
selector: 'app-plan',
imports: [Meal],
templateUrl: './plan.html',
styleUrl: './plan.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class Plan {
protected meals = signal<Array<TMeal>>([new TMeal(uuidv7(), 'breakfast', [])]);
}

View file

@ -13,7 +13,10 @@
"experimentalDecorators": true,
"importHelpers": true,
"target": "ES2022",
"module": "preserve"
"module": "preserve",
"paths": {
"models/*": ["./src/app/models/*"]
},
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,