Skip to content

Free Credit No Apk Loyalty Everything You Members Steps

365freecreditewallet, login mrluck88 online casino malaysia mrluck88 is a trusted online casino in malaysia that provides adult users with access to a wide range of casino games through Daftar Dapat Casino Joy A Casino Free Offers Access Mencuba a secure and user friendly platform the login feature is the first step to entering a world of entertainment where players can enjoy slots table games and live dealer experiences all optimized for both desktop and mobile.

Mega888 free credit no deposit 2026 claim free, daftar di mgi Freecreditking Big Rm88 365 Kkslot Is Duitnow Withdrawal untuk dapat free credit mega888 tanpa deposit semak langkah pendaftaran syarat promo permainan layak dan turnover sebelum withdraw, mega888 free credit how to claim play and win big in 2025.

Winrm Mega888 Apk Online Lkb8 Free Credit No Holy Cimb Rex

Free kredit rm10 malaysia 2026 Bolaking888 Malaysia Casino Claim Malaysian Joylink Free Credit Risk Pussy888 free credit no deposit guide, mega888 slot demo malaysia free credit slot, daftar dapat free credit mega888 no deposit on mgi.

Free credit and no Freecredit2026 Ace66 Bonus Gaming Now You Can Automatically Deposit Required deposit promotions in malaysia each read against the same checklist start with the full rm10 guide or go to the featured pick, login.

Freecredit2026 Win Cuci Cuci Download 918kiss Android Active Months Deposit

Joylink free credit 365 link malaysia rm10 rm50 rm100 enjoy joy link free kredit no deposit link terbaru 2026 malaysia latest update, kkslot kkslots777 malaysia top Bolaking888 Malaysia Kkslot Rm10 Sites Bolaking888 Free Credit Winnings Android online casino for real money.

Ace66 trusted online casino malaysia 2026, mega888 free credit 2026 claim rm8 rm10 no deposit bonus, mega888 free credit bonuses 2026 claim guide, deposit Daftar Dapat Trusted Kkslot Claim Mega888 Free Members Because Are a minimum of rm30 via online banking maybank cimb e wallet touch n go grabpay or usdt your 150 welcome bonus up to rm300 is credited automatically on qualifying first deposits to 918kiss and mega888 games.

Now you can enjoy a free slot demo of mega888 slot games at bp9 casino the perfect way to test out the malaysian favorite slot games with no deposit required bp9 Rm10 Free Credit Joy Freecredit2026 Compare 6 Desktop Users Atau gives you full access to mega888 demo slots for free.

Free Kredit Bonuses Download Get Mega888 Free Tournaments Mgi

Mega888 apk for android download, i1cash88 365 free share trusted online casino malaysia, freecreditking, mega888 free credit no deposit terbaik malaysia 2026 daftar percuma claim free credit mega888 rm10 rm30 panduan slot game popular dan strategi cuci, download 918kiss android free 918kiss is an online casino app for android with which you play and win some money with different sorts of games such as the roulette or slot machines the bellagio in las vegas the atlantis paradise island in the Rm10 Free Credit Money Malaysia Casino Conclusion In 2025 Apply Their bahamas the monte carlo casino in monaco the.

Browse files
authored
Merge pull request #32 from valentinarotondaro/feature/23-progress-bar
feat: add progress bar for completed todos
2 parents c94e243 + f64885a commit acd2085

7 files changed

Lines changed: 137 additions & 0 deletions

File tree

‎README.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Made by **Valentina Rotondaro** and **Alexa Cattaneo Piazza**.
2020
| Save todos in localStorage | 14 |`storage.ts`|
2121
| Input validation (empty, max 100 characters, no duplicates) | 16 |`features/validation.ts`|
2222
| Filter by status (All / Active / Completed) | 4 |`features/statusFilter.ts`|
23+
| Progress bar (follows the category filter) | 10 |`features/progress.ts`|
2324

2425
## Tech stack
2526

‎index.html‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@
1212
<body>
1313
<divclass="container">
1414
<h1>MinTask</h1>
15+
<divclass="progress">
16+
<divclass="progress-track" id="progress-bar" role="progressbar" aria-label="Todos done" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0">
17+
<divclass="progress-fill" id="progress-fill"></div>
18+
</div>
19+
<pclass="progress-label" id="progress-label"></p>
20+
</div>
1521
<formclass="todo-form">
1622
<inputtype="text" id="todo-input" placeholder="Add a new todo" />
1723
<inputtype="date" id="due-date-input" />

‎src/features/progress.ts‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
importtype{Todo}from'../types';
2+
3+
/** How many todos are done, out of how many, and the percentage. */
4+
exportinterfaceProgress{
5+
done: number;
6+
total: number;
7+
percent: number;
8+
}
9+
10+
/**
11+
* Calculates the progress of a list of todos.
12+
* @param todos - The list of todos.
13+
* @returns The number of done todos, the total and the percentage (0–100, rounded).
14+
*/
15+
exportconstgetProgress=(todos: Todo[]): Progress=>{
16+
consttotal=todos.length;
17+
constdone=todos.filter(todo=>todo.completed).length;
18+
constpercent=total===0 ? 0 : Math.round((done/total)*100);
19+
return{ done, total, percent };
20+
};

‎src/render.ts‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,16 @@ import { toggleTodo } from './features/toggle';
55
import{CATEGORIES,filterByCategory,countByCategory,getActiveCategory,setActiveCategory}from'./features/categories';
66
importtype{CategoryFilter}from'./features/categories';
77
import{STATUSES,filterByStatus,countByStatus,getActiveStatus,setActiveStatus}from'./features/statusFilter';
8+
import{getProgress}from'./features/progress';
89
importtype{Todo}from'./types';
910

1011
// Reference to the list in index.html (DOM + typecasting)
1112
consttodoList=document.getElementById('todo-list')asHTMLUListElement;
1213
constcategoryFilter=document.getElementById('category-filter')asHTMLDivElement;
1314
conststatusFilter=document.getElementById('status-filter')asHTMLDivElement;
15+
constprogressBar=document.getElementById('progress-bar')asHTMLDivElement;
16+
constprogressFill=document.getElementById('progress-fill')asHTMLDivElement;
17+
constprogressLabel=document.getElementById('progress-label')asHTMLParagraphElement;
1418

1519
// "work" → "Work"
1620
constlabel=(text: string): string=>text.charAt(0).toUpperCase()+text.slice(1);
@@ -25,6 +29,7 @@ export const renderTodos = (): void => {
2529
renderCategoryFilter();
2630
constinCategory=filterByCategory(todos,getActiveCategory());
2731
renderStatusFilter(inCategory);
32+
renderProgress(inCategory)
2833
sortByPriority(filterByStatus(inCategory,getActiveStatus())).forEach(todo=>{
2934
constli=document.createElement('li');
3035
conststatus=getDueStatus(todo);
@@ -111,4 +116,11 @@ const renderStatusFilter = (list: Todo[]): void => {
111116
});
112117
statusFilter.appendChild(button);
113118
});
119+
};
120+
// Updates the progress bar for the todos of the chosen category.
121+
constrenderProgress=(list: Todo[]): void=>{
122+
const{ done, total, percent }=getProgress(list);
123+
progressFill.style.width=`${percent}%`;
124+
progressBar.setAttribute('aria-valuenow',String(percent));
125+
progressLabel.textContent=total===0 ? 'No todos yet' : `${done} of ${total} done · ${percent}%`;
114126
};

‎src/style.css‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,3 +278,28 @@ h1 {
278278
font-weight:600;
279279
box-shadow:01px2pxrgba(0,0,0,0.08);
280280
}
281+
/* === Progress bar (Valen) === */
282+
.progress{
283+
margin-bottom:20px;
284+
}
285+
286+
.progress-track{
287+
height:8px;
288+
border-radius:999px;
289+
background:#e6e4de;
290+
overflow: hidden;
291+
}
292+
293+
.progress-fill{
294+
width:0;
295+
height:100%;
296+
border-radius:999px;
297+
background:#141414;
298+
transition: width 0.3s ease;
299+
}
300+
301+
.progress-label{
302+
margin:6px00;
303+
color:#5c5a55;
304+
font-size:13px;
305+
}

‎test/e2e/progress.test.js‎

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import{Selector}from'testcafe';
2+
3+
// Locally: BASE_URL=http://127.0.0.1:5173 npx testcafe chrome test/e2e/progress.test.js
4+
constBASE_URL=process.env.BASE_URL||'https://todo.mintask.dk/test/';
5+
6+
fixture('Todo app – progress bar').page(BASE_URL);
7+
8+
constinput=Selector('#todo-input');
9+
constcategoryInput=Selector('#category-input');
10+
constaddButton=Selector('.todo-form button');
11+
constitems=Selector('#todo-list li');
12+
constprogressBar=Selector('#progress-bar');
13+
constprogressLabel=Selector('#progress-label');
14+
constfilterPill=category=>Selector('.filter-pill').withAttribute('data-category',category);
15+
16+
constaddTodoWithCategory=async(t,text,category)=>{
17+
awaitt
18+
.typeText(input,text)
19+
.click(categoryInput)
20+
.click(categoryInput.find('option').withAttribute('value',category))
21+
.click(addButton);
22+
};
23+
24+
test('Shows 50% when one of two todos is done',asynct=>{
25+
// Arrange
26+
awaitaddTodoWithCategory(t,'Buy milk','personal');
27+
awaitaddTodoWithCategory(t,'Walk the dog','personal');
28+
29+
// Act
30+
awaitt.click(items.nth(0).find('.toggle'));
31+
32+
// Assert
33+
awaitt
34+
.expect(progressBar.getAttribute('aria-valuenow')).eql('50')
35+
.expect(progressLabel.innerText).contains('1 of 2 done');
36+
});
37+
38+
test('Follows the chosen category',asynct=>{
39+
// Arrange
40+
awaitaddTodoWithCategory(t,'Write report','work');
41+
awaitaddTodoWithCategory(t,'Call mum','personal');
42+
43+
// Act
44+
awaitt
45+
.click(filterPill('work'))
46+
.click(items.nth(0).find('.toggle'));
47+
48+
// Assert
49+
awaitt
50+
.expect(progressBar.getAttribute('aria-valuenow')).eql('100')
51+
.expect(progressLabel.innerText).contains('1 of 1 done');
52+
});

‎test/unit/progress.test.ts‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import{describe,it,expect}from'vitest';
2+
import{getProgress}from'../../src/features/progress';
3+
importtype{Todo}from'../../src/types';
4+
5+
constmakeTodo=(id: number,completed: boolean): Todo=>({ id,text: `Todo ${id}`, completed });
6+
7+
describe('getProgress',()=>{
8+
it('returns 0% when there are no todos',()=>{
9+
expect(getProgress([])).toEqual({done: 0,total: 0,percent: 0});
10+
});
11+
12+
it('counts the done todos and rounds the percentage',()=>{
13+
consttodos=[makeTodo(1,true),makeTodo(2,false),makeTodo(3,false)];
14+
expect(getProgress(todos)).toEqual({done: 1,total: 3,percent: 33});
15+
});
16+
17+
it('returns 100% when every todo is done',()=>{
18+
consttodos=[makeTodo(1,true),makeTodo(2,true)];
19+
expect(getProgress(todos).percent).toBe(100);
20+
});
21+
});

0 commit comments

Comments
 (0)