initial commit

This commit is contained in:
2026-09-17 22:11:28 -04:00
commit ff7aa065e3
19 changed files with 13309 additions and 0 deletions
+140
View File
@@ -0,0 +1,140 @@
### Node ###
# Logs
build/
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
+18
View File
@@ -0,0 +1,18 @@
MIT License
Copyright (c) 2026 CarGDev
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial
portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE.
+1
View File
@@ -0,0 +1 @@
### GUI
+46
View File
@@ -0,0 +1,46 @@
{
"name": "manager_employees_gui",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/dom": "^10.4.2",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.3",
"@testing-library/user-event": "^13.5.0",
"react": "^19.3.0",
"react-dom": "^19.3.0",
"react-router": "^8.4.0",
"react-scripts": "5.0.1",
"sass": "^1.104.1",
"web-vitals": "^2.1.4",
"zustand": "^5.0.15"
},
"scripts": {
"dev": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"identity-obj-proxy": "^3.0.0",
"typescript": "4.9.5"
}
}
+12394
View File
File diff suppressed because it is too large Load Diff
+4
View File
@@ -0,0 +1,4 @@
allowBuilds:
'@parcel/watcher': false
core-js: true
core-js-pure: true
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

+43
View File
@@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

+25
View File
@@ -0,0 +1,25 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
+3
View File
@@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:
+274
View File
@@ -0,0 +1,274 @@
import { useEffect, useState } from 'react';
import styles from './App.module.scss';
const navItems = [
{ label: 'Home', path: '/' },
{ label: 'Users', path: '/users' },
{ label: 'Schedule', path: '/schedule' },
{ label: 'Companies', path: '/companies' },
];
const users = [
{
name: 'Maya Chen',
company: 'Northwind Logistics',
working: true,
checkIn: '08:00 AM',
checkOut: '04:00 PM',
role: 'Shift Lead',
},
{
name: 'Carlos Rivera',
company: 'BluePeak Services',
working: true,
checkIn: '09:00 AM',
checkOut: '05:00 PM',
role: 'Technician',
},
{
name: 'Nora Patel',
company: 'Greenfield Foods',
working: false,
checkIn: '--',
checkOut: '--',
role: 'Coordinator',
},
{
name: 'Ethan Brooks',
company: 'Northwind Logistics',
working: true,
checkIn: '07:30 AM',
checkOut: '03:30 PM',
role: 'Warehouse Associate',
},
];
const companies = [
{
name: 'Northwind Logistics',
users: ['Maya Chen', 'Ethan Brooks'],
note: 'Morning and fulfillment teams',
},
{
name: 'BluePeak Services',
users: ['Carlos Rivera'],
note: 'Field support coverage',
},
{
name: 'Greenfield Foods',
users: ['Nora Patel'],
note: 'Operations coordination',
},
];
const weekSchedule = [
{ day: 'Sunday', shifts: [{ name: 'Maya Chen', time: '8 AM - 4 PM' }] },
{ day: 'Monday', shifts: [{ name: 'Carlos Rivera', time: '9 AM - 5 PM' }, { name: 'Ethan Brooks', time: '7:30 AM - 3:30 PM' }] },
{ day: 'Tuesday', shifts: [{ name: 'Nora Patel', time: '10 AM - 6 PM' }] },
{ day: 'Wednesday', shifts: [{ name: 'Maya Chen', time: '8 AM - 4 PM' }, { name: 'Carlos Rivera', time: '9 AM - 5 PM' }] },
{ day: 'Thursday', shifts: [{ name: 'Ethan Brooks', time: '7:30 AM - 3:30 PM' }] },
{ day: 'Friday', shifts: [{ name: 'Nora Patel', time: '10 AM - 6 PM' }, { name: 'Maya Chen', time: '8 AM - 4 PM' }] },
{ day: 'Saturday', shifts: [{ name: 'Carlos Rivera', time: '9 AM - 1 PM' }] },
];
function usePathname() {
const [pathname, setPathname] = useState(window.location.pathname);
useEffect(() => {
const handlePopState = () => setPathname(window.location.pathname);
window.addEventListener('popstate', handlePopState);
return () => window.removeEventListener('popstate', handlePopState);
}, []);
const navigate = (path) => {
window.history.pushState(null, '', path);
setPathname(path);
};
return [pathname, navigate];
}
function PageHeader({ title, description, actions }) {
return (
<header className={styles.pageHeader}>
<div>
<h2>{title}</h2>
<p>{description}</p>
</div>
{actions ? <div className={styles.buttonGroup}>{actions}</div> : null}
</header>
);
}
function HomePage() {
const workingToday = users.filter((user) => user.working).length;
return (
<>
<PageHeader
title="Employee Manager"
description="Track employee attendance, company teams, and weekly schedules from one dashboard."
/>
<section className={styles.statsGrid} aria-label="Dashboard overview">
<article className={styles.card}>
<span>Total users</span>
<strong>{users.length}</strong>
</article>
<article className={styles.card}>
<span>Working today</span>
<strong>{workingToday}</strong>
</article>
<article className={styles.card}>
<span>Companies</span>
<strong>{companies.length}</strong>
</article>
</section>
</>
);
}
function UsersPage() {
return (
<>
<PageHeader
title="Users"
description="Review all employees, assigned companies, active work status, check-in times, and check-out times."
actions={(
<>
<button className={styles.primaryButton}>Add user</button>
<button className={styles.secondaryButton}>Update preference</button>
</>
)}
/>
<section className={styles.tableCard} aria-label="Users table">
<div className={styles.tableScroll}>
<table className={styles.table}>
<thead>
<tr>
<th>Name</th>
<th>Company</th>
<th>Role</th>
<th>Working</th>
<th>Check in</th>
<th>Check out</th>
</tr>
</thead>
<tbody>
{users.map((user) => (
<tr key={user.name}>
<td>{user.name}</td>
<td>{user.company}</td>
<td>{user.role}</td>
<td>
<span className={`${styles.status} ${user.working ? '' : styles.statusOff}`}>
{user.working ? 'Working' : 'Off duty'}
</span>
</td>
<td>{user.checkIn}</td>
<td>{user.checkOut}</td>
</tr>
))}
</tbody>
</table>
</div>
</section>
</>
);
}
function CompaniesPage() {
return (
<>
<PageHeader
title="Companies"
description="Create companies, view company names, and see the users assigned to each company."
actions={<button className={styles.primaryButton}>Add company</button>}
/>
<section className={styles.companyGrid} aria-label="Company list">
{companies.map((company) => (
<article className={styles.companyCard} key={company.name}>
<div>
<h3>{company.name}</h3>
<p>{company.note}</p>
</div>
<div className={styles.employeeList} aria-label={`${company.name} users`}>
{company.users.map((user) => (
<span className={styles.pill} key={user}>{user}</span>
))}
</div>
<button className={styles.secondaryButton}>Add users</button>
</article>
))}
</section>
</>
);
}
function SchedulePage() {
return (
<>
<PageHeader
title="Schedule"
description="Weekly scheduler calendar starts on Sunday and ends on Saturday so you can see who is working each day."
/>
<section className={styles.calendar} aria-label="Weekly schedule from Sunday to Saturday">
{weekSchedule.map((day) => (
<article className={styles.dayCard} key={day.day}>
<h3>{day.day}</h3>
<div className={styles.shiftList}>
{day.shifts.map((shift) => (
<div className={styles.shift} key={`${day.day}-${shift.name}`}>
<strong>{shift.name}</strong>
<span>{shift.time}</span>
</div>
))}
</div>
</article>
))}
</section>
</>
);
}
function App() {
const [pathname, navigate] = usePathname();
const renderPage = () => {
switch (pathname) {
case '/users':
return <UsersPage />;
case '/schedule':
return <SchedulePage />;
case '/companies':
return <CompaniesPage />;
default:
return <HomePage />;
}
};
return (
<div className={styles.shell}>
<aside className={styles.sidebar}>
<div className={styles.brand}>
<h1>Manager GUI</h1>
<p>Employees, companies, and schedules</p>
</div>
<nav className={styles.nav} aria-label="Sidebar navigation">
{navItems.map((item) => (
<button
className={`${styles.navItem} ${pathname === item.path ? styles.active : ''}`}
key={item.path}
onClick={() => navigate(item.path)}
type="button"
>
{item.label}
</button>
))}
</nav>
</aside>
<main className={styles.main}>{renderPage()}</main>
</div>
);
}
export default App;
+306
View File
@@ -0,0 +1,306 @@
.shell {
min-height: 100vh;
display: grid;
grid-template-columns: 280px 1fr;
background: #eef2f6;
color: #172033;
}
.sidebar {
position: sticky;
top: 0;
height: 100vh;
padding: 28px 22px;
background: #111827;
color: #ffffff;
display: flex;
flex-direction: column;
gap: 28px;
}
.brand {
display: grid;
gap: 6px;
h1 {
margin: 0;
font-size: 1.35rem;
letter-spacing: -0.03em;
}
p {
margin: 0;
color: #aeb8c8;
font-size: 0.9rem;
}
}
.nav {
display: grid;
gap: 10px;
}
.navItem {
border: 0;
border-radius: 14px;
padding: 14px 16px;
background: transparent;
color: #d9e0eb;
cursor: pointer;
font: inherit;
text-align: left;
transition: background 160ms ease, color 160ms ease;
&:hover,
&.active {
background: #2563eb;
color: #ffffff;
}
}
.main {
padding: 32px;
overflow: auto;
}
.pageHeader {
display: flex;
justify-content: space-between;
gap: 18px;
align-items: flex-start;
margin-bottom: 24px;
h2 {
margin: 0 0 8px;
font-size: clamp(1.8rem, 4vw, 3.25rem);
letter-spacing: -0.05em;
}
p {
margin: 0;
max-width: 680px;
color: #5f6b7a;
line-height: 1.6;
}
}
.buttonGroup {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.primaryButton,
.secondaryButton {
border: 0;
border-radius: 999px;
padding: 12px 18px;
cursor: pointer;
font-weight: 700;
white-space: nowrap;
}
.primaryButton {
background: #2563eb;
color: #ffffff;
}
.secondaryButton {
background: #ffffff;
color: #172033;
box-shadow: inset 0 0 0 1px #d9e1ec;
}
.statsGrid,
.companyGrid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 18px;
}
.card,
.tableCard,
.companyCard,
.scheduleCard {
background: #ffffff;
border-radius: 24px;
box-shadow: 0 20px 50px rgba(17, 24, 39, 0.08);
}
.card {
padding: 24px;
span {
display: block;
color: #6b7280;
margin-bottom: 12px;
}
strong {
font-size: 2.25rem;
letter-spacing: -0.04em;
}
}
.tableCard {
overflow: hidden;
}
.tableScroll {
overflow-x: auto;
}
.table {
width: 100%;
border-collapse: collapse;
min-width: 760px;
th,
td {
padding: 16px 18px;
text-align: left;
border-bottom: 1px solid #edf1f6;
}
th {
color: #6b7280;
font-size: 0.78rem;
text-transform: uppercase;
letter-spacing: 0.08em;
background: #f8fafc;
}
td {
color: #293548;
}
}
.status {
display: inline-flex;
align-items: center;
border-radius: 999px;
padding: 6px 10px;
font-size: 0.82rem;
font-weight: 700;
color: #047857;
background: #d1fae5;
}
.statusOff {
color: #b45309;
background: #fef3c7;
}
.companyCard {
padding: 22px;
display: grid;
gap: 18px;
h3,
p {
margin: 0;
}
p {
color: #687487;
}
}
.employeeList {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.pill {
border-radius: 999px;
padding: 7px 11px;
background: #eef4ff;
color: #1d4ed8;
font-size: 0.84rem;
font-weight: 700;
}
.calendar {
display: grid;
grid-template-columns: repeat(7, minmax(130px, 1fr));
gap: 12px;
}
.dayCard {
min-height: 210px;
padding: 16px;
border-radius: 20px;
background: #ffffff;
box-shadow: 0 14px 35px rgba(17, 24, 39, 0.07);
h3 {
margin: 0 0 14px;
font-size: 1rem;
}
}
.shiftList {
display: grid;
gap: 10px;
}
.shift {
border-radius: 14px;
padding: 10px;
background: #f3f6fb;
strong,
span {
display: block;
}
span {
color: #687487;
font-size: 0.86rem;
margin-top: 4px;
}
}
@media (max-width: 980px) {
.shell {
grid-template-columns: 1fr;
}
.sidebar {
position: static;
height: auto;
}
.nav {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.navItem {
text-align: center;
}
.statsGrid,
.companyGrid {
grid-template-columns: 1fr;
}
.calendar {
overflow-x: auto;
}
}
@media (max-width: 640px) {
.main,
.sidebar {
padding: 20px;
}
.pageHeader {
display: grid;
}
.nav {
grid-template-columns: 1fr 1fr;
}
}
+19
View File
@@ -0,0 +1,19 @@
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
'Segoe UI', Roboto, 'Helvetica Neue', Arial,
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
button,
input,
select,
textarea {
font: inherit;
}
+17
View File
@@ -0,0 +1,17 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

+13
View File
@@ -0,0 +1,13 @@
const reportWebVitals = onPerfEntry => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
};
export default reportWebVitals;
+5
View File
@@ -0,0 +1,5 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';