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 README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ To create a new tool, there is a script that generate the boilerplate of the new
pnpm run script:create:tool my-tool-name
```

It will create a directory in `src/tools` with the correct files, and a the import in `src/tools/index.ts`. You will just need to add the imported tool in the proper category and develop the tool.
It will create a directory in `src/tools` with the correct files, and the import in `src/tools/index.ts`. You will just need to add the imported tool in the proper category and develop the tool.

## Contributors

Expand Down
4 changes: 4 additions & 0 deletions locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ tools:
title: Percentage calculator
description: Easily calculate percentages from a value to another value, or from a percentage to a value.

tip-calculator:
title: Tip calculator
description: Calculate the tip for a bill and split it among multiple people.

svg-placeholder-generator:
title: SVG placeholder generator
description: Generate svg images to use as a placeholder in your applications.
Expand Down
3 changes: 2 additions & 1 deletion src/tools/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ import { tool as dateTimeConverter } from './date-time-converter';
import { tool as deviceInformation } from './device-information';
import { tool as cypher } from './encryption';
import { tool as etaCalculator } from './eta-calculator';
import { tool as tipCalculator } from './tip-calculator';
import { tool as percentageCalculator } from './percentage-calculator';
import { tool as gitMemo } from './git-memo';
import { tool as hashText } from './hash-text';
Expand Down Expand Up @@ -168,7 +169,7 @@ export const toolsByCategory: ToolCategory[] = [
},
{
name: 'Math',
components: [mathEvaluator, etaCalculator, percentageCalculator],
components: [mathEvaluator, etaCalculator, percentageCalculator, tipCalculator],
},
{
name: 'Measurement',
Expand Down
13 changes: 13 additions & 0 deletions src/tools/tip-calculator/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Calculator } from '@vicons/tabler';
import { defineTool } from '../tool';
import { translate } from '@/plugins/i18n.plugin';

export const tool = defineTool({
name: translate('tools.tip-calculator.title'),
path: '/tip-calculator',
description: translate('tools.tip-calculator.description'),
keywords: ['tip', 'calculator', 'bill', 'split', 'restaurant', 'money', 'payment'],
component: () => import('./tip-calculator.vue'),
icon: Calculator,
createdAt: new Date('2024-04-17'),
});
49 changes: 49 additions & 0 deletions src/tools/tip-calculator/tip-calculator.e2e.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { expect, test } from '@playwright/test';

test.describe('Tool - Tip calculator', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/tip-calculator');
});

test('Has correct title', async ({ page }) => {
await expect(page).toHaveTitle('Tip calculator - IT Tools');
});

test('Correctly calculates tip and split', async ({ page }) => {
// Fill bill amount
await page.getByTestId('billAmount').locator('input').fill('100');

Check failure on line 15 in src/tools/tip-calculator/tip-calculator.e2e.spec.ts

View workflow job for this annotation

GitHub Actions / ci

Trailing spaces not allowed
// Fill tip percentage
await page.getByTestId('tipPercentage').locator('input').fill('15');

Check failure on line 18 in src/tools/tip-calculator/tip-calculator.e2e.spec.ts

View workflow job for this annotation

GitHub Actions / ci

Trailing spaces not allowed
// Fill number of people
await page.getByTestId('numberOfPeople').locator('input').fill('2');

// Check results
// 100 * 0.15 = 15.00
await expect(page.getByTestId('tipAmountResult').locator('input')).toHaveValue('15.00');

Check failure on line 25 in src/tools/tip-calculator/tip-calculator.e2e.spec.ts

View workflow job for this annotation

GitHub Actions / ci

Trailing spaces not allowed
// 100 + 15 = 115.00
await expect(page.getByTestId('totalBillResult').locator('input')).toHaveValue('115.00');

Check failure on line 28 in src/tools/tip-calculator/tip-calculator.e2e.spec.ts

View workflow job for this annotation

GitHub Actions / ci

Trailing spaces not allowed
// 115 / 2 = 57.50
await expect(page.getByTestId('amountPerPersonResult').locator('input')).toHaveValue('57.50');
});

test('Quick tip buttons work', async ({ page }) => {
await page.getByTestId('billAmount').locator('input').fill('100');

Check failure on line 35 in src/tools/tip-calculator/tip-calculator.e2e.spec.ts

View workflow job for this annotation

GitHub Actions / ci

Trailing spaces not allowed
// Click 20% button
await page.getByRole('button', { name: '20%' }).click();

Check failure on line 38 in src/tools/tip-calculator/tip-calculator.e2e.spec.ts

View workflow job for this annotation

GitHub Actions / ci

Trailing spaces not allowed
await expect(page.getByTestId('tipPercentage').locator('input')).toHaveValue('20');
await expect(page.getByTestId('tipAmountResult').locator('input')).toHaveValue('20.00');
});

test('Displays initial/empty results correctly', async ({ page }) => {
// Initial state with empty bill
await expect(page.getByTestId('tipAmountResult').locator('input')).toHaveValue('0.00');
await expect(page.getByTestId('totalBillResult').locator('input')).toHaveValue('0.00');
await expect(page.getByTestId('amountPerPersonResult').locator('input')).toHaveValue('0.00');
});
});
125 changes: 125 additions & 0 deletions src/tools/tip-calculator/tip-calculator.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
<script setup lang="ts">
const billAmount = ref<number>();
const tipPercentage = ref<number>(15);
const numberOfPeople = ref<number>(1);

const tipAmount = computed(() => {
if (billAmount.value === undefined || tipPercentage.value === undefined) {
return 0;
}
return (billAmount.value * tipPercentage.value) / 100;
});

const totalAmount = computed(() => {
if (billAmount.value === undefined) {
return 0;
}
return billAmount.value + tipAmount.value;
});

const amountPerPerson = computed(() => {
if (totalAmount.value === 0 || numberOfPeople.value === undefined || numberOfPeople.value <= 0) {
return 0;
}
return totalAmount.value / numberOfPeople.value;
});

const formatNumber = (value: number) => {

Check failure on line 27 in src/tools/tip-calculator/tip-calculator.vue

View workflow job for this annotation

GitHub Actions / ci

Top-level functions should be declared with function keyword
return new Intl.NumberFormat('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(value);
};

const tipAmountFormatted = computed(() => formatNumber(tipAmount.value));
const totalAmountFormatted = computed(() => formatNumber(totalAmount.value));
const amountPerPersonFormatted = computed(() => formatNumber(amountPerPerson.value));
</script>

<template>
<div style="flex: 0 0 100%">
<div style="margin: 0 auto; max-width: 600px">
<c-card mb-3>
<div mb-3>
Bill details
</div>
<div flex flex-col gap-4>
<div flex items-center gap-2>
<div style="min-width: 120px;">Bill Amount</div>

Check warning on line 45 in src/tools/tip-calculator/tip-calculator.vue

View workflow job for this annotation

GitHub Actions / ci

Expected 1 line break before closing tag (`</div>`), but no line breaks found

Check warning on line 45 in src/tools/tip-calculator/tip-calculator.vue

View workflow job for this annotation

GitHub Actions / ci

Expected 1 line break after opening tag (`<div>`), but no line breaks found
<n-input-number
v-model:value="billAmount"
data-test-id="billAmount"
:min="0"
placeholder="Total Bill"
style="flex: 1"
/>
</div>

Check failure on line 54 in src/tools/tip-calculator/tip-calculator.vue

View workflow job for this annotation

GitHub Actions / ci

Trailing spaces not allowed
<div flex items-center gap-2>
<div style="min-width: 120px;">Tip Percentage</div>

Check warning on line 56 in src/tools/tip-calculator/tip-calculator.vue

View workflow job for this annotation

GitHub Actions / ci

Expected 1 line break before closing tag (`</div>`), but no line breaks found

Check warning on line 56 in src/tools/tip-calculator/tip-calculator.vue

View workflow job for this annotation

GitHub Actions / ci

Expected 1 line break after opening tag (`<div>`), but no line breaks found
<n-input-number
v-model:value="tipPercentage"
data-test-id="tipPercentage"
:min="0"
placeholder="Tip %"
style="flex: 1"
>
<template #suffix>%</template>

Check warning on line 64 in src/tools/tip-calculator/tip-calculator.vue

View workflow job for this annotation

GitHub Actions / ci

Expected 1 line break before closing tag (`</template>`), but no line breaks found

Check warning on line 64 in src/tools/tip-calculator/tip-calculator.vue

View workflow job for this annotation

GitHub Actions / ci

Expected 1 line break after opening tag (`<template>`), but no line breaks found
</n-input-number>
</div>

<div flex items-center gap-2>
<div style="min-width: 120px;">Number of People</div>

Check warning on line 69 in src/tools/tip-calculator/tip-calculator.vue

View workflow job for this annotation

GitHub Actions / ci

Expected 1 line break before closing tag (`</div>`), but no line breaks found

Check warning on line 69 in src/tools/tip-calculator/tip-calculator.vue

View workflow job for this annotation

GitHub Actions / ci

Expected 1 line break after opening tag (`<div>`), but no line breaks found
<n-input-number
v-model:value="numberOfPeople"
data-test-id="numberOfPeople"
:min="1"
placeholder="People"
style="flex: 1"
/>
</div>
</div>
</c-card>

<c-card mb-3>
<div mb-3>Results</div>

Check warning on line 82 in src/tools/tip-calculator/tip-calculator.vue

View workflow job for this annotation

GitHub Actions / ci

Expected 1 line break after opening tag (`<div>`), but no line breaks found
<div flex flex-col gap-3>
<div flex justify-between items-center>
<span>Tip Amount:</span>
<input-copyable
:value="tipAmountFormatted"
data-test-id="tipAmountResult"
readonly
style="max-width: 200px;"
/>
</div>
<div flex justify-between items-center>
<span>Total Bill:</span>
<input-copyable
:value="totalAmountFormatted"
data-test-id="totalBillResult"
readonly
style="max-width: 200px;"
/>
</div>
<div border-t pt-3 flex justify-between items-center font-bold>
<span>Amount Per Person:</span>
<input-copyable
:value="amountPerPersonFormatted"
data-test-id="amountPerPersonResult"
readonly
style="max-width: 200px;"
/>
</div>
</div>
</c-card>

<c-card>
<div mb-2>Quick Tip %</div>
<div flex gap-2>
<n-button v-for="tip in [10, 15, 18, 20, 25]" :key="tip" @click="tipPercentage = tip" size="small">
{{ tip }}%
</n-button>
</div>
</c-card>
</div>
</div>
</template>

Check failure on line 125 in src/tools/tip-calculator/tip-calculator.vue

View workflow job for this annotation

GitHub Actions / ci

Too many blank lines at the end of file. Max of 0 allowed
Loading