Frontend, Backend und der ganze Zauber dazwischen – am Beispiel individueller Lohngegenstände
Der Teil, der mit dem User interagiert - die Oberfläche des Programms.
Läuft im Browser / App
Der Teil, den niemand sieht (aber jeder braucht).
Läuft auf dem Server / Cloud
Die Lieferstrecke zwischen Frontend und Backend.
Legt fest:
Der Bauplan der Daten.
Definiert, wie Daten aussehen müssen
Fertige Bausteine für Infrastruktur statt alles selbst bauen.
Bietet fertige Services:
Ist der Server / Cloud
// MonthYear schema for YYYY-MM format
const MonthYearSchema = z
.string()
.regex(/^\d{4}-\d{2}$/, 'Invalid date format, must be YYYY-MM')
.refine((val) => !isNaN(Date.parse(val + '-01')), {
message: 'Invalid month-year',
path: ['date'],
})
// Function to create a MonthYearSchema with date range validation
function createMonthYearWithRangeSchema(
offsetMonths: number = DEFAULT_OFFSET_MONTHS,
messageOverride?: string
) {
return MonthYearSchema.refine(
(date) => {
const { minDate, maxDate } = getValidMonthYearRange(offsetMonths)
return date >= minDate && date <= maxDate
},
{
message:
messageOverride ||
`date must be within current month ±${offsetMonths} month${offsetMonths !== 1 ? 's' : ''
}`,
path: ['date'],
}
)
}
// DateRange schema for YYYY-MM-DD format
const DateRangeSchema = z
.object({
from: z
.string()
.regex(/^\d{4}-\d{2}-\d{2}$/, 'Invalid date format, must be YYYY-MM-DD')
.refine((val) => !isNaN(Date.parse(val)), {
message: 'Invalid date format for "from"',
}),
to: z
.string()
.regex(/^\d{4}-\d{2}-\d{2}$/, 'Invalid date format, must be YYYY-MM-DD')
.refine((val) => !isNaN(Date.parse(val)), {
message: 'Invalid date format for "to"',
}),
})
.refine((data) => new Date(data.from) <= new Date(data.to), {
message: '"from" date must be before or equal to "to" date',
})
// Validators for increments
const hundredthHourIncrement = (val: number) => {
// Check if value is in 0.01h increments by comparing to nearest 0.01
const nearestHundredth = Math.round(val * 100) / 100
return Math.abs(val - nearestHundredth) < Number.EPSILON
}
const HundredthHourSchema = (path: string) =>
z.number().refine(hundredthHourIncrement, {
message: 'Value must be in 0.01h increments',
path: [path],
})
const halfDayIncrement = (val: number) => {
// Check if value is in 0.5d increments by comparing to nearest 0.5
const nearestHalf = Math.round(val * 2) / 2
return Math.abs(val - nearestHalf) < Number.EPSILON
}
const HalfDaySchema = (path: string) =>
z.number().refine(halfDayIncrement, {
message: 'Value must be in 0.5d increments',
path: [path],
})
const CommentSchema = z.string().optional()
// Base Bewegungsdaten schema with type discriminator
const BewegungsdatenBaseSchema = z.object({
id: z.string().uuid(),
type: z.enum(BEWEGUNGSDATEN_TYPE_VALUES),
comment: CommentSchema,
isCorrection: z.boolean().optional(),
previousValue: z.number().optional(),
})
// Specific Bewegungsdaten Schemas
// Arbeitsstunden
const ArbeitsstundenSchema = BewegungsdatenBaseSchema.extend({
type: z.literal('Arbeitsstunden'),
arbeitsstunden: HundredthHourSchema('arbeitsstunden'),
date: createMonthYearWithRangeSchema(DEFAULT_OFFSET_MONTHS),
})
// Urlaubstage
const UrlaubstageSchema = BewegungsdatenBaseSchema.extend({
type: z.literal('Urlaubstage'),
urlaubstage: HalfDaySchema('urlaubstage'),
date: createMonthYearWithRangeSchema(DEFAULT_OFFSET_MONTHS),
})
// KrankMitLohnfortzahlung
const KrankMitLohnfortzahlungSchema = BewegungsdatenBaseSchema.extend({
type: z.literal('KrankMitLohnfortzahlung'),
tage: HalfDaySchema('tage'),
dateRange: DateRangeSchema.refine(
(data) => {
const fromDate = new Date(data.from)
const minDate = new Date(
getValidMonthYearRange(DEFAULT_OFFSET_MONTHS).minDate + '-01'
)
return fromDate >= minDate
},
{
message: `"from" date must be from current month -${DEFAULT_OFFSET_MONTHS} months or later`,
path: ['dateRange', 'from'],
}
),
mitAU: z.boolean(),
})
// Ueberstunden
const UeberstundenSchema = BewegungsdatenBaseSchema.extend({
type: z.literal('Ueberstunden'),
ueberstunden: HundredthHourSchema('ueberstunden'),
date: createMonthYearWithRangeSchema(DEFAULT_OFFSET_MONTHS),
})
// Einmalzahlung
const EinmalzahlungSchema = BewegungsdatenBaseSchema.extend({
type: z.literal('Einmalzahlung'),
amountInCents: z.number().int(),
date: MonthYearSchema,
})
// Spesen/Verpflegungsmehraufwand
const SpesenVerpflegungsmehraufwandSchema = BewegungsdatenBaseSchema.extend({
type: z.literal('SpesenVerpflegungsmehraufwand'),
amountInCents: z.number().int(),
date: MonthYearSchema,
})
// Feiertagsstunden
const FeiertagsstundenSchema = BewegungsdatenBaseSchema.extend({
type: z.literal('Feiertagsstunden'),
feiertagsstunden: HundredthHourSchema('feiertagsstunden'),
date: createMonthYearWithRangeSchema(DEFAULT_OFFSET_MONTHS),
})
// Urlaubsstunden
const UrlaubsstundenSchema = BewegungsdatenBaseSchema.extend({
type: z.literal('Urlaubsstunden'),
urlaubsstunden: HundredthHourSchema('urlaubsstunden'),
date: createMonthYearWithRangeSchema(DEFAULT_OFFSET_MONTHS),
})
// Krankstunden
const KrankstundenSchema = BewegungsdatenBaseSchema.extend({
type: z.literal('Krankstunden'),
krankstunden: HundredthHourSchema('krankstunden'),
date: createMonthYearWithRangeSchema(DEFAULT_OFFSET_MONTHS),
})
// Nachtstunden
const NachtstundenSchema = BewegungsdatenBaseSchema.extend({
type: z.literal('Nachtstunden'),
nachtstunden: HundredthHourSchema('nachtstunden'),
date: createMonthYearWithRangeSchema(DEFAULT_OFFSET_MONTHS),
})
// Sonntagsstunden
const SonntagsstundenSchema = BewegungsdatenBaseSchema.extend({
type: z.literal('Sonntagsstunden'),
sonntagsstunden: HundredthHourSchema('sonntagsstunden'),
date: createMonthYearWithRangeSchema(DEFAULT_OFFSET_MONTHS),
})
// Urlaubsgeld
const UrlaubsgeldSchema = BewegungsdatenBaseSchema.extend({
type: z.literal('Urlaubsgeld'),
amountInCents: z.number().int(),
date: createMonthYearWithRangeSchema(DEFAULT_OFFSET_MONTHS),
})
// ErholungsbeihilfeEhegatte
const ErholungsbeihilfeEhegatteSchema = BewegungsdatenBaseSchema.extend({
type: z.literal('ErholungsbeihilfeEhegatte'),
amountInCents: z.number().int(),
date: createMonthYearWithRangeSchema(DEFAULT_OFFSET_MONTHS),
})
// ErholungsbeihilfeKind
const ErholungsbeihilfeKindSchema = BewegungsdatenBaseSchema.extend({
type: z.literal('ErholungsbeihilfeKind'),
amountInCents: z.number().int(),
date: createMonthYearWithRangeSchema(DEFAULT_OFFSET_MONTHS),
})
// Erholungsbeihilfe
const ErholungsbeihilfeSchema = BewegungsdatenBaseSchema.extend({
type: z.literal('Erholungsbeihilfe'),
amountInCents: z.number().int(),
date: createMonthYearWithRangeSchema(DEFAULT_OFFSET_MONTHS),
})
// Fahrtkostenzuschuss15PctPauschalsteuer
const Fahrtkostenzuschuss15PctPauschalsteuerSchema =
BewegungsdatenBaseSchema.extend({
type: z.literal('Fahrtkostenzuschuss15PctPauschalsteuer'),
amountInCents: z.number().int(),
date: createMonthYearWithRangeSchema(DEFAULT_OFFSET_MONTHS),
})
(endlich Code)
Wenn sich das Schema ändert, bewegt sich alles
Press ESC to enter the slide overview.
Hold down the alt key (ctrl in Linux) and click on any element to zoom towards it using zoom.js. Click again to zoom back out.
(NOTE: Use ctrl + click in Linux.)
Automatically animate matching elements across slides with Auto-Animate.
Presentations look great on touch devices, like mobile phones and tablets. Simply swipe through your slides.
<img src="image.png" data-preview-image="image.png">
<img src="video.png" data-preview-video="video.mp4">
Add the r-fit-text class to auto-size text
Hit the next arrow...
... to step through ...
... a fragmented slide.
There's different types of fragments, like:
grow
shrink
fade-out
fade-right, up, down, left
fade-in-then-out
fade-in-then-semi-out
Highlight red blue green
You can select from different transitions, like:
None -
Fade -
Slide -
Convex -
Concave -
Zoom
reveal.js comes with a few themes built in:
Black (default)
-
White
-
League
-
Sky
-
Beige
-
Simple
Serif
-
Blood
-
Night
-
Moon
-
Solarized
Set data-background="#dddddd" on a slide to change
the background color. All CSS color formats are supported.
<section data-background-gradient=
"linear-gradient(to bottom, #ddd, #191919)">
<section data-background="image.png">
<section data-background="image.png" data-background-repeat="repeat" data-background-size="100px">
<section data-background-video="video.mp4,video.webm">
Different background transitions are available via the backgroundTransition option. This one's called "zoom".
Reveal.configure({ backgroundTransition: 'zoom' })
You can override background transitions per-slide.
<section data-background-transition="zoom">
Since reveal.js runs on the web, you can easily embed other web content. Try interacting with the page in the background.
| Item | Value | Quantity |
|---|---|---|
| Apples | $1 | 7 |
| Lemonade | $2 | 18 |
| Bread | $3 | 2 |
These guys come in two forms, inline:
The nice thing about standards is that there are so many to
choose from
and block:
“For years there has been a theory that millions of monkeys typing at random on millions of typewriters would reproduce the entire works of Shakespeare. The Internet has proven this theory to be untrue.”
You can link between slides internally, like this.
There's a speaker view. It includes a timer, preview of the upcoming slide as well as your speaker notes.
Press the S key to try it out.
Presentations can be exported to PDF, here's an example:
Set data-state="something" on a slide and
"something"
will be added as a class to the document element when the slide is
open. This lets you apply broader style changes, like switching the
page background.
Additionally custom events can be triggered on a per slide basis by
binding to the
data-state name.
Reveal.on( 'customevent', function() {
console.log( '"customevent" has fired' );
} );
Press B or . on your keyboard to pause the presentation. This is helpful when you're on stage and want to take distracting slides off the screen.